@m8t-stack/cli 0.2.43 → 0.2.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1310,7 +1310,7 @@ var init_enable_hosted_brain = __esm({
1310
1310
  import { Builtins, Cli } from "clipanion";
1311
1311
 
1312
1312
  // src/lib/package-version.ts
1313
- var CLI_VERSION = "0.2.43";
1313
+ var CLI_VERSION = "0.2.45";
1314
1314
 
1315
1315
  // src/lib/render-error.ts
1316
1316
  init_errors();
@@ -18056,6 +18056,7 @@ var DEFAULT_IMAGE = "m8t-coding-agent";
18056
18056
  var DEFAULT_TAG = "v0.1.2";
18057
18057
  var DEFAULT_MODEL = "gpt-4.1-mini";
18058
18058
  var NAME_RE = /^[a-z0-9-]+$/;
18059
+ var PUBLIC_PRODUCT_AGENTS = /* @__PURE__ */ new Set(["stacey", "azzy"]);
18059
18060
  var CoderDeployCommand = class extends M8tCommand {
18060
18061
  static paths = [["coder", "deploy"]];
18061
18062
  static usage = Command28.Usage({
@@ -18188,7 +18189,14 @@ var CoderDeployCommand = class extends M8tCommand {
18188
18189
  cpu: preset.cpu,
18189
18190
  memory: preset.memory,
18190
18191
  env,
18191
- metadata: { ...currentMetadata, source: "m8t", kind: "hosted", persona: personaName, personaVersion },
18192
+ metadata: {
18193
+ ...currentMetadata,
18194
+ source: "m8t",
18195
+ kind: "hosted",
18196
+ persona: personaName,
18197
+ personaVersion,
18198
+ productVisibility: PUBLIC_PRODUCT_AGENTS.has(this.name) ? "public" : "internal"
18199
+ },
18192
18200
  onProgress
18193
18201
  });
18194
18202
  if (typeof this.brain === "string") {
@@ -18396,6 +18404,7 @@ import * as path18 from "path";
18396
18404
  import { Command as Command30, Option as Option28 } from "clipanion";
18397
18405
  import { DefaultAzureCredential as DefaultAzureCredential15 } from "@azure/identity";
18398
18406
  init_errors();
18407
+ init_foundry_agent_get();
18399
18408
  init_rbac();
18400
18409
 
18401
18410
  // src/lib/acs-provision.ts
@@ -18542,6 +18551,12 @@ var AzureExecDeployCommand = class extends M8tCommand {
18542
18551
  interactive,
18543
18552
  endpoint: this.endpoint
18544
18553
  });
18554
+ let currentMetadata = {};
18555
+ try {
18556
+ const current = await getAgentVersion({ credential: credential2, projectEndpoint: project.endpoint, agentName: this.name });
18557
+ currentMetadata = { ...current.metadata ?? {} };
18558
+ } catch {
18559
+ }
18545
18560
  const staged = await stagePublicImageIfNeeded({ image: requestedImage, project });
18546
18561
  for (const n of staged.notes) {
18547
18562
  this.context.stderr.write(`${colors.hint("note:")} ${n}
@@ -18635,7 +18650,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
18635
18650
  cpu: preset.cpu,
18636
18651
  memory: preset.memory,
18637
18652
  env,
18638
- metadata: { source: "m8t", kind: "hosted", persona: personaName, personaVersion },
18653
+ metadata: { ...currentMetadata, source: "m8t", kind: "hosted", persona: personaName, personaVersion, productVisibility: "internal" },
18639
18654
  onProgress
18640
18655
  });
18641
18656
  onProgress?.(`granting Contributor at ${grantScope}\u2026`);
@@ -28057,6 +28072,34 @@ import * as path33 from "path";
28057
28072
  init_errors();
28058
28073
 
28059
28074
  // src/lib/onboarding-profile.ts
28075
+ function describeBlockRejection(reason) {
28076
+ switch (reason) {
28077
+ case "unknown-schema-version":
28078
+ return "the onboarding block's schema_version was missing or unrecognized";
28079
+ case "unexpected-key":
28080
+ return "the onboarding JSON carried a field it shouldn't have";
28081
+ case "missing-key":
28082
+ return "the onboarding block was missing a required field";
28083
+ case "non-string-value":
28084
+ return "the onboarding block held a value of the wrong type";
28085
+ case "disallowed-value":
28086
+ return "an onboarding block field held a value outside its allowed set";
28087
+ case "too-many-pending-requests":
28088
+ return "the onboarding block listed more pending requests than the one allowed";
28089
+ case "malformed-json":
28090
+ return "the onboarding block's JSON could not be safely parsed \u2014 check for duplicate keys, excessive nesting, or a syntax error";
28091
+ case "multiple-artifacts":
28092
+ return "more than one onboarding block was found in your onboarding conversation";
28093
+ case "unreadable-fences":
28094
+ return "the message's code fences could not be reliably delimited, so any onboarding block inside them could not be safely read";
28095
+ case "mistagged-fence":
28096
+ return "the onboarding block was inside a code fence that wasn't tagged json, so it could not be safely read";
28097
+ case "unfenced-artifact":
28098
+ return "the onboarding block appeared outside of any code fence, so it could not be safely read";
28099
+ case "no-artifact":
28100
+ return "no onboarding block was found";
28101
+ }
28102
+ }
28060
28103
  var COMPANY_PROFILE_PATH = "memory/company-profile.md";
28061
28104
  var DEFAULT_MEMORY_INDEX_HEADER = [
28062
28105
  `# Memory index`,
@@ -28178,24 +28221,96 @@ function hasValidUniqueJsonKeys(source) {
28178
28221
  return false;
28179
28222
  }
28180
28223
  }
28224
+ var V3_BLOCK_KEYS = ["schema_version", "founder", "company", "advisor_email", "pending_requests"];
28225
+ var V3_FOUNDER_KEYS = ["name", "email", "azure_identity_note"];
28226
+ var V3_COMPANY_KEYS = ["name", "one_liner"];
28227
+ var V3_ADDRESS_KEYS = ["address", "city", "postal_code", "country"];
28228
+ var V3_REQUEST_KEYS = ["type", "model", "region", "consent", "company_address"];
28229
+ function exactStringRecord(value, keys) {
28230
+ if (!isRecord(value)) return "non-string-value";
28231
+ const present = new Set(Object.keys(value));
28232
+ for (const key2 of keys) if (!present.has(key2)) return "missing-key";
28233
+ if (present.size !== keys.length) return "unexpected-key";
28234
+ for (const key2 of keys) if (typeof value[key2] !== "string") return "non-string-value";
28235
+ return null;
28236
+ }
28237
+ function canonicalPendingRequests(value) {
28238
+ if (!Array.isArray(value)) return { ok: false, reason: "non-string-value" };
28239
+ if (value.length > 1) return { ok: false, reason: "too-many-pending-requests" };
28240
+ const requests = [];
28241
+ for (const entry of value) {
28242
+ if (!isRecord(entry)) return { ok: false, reason: "non-string-value" };
28243
+ const present = new Set(Object.keys(entry));
28244
+ for (const key2 of V3_REQUEST_KEYS) {
28245
+ if (!present.has(key2)) return { ok: false, reason: "missing-key" };
28246
+ }
28247
+ if (present.size !== V3_REQUEST_KEYS.length) return { ok: false, reason: "unexpected-key" };
28248
+ if (entry.type !== "quota") return { ok: false, reason: "disallowed-value" };
28249
+ if (entry.consent !== "submit" && entry.consent !== "prepare") return { ok: false, reason: "disallowed-value" };
28250
+ if (typeof entry.model !== "string" || typeof entry.region !== "string") {
28251
+ return { ok: false, reason: "non-string-value" };
28252
+ }
28253
+ if (entry.company_address !== null) {
28254
+ const bad = exactStringRecord(entry.company_address, V3_ADDRESS_KEYS);
28255
+ if (bad) return { ok: false, reason: bad };
28256
+ }
28257
+ requests.push(entry);
28258
+ }
28259
+ return { ok: true, requests };
28260
+ }
28261
+ function canonicalV3Block(value) {
28262
+ const present = new Set(Object.keys(value));
28263
+ for (const key2 of V3_BLOCK_KEYS) if (!present.has(key2)) return { ok: false, reason: "missing-key" };
28264
+ if (present.size !== V3_BLOCK_KEYS.length) return { ok: false, reason: "unexpected-key" };
28265
+ const founderBad = exactStringRecord(value.founder, V3_FOUNDER_KEYS);
28266
+ if (founderBad) return { ok: false, reason: founderBad };
28267
+ const companyBad = exactStringRecord(value.company, V3_COMPANY_KEYS);
28268
+ if (companyBad) return { ok: false, reason: companyBad };
28269
+ if (value.advisor_email !== null && typeof value.advisor_email !== "string") {
28270
+ return { ok: false, reason: "non-string-value" };
28271
+ }
28272
+ const requests = canonicalPendingRequests(value.pending_requests);
28273
+ if (!requests.ok) return requests;
28274
+ const founder = value.founder;
28275
+ const company = value.company;
28276
+ return {
28277
+ ok: true,
28278
+ block: {
28279
+ schema_version: "3",
28280
+ founder_name: founder.name,
28281
+ founder_email: founder.email,
28282
+ azure_identity_note: founder.azure_identity_note,
28283
+ company_name: company.name,
28284
+ context: company.one_liner,
28285
+ advisor_name: "",
28286
+ advisor_email: typeof value.advisor_email === "string" ? value.advisor_email : "",
28287
+ pending_requests: requests.requests
28288
+ }
28289
+ };
28290
+ }
28181
28291
  function canonicalBlock(value) {
28182
- if (!isRecord(value)) return null;
28183
- const keys = Object.keys(value).sort();
28184
- const expected = [...ONBOARDING_BLOCK_KEYS].sort();
28185
- if (keys.length !== expected.length || keys.some((key2, index) => key2 !== expected[index])) return null;
28186
- if (value.schema_version !== "2") return null;
28292
+ if (!isRecord(value)) return { ok: false, reason: "non-string-value" };
28293
+ if (value.schema_version === "3") return canonicalV3Block(value);
28294
+ if (value.schema_version !== "2") return { ok: false, reason: "unknown-schema-version" };
28295
+ const keys = new Set(Object.keys(value));
28187
28296
  for (const key2 of ONBOARDING_BLOCK_KEYS) {
28188
- if (typeof value[key2] !== "string") return null;
28297
+ if (!keys.has(key2)) return { ok: false, reason: "missing-key" };
28189
28298
  }
28190
- return value;
28299
+ if (keys.size !== ONBOARDING_BLOCK_KEYS.length) return { ok: false, reason: "unexpected-key" };
28300
+ for (const key2 of ONBOARDING_BLOCK_KEYS) {
28301
+ if (typeof value[key2] !== "string") return { ok: false, reason: "non-string-value" };
28302
+ }
28303
+ return { ok: true, block: value };
28191
28304
  }
28192
- function parseOnboardingArtifact(machineText) {
28305
+ function parseOnboardingArtifactResult(machineText) {
28193
28306
  const fencePattern = /^```([^\r\n]*)\r?\n([\s\S]*?)^```[ \t]*(?=\r?$)/gm;
28194
28307
  const fences = [...machineText.matchAll(fencePattern)];
28195
28308
  const jsonFenceStarts = [...machineText.matchAll(/^```[ \t]*json[ \t]*\r?$/gm)];
28196
28309
  const matchedJsonFences = fences.filter((match) => match[1].trim() === "json");
28197
- if (jsonFenceStarts.length !== matchedJsonFences.length) return null;
28198
- if (fences.some((match) => match[1].trim() !== "json" && /"m8t_onboarding"\s*:/.test(match[2]))) return null;
28310
+ if (jsonFenceStarts.length !== matchedJsonFences.length) return { ok: false, reason: "unreadable-fences" };
28311
+ if (fences.some((match) => match[1].trim() !== "json" && /"m8t_onboarding"\s*:/.test(match[2]))) {
28312
+ return { ok: false, reason: "mistagged-fence" };
28313
+ }
28199
28314
  let outsideFences = "";
28200
28315
  let previousEnd = 0;
28201
28316
  for (const fence of fences) {
@@ -28204,43 +28319,48 @@ function parseOnboardingArtifact(machineText) {
28204
28319
  previousEnd = start + fence[0].length;
28205
28320
  }
28206
28321
  outsideFences += machineText.slice(previousEnd);
28207
- if (/"m8t_onboarding"\s*:/.test(outsideFences)) return null;
28322
+ if (/"m8t_onboarding"\s*:/.test(outsideFences)) return { ok: false, reason: "unfenced-artifact" };
28208
28323
  const artifacts = [];
28209
28324
  for (const fence of matchedJsonFences) {
28210
28325
  const json = fence[2].trim();
28211
28326
  let parsed;
28212
28327
  if (!hasValidUniqueJsonKeys(json)) {
28213
- if (json.includes("m8t_onboarding")) return null;
28328
+ if (json.includes("m8t_onboarding")) return { ok: false, reason: "malformed-json" };
28214
28329
  continue;
28215
28330
  }
28216
28331
  try {
28217
28332
  parsed = JSON.parse(json);
28218
28333
  } catch {
28219
- if (json.includes("m8t_onboarding")) return null;
28334
+ if (json.includes("m8t_onboarding")) return { ok: false, reason: "malformed-json" };
28220
28335
  continue;
28221
28336
  }
28222
28337
  if (!isRecord(parsed) || !Object.hasOwn(parsed, "m8t_onboarding")) continue;
28223
- if (Object.keys(parsed).length !== 1) return null;
28224
- const block = canonicalBlock(parsed.m8t_onboarding);
28225
- if (!block) return null;
28338
+ if (Object.keys(parsed).length !== 1) return { ok: false, reason: "unexpected-key" };
28339
+ const outcome = canonicalBlock(parsed.m8t_onboarding);
28340
+ if (!outcome.ok) return outcome;
28226
28341
  const start = fence.index;
28227
- artifacts.push({ block, start, end: start + fence[0].length });
28342
+ artifacts.push({ block: outcome.block, start, end: start + fence[0].length });
28228
28343
  }
28229
- if (artifacts.length !== 1) return null;
28344
+ if (artifacts.length === 0) return { ok: false, reason: "no-artifact" };
28345
+ if (artifacts.length > 1) return { ok: false, reason: "multiple-artifacts" };
28230
28346
  const artifact = artifacts[0];
28231
28347
  const before = machineText.slice(0, artifact.start).trimEnd();
28232
28348
  const after = machineText.slice(artifact.end).trimStart();
28233
28349
  return {
28234
- block: artifact.block,
28235
- machineText,
28236
- speechText: [before, after].filter((part) => part.length > 0).join("\n").trim()
28350
+ ok: true,
28351
+ artifact: {
28352
+ block: artifact.block,
28353
+ machineText,
28354
+ speechText: [before, after].filter((part) => part.length > 0).join("\n").trim()
28355
+ }
28237
28356
  };
28238
28357
  }
28239
28358
  var EMPTY_PROFILE_RESULT = {
28240
28359
  hadIntake: false,
28241
28360
  block: null,
28242
28361
  machineText: null,
28243
- speechText: null
28362
+ speechText: null,
28363
+ rejection: null
28244
28364
  };
28245
28365
  async function readCursorPages(args) {
28246
28366
  const all = [];
@@ -28314,7 +28434,7 @@ async function findOnboardingProfile(args) {
28314
28434
  const conversations = orderNewest(listed.flatMap((value, ordinal) => {
28315
28435
  if (!isRecord(value) || typeof value.id !== "string" || value.id.length === 0 || !isRecord(value.metadata)) return [];
28316
28436
  const metadata = value.metadata;
28317
- if (metadata.app !== "m8t-webapp" || metadata.agent !== "stacey-intake") return [];
28437
+ if (metadata.app !== "m8t-webapp" || metadata.agent !== "azzy-intake") return [];
28318
28438
  return [{
28319
28439
  id: value.id,
28320
28440
  createdAt: timestamp(value.created_at) !== Number.NEGATIVE_INFINITY ? timestamp(value.created_at) : timestamp(metadata.createdAt),
@@ -28328,14 +28448,14 @@ async function findOnboardingProfile(args) {
28328
28448
  headers: H,
28329
28449
  fetchImpl: doFetch
28330
28450
  });
28331
- if (!items) return { hadIntake: true, block: null, machineText: null, speechText: null };
28451
+ if (!items) return { hadIntake: true, block: null, machineText: null, speechText: null, rejection: null };
28332
28452
  const assistantItems = orderNewest(items.flatMap((value, ordinal) => {
28333
28453
  if (!isRecord(value) || value.type !== "message" || value.role !== "assistant" || !Array.isArray(value.content)) return [];
28334
28454
  const id = typeof value.id === "string" ? value.id : "";
28335
28455
  return [{ value, id, createdAt: timestamp(value.created_at), ordinal }];
28336
28456
  }));
28337
28457
  const artifacts = [];
28338
- let malformedArtifact = false;
28458
+ let rejection = null;
28339
28459
  for (const item of assistantItems) {
28340
28460
  const content = item.value.content;
28341
28461
  const machineText = content.flatMap((part) => {
@@ -28344,23 +28464,32 @@ async function findOnboardingProfile(args) {
28344
28464
  if (typeof part.transcript === "string") return [part.transcript];
28345
28465
  return [];
28346
28466
  }).join("\n");
28347
- const artifact2 = parseOnboardingArtifact(machineText);
28348
- if (artifact2) artifacts.push(artifact2);
28349
- else if (machineText.includes("m8t_onboarding")) malformedArtifact = true;
28467
+ const result = parseOnboardingArtifactResult(machineText);
28468
+ if (result.ok) artifacts.push(result.artifact);
28469
+ else if (machineText.includes("m8t_onboarding")) rejection ??= result.reason;
28350
28470
  }
28351
- if (malformedArtifact || artifacts.length !== 1) {
28352
- return { hadIntake: true, block: null, machineText: null, speechText: null };
28471
+ if (artifacts.length === 0) {
28472
+ return {
28473
+ hadIntake: true,
28474
+ block: null,
28475
+ machineText: null,
28476
+ speechText: null,
28477
+ rejection: rejection ?? "no-artifact"
28478
+ };
28353
28479
  }
28354
28480
  const artifact = artifacts[0];
28355
28481
  return {
28356
28482
  hadIntake: true,
28357
28483
  block: artifact.block,
28358
28484
  machineText: artifact.machineText,
28359
- speechText: artifact.speechText
28485
+ speechText: artifact.speechText,
28486
+ rejection: null,
28487
+ ...artifacts.length > 1 ? { supersededCount: artifacts.length - 1 } : {}
28360
28488
  };
28361
28489
  }
28490
+ var dash = (v) => v?.trim() ? v.trim() : "\u2014";
28491
+ var bullet = (label, value) => value === void 0 ? [] : [`- **${label}:** ${dash(value)}`];
28362
28492
  function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOString()) {
28363
- const dash = (v) => v?.trim() ? v.trim() : "\u2014";
28364
28493
  const profileMd = [
28365
28494
  `---`,
28366
28495
  `type: memory`,
@@ -28375,22 +28504,30 @@ function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOStr
28375
28504
  ``,
28376
28505
  `_Seeded from the onboarding questionnaire._`,
28377
28506
  ``,
28378
- `- **Stage:** ${dash(block.company_stage)}`,
28379
- `- **ICP:** ${dash(block.icp)}`,
28380
- `- **Industry:** ${dash(block.industry)}`,
28381
- `- **Team size:** ${dash(block.team_size)}`,
28507
+ ...bullet("Company", block.company_name),
28508
+ ...bullet("Stage", block.company_stage),
28509
+ ...bullet("ICP", block.icp),
28510
+ ...bullet("Industry", block.industry),
28511
+ ...bullet("Team size", block.team_size),
28382
28512
  ``,
28383
28513
  `## Context`,
28384
28514
  ``,
28385
- block.context.trim(),
28515
+ (block.context ?? "").trim(),
28386
28516
  ``
28387
28517
  ].join("\n");
28388
- const bits = [block.company_stage.trim(), block.industry?.trim(), block.icp?.trim() ? `ICP ${block.icp.trim()}` : void 0].filter(Boolean).join(" \xB7 ");
28389
- const memoryIndexLine = `- \`${COMPANY_PROFILE_PATH}\` \u2014 **Company profile**: ${bits}. (seeded from onboarding)`;
28518
+ const bits = [
28519
+ block.company_name?.trim(),
28520
+ block.company_stage?.trim(),
28521
+ block.industry?.trim(),
28522
+ block.icp?.trim() ? `ICP ${block.icp.trim()}` : void 0
28523
+ ].filter(Boolean).join(" \xB7 ");
28524
+ const summary = bits || (block.schema_version === "3" ? "no details captured at intake" : "");
28525
+ const memoryIndexLine = `- \`${COMPANY_PROFILE_PATH}\` \u2014 **Company profile**: ${summary}. (seeded from onboarding)`;
28390
28526
  return { profileMd, memoryIndexLine };
28391
28527
  }
28392
28528
  var FOUNDER_RECORD_PATH = "memory/founder.md";
28393
- var NOT_CAPTURED = "_not captured yet \u2014 add it anytime (run `m8t bootstrap seed-profile`, or just tell me)_";
28529
+ var NOT_CAPTURED = "_not captured yet \u2014 just tell me and I'll add it_";
28530
+ var REGION_UNKNOWN_AT_INTAKE = "_not known at intake \u2014 filled in when the request is filed_";
28394
28531
  function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).toISOString()) {
28395
28532
  const pick = (...vals) => vals.map((v) => v?.trim()).find(Boolean) ?? "";
28396
28533
  const founderName = pick(block.founder_name, inputs.azIdentity?.name);
@@ -28398,8 +28535,29 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
28398
28535
  const advisorName = (block.advisor_name ?? "").trim();
28399
28536
  const advisorEmail = (block.advisor_email ?? "").trim();
28400
28537
  const subscription = (inputs.subscriptionId ?? "").trim();
28401
- const teamSize = (block.team_size ?? "").trim();
28402
28538
  const advisorRendered = advisorName || advisorEmail ? [advisorName, advisorEmail ? `<${advisorEmail}>` : ""].filter(Boolean).join(" ") : NOT_CAPTURED;
28539
+ const request = block.pending_requests?.[0];
28540
+ const requestLines = request === void 0 ? [] : (() => {
28541
+ const modelLabel = request.model.trim() ? `\`${request.model.trim()}\`` : "(model not recorded)";
28542
+ const consentLabel = request.consent === "submit" ? "submit the request" : "prepare it only, don't submit";
28543
+ const addressLine = request.company_address === null ? "not provided \u2014 the founder skipped it" : [
28544
+ request.company_address.address,
28545
+ request.company_address.city,
28546
+ request.company_address.postal_code,
28547
+ request.company_address.country
28548
+ ].map((part) => part.trim()).filter(Boolean).join(", ") || "not provided";
28549
+ return [
28550
+ ``,
28551
+ `## Requested at onboarding`,
28552
+ ``,
28553
+ `_The founder asked for this during setup. Act on it when they bring it up._`,
28554
+ ``,
28555
+ `- **What:** ${request.type} increase for ${modelLabel}`,
28556
+ `- **Region:** ${request.region.trim() || REGION_UNKNOWN_AT_INTAKE}`,
28557
+ `- **Consent:** ${consentLabel}`,
28558
+ `- **Company address:** ${addressLine}`
28559
+ ];
28560
+ })();
28403
28561
  const founderMd = [
28404
28562
  `---`,
28405
28563
  `type: memory`,
@@ -28418,7 +28576,8 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
28418
28576
  `- **Founder email (company_email):** ${founderEmail || NOT_CAPTURED}`,
28419
28577
  `- **Microsoft Startup Advisor (SA):** ${advisorRendered}`,
28420
28578
  `- **Azure subscription:** ${subscription || NOT_CAPTURED}`,
28421
- `- **Team size:** ${teamSize || "\u2014"}`,
28579
+ ...bullet("Team size", block.team_size),
28580
+ ...requestLines,
28422
28581
  ``
28423
28582
  ].join("\n");
28424
28583
  const idxAdvisor = advisorEmail || advisorName || "\u2014";
@@ -28427,7 +28586,168 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
28427
28586
  return { founderMd, memoryIndexLine };
28428
28587
  }
28429
28588
 
28589
+ // src/lib/model-cascade.ts
28590
+ var WHITELIST = [
28591
+ { model: "gpt-5.6-luna", format: "OpenAI", version: "2026-07-09", capacity: 100 },
28592
+ { model: "gpt-5.6-sol", format: "OpenAI", version: "2026-07-09", capacity: 100 },
28593
+ { model: "gpt-5.6-terra", format: "OpenAI", version: "2026-07-09", capacity: 100 },
28594
+ { model: "gpt-5.4", format: "OpenAI", version: "2026-03-05", capacity: 100 },
28595
+ { model: "grok-4.3", format: "xAI", version: "1", capacity: 100 },
28596
+ { model: "gpt-4.1-mini", format: "OpenAI", version: "2025-04-14", capacity: 50 }
28597
+ ];
28598
+ var GLOBAL_STANDARD = "GlobalStandard";
28599
+ function planCascade(whitelist, catalog, quota) {
28600
+ if (!catalog.ok) {
28601
+ return {
28602
+ candidates: [],
28603
+ skipped: whitelist.map((r) => ({ model: r.model, outcome: "catalog-unverified", detail: catalog.error }))
28604
+ };
28605
+ }
28606
+ const rowsByName = /* @__PURE__ */ new Map();
28607
+ for (const row of catalog.rows) {
28608
+ const list = rowsByName.get(row.name);
28609
+ if (list) list.push(row);
28610
+ else rowsByName.set(row.name, [row]);
28611
+ }
28612
+ const candidates = [];
28613
+ const skipped = [];
28614
+ for (const rung of whitelist) {
28615
+ const rows = rowsByName.get(rung.model);
28616
+ if (!rows || rows.length === 0) {
28617
+ skipped.push({ model: rung.model, outcome: "not-in-catalog" });
28618
+ continue;
28619
+ }
28620
+ const agentRows = rows.filter((r) => r.agentsV2);
28621
+ if (agentRows.length === 0) {
28622
+ skipped.push({ model: rung.model, outcome: "not-agent-eligible" });
28623
+ continue;
28624
+ }
28625
+ if (!agentRows.some((r) => r.skus.includes(GLOBAL_STANDARD))) {
28626
+ skipped.push({ model: rung.model, outcome: "no-global-standard" });
28627
+ continue;
28628
+ }
28629
+ if (modelQuotaVerdict(quota, rung.model).verdict === "no_quota") {
28630
+ skipped.push({ model: rung.model, outcome: "no-quota" });
28631
+ continue;
28632
+ }
28633
+ candidates.push(rung);
28634
+ }
28635
+ return { candidates, skipped };
28636
+ }
28637
+ var QUOTA_RE = /insufficient\s*quota|not enough quota|quota limit|quota\b[^.]{0,40}exceed|exceed\w*\b[^.]{0,40}quota|capacity[^.]{0,40}exceed|exceed\w*\b[^.]{0,40}capacity/i;
28638
+ var REGION_RE = /not available in|not supported in|NotAvailableInRegion|InvalidResourceLocation/i;
28639
+ var CONFLICT_RE = /\bconflict\b|\b409\b/i;
28640
+ function classifyDeployError(message) {
28641
+ if (QUOTA_RE.test(message)) return "deploy-rejected-quota";
28642
+ if (REGION_RE.test(message)) return "deploy-rejected-region";
28643
+ return "deploy-unverified";
28644
+ }
28645
+ async function walkCascade(whitelist, plan, deploy, opts) {
28646
+ const outcomes = /* @__PURE__ */ new Map();
28647
+ for (const s of plan.skipped) outcomes.set(s.model, s);
28648
+ let chosen = null;
28649
+ let aborted = false;
28650
+ for (const rung of plan.candidates) {
28651
+ if (chosen) break;
28652
+ if (opts.now() >= opts.deadlineAt) {
28653
+ aborted = true;
28654
+ break;
28655
+ }
28656
+ let row;
28657
+ try {
28658
+ await deploy(rung);
28659
+ row = { model: rung.model, outcome: "deployed" };
28660
+ chosen = rung;
28661
+ } catch (e) {
28662
+ const msg = e instanceof Error ? e.message : String(e);
28663
+ if (CONFLICT_RE.test(msg)) {
28664
+ try {
28665
+ await deploy(rung);
28666
+ row = { model: rung.model, outcome: "deployed" };
28667
+ chosen = rung;
28668
+ } catch (e2) {
28669
+ const msg2 = e2 instanceof Error ? e2.message : String(e2);
28670
+ row = {
28671
+ model: rung.model,
28672
+ outcome: CONFLICT_RE.test(msg2) ? "deploy-unverified" : classifyDeployError(msg2),
28673
+ detail: msg2
28674
+ };
28675
+ }
28676
+ } else {
28677
+ row = { model: rung.model, outcome: classifyDeployError(msg), detail: msg };
28678
+ }
28679
+ }
28680
+ outcomes.set(rung.model, row);
28681
+ opts.onRung?.(row.model, row.outcome);
28682
+ }
28683
+ const trace = whitelist.map(
28684
+ (r) => outcomes.get(r.model) ?? { model: r.model, outcome: aborted ? "aborted-deadline" : "not-reached" }
28685
+ );
28686
+ return { chosen, trace };
28687
+ }
28688
+ var QUOTA_INVITATION = "You can offer to help request that quota.";
28689
+ var CERTAIN_UNAVAILABLE = /* @__PURE__ */ new Set([
28690
+ "not-in-catalog",
28691
+ "not-agent-eligible",
28692
+ "no-global-standard",
28693
+ "deploy-rejected-region"
28694
+ ]);
28695
+ var QUOTA_BLOCKED = /* @__PURE__ */ new Set(["no-quota", "deploy-rejected-quota"]);
28696
+ function decideNote(whitelist, result) {
28697
+ const chosenIdx = result.chosen ? whitelist.findIndex((r) => r.model === result.chosen?.model) : whitelist.length;
28698
+ const better = result.trace.slice(0, Math.max(chosenIdx, 0));
28699
+ const runningModel = result.chosen?.model ?? null;
28700
+ if (chosenIdx === 0) {
28701
+ return { status: "top", runningModel, pitchModel: null, unavailableAbovePitch: [] };
28702
+ }
28703
+ const pitch = better.find((t) => QUOTA_BLOCKED.has(t.outcome));
28704
+ if (pitch) {
28705
+ const pitchIdx = better.findIndex((t) => t.model === pitch.model);
28706
+ return {
28707
+ status: "lesser-quota",
28708
+ runningModel,
28709
+ pitchModel: pitch.model,
28710
+ unavailableAbovePitch: better.slice(0, pitchIdx).filter((t) => CERTAIN_UNAVAILABLE.has(t.outcome)).map((t) => t.model)
28711
+ };
28712
+ }
28713
+ const allCertain = better.length > 0 && better.every((t) => CERTAIN_UNAVAILABLE.has(t.outcome));
28714
+ return {
28715
+ status: allCertain ? "lesser-unavailable" : "lesser-unverified",
28716
+ runningModel,
28717
+ pitchModel: null,
28718
+ unavailableAbovePitch: []
28719
+ };
28720
+ }
28721
+ var listNames = (names) => names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
28722
+ function renderChosenModelNote(d) {
28723
+ const subject = d.runningModel ? `You are running on ${d.runningModel}.` : "You are running on this install's default model.";
28724
+ switch (d.status) {
28725
+ case "top":
28726
+ return `${subject} That is the best available model for this install.`;
28727
+ case "lesser-quota": {
28728
+ const aside = d.unavailableAbovePitch.length > 0 ? ` ${listNames(d.unavailableAbovePitch)} ${d.unavailableAbovePitch.length > 1 ? "are" : "is"} not offered for this install.` : "";
28729
+ return `${subject} A stronger model, ${d.pitchModel ?? ""}, is offered here, but this subscription has no quota for it.${aside} ${QUOTA_INVITATION}`;
28730
+ }
28731
+ case "lesser-unavailable":
28732
+ return `${subject} No stronger model is offered for this install right now.`;
28733
+ case "lesser-unverified":
28734
+ return `${subject} It was not possible to check which stronger models this subscription can run, so do not make claims about what is or is not available.`;
28735
+ }
28736
+ }
28737
+
28430
28738
  // src/lib/founder-identity.ts
28739
+ var INVENTORY_INVITATION = "You can offer to walk the founder through these resources.";
28740
+ var NOTE_INVITATIONS = [QUOTA_INVITATION, INVENTORY_INVITATION];
28741
+ function escapeRegExp(value) {
28742
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28743
+ }
28744
+ function stripNoteInvitations(value) {
28745
+ let out = value;
28746
+ for (const sentence of NOTE_INVITATIONS) {
28747
+ out = out.replace(new RegExp(escapeRegExp(sentence), "gi"), " ");
28748
+ }
28749
+ return out.replace(/\s{2,}/g, " ").trim();
28750
+ }
28431
28751
  function deriveEmailCandidate(raw) {
28432
28752
  const mail = (raw.mail ?? "").trim();
28433
28753
  if (mail) return mail;
@@ -28459,11 +28779,13 @@ async function getSignedInUserIdentity(runAzImpl = runAz) {
28459
28779
  }
28460
28780
  }
28461
28781
  function composeFounderIdentityNote(id) {
28462
- if (id.name && id.email) {
28463
- return `You're speaking with **${id.name}**, signed in to Azure (their email looks like **${id.email}**). Open by confirming both, in your own voice \u2014 e.g. "I see you're signed in as ${id.name}, and I'll use ${id.email} for your advisor handoffs and cost reports \u2014 is that right, or should I change either?" Record exactly what they approve or correct.`;
28782
+ const name = stripNoteInvitations(id.name);
28783
+ const email = stripNoteInvitations(id.email);
28784
+ if (name && email) {
28785
+ return `You're speaking with **${name}**, signed in to Azure (their email looks like **${email}**). Open by confirming both, in your own voice \u2014 e.g. "I see you're signed in as ${name}, and I'll use ${email} for your advisor handoffs and cost reports \u2014 is that right, or should I change either?" Record exactly what they approve or correct.`;
28464
28786
  }
28465
- if (id.name) {
28466
- return `You're speaking with **${id.name}** (signed in to Azure). Confirm their name, then ask for the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
28787
+ if (name) {
28788
+ return `You're speaking with **${name}** (signed in to Azure). Confirm their name, then ask for the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
28467
28789
  }
28468
28790
  return `Ask the founder their name and the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
28469
28791
  }
@@ -28663,7 +28985,7 @@ async function reactiveSeedOnFinish(args) {
28663
28985
  if (!hadIntake) return;
28664
28986
  (args.spawnWatch ?? spawnDetachedSeedWatch)();
28665
28987
  args.stdout(
28666
- `${colors.dim("\u2139 Your advisors will pick up your company profile when you finish the questionnaire (watching in the background).")}
28988
+ `${colors.dim("\u2139 Your advisors will pick up your company profile when you finish the intake (watching in the background).")}
28667
28989
  ${colors.hint("or run:")} m8t bootstrap seed-profile
28668
28990
  `
28669
28991
  );
@@ -28705,7 +29027,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
28705
29027
  static paths = [["bootstrap", "finish"]];
28706
29028
  static usage = Command55.Usage({
28707
29029
  description: "Point your local tools at the now-live platform (repo-root marker, discovery cache, next steps).",
28708
- details: "Final step of `m8t bootstrap` (after the install reaches done). Writes ~/.m8t/repo-root, points your local tools at the live gateway, and \u2014 if you completed the onboarding questionnaire \u2014 seeds your brain-backed advisor's brain with your company profile (best-effort; never blocks finish). Run `m8t bootstrap seed-profile` to seed manually later.",
29030
+ details: "Final step of `m8t bootstrap` (after the install reaches done). Writes ~/.m8t/repo-root, points your local tools at the live gateway, and \u2014 if you completed the onboarding intake \u2014 seeds your brain-backed advisor's brain with your company profile (best-effort; never blocks finish). Run `m8t bootstrap seed-profile` to seed manually later.",
28709
29031
  examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
28710
29032
  });
28711
29033
  repoRoot = Option52.String("--repo-root");
@@ -28827,17 +29149,17 @@ import { spawn as spawn7, spawnSync as spawnSync6 } from "child_process";
28827
29149
  init_errors();
28828
29150
  init_rbac();
28829
29151
 
28830
- // src/lib/simple-stacey.ts
28831
- var SIMPLE_STACEY_PERSONA = "startup-advisor-intake";
28832
- var SIMPLE_STACEY_AGENT = "stacey-intake";
28833
- async function deploySimpleStacey(args) {
29152
+ // src/lib/intake-agent.ts
29153
+ var INTAKE_PERSONA = "azure-advisor-intake";
29154
+ var INTAKE_AGENT = "azzy-intake";
29155
+ async function deployIntakeAgent(args) {
28834
29156
  try {
28835
29157
  return await deployPromptAdvisor({
28836
29158
  credential: args.credential,
28837
29159
  endpoint: args.endpoint,
28838
29160
  repoRoot: args.repoRoot,
28839
- persona: SIMPLE_STACEY_PERSONA,
28840
- agentName: SIMPLE_STACEY_AGENT,
29161
+ persona: INTAKE_PERSONA,
29162
+ agentName: INTAKE_AGENT,
28841
29163
  model: args.model,
28842
29164
  fieldOverrides: args.fieldOverrides
28843
29165
  });
@@ -28847,7 +29169,7 @@ async function deploySimpleStacey(args) {
28847
29169
  if (err.code === "ADVISOR_PERSONA_MISSING") {
28848
29170
  const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
28849
29171
  throw new LocalCliError2({
28850
- code: "SIMPLE_STACEY_PERSONA_MISSING",
29172
+ code: "INTAKE_PERSONA_MISSING",
28851
29173
  message: err.message,
28852
29174
  hint: err.hint,
28853
29175
  cause: err.cause
@@ -28856,7 +29178,7 @@ async function deploySimpleStacey(args) {
28856
29178
  if (err.code === "ADVISOR_NO_MODEL") {
28857
29179
  const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
28858
29180
  throw new LocalCliError2({
28859
- code: "SIMPLE_STACEY_NO_MODEL",
29181
+ code: "INTAKE_NO_MODEL",
28860
29182
  message: err.message,
28861
29183
  hint: err.hint,
28862
29184
  cause: err.cause
@@ -28865,7 +29187,7 @@ async function deploySimpleStacey(args) {
28865
29187
  if (err.code === "ADVISOR_BAD_EFFORT") {
28866
29188
  const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
28867
29189
  throw new LocalCliError2({
28868
- code: "SIMPLE_STACEY_BAD_EFFORT",
29190
+ code: "INTAKE_BAD_EFFORT",
28869
29191
  message: err.message,
28870
29192
  hint: err.hint,
28871
29193
  cause: err.cause
@@ -28897,13 +29219,13 @@ function isAuthorizationShapedError(error) {
28897
29219
  }
28898
29220
  return false;
28899
29221
  }
28900
- async function deploySimpleStaceyWithRetry(args) {
29222
+ async function deployIntakeAgentWithRetry(args) {
28901
29223
  const maxWaitMs = args.maxWaitMs ?? 3e5;
28902
29224
  const intervalMs = args.intervalMs ?? 1e4;
28903
29225
  const deadline = Date.now() + maxWaitMs;
28904
29226
  for (; ; ) {
28905
29227
  try {
28906
- return await deploySimpleStacey({
29228
+ return await deployIntakeAgent({
28907
29229
  credential: args.credential,
28908
29230
  endpoint: args.endpoint,
28909
29231
  repoRoot: args.repoRoot,
@@ -28916,7 +29238,7 @@ async function deploySimpleStaceyWithRetry(args) {
28916
29238
  if (remainingMs <= 0) {
28917
29239
  throw new LocalCliError({
28918
29240
  code: "BOOTSTRAP_UI_ROLE_PROPAGATION_TIMEOUT",
28919
- message: "Timed out waiting for Azure role propagation before deploying Simple Stacey.",
29241
+ message: "Timed out waiting for Azure role propagation before deploying the intake agent.",
28920
29242
  hint: "Azure role propagation can take a few minutes - re-run 'm8t bootstrap ui' (idempotent).",
28921
29243
  cause: error
28922
29244
  });
@@ -28946,7 +29268,7 @@ async function resolveFoundryEndpointWithWait(args, opts = {}) {
28946
29268
  if (e instanceof LocalCliError && e.code === "FOUNDRY_PROJECT_MULTIPLE") {
28947
29269
  throw new LocalCliError({
28948
29270
  code: "BOOTSTRAP_UI_MULTIPLE_PROJECTS",
28949
- message: `${e.message} bootstrap ui can't pick one safely (it must not deploy Simple Stacey into the wrong project).`,
29271
+ message: `${e.message} bootstrap ui can't pick one safely (it must not deploy the intake agent into the wrong project).`,
28950
29272
  hint: `Re-run with the throwaway project's endpoint, e.g. 'm8t bootstrap ui --repo-root "$(pwd)" --endpoint https://<account>.services.ai.azure.com/api/projects/m8t'. The installer prints it in 'm8t bootstrap status'.`,
28951
29273
  cause: e
28952
29274
  });
@@ -29003,7 +29325,7 @@ function writeWebEnvLocal(args) {
29003
29325
  });
29004
29326
  }
29005
29327
  const body = [
29006
- "# Written by `m8t bootstrap ui` \u2014 local onboarding run (Simple Stacey).",
29328
+ "# Written by `m8t bootstrap ui` \u2014 local onboarding run (the onboarding intake agent).",
29007
29329
  "# Text-first: the intake voice stack is off unless the run passed --voice.",
29008
29330
  "# STORAGE unset \u2192 gateway boot skipped; the webapp forwards your MSAL token to Foundry.",
29009
29331
  `AZURE_TENANT_ID=${args.tenantId}`,
@@ -29303,6 +29625,13 @@ function stopOnboardingUi(home = os17.homedir()) {
29303
29625
  const relayStopped = stopPidFile(onboardingRelayPaths(home).pidPath);
29304
29626
  return uiStopped || relayStopped;
29305
29627
  }
29628
+ function buildIntakeFieldOverrides(args) {
29629
+ return {
29630
+ founder_identity_note: args.founderIdentityNote,
29631
+ chosen_model_note: args.chosenModelNote,
29632
+ ...args.inventoryNote ? { subscription_inventory_note: args.inventoryNote } : {}
29633
+ };
29634
+ }
29306
29635
 
29307
29636
  // src/lib/model-catalog.ts
29308
29637
  async function listAgentModels(region) {
@@ -29337,154 +29666,6 @@ async function listAgentModels(region) {
29337
29666
  }
29338
29667
  }
29339
29668
 
29340
- // src/lib/model-cascade.ts
29341
- var WHITELIST = [
29342
- { model: "gpt-5.6-luna", format: "OpenAI", version: "2026-07-09", capacity: 100 },
29343
- { model: "gpt-5.6-sol", format: "OpenAI", version: "2026-07-09", capacity: 100 },
29344
- { model: "gpt-5.6-terra", format: "OpenAI", version: "2026-07-09", capacity: 100 },
29345
- { model: "gpt-5.4", format: "OpenAI", version: "2026-03-05", capacity: 100 },
29346
- { model: "grok-4.3", format: "xAI", version: "1", capacity: 100 },
29347
- { model: "gpt-4.1-mini", format: "OpenAI", version: "2025-04-14", capacity: 50 }
29348
- ];
29349
- var GLOBAL_STANDARD = "GlobalStandard";
29350
- function planCascade(whitelist, catalog, quota) {
29351
- if (!catalog.ok) {
29352
- return {
29353
- candidates: [],
29354
- skipped: whitelist.map((r) => ({ model: r.model, outcome: "catalog-unverified", detail: catalog.error }))
29355
- };
29356
- }
29357
- const rowsByName = /* @__PURE__ */ new Map();
29358
- for (const row of catalog.rows) {
29359
- const list = rowsByName.get(row.name);
29360
- if (list) list.push(row);
29361
- else rowsByName.set(row.name, [row]);
29362
- }
29363
- const candidates = [];
29364
- const skipped = [];
29365
- for (const rung of whitelist) {
29366
- const rows = rowsByName.get(rung.model);
29367
- if (!rows || rows.length === 0) {
29368
- skipped.push({ model: rung.model, outcome: "not-in-catalog" });
29369
- continue;
29370
- }
29371
- const agentRows = rows.filter((r) => r.agentsV2);
29372
- if (agentRows.length === 0) {
29373
- skipped.push({ model: rung.model, outcome: "not-agent-eligible" });
29374
- continue;
29375
- }
29376
- if (!agentRows.some((r) => r.skus.includes(GLOBAL_STANDARD))) {
29377
- skipped.push({ model: rung.model, outcome: "no-global-standard" });
29378
- continue;
29379
- }
29380
- if (modelQuotaVerdict(quota, rung.model).verdict === "no_quota") {
29381
- skipped.push({ model: rung.model, outcome: "no-quota" });
29382
- continue;
29383
- }
29384
- candidates.push(rung);
29385
- }
29386
- return { candidates, skipped };
29387
- }
29388
- var QUOTA_RE = /insufficient\s*quota|not enough quota|quota limit|quota\b[^.]{0,40}exceed|exceed\w*\b[^.]{0,40}quota|capacity[^.]{0,40}exceed|exceed\w*\b[^.]{0,40}capacity/i;
29389
- var REGION_RE = /not available in|not supported in|NotAvailableInRegion|InvalidResourceLocation/i;
29390
- var CONFLICT_RE = /\bconflict\b|\b409\b/i;
29391
- function classifyDeployError(message) {
29392
- if (QUOTA_RE.test(message)) return "deploy-rejected-quota";
29393
- if (REGION_RE.test(message)) return "deploy-rejected-region";
29394
- return "deploy-unverified";
29395
- }
29396
- async function walkCascade(whitelist, plan, deploy, opts) {
29397
- const outcomes = /* @__PURE__ */ new Map();
29398
- for (const s of plan.skipped) outcomes.set(s.model, s);
29399
- let chosen = null;
29400
- let aborted = false;
29401
- for (const rung of plan.candidates) {
29402
- if (chosen) break;
29403
- if (opts.now() >= opts.deadlineAt) {
29404
- aborted = true;
29405
- break;
29406
- }
29407
- let row;
29408
- try {
29409
- await deploy(rung);
29410
- row = { model: rung.model, outcome: "deployed" };
29411
- chosen = rung;
29412
- } catch (e) {
29413
- const msg = e instanceof Error ? e.message : String(e);
29414
- if (CONFLICT_RE.test(msg)) {
29415
- try {
29416
- await deploy(rung);
29417
- row = { model: rung.model, outcome: "deployed" };
29418
- chosen = rung;
29419
- } catch (e2) {
29420
- const msg2 = e2 instanceof Error ? e2.message : String(e2);
29421
- row = {
29422
- model: rung.model,
29423
- outcome: CONFLICT_RE.test(msg2) ? "deploy-unverified" : classifyDeployError(msg2),
29424
- detail: msg2
29425
- };
29426
- }
29427
- } else {
29428
- row = { model: rung.model, outcome: classifyDeployError(msg), detail: msg };
29429
- }
29430
- }
29431
- outcomes.set(rung.model, row);
29432
- opts.onRung?.(row.model, row.outcome);
29433
- }
29434
- const trace = whitelist.map(
29435
- (r) => outcomes.get(r.model) ?? { model: r.model, outcome: aborted ? "aborted-deadline" : "not-reached" }
29436
- );
29437
- return { chosen, trace };
29438
- }
29439
- var CERTAIN_UNAVAILABLE = /* @__PURE__ */ new Set([
29440
- "not-in-catalog",
29441
- "not-agent-eligible",
29442
- "no-global-standard",
29443
- "deploy-rejected-region"
29444
- ]);
29445
- var QUOTA_BLOCKED = /* @__PURE__ */ new Set(["no-quota", "deploy-rejected-quota"]);
29446
- function decideNote(whitelist, result) {
29447
- const chosenIdx = result.chosen ? whitelist.findIndex((r) => r.model === result.chosen?.model) : whitelist.length;
29448
- const better = result.trace.slice(0, Math.max(chosenIdx, 0));
29449
- const runningModel = result.chosen?.model ?? null;
29450
- if (chosenIdx === 0) {
29451
- return { status: "top", runningModel, pitchModel: null, unavailableAbovePitch: [] };
29452
- }
29453
- const pitch = better.find((t) => QUOTA_BLOCKED.has(t.outcome));
29454
- if (pitch) {
29455
- const pitchIdx = better.findIndex((t) => t.model === pitch.model);
29456
- return {
29457
- status: "lesser-quota",
29458
- runningModel,
29459
- pitchModel: pitch.model,
29460
- unavailableAbovePitch: better.slice(0, pitchIdx).filter((t) => CERTAIN_UNAVAILABLE.has(t.outcome)).map((t) => t.model)
29461
- };
29462
- }
29463
- const allCertain = better.length > 0 && better.every((t) => CERTAIN_UNAVAILABLE.has(t.outcome));
29464
- return {
29465
- status: allCertain ? "lesser-unavailable" : "lesser-unverified",
29466
- runningModel,
29467
- pitchModel: null,
29468
- unavailableAbovePitch: []
29469
- };
29470
- }
29471
- var listNames = (names) => names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
29472
- function renderChosenModelNote(d) {
29473
- const subject = d.runningModel ? `You are running on ${d.runningModel}.` : "You are running on this install's default model.";
29474
- switch (d.status) {
29475
- case "top":
29476
- return `${subject} That is the best available model for this install.`;
29477
- case "lesser-quota": {
29478
- const aside = d.unavailableAbovePitch.length > 0 ? ` ${listNames(d.unavailableAbovePitch)} ${d.unavailableAbovePitch.length > 1 ? "are" : "is"} not offered for this install.` : "";
29479
- return `${subject} A stronger model, ${d.pitchModel ?? ""}, is offered here, but this subscription has no quota for it.${aside} You can offer to help request that quota.`;
29480
- }
29481
- case "lesser-unavailable":
29482
- return `${subject} No stronger model is offered for this install right now.`;
29483
- case "lesser-unverified":
29484
- return `${subject} It was not possible to check which stronger models this subscription can run, so do not make claims about what is or is not available.`;
29485
- }
29486
- }
29487
-
29488
29669
  // src/lib/intake-model.ts
29489
29670
  var DEFAULT_DEADLINE_MS = 18e4;
29490
29671
  var SCOPE_RE = /\/resourceGroups\/([^/]+)\/providers\/Microsoft\.CognitiveServices\/accounts\/([^/]+)/i;
@@ -29555,29 +29736,219 @@ async function resolveIntakeModel(args) {
29555
29736
  }
29556
29737
  }
29557
29738
 
29739
+ // src/lib/inventory-summary.ts
29740
+ var HEAVY_THRESHOLD = 10;
29741
+ var INVENTORY_INVITATION2 = "You can offer to walk the founder through these resources.";
29742
+ var NOTABLE_TYPES = {
29743
+ // storage
29744
+ "microsoft.storage/storageaccounts": "storage accounts",
29745
+ // compute
29746
+ "microsoft.compute/virtualmachines": "virtual machines",
29747
+ "microsoft.compute/virtualmachinescalesets": "virtual machine scale sets",
29748
+ "microsoft.app/containerapps": "container apps",
29749
+ "microsoft.containerservice/managedclusters": "Kubernetes clusters",
29750
+ "microsoft.web/sites": "web apps",
29751
+ // data (shared noun)
29752
+ "microsoft.sql/servers": "databases",
29753
+ "microsoft.dbforpostgresql/servers": "databases",
29754
+ "microsoft.dbforpostgresql/flexibleservers": "databases",
29755
+ "microsoft.dbformysql/servers": "databases",
29756
+ "microsoft.dbformysql/flexibleservers": "databases",
29757
+ "microsoft.documentdb/databaseaccounts": "databases",
29758
+ "microsoft.cache/redis": "databases",
29759
+ // AI (shared noun)
29760
+ "microsoft.cognitiveservices/accounts": "AI services",
29761
+ "microsoft.machinelearningservices/workspaces": "AI services",
29762
+ // key vaults
29763
+ "microsoft.keyvault/vaults": "key vaults",
29764
+ // container registries
29765
+ "microsoft.containerregistry/registries": "container registries",
29766
+ // messaging (shared noun)
29767
+ "microsoft.servicebus/namespaces": "messaging namespaces",
29768
+ "microsoft.eventhub/namespaces": "messaging namespaces",
29769
+ // networking (shared noun); public DNS zones only — private zones are plumbing
29770
+ "microsoft.network/virtualnetworks": "networking resources",
29771
+ "microsoft.network/loadbalancers": "networking resources",
29772
+ "microsoft.network/applicationgateways": "networking resources",
29773
+ "microsoft.network/dnszones": "networking resources"
29774
+ };
29775
+ var MS_PER_DAY = 864e5;
29776
+ var REGION_MAJORITY = 0.6;
29777
+ var AGE_BUCKETS = [
29778
+ { maxDays: 31, phrase: "all created within the last month" },
29779
+ { maxDays: 93, phrase: "the oldest created a couple of months ago" },
29780
+ { maxDays: 186, phrase: "the oldest created several months ago" },
29781
+ { maxDays: 365, phrase: "the oldest created about a year ago" },
29782
+ { maxDays: 730, phrase: "the oldest created over a year ago" }
29783
+ ];
29784
+ var OLDEST_FALLBACK = "the oldest created a couple of years ago";
29785
+ function ageBucket(days) {
29786
+ for (const b of AGE_BUCKETS) if (days <= b.maxDays) return b.phrase;
29787
+ return OLDEST_FALLBACK;
29788
+ }
29789
+ function summarizeInventory(rows, installResourceGroup, now) {
29790
+ const installRg = installResourceGroup.toLowerCase();
29791
+ const notable = rows.filter(
29792
+ (r) => r.resourceGroup.toLowerCase() !== installRg && Object.hasOwn(NOTABLE_TYPES, r.type.toLowerCase())
29793
+ );
29794
+ const notableCount = notable.length;
29795
+ const byNoun = /* @__PURE__ */ new Map();
29796
+ for (const r of notable) {
29797
+ const noun = NOTABLE_TYPES[r.type.toLowerCase()];
29798
+ byNoun.set(noun, (byNoun.get(noun) ?? 0) + 1);
29799
+ }
29800
+ const categories = [...byNoun.entries()].map(([noun, count]) => ({ noun, count })).sort((a, b) => b.count - a.count || a.noun.localeCompare(b.noun));
29801
+ const byRegion = /* @__PURE__ */ new Map();
29802
+ for (const r of notable) {
29803
+ if (!r.location) continue;
29804
+ const loc = r.location.toLowerCase();
29805
+ byRegion.set(loc, (byRegion.get(loc) ?? 0) + 1);
29806
+ }
29807
+ const regionSpread = byRegion.size;
29808
+ let topRegion = null;
29809
+ for (const [region, count] of byRegion) {
29810
+ if (notableCount > 0 && count / notableCount >= REGION_MAJORITY) {
29811
+ topRegion = region;
29812
+ break;
29813
+ }
29814
+ }
29815
+ let oldestMs = null;
29816
+ for (const r of notable) {
29817
+ if (!r.createdTime) continue;
29818
+ const t = Date.parse(r.createdTime);
29819
+ if (Number.isNaN(t)) continue;
29820
+ if (oldestMs === null || t < oldestMs) oldestMs = t;
29821
+ }
29822
+ const oldestBucket = oldestMs === null ? null : ageBucket((now - oldestMs) / MS_PER_DAY);
29823
+ return {
29824
+ verdict: notableCount >= HEAVY_THRESHOLD ? "heavy" : "light",
29825
+ notableCount,
29826
+ categories,
29827
+ topRegion,
29828
+ regionSpread,
29829
+ oldestBucket
29830
+ };
29831
+ }
29832
+ var LEAD = "I looked over what's already in this subscription, outside the resources m8t is setting up.";
29833
+ var MAX_NOUNS = 4;
29834
+ var REGION_NAMES = {
29835
+ eastus: "East US",
29836
+ eastus2: "East US 2",
29837
+ westus: "West US",
29838
+ westus2: "West US 2",
29839
+ westus3: "West US 3",
29840
+ centralus: "Central US",
29841
+ southcentralus: "South Central US",
29842
+ westeurope: "West Europe",
29843
+ northeurope: "North Europe",
29844
+ uksouth: "UK South",
29845
+ swedencentral: "Sweden Central",
29846
+ australiaeast: "Australia East",
29847
+ eastasia: "East Asia",
29848
+ southeastasia: "Southeast Asia"
29849
+ };
29850
+ function regionDisplay(region) {
29851
+ return REGION_NAMES[region.toLowerCase()] ?? region;
29852
+ }
29853
+ function joinList(items) {
29854
+ if (items.length <= 1) return items[0] ?? "";
29855
+ return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
29856
+ }
29857
+ function piecesClause(s) {
29858
+ const shown = s.categories.slice(0, MAX_NOUNS).map((c) => `${String(c.count)} ${c.noun}`);
29859
+ if (s.categories.length > MAX_NOUNS) {
29860
+ return `The notable pieces: ${shown.join(", ")}, and a handful of others`;
29861
+ }
29862
+ return `The notable pieces: ${joinList(shown)}`;
29863
+ }
29864
+ function regionAgeClause(s) {
29865
+ const region = s.topRegion ? `mostly in ${regionDisplay(s.topRegion)}` : s.regionSpread > 1 ? `spread across ${String(s.regionSpread)} regions` : "";
29866
+ const parts = [region, s.oldestBucket ?? ""].filter((p) => p !== "");
29867
+ return parts.length > 0 ? ` \u2014 ${parts.join(", ")}` : "";
29868
+ }
29869
+ function renderInventoryNote(summary) {
29870
+ const body = summary.notableCount === 0 ? `${LEAD} There are no notable pre-existing resources.` : `${LEAD} ${piecesClause(summary)}${regionAgeClause(summary)}.`;
29871
+ return summary.verdict === "heavy" ? `${body} ${INVENTORY_INVITATION2}` : body;
29872
+ }
29873
+
29874
+ // src/lib/subscription-inventory.ts
29875
+ async function listSubscriptionResources(subscriptionId) {
29876
+ let out;
29877
+ try {
29878
+ out = await runAz(["resource", "list", "--subscription", subscriptionId, "--output", "json"]);
29879
+ } catch (e) {
29880
+ const msg = e instanceof Error ? e.message : String(e);
29881
+ const denied = /AuthorizationFailed|Forbidden|\b403\b|does not have authorization/i.test(msg);
29882
+ return { ok: false, error: denied ? "denied" : "unavailable" };
29883
+ }
29884
+ let raw;
29885
+ try {
29886
+ raw = JSON.parse(out);
29887
+ } catch {
29888
+ return { ok: false, error: "malformed" };
29889
+ }
29890
+ if (!Array.isArray(raw) || raw.some((e) => typeof e !== "object" || e === null)) {
29891
+ return { ok: false, error: "malformed" };
29892
+ }
29893
+ const rows = raw.map((e) => ({
29894
+ type: e.type ?? "",
29895
+ resourceGroup: e.resourceGroup ?? "",
29896
+ location: e.location ?? "",
29897
+ createdTime: e.createdTime ?? null
29898
+ }));
29899
+ return { ok: true, rows };
29900
+ }
29901
+ var DEFAULT_DEADLINE_MS2 = 25e3;
29902
+ async function resolveSubscriptionInventory(args) {
29903
+ const now = args.now ?? (() => Date.now());
29904
+ const deadlineMs = args.deadlineMs ?? DEFAULT_DEADLINE_MS2;
29905
+ const scanFn = args.scanImpl ?? listSubscriptionResources;
29906
+ let timer;
29907
+ try {
29908
+ args.onNarrate?.("checking what's already in your subscription...");
29909
+ const scan2 = scanFn(args.subscriptionId);
29910
+ void scan2.catch(() => {
29911
+ });
29912
+ const timeout = new Promise((resolve3) => {
29913
+ timer = setTimeout(() => {
29914
+ resolve3("timeout");
29915
+ }, deadlineMs);
29916
+ });
29917
+ const raced = await Promise.race([scan2, timeout]);
29918
+ if (raced === "timeout" || !raced.ok) return {};
29919
+ const summary = summarizeInventory(raced.rows, args.installResourceGroup, now());
29920
+ return { note: renderInventoryNote(summary) };
29921
+ } catch {
29922
+ return {};
29923
+ } finally {
29924
+ if (timer) clearTimeout(timer);
29925
+ }
29926
+ }
29927
+
29558
29928
  // src/commands/bootstrap/ui.ts
29559
29929
  function renderDeploySuccess(version, envPath) {
29560
- return `${colors.success("\u2713")} Simple Stacey is live (stacey-intake v${version}).
29930
+ return `${colors.success("\u2713")} Azzy is live (${INTAKE_AGENT} v${version}).
29561
29931
  env: ${envPath}
29562
- Opening ${colors.field("http://localhost:3000")} in your browser \u2192 Sign in with Microsoft \u2192 chat with Stacey.
29932
+ Opening ${colors.field("http://localhost:3000")} in your browser \u2192 Sign in with Microsoft \u2192 chat with Azzy.
29563
29933
  ${colors.dim("(If it doesn't open, browse to http://localhost:3000 yourself.)")}
29564
29934
  ${colors.dim("First turn may say 'warming up' for a few minutes while access propagates \u2014 that's expected.")}
29565
29935
  `;
29566
29936
  }
29567
29937
  function renderDeployFailure(error) {
29568
29938
  const hint = error instanceof LocalCliError && error.hint ? error.hint : "Re-run 'm8t bootstrap ui' (idempotent).";
29569
- return `${colors.error("\u2717")} Simple Stacey deploy failed: ${error.message}
29939
+ return `${colors.error("\u2717")} Azzy deploy failed: ${error.message}
29570
29940
  ${colors.hint(hint)}
29571
29941
  `;
29572
29942
  }
29573
29943
  var BootstrapUiCommand = class extends M8tCommand {
29574
29944
  static paths = [["bootstrap", "ui"]];
29575
29945
  static usage = Command56.Usage({
29576
- description: "Deploy Simple Stacey + start the local onboarding chat UI in the background (returns immediately).",
29946
+ description: "Deploy Azzy + start the local onboarding chat UI in the background (returns immediately).",
29577
29947
  details: [
29578
29948
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
29579
- "installer's foundry-create phase, grants you Foundry data-plane access, deploys a no-brain",
29580
- "Simple Stacey (stacey-intake), writes apps/web/.env.local (chat-only unless --voice is set),",
29949
+ "installer's foundry-create phase, grants you Foundry data-plane access, deploys the no-brain",
29950
+ "onboarding intake agent (azzy-intake), writes apps/web/.env.local (voice relay config is added",
29951
+ "only when --voice is set \u2014 see --voice for what that does and does not affect),",
29581
29952
  "installs deps, and starts the webapp detached so a coding agent can continue without opening",
29582
29953
  "a separate terminal.",
29583
29954
  "",
@@ -29589,7 +29960,7 @@ var BootstrapUiCommand = class extends M8tCommand {
29589
29960
  ["Stop the running onboarding UI", "$0 bootstrap ui --stop"],
29590
29961
  ["Prep only (don't serve at all)", "$0 bootstrap ui --repo-root /path/to/m8t --prep-only"],
29591
29962
  ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"],
29592
- ["Experimental: enable the intake voice call", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
29963
+ ["Experimental: start the voice relay (no effect on the text-only intake)", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
29593
29964
  ]
29594
29965
  });
29595
29966
  repoRoot = Option53.String("--repo-root");
@@ -29602,7 +29973,7 @@ var BootstrapUiCommand = class extends M8tCommand {
29602
29973
  stop = Option53.Boolean("--stop", false);
29603
29974
  foreground = Option53.Boolean("--foreground", false);
29604
29975
  voice = Option53.Boolean("--voice", false, {
29605
- description: "Experimental/preview: enable the intake voice call and start the voice relay on :8790. Not a supported founder path \u2014 the intake is text-only by default."
29976
+ description: "Experimental: when serving, starts the voice relay and writes the intake voice env var. The onboarding intake is text-only and is unaffected by this flag \u2014 no voice worker is registered for it."
29606
29977
  });
29607
29978
  async executeCommand() {
29608
29979
  if (this.stop === true) {
@@ -29655,22 +30026,30 @@ var BootstrapUiCommand = class extends M8tCommand {
29655
30026
  const oid = await getSignedInUserOid();
29656
30027
  await ensureFounderFoundryRole({ credential: credential2, subscriptionId: state.subscriptionId, principalId: oid, accountScope });
29657
30028
  const identity = await getSignedInUserIdentity();
29658
- out("deploying Simple Stacey (stacey-intake) in the background...");
30029
+ out("deploying the intake agent (azzy-intake) in the background...");
29659
30030
  const deployOutcome = (async () => {
29660
- const choice = await resolveIntakeModel({
29661
- accountScope,
29662
- fallbackRegion: state.location,
29663
- onNarrate: out
29664
- });
29665
- return deploySimpleStaceyWithRetry({
30031
+ const [choice, inventory] = await Promise.all([
30032
+ resolveIntakeModel({
30033
+ accountScope,
30034
+ fallbackRegion: state.location,
30035
+ onNarrate: out
30036
+ }),
30037
+ resolveSubscriptionInventory({
30038
+ subscriptionId: state.subscriptionId,
30039
+ installResourceGroup: state.resourceGroup,
30040
+ onNarrate: out
30041
+ })
30042
+ ]);
30043
+ return deployIntakeAgentWithRetry({
29666
30044
  credential: credential2,
29667
30045
  endpoint,
29668
30046
  repoRoot,
29669
30047
  model: choice.model,
29670
- fieldOverrides: {
29671
- founder_identity_note: composeFounderIdentityNote(identity),
29672
- chosen_model_note: choice.note
29673
- },
30048
+ fieldOverrides: buildIntakeFieldOverrides({
30049
+ founderIdentityNote: composeFounderIdentityNote(identity),
30050
+ chosenModelNote: choice.note,
30051
+ inventoryNote: inventory.note
30052
+ }),
29674
30053
  onWait: out
29675
30054
  });
29676
30055
  })().then(
@@ -29759,16 +30138,16 @@ import { Command as Command57, Option as Option54 } from "clipanion";
29759
30138
  var BootstrapSeedProfileCommand = class extends M8tCommand {
29760
30139
  static paths = [["bootstrap", "seed-profile"]];
29761
30140
  static usage = Command57.Usage({
29762
- description: "Seed your advisors' brains with the founder + company profile from the onboarding questionnaire.",
29763
- details: "Reads the latest stacey-intake conversation, renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines), and commits them to both <org>/stacey-brain and <org>/azzy-brain via the GitHub App. Idempotent. --watch polls until the founder completes the questionnaire.",
30141
+ description: "Seed your advisors' brains with the founder + company profile from the onboarding intake.",
30142
+ details: "Reads the latest onboarding conversation, renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines), and commits them to both <org>/stacey-brain and <org>/azzy-brain via the GitHub App. Idempotent. --watch polls until the founder finishes the intake.",
29764
30143
  examples: [
29765
30144
  ["Seed now (idempotent)", "$0 bootstrap seed-profile"],
29766
30145
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
29767
30146
  ]
29768
30147
  });
29769
30148
  endpoint = Option54.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
29770
- brain = Option54.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
29771
- watch = Option54.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
30149
+ brain = Option54.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/azzy-brain." });
30150
+ watch = Option54.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
29772
30151
  timeout = Option54.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
29773
30152
  githubAppCreds = Option54.String("--github-app-creds");
29774
30153
  async executeCommand() {
@@ -29790,7 +30169,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
29790
30169
  const sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
29791
30170
  for (; ; ) {
29792
30171
  const token = await getFoundryToken();
29793
- const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
30172
+ const { hadIntake, block, rejection } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
29794
30173
  if (block) {
29795
30174
  const azIdentity = await getSignedInUserIdentity();
29796
30175
  await applyProfileToBrains({
@@ -29801,19 +30180,28 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
29801
30180
  subscriptionId: ctx.subscriptionId,
29802
30181
  azIdentity
29803
30182
  });
29804
- this.context.stdout.write(`${colors.success("\u2713")} seeded your advisors' brains (${ctx.brainRepos.join(", ")}) from your questionnaire.
30183
+ this.context.stdout.write(`${colors.success("\u2713")} seeded your advisors' brains (${ctx.brainRepos.join(", ")}) from your intake.
29805
30184
  `);
29806
30185
  return 0;
29807
30186
  }
29808
30187
  if (!watch || Date.now() >= deadline) {
30188
+ if (rejection !== null && rejection !== "no-artifact") {
30189
+ this.context.stderr.write(
30190
+ ` ${colors.dim(`found an onboarding block but could not read it \u2014 ${describeBlockRejection(rejection)}.`)}
30191
+ ${colors.hint("fix:")} delete this onboarding conversation in the Azure AI Foundry portal, then redo the intake and run: m8t bootstrap seed-profile
30192
+ `
30193
+ );
30194
+ return 3;
30195
+ }
30196
+ const why = hadIntake ? "intake not complete yet \u2014 no m8t_onboarding block found." : "no onboarding intake found.";
29809
30197
  this.context.stderr.write(
29810
- ` ${colors.dim(hadIntake ? "questionnaire not complete yet \u2014 no m8t_onboarding block found." : "no onboarding questionnaire found.")}
30198
+ ` ${colors.dim(why)}
29811
30199
  ${colors.hint("retry:")} m8t bootstrap seed-profile
29812
30200
  `
29813
30201
  );
29814
30202
  return 3;
29815
30203
  }
29816
- this.context.stderr.write(` ${colors.dim("waiting for the questionnaire to complete\u2026")}
30204
+ this.context.stderr.write(` ${colors.dim("waiting for the intake to complete\u2026")}
29817
30205
  `);
29818
30206
  await sleep5(2e4);
29819
30207
  }