@m8t-stack/cli 0.2.44 → 0.2.46

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
@@ -204,6 +204,19 @@ var init_founder_benchmark = __esm({
204
204
  }
205
205
  });
206
206
 
207
+ // ../../packages/api-contract/dist/esm/agent-roster.js
208
+ function productVisibilityFor(agentName) {
209
+ return PUBLIC_PRODUCT_AGENTS.has(agentName) ? "public" : "internal";
210
+ }
211
+ var INTAKE_AGENT_NAME, PUBLIC_PRODUCT_AGENTS;
212
+ var init_agent_roster = __esm({
213
+ "../../packages/api-contract/dist/esm/agent-roster.js"() {
214
+ "use strict";
215
+ INTAKE_AGENT_NAME = "azzy-intake";
216
+ PUBLIC_PRODUCT_AGENTS = /* @__PURE__ */ new Set(["stacey", "azzy"]);
217
+ }
218
+ });
219
+
207
220
  // ../../packages/api-contract/dist/esm/index.js
208
221
  var init_esm = __esm({
209
222
  "../../packages/api-contract/dist/esm/index.js"() {
@@ -220,6 +233,7 @@ var init_esm = __esm({
220
233
  init_a2a_card();
221
234
  init_brain_eval();
222
235
  init_founder_benchmark();
236
+ init_agent_roster();
223
237
  }
224
238
  });
225
239
 
@@ -948,7 +962,12 @@ async function createPromptVersion(args) {
948
962
  const metadata = {
949
963
  source: METADATA_SOURCE,
950
964
  kind: "prompt",
951
- persona: args.metadata.persona
965
+ persona: args.metadata.persona,
966
+ // Read by the web app to decide roster membership. Written on EVERY version:
967
+ // createVersion replaces version metadata wholesale rather than merging the
968
+ // previous version's, so omitting it here silently un-publishes an agent on
969
+ // its next re-deploy.
970
+ productVisibility: args.metadata.productVisibility
952
971
  };
953
972
  if (args.metadata.personaVersion !== null) metadata.personaVersion = args.metadata.personaVersion;
954
973
  if (args.metadata.fillableFieldValues !== void 0) {
@@ -1310,7 +1329,7 @@ var init_enable_hosted_brain = __esm({
1310
1329
  import { Builtins, Cli } from "clipanion";
1311
1330
 
1312
1331
  // src/lib/package-version.ts
1313
- var CLI_VERSION = "0.2.44";
1332
+ var CLI_VERSION = "0.2.46";
1314
1333
 
1315
1334
  // src/lib/render-error.ts
1316
1335
  init_errors();
@@ -16551,6 +16570,7 @@ import * as path12 from "path";
16551
16570
  import { parse as parseYaml6 } from "yaml";
16552
16571
  init_foundry_agents();
16553
16572
  init_errors();
16573
+ init_esm();
16554
16574
  function findPersonasRoot(hint) {
16555
16575
  let dir = path12.resolve(hint);
16556
16576
  const fsRoot = path12.parse(dir).root;
@@ -16616,7 +16636,14 @@ async function deployPromptAdvisor(args) {
16616
16636
  model,
16617
16637
  instructions,
16618
16638
  reasoningEffort,
16619
- metadata: { persona: args.persona, personaVersion, fillableFieldValues: JSON.stringify(valuesMap) },
16639
+ metadata: {
16640
+ persona: args.persona,
16641
+ personaVersion,
16642
+ fillableFieldValues: JSON.stringify(valuesMap),
16643
+ // Derived here, once, from the agent NAME — so no call site can forget it
16644
+ // and no two call sites can disagree.
16645
+ productVisibility: productVisibilityFor(args.agentName)
16646
+ },
16620
16647
  ...args.onNotice ? { onNotice: args.onNotice } : {}
16621
16648
  });
16622
16649
  }
@@ -18046,6 +18073,7 @@ async function stagePublicImageIfNeeded(args) {
18046
18073
  }
18047
18074
 
18048
18075
  // src/commands/coder/deploy.ts
18076
+ init_esm();
18049
18077
  var SIZE_PRESETS = {
18050
18078
  small: { cpu: "0.5", memory: "1Gi" },
18051
18079
  medium: { cpu: "1", memory: "2Gi" },
@@ -18188,7 +18216,14 @@ var CoderDeployCommand = class extends M8tCommand {
18188
18216
  cpu: preset.cpu,
18189
18217
  memory: preset.memory,
18190
18218
  env,
18191
- metadata: { ...currentMetadata, source: "m8t", kind: "hosted", persona: personaName, personaVersion },
18219
+ metadata: {
18220
+ ...currentMetadata,
18221
+ source: "m8t",
18222
+ kind: "hosted",
18223
+ persona: personaName,
18224
+ personaVersion,
18225
+ productVisibility: productVisibilityFor(this.name)
18226
+ },
18192
18227
  onProgress
18193
18228
  });
18194
18229
  if (typeof this.brain === "string") {
@@ -18396,6 +18431,7 @@ import * as path18 from "path";
18396
18431
  import { Command as Command30, Option as Option28 } from "clipanion";
18397
18432
  import { DefaultAzureCredential as DefaultAzureCredential15 } from "@azure/identity";
18398
18433
  init_errors();
18434
+ init_foundry_agent_get();
18399
18435
  init_rbac();
18400
18436
 
18401
18437
  // src/lib/acs-provision.ts
@@ -18542,6 +18578,12 @@ var AzureExecDeployCommand = class extends M8tCommand {
18542
18578
  interactive,
18543
18579
  endpoint: this.endpoint
18544
18580
  });
18581
+ let currentMetadata = {};
18582
+ try {
18583
+ const current = await getAgentVersion({ credential: credential2, projectEndpoint: project.endpoint, agentName: this.name });
18584
+ currentMetadata = { ...current.metadata ?? {} };
18585
+ } catch {
18586
+ }
18545
18587
  const staged = await stagePublicImageIfNeeded({ image: requestedImage, project });
18546
18588
  for (const n of staged.notes) {
18547
18589
  this.context.stderr.write(`${colors.hint("note:")} ${n}
@@ -18635,7 +18677,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
18635
18677
  cpu: preset.cpu,
18636
18678
  memory: preset.memory,
18637
18679
  env,
18638
- metadata: { source: "m8t", kind: "hosted", persona: personaName, personaVersion },
18680
+ metadata: { ...currentMetadata, source: "m8t", kind: "hosted", persona: personaName, personaVersion, productVisibility: "internal" },
18639
18681
  onProgress
18640
18682
  });
18641
18683
  onProgress?.(`granting Contributor at ${grantScope}\u2026`);
@@ -19303,6 +19345,7 @@ async function readStamp(opts) {
19303
19345
  }
19304
19346
 
19305
19347
  // src/lib/platform-converge.ts
19348
+ init_esm();
19306
19349
  import * as fs22 from "fs";
19307
19350
  import * as path21 from "path";
19308
19351
  import { parse as parseYaml10 } from "yaml";
@@ -19894,6 +19937,10 @@ async function applyPersona(a, ctx, opts) {
19894
19937
  const metadata = {
19895
19938
  ...current.metadata ?? {},
19896
19939
  persona: a.personaName ?? opts.agentName,
19940
+ // Re-asserted on every converge, not just at initial deploy — heals an install whose
19941
+ // advisor was deployed by a CLI that predates the roster-visibility stamp (see
19942
+ // deploy-prompt-advisor.ts). Derived from the deployed agent name, same as there.
19943
+ productVisibility: productVisibilityFor(opts.agentName),
19897
19944
  ...values ? { fillableFieldValues: JSON.stringify(values) } : {}
19898
19945
  };
19899
19946
  const candidate2 = { definition: { ...current.definition, instructions }, metadata };
@@ -28057,6 +28104,35 @@ import * as path33 from "path";
28057
28104
  init_errors();
28058
28105
 
28059
28106
  // src/lib/onboarding-profile.ts
28107
+ init_esm();
28108
+ function describeBlockRejection(reason) {
28109
+ switch (reason) {
28110
+ case "unknown-schema-version":
28111
+ return "the onboarding block's schema_version was missing or unrecognized";
28112
+ case "unexpected-key":
28113
+ return "the onboarding JSON carried a field it shouldn't have";
28114
+ case "missing-key":
28115
+ return "the onboarding block was missing a required field";
28116
+ case "non-string-value":
28117
+ return "the onboarding block held a value of the wrong type";
28118
+ case "disallowed-value":
28119
+ return "an onboarding block field held a value outside its allowed set";
28120
+ case "too-many-pending-requests":
28121
+ return "the onboarding block listed more pending requests than the one allowed";
28122
+ case "malformed-json":
28123
+ return "the onboarding block's JSON could not be safely parsed \u2014 check for duplicate keys, excessive nesting, or a syntax error";
28124
+ case "multiple-artifacts":
28125
+ return "more than one onboarding block was found in your onboarding conversation";
28126
+ case "unreadable-fences":
28127
+ return "the message's code fences could not be reliably delimited, so any onboarding block inside them could not be safely read";
28128
+ case "mistagged-fence":
28129
+ return "the onboarding block was inside a code fence that wasn't tagged json, so it could not be safely read";
28130
+ case "unfenced-artifact":
28131
+ return "the onboarding block appeared outside of any code fence, so it could not be safely read";
28132
+ case "no-artifact":
28133
+ return "no onboarding block was found";
28134
+ }
28135
+ }
28060
28136
  var COMPANY_PROFILE_PATH = "memory/company-profile.md";
28061
28137
  var DEFAULT_MEMORY_INDEX_HEADER = [
28062
28138
  `# Memory index`,
@@ -28178,24 +28254,96 @@ function hasValidUniqueJsonKeys(source) {
28178
28254
  return false;
28179
28255
  }
28180
28256
  }
28257
+ var V3_BLOCK_KEYS = ["schema_version", "founder", "company", "advisor_email", "pending_requests"];
28258
+ var V3_FOUNDER_KEYS = ["name", "email", "azure_identity_note"];
28259
+ var V3_COMPANY_KEYS = ["name", "one_liner"];
28260
+ var V3_ADDRESS_KEYS = ["address", "city", "postal_code", "country"];
28261
+ var V3_REQUEST_KEYS = ["type", "model", "region", "consent", "company_address"];
28262
+ function exactStringRecord(value, keys) {
28263
+ if (!isRecord(value)) return "non-string-value";
28264
+ const present = new Set(Object.keys(value));
28265
+ for (const key2 of keys) if (!present.has(key2)) return "missing-key";
28266
+ if (present.size !== keys.length) return "unexpected-key";
28267
+ for (const key2 of keys) if (typeof value[key2] !== "string") return "non-string-value";
28268
+ return null;
28269
+ }
28270
+ function canonicalPendingRequests(value) {
28271
+ if (!Array.isArray(value)) return { ok: false, reason: "non-string-value" };
28272
+ if (value.length > 1) return { ok: false, reason: "too-many-pending-requests" };
28273
+ const requests = [];
28274
+ for (const entry of value) {
28275
+ if (!isRecord(entry)) return { ok: false, reason: "non-string-value" };
28276
+ const present = new Set(Object.keys(entry));
28277
+ for (const key2 of V3_REQUEST_KEYS) {
28278
+ if (!present.has(key2)) return { ok: false, reason: "missing-key" };
28279
+ }
28280
+ if (present.size !== V3_REQUEST_KEYS.length) return { ok: false, reason: "unexpected-key" };
28281
+ if (entry.type !== "quota") return { ok: false, reason: "disallowed-value" };
28282
+ if (entry.consent !== "submit" && entry.consent !== "prepare") return { ok: false, reason: "disallowed-value" };
28283
+ if (typeof entry.model !== "string" || typeof entry.region !== "string") {
28284
+ return { ok: false, reason: "non-string-value" };
28285
+ }
28286
+ if (entry.company_address !== null) {
28287
+ const bad = exactStringRecord(entry.company_address, V3_ADDRESS_KEYS);
28288
+ if (bad) return { ok: false, reason: bad };
28289
+ }
28290
+ requests.push(entry);
28291
+ }
28292
+ return { ok: true, requests };
28293
+ }
28294
+ function canonicalV3Block(value) {
28295
+ const present = new Set(Object.keys(value));
28296
+ for (const key2 of V3_BLOCK_KEYS) if (!present.has(key2)) return { ok: false, reason: "missing-key" };
28297
+ if (present.size !== V3_BLOCK_KEYS.length) return { ok: false, reason: "unexpected-key" };
28298
+ const founderBad = exactStringRecord(value.founder, V3_FOUNDER_KEYS);
28299
+ if (founderBad) return { ok: false, reason: founderBad };
28300
+ const companyBad = exactStringRecord(value.company, V3_COMPANY_KEYS);
28301
+ if (companyBad) return { ok: false, reason: companyBad };
28302
+ if (value.advisor_email !== null && typeof value.advisor_email !== "string") {
28303
+ return { ok: false, reason: "non-string-value" };
28304
+ }
28305
+ const requests = canonicalPendingRequests(value.pending_requests);
28306
+ if (!requests.ok) return requests;
28307
+ const founder = value.founder;
28308
+ const company = value.company;
28309
+ return {
28310
+ ok: true,
28311
+ block: {
28312
+ schema_version: "3",
28313
+ founder_name: founder.name,
28314
+ founder_email: founder.email,
28315
+ azure_identity_note: founder.azure_identity_note,
28316
+ company_name: company.name,
28317
+ context: company.one_liner,
28318
+ advisor_name: "",
28319
+ advisor_email: typeof value.advisor_email === "string" ? value.advisor_email : "",
28320
+ pending_requests: requests.requests
28321
+ }
28322
+ };
28323
+ }
28181
28324
  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;
28325
+ if (!isRecord(value)) return { ok: false, reason: "non-string-value" };
28326
+ if (value.schema_version === "3") return canonicalV3Block(value);
28327
+ if (value.schema_version !== "2") return { ok: false, reason: "unknown-schema-version" };
28328
+ const keys = new Set(Object.keys(value));
28187
28329
  for (const key2 of ONBOARDING_BLOCK_KEYS) {
28188
- if (typeof value[key2] !== "string") return null;
28330
+ if (!keys.has(key2)) return { ok: false, reason: "missing-key" };
28189
28331
  }
28190
- return value;
28332
+ if (keys.size !== ONBOARDING_BLOCK_KEYS.length) return { ok: false, reason: "unexpected-key" };
28333
+ for (const key2 of ONBOARDING_BLOCK_KEYS) {
28334
+ if (typeof value[key2] !== "string") return { ok: false, reason: "non-string-value" };
28335
+ }
28336
+ return { ok: true, block: value };
28191
28337
  }
28192
- function parseOnboardingArtifact(machineText) {
28338
+ function parseOnboardingArtifactResult(machineText) {
28193
28339
  const fencePattern = /^```([^\r\n]*)\r?\n([\s\S]*?)^```[ \t]*(?=\r?$)/gm;
28194
28340
  const fences = [...machineText.matchAll(fencePattern)];
28195
28341
  const jsonFenceStarts = [...machineText.matchAll(/^```[ \t]*json[ \t]*\r?$/gm)];
28196
28342
  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;
28343
+ if (jsonFenceStarts.length !== matchedJsonFences.length) return { ok: false, reason: "unreadable-fences" };
28344
+ if (fences.some((match) => match[1].trim() !== "json" && /"m8t_onboarding"\s*:/.test(match[2]))) {
28345
+ return { ok: false, reason: "mistagged-fence" };
28346
+ }
28199
28347
  let outsideFences = "";
28200
28348
  let previousEnd = 0;
28201
28349
  for (const fence of fences) {
@@ -28204,43 +28352,48 @@ function parseOnboardingArtifact(machineText) {
28204
28352
  previousEnd = start + fence[0].length;
28205
28353
  }
28206
28354
  outsideFences += machineText.slice(previousEnd);
28207
- if (/"m8t_onboarding"\s*:/.test(outsideFences)) return null;
28355
+ if (/"m8t_onboarding"\s*:/.test(outsideFences)) return { ok: false, reason: "unfenced-artifact" };
28208
28356
  const artifacts = [];
28209
28357
  for (const fence of matchedJsonFences) {
28210
28358
  const json = fence[2].trim();
28211
28359
  let parsed;
28212
28360
  if (!hasValidUniqueJsonKeys(json)) {
28213
- if (json.includes("m8t_onboarding")) return null;
28361
+ if (json.includes("m8t_onboarding")) return { ok: false, reason: "malformed-json" };
28214
28362
  continue;
28215
28363
  }
28216
28364
  try {
28217
28365
  parsed = JSON.parse(json);
28218
28366
  } catch {
28219
- if (json.includes("m8t_onboarding")) return null;
28367
+ if (json.includes("m8t_onboarding")) return { ok: false, reason: "malformed-json" };
28220
28368
  continue;
28221
28369
  }
28222
28370
  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;
28371
+ if (Object.keys(parsed).length !== 1) return { ok: false, reason: "unexpected-key" };
28372
+ const outcome = canonicalBlock(parsed.m8t_onboarding);
28373
+ if (!outcome.ok) return outcome;
28226
28374
  const start = fence.index;
28227
- artifacts.push({ block, start, end: start + fence[0].length });
28375
+ artifacts.push({ block: outcome.block, start, end: start + fence[0].length });
28228
28376
  }
28229
- if (artifacts.length !== 1) return null;
28377
+ if (artifacts.length === 0) return { ok: false, reason: "no-artifact" };
28378
+ if (artifacts.length > 1) return { ok: false, reason: "multiple-artifacts" };
28230
28379
  const artifact = artifacts[0];
28231
28380
  const before = machineText.slice(0, artifact.start).trimEnd();
28232
28381
  const after = machineText.slice(artifact.end).trimStart();
28233
28382
  return {
28234
- block: artifact.block,
28235
- machineText,
28236
- speechText: [before, after].filter((part) => part.length > 0).join("\n").trim()
28383
+ ok: true,
28384
+ artifact: {
28385
+ block: artifact.block,
28386
+ machineText,
28387
+ speechText: [before, after].filter((part) => part.length > 0).join("\n").trim()
28388
+ }
28237
28389
  };
28238
28390
  }
28239
28391
  var EMPTY_PROFILE_RESULT = {
28240
28392
  hadIntake: false,
28241
28393
  block: null,
28242
28394
  machineText: null,
28243
- speechText: null
28395
+ speechText: null,
28396
+ rejection: null
28244
28397
  };
28245
28398
  async function readCursorPages(args) {
28246
28399
  const all = [];
@@ -28314,7 +28467,7 @@ async function findOnboardingProfile(args) {
28314
28467
  const conversations = orderNewest(listed.flatMap((value, ordinal) => {
28315
28468
  if (!isRecord(value) || typeof value.id !== "string" || value.id.length === 0 || !isRecord(value.metadata)) return [];
28316
28469
  const metadata = value.metadata;
28317
- if (metadata.app !== "m8t-webapp" || metadata.agent !== "stacey-intake") return [];
28470
+ if (metadata.app !== "m8t-webapp" || metadata.agent !== INTAKE_AGENT_NAME) return [];
28318
28471
  return [{
28319
28472
  id: value.id,
28320
28473
  createdAt: timestamp(value.created_at) !== Number.NEGATIVE_INFINITY ? timestamp(value.created_at) : timestamp(metadata.createdAt),
@@ -28328,14 +28481,14 @@ async function findOnboardingProfile(args) {
28328
28481
  headers: H,
28329
28482
  fetchImpl: doFetch
28330
28483
  });
28331
- if (!items) return { hadIntake: true, block: null, machineText: null, speechText: null };
28484
+ if (!items) return { hadIntake: true, block: null, machineText: null, speechText: null, rejection: null };
28332
28485
  const assistantItems = orderNewest(items.flatMap((value, ordinal) => {
28333
28486
  if (!isRecord(value) || value.type !== "message" || value.role !== "assistant" || !Array.isArray(value.content)) return [];
28334
28487
  const id = typeof value.id === "string" ? value.id : "";
28335
28488
  return [{ value, id, createdAt: timestamp(value.created_at), ordinal }];
28336
28489
  }));
28337
28490
  const artifacts = [];
28338
- let malformedArtifact = false;
28491
+ let rejection = null;
28339
28492
  for (const item of assistantItems) {
28340
28493
  const content = item.value.content;
28341
28494
  const machineText = content.flatMap((part) => {
@@ -28344,23 +28497,32 @@ async function findOnboardingProfile(args) {
28344
28497
  if (typeof part.transcript === "string") return [part.transcript];
28345
28498
  return [];
28346
28499
  }).join("\n");
28347
- const artifact2 = parseOnboardingArtifact(machineText);
28348
- if (artifact2) artifacts.push(artifact2);
28349
- else if (machineText.includes("m8t_onboarding")) malformedArtifact = true;
28500
+ const result = parseOnboardingArtifactResult(machineText);
28501
+ if (result.ok) artifacts.push(result.artifact);
28502
+ else if (machineText.includes("m8t_onboarding")) rejection ??= result.reason;
28350
28503
  }
28351
- if (malformedArtifact || artifacts.length !== 1) {
28352
- return { hadIntake: true, block: null, machineText: null, speechText: null };
28504
+ if (artifacts.length === 0) {
28505
+ return {
28506
+ hadIntake: true,
28507
+ block: null,
28508
+ machineText: null,
28509
+ speechText: null,
28510
+ rejection: rejection ?? "no-artifact"
28511
+ };
28353
28512
  }
28354
28513
  const artifact = artifacts[0];
28355
28514
  return {
28356
28515
  hadIntake: true,
28357
28516
  block: artifact.block,
28358
28517
  machineText: artifact.machineText,
28359
- speechText: artifact.speechText
28518
+ speechText: artifact.speechText,
28519
+ rejection: null,
28520
+ ...artifacts.length > 1 ? { supersededCount: artifacts.length - 1 } : {}
28360
28521
  };
28361
28522
  }
28523
+ var dash = (v) => v?.trim() ? v.trim() : "\u2014";
28524
+ var bullet = (label, value) => value === void 0 ? [] : [`- **${label}:** ${dash(value)}`];
28362
28525
  function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOString()) {
28363
- const dash = (v) => v?.trim() ? v.trim() : "\u2014";
28364
28526
  const profileMd = [
28365
28527
  `---`,
28366
28528
  `type: memory`,
@@ -28375,22 +28537,30 @@ function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOStr
28375
28537
  ``,
28376
28538
  `_Seeded from the onboarding questionnaire._`,
28377
28539
  ``,
28378
- `- **Stage:** ${dash(block.company_stage)}`,
28379
- `- **ICP:** ${dash(block.icp)}`,
28380
- `- **Industry:** ${dash(block.industry)}`,
28381
- `- **Team size:** ${dash(block.team_size)}`,
28540
+ ...bullet("Company", block.company_name),
28541
+ ...bullet("Stage", block.company_stage),
28542
+ ...bullet("ICP", block.icp),
28543
+ ...bullet("Industry", block.industry),
28544
+ ...bullet("Team size", block.team_size),
28382
28545
  ``,
28383
28546
  `## Context`,
28384
28547
  ``,
28385
- block.context.trim(),
28548
+ (block.context ?? "").trim(),
28386
28549
  ``
28387
28550
  ].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)`;
28551
+ const bits = [
28552
+ block.company_name?.trim(),
28553
+ block.company_stage?.trim(),
28554
+ block.industry?.trim(),
28555
+ block.icp?.trim() ? `ICP ${block.icp.trim()}` : void 0
28556
+ ].filter(Boolean).join(" \xB7 ");
28557
+ const summary = bits || (block.schema_version === "3" ? "no details captured at intake" : "");
28558
+ const memoryIndexLine = `- \`${COMPANY_PROFILE_PATH}\` \u2014 **Company profile**: ${summary}. (seeded from onboarding)`;
28390
28559
  return { profileMd, memoryIndexLine };
28391
28560
  }
28392
28561
  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)_";
28562
+ var NOT_CAPTURED = "_not captured yet \u2014 just tell me and I'll add it_";
28563
+ var REGION_UNKNOWN_AT_INTAKE = "_not known at intake \u2014 filled in when the request is filed_";
28394
28564
  function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).toISOString()) {
28395
28565
  const pick = (...vals) => vals.map((v) => v?.trim()).find(Boolean) ?? "";
28396
28566
  const founderName = pick(block.founder_name, inputs.azIdentity?.name);
@@ -28398,8 +28568,29 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
28398
28568
  const advisorName = (block.advisor_name ?? "").trim();
28399
28569
  const advisorEmail = (block.advisor_email ?? "").trim();
28400
28570
  const subscription = (inputs.subscriptionId ?? "").trim();
28401
- const teamSize = (block.team_size ?? "").trim();
28402
28571
  const advisorRendered = advisorName || advisorEmail ? [advisorName, advisorEmail ? `<${advisorEmail}>` : ""].filter(Boolean).join(" ") : NOT_CAPTURED;
28572
+ const request = block.pending_requests?.[0];
28573
+ const requestLines = request === void 0 ? [] : (() => {
28574
+ const modelLabel = request.model.trim() ? `\`${request.model.trim()}\`` : "(model not recorded)";
28575
+ const consentLabel = request.consent === "submit" ? "submit the request" : "prepare it only, don't submit";
28576
+ const addressLine = request.company_address === null ? "not provided \u2014 the founder skipped it" : [
28577
+ request.company_address.address,
28578
+ request.company_address.city,
28579
+ request.company_address.postal_code,
28580
+ request.company_address.country
28581
+ ].map((part) => part.trim()).filter(Boolean).join(", ") || "not provided";
28582
+ return [
28583
+ ``,
28584
+ `## Requested at onboarding`,
28585
+ ``,
28586
+ `_The founder asked for this during setup. Act on it when they bring it up._`,
28587
+ ``,
28588
+ `- **What:** ${request.type} increase for ${modelLabel}`,
28589
+ `- **Region:** ${request.region.trim() || REGION_UNKNOWN_AT_INTAKE}`,
28590
+ `- **Consent:** ${consentLabel}`,
28591
+ `- **Company address:** ${addressLine}`
28592
+ ];
28593
+ })();
28403
28594
  const founderMd = [
28404
28595
  `---`,
28405
28596
  `type: memory`,
@@ -28418,7 +28609,8 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
28418
28609
  `- **Founder email (company_email):** ${founderEmail || NOT_CAPTURED}`,
28419
28610
  `- **Microsoft Startup Advisor (SA):** ${advisorRendered}`,
28420
28611
  `- **Azure subscription:** ${subscription || NOT_CAPTURED}`,
28421
- `- **Team size:** ${teamSize || "\u2014"}`,
28612
+ ...bullet("Team size", block.team_size),
28613
+ ...requestLines,
28422
28614
  ``
28423
28615
  ].join("\n");
28424
28616
  const idxAdvisor = advisorEmail || advisorName || "\u2014";
@@ -28427,7 +28619,168 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
28427
28619
  return { founderMd, memoryIndexLine };
28428
28620
  }
28429
28621
 
28622
+ // src/lib/model-cascade.ts
28623
+ var WHITELIST = [
28624
+ { model: "gpt-5.6-luna", format: "OpenAI", version: "2026-07-09", capacity: 100 },
28625
+ { model: "gpt-5.6-sol", format: "OpenAI", version: "2026-07-09", capacity: 100 },
28626
+ { model: "gpt-5.6-terra", format: "OpenAI", version: "2026-07-09", capacity: 100 },
28627
+ { model: "gpt-5.4", format: "OpenAI", version: "2026-03-05", capacity: 100 },
28628
+ { model: "grok-4.3", format: "xAI", version: "1", capacity: 100 },
28629
+ { model: "gpt-4.1-mini", format: "OpenAI", version: "2025-04-14", capacity: 50 }
28630
+ ];
28631
+ var GLOBAL_STANDARD = "GlobalStandard";
28632
+ function planCascade(whitelist, catalog, quota) {
28633
+ if (!catalog.ok) {
28634
+ return {
28635
+ candidates: [],
28636
+ skipped: whitelist.map((r) => ({ model: r.model, outcome: "catalog-unverified", detail: catalog.error }))
28637
+ };
28638
+ }
28639
+ const rowsByName = /* @__PURE__ */ new Map();
28640
+ for (const row of catalog.rows) {
28641
+ const list = rowsByName.get(row.name);
28642
+ if (list) list.push(row);
28643
+ else rowsByName.set(row.name, [row]);
28644
+ }
28645
+ const candidates = [];
28646
+ const skipped = [];
28647
+ for (const rung of whitelist) {
28648
+ const rows = rowsByName.get(rung.model);
28649
+ if (!rows || rows.length === 0) {
28650
+ skipped.push({ model: rung.model, outcome: "not-in-catalog" });
28651
+ continue;
28652
+ }
28653
+ const agentRows = rows.filter((r) => r.agentsV2);
28654
+ if (agentRows.length === 0) {
28655
+ skipped.push({ model: rung.model, outcome: "not-agent-eligible" });
28656
+ continue;
28657
+ }
28658
+ if (!agentRows.some((r) => r.skus.includes(GLOBAL_STANDARD))) {
28659
+ skipped.push({ model: rung.model, outcome: "no-global-standard" });
28660
+ continue;
28661
+ }
28662
+ if (modelQuotaVerdict(quota, rung.model).verdict === "no_quota") {
28663
+ skipped.push({ model: rung.model, outcome: "no-quota" });
28664
+ continue;
28665
+ }
28666
+ candidates.push(rung);
28667
+ }
28668
+ return { candidates, skipped };
28669
+ }
28670
+ 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;
28671
+ var REGION_RE = /not available in|not supported in|NotAvailableInRegion|InvalidResourceLocation/i;
28672
+ var CONFLICT_RE = /\bconflict\b|\b409\b/i;
28673
+ function classifyDeployError(message) {
28674
+ if (QUOTA_RE.test(message)) return "deploy-rejected-quota";
28675
+ if (REGION_RE.test(message)) return "deploy-rejected-region";
28676
+ return "deploy-unverified";
28677
+ }
28678
+ async function walkCascade(whitelist, plan, deploy, opts) {
28679
+ const outcomes = /* @__PURE__ */ new Map();
28680
+ for (const s of plan.skipped) outcomes.set(s.model, s);
28681
+ let chosen = null;
28682
+ let aborted = false;
28683
+ for (const rung of plan.candidates) {
28684
+ if (chosen) break;
28685
+ if (opts.now() >= opts.deadlineAt) {
28686
+ aborted = true;
28687
+ break;
28688
+ }
28689
+ let row;
28690
+ try {
28691
+ await deploy(rung);
28692
+ row = { model: rung.model, outcome: "deployed" };
28693
+ chosen = rung;
28694
+ } catch (e) {
28695
+ const msg = e instanceof Error ? e.message : String(e);
28696
+ if (CONFLICT_RE.test(msg)) {
28697
+ try {
28698
+ await deploy(rung);
28699
+ row = { model: rung.model, outcome: "deployed" };
28700
+ chosen = rung;
28701
+ } catch (e2) {
28702
+ const msg2 = e2 instanceof Error ? e2.message : String(e2);
28703
+ row = {
28704
+ model: rung.model,
28705
+ outcome: CONFLICT_RE.test(msg2) ? "deploy-unverified" : classifyDeployError(msg2),
28706
+ detail: msg2
28707
+ };
28708
+ }
28709
+ } else {
28710
+ row = { model: rung.model, outcome: classifyDeployError(msg), detail: msg };
28711
+ }
28712
+ }
28713
+ outcomes.set(rung.model, row);
28714
+ opts.onRung?.(row.model, row.outcome);
28715
+ }
28716
+ const trace = whitelist.map(
28717
+ (r) => outcomes.get(r.model) ?? { model: r.model, outcome: aborted ? "aborted-deadline" : "not-reached" }
28718
+ );
28719
+ return { chosen, trace };
28720
+ }
28721
+ var QUOTA_INVITATION = "You can offer to help request that quota.";
28722
+ var CERTAIN_UNAVAILABLE = /* @__PURE__ */ new Set([
28723
+ "not-in-catalog",
28724
+ "not-agent-eligible",
28725
+ "no-global-standard",
28726
+ "deploy-rejected-region"
28727
+ ]);
28728
+ var QUOTA_BLOCKED = /* @__PURE__ */ new Set(["no-quota", "deploy-rejected-quota"]);
28729
+ function decideNote(whitelist, result) {
28730
+ const chosenIdx = result.chosen ? whitelist.findIndex((r) => r.model === result.chosen?.model) : whitelist.length;
28731
+ const better = result.trace.slice(0, Math.max(chosenIdx, 0));
28732
+ const runningModel = result.chosen?.model ?? null;
28733
+ if (chosenIdx === 0) {
28734
+ return { status: "top", runningModel, pitchModel: null, unavailableAbovePitch: [] };
28735
+ }
28736
+ const pitch = better.find((t) => QUOTA_BLOCKED.has(t.outcome));
28737
+ if (pitch) {
28738
+ const pitchIdx = better.findIndex((t) => t.model === pitch.model);
28739
+ return {
28740
+ status: "lesser-quota",
28741
+ runningModel,
28742
+ pitchModel: pitch.model,
28743
+ unavailableAbovePitch: better.slice(0, pitchIdx).filter((t) => CERTAIN_UNAVAILABLE.has(t.outcome)).map((t) => t.model)
28744
+ };
28745
+ }
28746
+ const allCertain = better.length > 0 && better.every((t) => CERTAIN_UNAVAILABLE.has(t.outcome));
28747
+ return {
28748
+ status: allCertain ? "lesser-unavailable" : "lesser-unverified",
28749
+ runningModel,
28750
+ pitchModel: null,
28751
+ unavailableAbovePitch: []
28752
+ };
28753
+ }
28754
+ var listNames = (names) => names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
28755
+ function renderChosenModelNote(d) {
28756
+ const subject = d.runningModel ? `You are running on ${d.runningModel}.` : "You are running on this install's default model.";
28757
+ switch (d.status) {
28758
+ case "top":
28759
+ return `${subject} That is the best available model for this install.`;
28760
+ case "lesser-quota": {
28761
+ const aside = d.unavailableAbovePitch.length > 0 ? ` ${listNames(d.unavailableAbovePitch)} ${d.unavailableAbovePitch.length > 1 ? "are" : "is"} not offered for this install.` : "";
28762
+ return `${subject} A stronger model, ${d.pitchModel ?? ""}, is offered here, but this subscription has no quota for it.${aside} ${QUOTA_INVITATION}`;
28763
+ }
28764
+ case "lesser-unavailable":
28765
+ return `${subject} No stronger model is offered for this install right now.`;
28766
+ case "lesser-unverified":
28767
+ 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.`;
28768
+ }
28769
+ }
28770
+
28430
28771
  // src/lib/founder-identity.ts
28772
+ var INVENTORY_INVITATION = "You can offer to walk the founder through these resources.";
28773
+ var NOTE_INVITATIONS = [QUOTA_INVITATION, INVENTORY_INVITATION];
28774
+ function escapeRegExp(value) {
28775
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28776
+ }
28777
+ function stripNoteInvitations(value) {
28778
+ let out = value;
28779
+ for (const sentence of NOTE_INVITATIONS) {
28780
+ out = out.replace(new RegExp(escapeRegExp(sentence), "gi"), " ");
28781
+ }
28782
+ return out.replace(/\s{2,}/g, " ").trim();
28783
+ }
28431
28784
  function deriveEmailCandidate(raw) {
28432
28785
  const mail = (raw.mail ?? "").trim();
28433
28786
  if (mail) return mail;
@@ -28459,11 +28812,13 @@ async function getSignedInUserIdentity(runAzImpl = runAz) {
28459
28812
  }
28460
28813
  }
28461
28814
  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.`;
28815
+ const name = stripNoteInvitations(id.name);
28816
+ const email = stripNoteInvitations(id.email);
28817
+ if (name && email) {
28818
+ 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
28819
  }
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).`;
28820
+ if (name) {
28821
+ 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
28822
  }
28468
28823
  return `Ask the founder their name and the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
28469
28824
  }
@@ -28663,7 +29018,7 @@ async function reactiveSeedOnFinish(args) {
28663
29018
  if (!hadIntake) return;
28664
29019
  (args.spawnWatch ?? spawnDetachedSeedWatch)();
28665
29020
  args.stdout(
28666
- `${colors.dim("\u2139 Your advisors will pick up your company profile when you finish the questionnaire (watching in the background).")}
29021
+ `${colors.dim("\u2139 Your advisors will pick up your company profile when you finish the intake (watching in the background).")}
28667
29022
  ${colors.hint("or run:")} m8t bootstrap seed-profile
28668
29023
  `
28669
29024
  );
@@ -28705,7 +29060,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
28705
29060
  static paths = [["bootstrap", "finish"]];
28706
29061
  static usage = Command55.Usage({
28707
29062
  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.",
29063
+ 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
29064
  examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
28710
29065
  });
28711
29066
  repoRoot = Option52.String("--repo-root");
@@ -28827,17 +29182,18 @@ import { spawn as spawn7, spawnSync as spawnSync6 } from "child_process";
28827
29182
  init_errors();
28828
29183
  init_rbac();
28829
29184
 
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) {
29185
+ // src/lib/intake-agent.ts
29186
+ init_esm();
29187
+ var INTAKE_PERSONA = "azure-advisor-intake";
29188
+ var INTAKE_AGENT = INTAKE_AGENT_NAME;
29189
+ async function deployIntakeAgent(args) {
28834
29190
  try {
28835
29191
  return await deployPromptAdvisor({
28836
29192
  credential: args.credential,
28837
29193
  endpoint: args.endpoint,
28838
29194
  repoRoot: args.repoRoot,
28839
- persona: SIMPLE_STACEY_PERSONA,
28840
- agentName: SIMPLE_STACEY_AGENT,
29195
+ persona: INTAKE_PERSONA,
29196
+ agentName: INTAKE_AGENT,
28841
29197
  model: args.model,
28842
29198
  fieldOverrides: args.fieldOverrides
28843
29199
  });
@@ -28847,7 +29203,7 @@ async function deploySimpleStacey(args) {
28847
29203
  if (err.code === "ADVISOR_PERSONA_MISSING") {
28848
29204
  const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
28849
29205
  throw new LocalCliError2({
28850
- code: "SIMPLE_STACEY_PERSONA_MISSING",
29206
+ code: "INTAKE_PERSONA_MISSING",
28851
29207
  message: err.message,
28852
29208
  hint: err.hint,
28853
29209
  cause: err.cause
@@ -28856,7 +29212,7 @@ async function deploySimpleStacey(args) {
28856
29212
  if (err.code === "ADVISOR_NO_MODEL") {
28857
29213
  const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
28858
29214
  throw new LocalCliError2({
28859
- code: "SIMPLE_STACEY_NO_MODEL",
29215
+ code: "INTAKE_NO_MODEL",
28860
29216
  message: err.message,
28861
29217
  hint: err.hint,
28862
29218
  cause: err.cause
@@ -28865,7 +29221,7 @@ async function deploySimpleStacey(args) {
28865
29221
  if (err.code === "ADVISOR_BAD_EFFORT") {
28866
29222
  const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
28867
29223
  throw new LocalCliError2({
28868
- code: "SIMPLE_STACEY_BAD_EFFORT",
29224
+ code: "INTAKE_BAD_EFFORT",
28869
29225
  message: err.message,
28870
29226
  hint: err.hint,
28871
29227
  cause: err.cause
@@ -28897,13 +29253,13 @@ function isAuthorizationShapedError(error) {
28897
29253
  }
28898
29254
  return false;
28899
29255
  }
28900
- async function deploySimpleStaceyWithRetry(args) {
29256
+ async function deployIntakeAgentWithRetry(args) {
28901
29257
  const maxWaitMs = args.maxWaitMs ?? 3e5;
28902
29258
  const intervalMs = args.intervalMs ?? 1e4;
28903
29259
  const deadline = Date.now() + maxWaitMs;
28904
29260
  for (; ; ) {
28905
29261
  try {
28906
- return await deploySimpleStacey({
29262
+ return await deployIntakeAgent({
28907
29263
  credential: args.credential,
28908
29264
  endpoint: args.endpoint,
28909
29265
  repoRoot: args.repoRoot,
@@ -28916,7 +29272,7 @@ async function deploySimpleStaceyWithRetry(args) {
28916
29272
  if (remainingMs <= 0) {
28917
29273
  throw new LocalCliError({
28918
29274
  code: "BOOTSTRAP_UI_ROLE_PROPAGATION_TIMEOUT",
28919
- message: "Timed out waiting for Azure role propagation before deploying Simple Stacey.",
29275
+ message: "Timed out waiting for Azure role propagation before deploying the intake agent.",
28920
29276
  hint: "Azure role propagation can take a few minutes - re-run 'm8t bootstrap ui' (idempotent).",
28921
29277
  cause: error
28922
29278
  });
@@ -28946,7 +29302,7 @@ async function resolveFoundryEndpointWithWait(args, opts = {}) {
28946
29302
  if (e instanceof LocalCliError && e.code === "FOUNDRY_PROJECT_MULTIPLE") {
28947
29303
  throw new LocalCliError({
28948
29304
  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).`,
29305
+ message: `${e.message} bootstrap ui can't pick one safely (it must not deploy the intake agent into the wrong project).`,
28950
29306
  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
29307
  cause: e
28952
29308
  });
@@ -29003,7 +29359,7 @@ function writeWebEnvLocal(args) {
29003
29359
  });
29004
29360
  }
29005
29361
  const body = [
29006
- "# Written by `m8t bootstrap ui` \u2014 local onboarding run (Simple Stacey).",
29362
+ "# Written by `m8t bootstrap ui` \u2014 local onboarding run (the onboarding intake agent).",
29007
29363
  "# Text-first: the intake voice stack is off unless the run passed --voice.",
29008
29364
  "# STORAGE unset \u2192 gateway boot skipped; the webapp forwards your MSAL token to Foundry.",
29009
29365
  `AZURE_TENANT_ID=${args.tenantId}`,
@@ -29344,154 +29700,6 @@ async function listAgentModels(region) {
29344
29700
  }
29345
29701
  }
29346
29702
 
29347
- // src/lib/model-cascade.ts
29348
- var WHITELIST = [
29349
- { model: "gpt-5.6-luna", format: "OpenAI", version: "2026-07-09", capacity: 100 },
29350
- { model: "gpt-5.6-sol", format: "OpenAI", version: "2026-07-09", capacity: 100 },
29351
- { model: "gpt-5.6-terra", format: "OpenAI", version: "2026-07-09", capacity: 100 },
29352
- { model: "gpt-5.4", format: "OpenAI", version: "2026-03-05", capacity: 100 },
29353
- { model: "grok-4.3", format: "xAI", version: "1", capacity: 100 },
29354
- { model: "gpt-4.1-mini", format: "OpenAI", version: "2025-04-14", capacity: 50 }
29355
- ];
29356
- var GLOBAL_STANDARD = "GlobalStandard";
29357
- function planCascade(whitelist, catalog, quota) {
29358
- if (!catalog.ok) {
29359
- return {
29360
- candidates: [],
29361
- skipped: whitelist.map((r) => ({ model: r.model, outcome: "catalog-unverified", detail: catalog.error }))
29362
- };
29363
- }
29364
- const rowsByName = /* @__PURE__ */ new Map();
29365
- for (const row of catalog.rows) {
29366
- const list = rowsByName.get(row.name);
29367
- if (list) list.push(row);
29368
- else rowsByName.set(row.name, [row]);
29369
- }
29370
- const candidates = [];
29371
- const skipped = [];
29372
- for (const rung of whitelist) {
29373
- const rows = rowsByName.get(rung.model);
29374
- if (!rows || rows.length === 0) {
29375
- skipped.push({ model: rung.model, outcome: "not-in-catalog" });
29376
- continue;
29377
- }
29378
- const agentRows = rows.filter((r) => r.agentsV2);
29379
- if (agentRows.length === 0) {
29380
- skipped.push({ model: rung.model, outcome: "not-agent-eligible" });
29381
- continue;
29382
- }
29383
- if (!agentRows.some((r) => r.skus.includes(GLOBAL_STANDARD))) {
29384
- skipped.push({ model: rung.model, outcome: "no-global-standard" });
29385
- continue;
29386
- }
29387
- if (modelQuotaVerdict(quota, rung.model).verdict === "no_quota") {
29388
- skipped.push({ model: rung.model, outcome: "no-quota" });
29389
- continue;
29390
- }
29391
- candidates.push(rung);
29392
- }
29393
- return { candidates, skipped };
29394
- }
29395
- 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;
29396
- var REGION_RE = /not available in|not supported in|NotAvailableInRegion|InvalidResourceLocation/i;
29397
- var CONFLICT_RE = /\bconflict\b|\b409\b/i;
29398
- function classifyDeployError(message) {
29399
- if (QUOTA_RE.test(message)) return "deploy-rejected-quota";
29400
- if (REGION_RE.test(message)) return "deploy-rejected-region";
29401
- return "deploy-unverified";
29402
- }
29403
- async function walkCascade(whitelist, plan, deploy, opts) {
29404
- const outcomes = /* @__PURE__ */ new Map();
29405
- for (const s of plan.skipped) outcomes.set(s.model, s);
29406
- let chosen = null;
29407
- let aborted = false;
29408
- for (const rung of plan.candidates) {
29409
- if (chosen) break;
29410
- if (opts.now() >= opts.deadlineAt) {
29411
- aborted = true;
29412
- break;
29413
- }
29414
- let row;
29415
- try {
29416
- await deploy(rung);
29417
- row = { model: rung.model, outcome: "deployed" };
29418
- chosen = rung;
29419
- } catch (e) {
29420
- const msg = e instanceof Error ? e.message : String(e);
29421
- if (CONFLICT_RE.test(msg)) {
29422
- try {
29423
- await deploy(rung);
29424
- row = { model: rung.model, outcome: "deployed" };
29425
- chosen = rung;
29426
- } catch (e2) {
29427
- const msg2 = e2 instanceof Error ? e2.message : String(e2);
29428
- row = {
29429
- model: rung.model,
29430
- outcome: CONFLICT_RE.test(msg2) ? "deploy-unverified" : classifyDeployError(msg2),
29431
- detail: msg2
29432
- };
29433
- }
29434
- } else {
29435
- row = { model: rung.model, outcome: classifyDeployError(msg), detail: msg };
29436
- }
29437
- }
29438
- outcomes.set(rung.model, row);
29439
- opts.onRung?.(row.model, row.outcome);
29440
- }
29441
- const trace = whitelist.map(
29442
- (r) => outcomes.get(r.model) ?? { model: r.model, outcome: aborted ? "aborted-deadline" : "not-reached" }
29443
- );
29444
- return { chosen, trace };
29445
- }
29446
- var CERTAIN_UNAVAILABLE = /* @__PURE__ */ new Set([
29447
- "not-in-catalog",
29448
- "not-agent-eligible",
29449
- "no-global-standard",
29450
- "deploy-rejected-region"
29451
- ]);
29452
- var QUOTA_BLOCKED = /* @__PURE__ */ new Set(["no-quota", "deploy-rejected-quota"]);
29453
- function decideNote(whitelist, result) {
29454
- const chosenIdx = result.chosen ? whitelist.findIndex((r) => r.model === result.chosen?.model) : whitelist.length;
29455
- const better = result.trace.slice(0, Math.max(chosenIdx, 0));
29456
- const runningModel = result.chosen?.model ?? null;
29457
- if (chosenIdx === 0) {
29458
- return { status: "top", runningModel, pitchModel: null, unavailableAbovePitch: [] };
29459
- }
29460
- const pitch = better.find((t) => QUOTA_BLOCKED.has(t.outcome));
29461
- if (pitch) {
29462
- const pitchIdx = better.findIndex((t) => t.model === pitch.model);
29463
- return {
29464
- status: "lesser-quota",
29465
- runningModel,
29466
- pitchModel: pitch.model,
29467
- unavailableAbovePitch: better.slice(0, pitchIdx).filter((t) => CERTAIN_UNAVAILABLE.has(t.outcome)).map((t) => t.model)
29468
- };
29469
- }
29470
- const allCertain = better.length > 0 && better.every((t) => CERTAIN_UNAVAILABLE.has(t.outcome));
29471
- return {
29472
- status: allCertain ? "lesser-unavailable" : "lesser-unverified",
29473
- runningModel,
29474
- pitchModel: null,
29475
- unavailableAbovePitch: []
29476
- };
29477
- }
29478
- var listNames = (names) => names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
29479
- function renderChosenModelNote(d) {
29480
- const subject = d.runningModel ? `You are running on ${d.runningModel}.` : "You are running on this install's default model.";
29481
- switch (d.status) {
29482
- case "top":
29483
- return `${subject} That is the best available model for this install.`;
29484
- case "lesser-quota": {
29485
- const aside = d.unavailableAbovePitch.length > 0 ? ` ${listNames(d.unavailableAbovePitch)} ${d.unavailableAbovePitch.length > 1 ? "are" : "is"} not offered for this install.` : "";
29486
- 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.`;
29487
- }
29488
- case "lesser-unavailable":
29489
- return `${subject} No stronger model is offered for this install right now.`;
29490
- case "lesser-unverified":
29491
- 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.`;
29492
- }
29493
- }
29494
-
29495
29703
  // src/lib/intake-model.ts
29496
29704
  var DEFAULT_DEADLINE_MS = 18e4;
29497
29705
  var SCOPE_RE = /\/resourceGroups\/([^/]+)\/providers\/Microsoft\.CognitiveServices\/accounts\/([^/]+)/i;
@@ -29564,7 +29772,7 @@ async function resolveIntakeModel(args) {
29564
29772
 
29565
29773
  // src/lib/inventory-summary.ts
29566
29774
  var HEAVY_THRESHOLD = 10;
29567
- var INVENTORY_INVITATION = "You can offer to walk the founder through these resources.";
29775
+ var INVENTORY_INVITATION2 = "You can offer to walk the founder through these resources.";
29568
29776
  var NOTABLE_TYPES = {
29569
29777
  // storage
29570
29778
  "microsoft.storage/storageaccounts": "storage accounts",
@@ -29694,7 +29902,7 @@ function regionAgeClause(s) {
29694
29902
  }
29695
29903
  function renderInventoryNote(summary) {
29696
29904
  const body = summary.notableCount === 0 ? `${LEAD} There are no notable pre-existing resources.` : `${LEAD} ${piecesClause(summary)}${regionAgeClause(summary)}.`;
29697
- return summary.verdict === "heavy" ? `${body} ${INVENTORY_INVITATION}` : body;
29905
+ return summary.verdict === "heavy" ? `${body} ${INVENTORY_INVITATION2}` : body;
29698
29906
  }
29699
29907
 
29700
29908
  // src/lib/subscription-inventory.ts
@@ -29753,27 +29961,28 @@ async function resolveSubscriptionInventory(args) {
29753
29961
 
29754
29962
  // src/commands/bootstrap/ui.ts
29755
29963
  function renderDeploySuccess(version, envPath) {
29756
- return `${colors.success("\u2713")} Simple Stacey is live (stacey-intake v${version}).
29964
+ return `${colors.success("\u2713")} Azzy is live (${INTAKE_AGENT} v${version}).
29757
29965
  env: ${envPath}
29758
- Opening ${colors.field("http://localhost:3000")} in your browser \u2192 Sign in with Microsoft \u2192 chat with Stacey.
29966
+ Opening ${colors.field("http://localhost:3000")} in your browser \u2192 Sign in with Microsoft \u2192 chat with Azzy.
29759
29967
  ${colors.dim("(If it doesn't open, browse to http://localhost:3000 yourself.)")}
29760
29968
  ${colors.dim("First turn may say 'warming up' for a few minutes while access propagates \u2014 that's expected.")}
29761
29969
  `;
29762
29970
  }
29763
29971
  function renderDeployFailure(error) {
29764
29972
  const hint = error instanceof LocalCliError && error.hint ? error.hint : "Re-run 'm8t bootstrap ui' (idempotent).";
29765
- return `${colors.error("\u2717")} Simple Stacey deploy failed: ${error.message}
29973
+ return `${colors.error("\u2717")} Azzy deploy failed: ${error.message}
29766
29974
  ${colors.hint(hint)}
29767
29975
  `;
29768
29976
  }
29769
29977
  var BootstrapUiCommand = class extends M8tCommand {
29770
29978
  static paths = [["bootstrap", "ui"]];
29771
29979
  static usage = Command56.Usage({
29772
- description: "Deploy Simple Stacey + start the local onboarding chat UI in the background (returns immediately).",
29980
+ description: "Deploy Azzy + start the local onboarding chat UI in the background (returns immediately).",
29773
29981
  details: [
29774
29982
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
29775
- "installer's foundry-create phase, grants you Foundry data-plane access, deploys a no-brain",
29776
- "Simple Stacey (stacey-intake), writes apps/web/.env.local (chat-only unless --voice is set),",
29983
+ "installer's foundry-create phase, grants you Foundry data-plane access, deploys the no-brain",
29984
+ "onboarding intake agent (azzy-intake), writes apps/web/.env.local (voice relay config is added",
29985
+ "only when --voice is set \u2014 see --voice for what that does and does not affect),",
29777
29986
  "installs deps, and starts the webapp detached so a coding agent can continue without opening",
29778
29987
  "a separate terminal.",
29779
29988
  "",
@@ -29785,7 +29994,7 @@ var BootstrapUiCommand = class extends M8tCommand {
29785
29994
  ["Stop the running onboarding UI", "$0 bootstrap ui --stop"],
29786
29995
  ["Prep only (don't serve at all)", "$0 bootstrap ui --repo-root /path/to/m8t --prep-only"],
29787
29996
  ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"],
29788
- ["Experimental: enable the intake voice call", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
29997
+ ["Experimental: start the voice relay (no effect on the text-only intake)", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
29789
29998
  ]
29790
29999
  });
29791
30000
  repoRoot = Option53.String("--repo-root");
@@ -29798,7 +30007,7 @@ var BootstrapUiCommand = class extends M8tCommand {
29798
30007
  stop = Option53.Boolean("--stop", false);
29799
30008
  foreground = Option53.Boolean("--foreground", false);
29800
30009
  voice = Option53.Boolean("--voice", false, {
29801
- 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."
30010
+ 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."
29802
30011
  });
29803
30012
  async executeCommand() {
29804
30013
  if (this.stop === true) {
@@ -29851,7 +30060,7 @@ var BootstrapUiCommand = class extends M8tCommand {
29851
30060
  const oid = await getSignedInUserOid();
29852
30061
  await ensureFounderFoundryRole({ credential: credential2, subscriptionId: state.subscriptionId, principalId: oid, accountScope });
29853
30062
  const identity = await getSignedInUserIdentity();
29854
- out("deploying Simple Stacey (stacey-intake) in the background...");
30063
+ out("deploying the intake agent (azzy-intake) in the background...");
29855
30064
  const deployOutcome = (async () => {
29856
30065
  const [choice, inventory] = await Promise.all([
29857
30066
  resolveIntakeModel({
@@ -29865,7 +30074,7 @@ var BootstrapUiCommand = class extends M8tCommand {
29865
30074
  onNarrate: out
29866
30075
  })
29867
30076
  ]);
29868
- return deploySimpleStaceyWithRetry({
30077
+ return deployIntakeAgentWithRetry({
29869
30078
  credential: credential2,
29870
30079
  endpoint,
29871
30080
  repoRoot,
@@ -29963,16 +30172,16 @@ import { Command as Command57, Option as Option54 } from "clipanion";
29963
30172
  var BootstrapSeedProfileCommand = class extends M8tCommand {
29964
30173
  static paths = [["bootstrap", "seed-profile"]];
29965
30174
  static usage = Command57.Usage({
29966
- description: "Seed your advisors' brains with the founder + company profile from the onboarding questionnaire.",
29967
- 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.",
30175
+ description: "Seed your advisors' brains with the founder + company profile from the onboarding intake.",
30176
+ 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.",
29968
30177
  examples: [
29969
30178
  ["Seed now (idempotent)", "$0 bootstrap seed-profile"],
29970
30179
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
29971
30180
  ]
29972
30181
  });
29973
30182
  endpoint = Option54.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
29974
- brain = Option54.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
29975
- watch = Option54.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
30183
+ brain = Option54.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/azzy-brain." });
30184
+ watch = Option54.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
29976
30185
  timeout = Option54.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
29977
30186
  githubAppCreds = Option54.String("--github-app-creds");
29978
30187
  async executeCommand() {
@@ -29994,7 +30203,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
29994
30203
  const sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
29995
30204
  for (; ; ) {
29996
30205
  const token = await getFoundryToken();
29997
- const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
30206
+ const { hadIntake, block, rejection } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
29998
30207
  if (block) {
29999
30208
  const azIdentity = await getSignedInUserIdentity();
30000
30209
  await applyProfileToBrains({
@@ -30005,19 +30214,28 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
30005
30214
  subscriptionId: ctx.subscriptionId,
30006
30215
  azIdentity
30007
30216
  });
30008
- this.context.stdout.write(`${colors.success("\u2713")} seeded your advisors' brains (${ctx.brainRepos.join(", ")}) from your questionnaire.
30217
+ this.context.stdout.write(`${colors.success("\u2713")} seeded your advisors' brains (${ctx.brainRepos.join(", ")}) from your intake.
30009
30218
  `);
30010
30219
  return 0;
30011
30220
  }
30012
30221
  if (!watch || Date.now() >= deadline) {
30222
+ if (rejection !== null && rejection !== "no-artifact") {
30223
+ this.context.stderr.write(
30224
+ ` ${colors.dim(`found an onboarding block but could not read it \u2014 ${describeBlockRejection(rejection)}.`)}
30225
+ ${colors.hint("fix:")} delete this onboarding conversation in the Azure AI Foundry portal, then redo the intake and run: m8t bootstrap seed-profile
30226
+ `
30227
+ );
30228
+ return 3;
30229
+ }
30230
+ const why = hadIntake ? "intake not complete yet \u2014 no m8t_onboarding block found." : "no onboarding intake found.";
30013
30231
  this.context.stderr.write(
30014
- ` ${colors.dim(hadIntake ? "questionnaire not complete yet \u2014 no m8t_onboarding block found." : "no onboarding questionnaire found.")}
30232
+ ` ${colors.dim(why)}
30015
30233
  ${colors.hint("retry:")} m8t bootstrap seed-profile
30016
30234
  `
30017
30235
  );
30018
30236
  return 3;
30019
30237
  }
30020
- this.context.stderr.write(` ${colors.dim("waiting for the questionnaire to complete\u2026")}
30238
+ this.context.stderr.write(` ${colors.dim("waiting for the intake to complete\u2026")}
30021
30239
  `);
30022
30240
  await sleep5(2e4);
30023
30241
  }