@m8t-stack/cli 0.2.20 → 0.2.22

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
@@ -1157,6 +1157,10 @@ async function enableHostedBrain(args) {
1157
1157
  memory: def.memory,
1158
1158
  env,
1159
1159
  metadata: {
1160
+ // Spread the current metadata first so unmanaged keys (a2a, a2aCard, …)
1161
+ // survive the relink version; the managed fields are re-asserted over the
1162
+ // spread, and createCoderVersion applies the `brain` arg last.
1163
+ ...current.metadata,
1160
1164
  source: "m8t-stack-POC",
1161
1165
  kind: "hosted",
1162
1166
  persona: current.metadata?.persona ?? "coding-agent",
@@ -1227,7 +1231,7 @@ var init_enable_hosted_brain = __esm({
1227
1231
  import { Builtins, Cli } from "clipanion";
1228
1232
 
1229
1233
  // src/lib/package-version.ts
1230
- var CLI_VERSION = "0.2.20";
1234
+ var CLI_VERSION = "0.2.22";
1231
1235
 
1232
1236
  // src/lib/render-error.ts
1233
1237
  init_errors();
@@ -15649,7 +15653,7 @@ async function awaitDataPlaneReady(opts) {
15649
15653
  const consecutive = opts.consecutive ?? 3;
15650
15654
  const attempts = opts.attempts ?? 60;
15651
15655
  const intervalMs = opts.intervalMs ?? 5e3;
15652
- const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15656
+ const sleep4 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15653
15657
  let streak = 0;
15654
15658
  for (let i = 1; i <= attempts; i++) {
15655
15659
  const r = await opts.probe();
@@ -15671,7 +15675,7 @@ async function awaitDataPlaneReady(opts) {
15671
15675
  streak = 0;
15672
15676
  opts.onProgress?.(`data-plane not ready (${forcedRetryable ? `status ${(r.status ?? 0).toString()}` : cls.category}), waiting\u2026`);
15673
15677
  }
15674
- if (i < attempts) await sleep3(intervalMs);
15678
+ if (i < attempts) await sleep4(intervalMs);
15675
15679
  }
15676
15680
  return { ready: false, attempts };
15677
15681
  }
@@ -16699,7 +16703,7 @@ init_errors();
16699
16703
  async function deployHostedWorker(args) {
16700
16704
  const onProgress = args.onProgress ?? ((_m) => {
16701
16705
  });
16702
- const sleep3 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
16706
+ const sleep4 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
16703
16707
  const now = args.now ?? (() => Date.now());
16704
16708
  const timeout = args.pollTimeoutMs ?? 8 * 60 * 1e3;
16705
16709
  const interval = args.pollIntervalMs ?? 1e4;
@@ -16751,7 +16755,7 @@ async function deployHostedWorker(args) {
16751
16755
  hint: "Provisioning can take 2\u20135 min; re-run with the same name to resume, or check the version status in the Foundry portal."
16752
16756
  });
16753
16757
  }
16754
- await sleep3(interval);
16758
+ await sleep4(interval);
16755
16759
  }
16756
16760
  }
16757
16761
 
@@ -22568,7 +22572,7 @@ init_errors();
22568
22572
  // ../../packages/brain/engine/dist/esm/types.js
22569
22573
  var ENGINE_VERSION = "0.1.0";
22570
22574
  var DEFAULT_SAFETY_LAG_SECONDS = 3600;
22571
- var SOURCES = ["webapp", "a2a", "mcp", "telegram"];
22575
+ var SOURCES = ["webapp", "a2a", "mcp", "telegram", "voice-relay"];
22572
22576
  var WORKER_ALIASES = { "azure-advisor": "azzy" };
22573
22577
  function canonicalWorker(pk) {
22574
22578
  const lower = pk.toLowerCase();
@@ -22632,9 +22636,11 @@ var TableCursor = class {
22632
22636
  throw e;
22633
22637
  }
22634
22638
  const watermarks = row.watermarks ? JSON.parse(row.watermarks) : {};
22639
+ const fetchFailures = row.fetchFailures ? JSON.parse(row.fetchFailures) : void 0;
22635
22640
  return {
22636
22641
  worker: canonical,
22637
22642
  watermarks,
22643
+ ...fetchFailures && Object.keys(fetchFailures).length > 0 ? { fetchFailures } : {},
22638
22644
  safetyLagSeconds: row.safetyLagSeconds ?? DEFAULT_SAFETY_LAG_SECONDS,
22639
22645
  engineVersion: row.engineVersion ?? ENGINE_VERSION,
22640
22646
  ...row.lastDreamAt ? { lastDreamAt: row.lastDreamAt } : {}
@@ -22657,9 +22663,12 @@ var TableCursor = class {
22657
22663
  safetyLagSeconds: cursor.safetyLagSeconds,
22658
22664
  engineVersion: cursor.engineVersion
22659
22665
  };
22666
+ if (cursor.fetchFailures && Object.keys(cursor.fetchFailures).length > 0) {
22667
+ entity.fetchFailures = JSON.stringify(cursor.fetchFailures);
22668
+ }
22660
22669
  if (cursor.lastDreamAt)
22661
22670
  entity.lastDreamAt = cursor.lastDreamAt;
22662
- await this.client.upsertEntity(entity);
22671
+ await this.client.upsertEntity(entity, "Replace");
22663
22672
  }
22664
22673
  async ensureTable() {
22665
22674
  try {
@@ -22695,9 +22704,22 @@ function computeWatermark(rows, now, safetyLagSeconds) {
22695
22704
  }
22696
22705
  async function commitCursorAdvance(plan, cursor) {
22697
22706
  const prior = await cursor.read(plan.worker);
22707
+ const watermarks = {};
22708
+ for (const [pk, watermark] of Object.entries(plan.watermarks)) {
22709
+ const priorWatermark = Object.hasOwn(prior.watermarks, pk) ? prior.watermarks[pk] : void 0;
22710
+ if (priorWatermark && watermark.ts <= priorWatermark.ts) {
22711
+ watermarks[pk] = {
22712
+ ts: watermark.ts,
22713
+ consumedRowKeys: [.../* @__PURE__ */ new Set([...priorWatermark.consumedRowKeys, ...watermark.consumedRowKeys])]
22714
+ };
22715
+ } else {
22716
+ watermarks[pk] = watermark;
22717
+ }
22718
+ }
22698
22719
  const next = {
22699
22720
  worker: plan.worker,
22700
- watermarks: plan.watermarks,
22721
+ watermarks,
22722
+ ...plan.fetchFailures && Object.keys(plan.fetchFailures).length > 0 ? { fetchFailures: plan.fetchFailures } : {},
22701
22723
  safetyLagSeconds: prior.safetyLagSeconds || DEFAULT_SAFETY_LAG_SECONDS,
22702
22724
  engineVersion: ENGINE_VERSION,
22703
22725
  lastDreamAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -22783,7 +22805,7 @@ function mapItems(raw) {
22783
22805
  return acc.reverse();
22784
22806
  }
22785
22807
  function makeConversationSource(client, opts = {}) {
22786
- const sleep3 = opts.sleep ?? defaultSleep;
22808
+ const sleep4 = opts.sleep ?? defaultSleep;
22787
22809
  return {
22788
22810
  async items(conversationId) {
22789
22811
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
@@ -22801,7 +22823,7 @@ function makeConversationSource(client, opts = {}) {
22801
22823
  throw new ConvFetchError("auth", `auth failed reading ${conversationId}`);
22802
22824
  const retryable = status === void 0 || status === TRANSIENT;
22803
22825
  if (retryable && attempt < MAX_ATTEMPTS - 1) {
22804
- await sleep3(attempt);
22826
+ await sleep4(attempt);
22805
22827
  continue;
22806
22828
  }
22807
22829
  throw new ConvFetchError("terminal", `exhausted ${String(MAX_ATTEMPTS)} retries reading ${conversationId}: ${e?.message ?? String(e)}`);
@@ -22951,6 +22973,7 @@ function normalize(group, items, enrichment) {
22951
22973
 
22952
22974
  // ../../packages/brain/engine/dist/esm/pipeline.js
22953
22975
  var isKnownSource = (s) => SOURCES.includes(s);
22976
+ var MAX_CONV_FETCH_ATTEMPTS = 3;
22954
22977
  async function runPipeline(args) {
22955
22978
  const { worker, sources, now, since, reset } = args;
22956
22979
  const monotonicNow = args.monotonicNow ?? Date.now;
@@ -22977,6 +23000,7 @@ async function runPipeline(args) {
22977
23000
  let recovered = 0;
22978
23001
  const consumedRowsByPk = {};
22979
23002
  const failedTsByPk = {};
23003
+ const newFailures = {};
22980
23004
  const window2 = windowFor(rows, now, since);
22981
23005
  for (const g of groups) {
22982
23006
  const pk = g.worker;
@@ -23067,8 +23091,17 @@ async function runPipeline(args) {
23067
23091
  else
23068
23092
  recovered += 1;
23069
23093
  } else {
23070
- skips.addSkip(outcome, sampleId(g), fetchError?.message);
23071
- if (outcome === "conv-fetch-failed" || outcome === "auth-failed") {
23094
+ let lastError = fetchError?.message;
23095
+ if (outcome === "conv-fetch-failed") {
23096
+ const id = conversationId ?? "";
23097
+ const next = (cursor.fetchFailures?.[id] ?? 0) + 1;
23098
+ newFailures[id] = next;
23099
+ if (next >= MAX_CONV_FETCH_ATTEMPTS) {
23100
+ lastError = `terminal after ${String(next)} cycles \u2014 watermark released`;
23101
+ }
23102
+ }
23103
+ skips.addSkip(outcome, sampleId(g), lastError);
23104
+ if (outcome === "auth-failed" || outcome === "conv-fetch-failed" && conversationId && newFailures[conversationId] < MAX_CONV_FETCH_ATTEMPTS) {
23072
23105
  const cur = failedTsByPk[pk];
23073
23106
  if (!cur || g.startedAt < cur)
23074
23107
  failedTsByPk[pk] = g.startedAt;
@@ -23088,7 +23121,7 @@ async function runPipeline(args) {
23088
23121
  latencyMs: Math.round(monotonicNow() - t0)
23089
23122
  };
23090
23123
  assertInvariant(stats, groups.length);
23091
- const advance = buildAdvancePlan(target, cursor, physicalPks, consumedRowsByPk, failedTsByPk, now, safetyLag);
23124
+ const advance = buildAdvancePlan(target, cursor, physicalPks, consumedRowsByPk, failedTsByPk, newFailures, now, safetyLag);
23092
23125
  const out = {
23093
23126
  worker: target,
23094
23127
  window: window2,
@@ -23167,7 +23200,7 @@ function windowFor(rows, now, since) {
23167
23200
  from = r.eventTimestamp;
23168
23201
  return { from, to };
23169
23202
  }
23170
- function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedTsByPk, now, safetyLagSeconds) {
23203
+ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedTsByPk, fetchFailures, now, safetyLagSeconds) {
23171
23204
  const watermarks = {};
23172
23205
  const consumedKeysByPk = {};
23173
23206
  const priorTsByPk = {};
@@ -23191,6 +23224,7 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
23191
23224
  watermarks,
23192
23225
  consumedRowsByPk: consumedKeysByPk,
23193
23226
  failedTsByPk,
23227
+ ...Object.keys(fetchFailures).length > 0 ? { fetchFailures } : {},
23194
23228
  now: now.toISOString(),
23195
23229
  safetyLagSeconds,
23196
23230
  priorTsByPk
@@ -23591,7 +23625,7 @@ function extractJson(text) {
23591
23625
  return JSON.parse(candidate2.slice(start, end + 1));
23592
23626
  }
23593
23627
  async function propose(model, system, user, opts = {}) {
23594
- const sleep3 = opts.sleep ?? defaultSleep2;
23628
+ const sleep4 = opts.sleep ?? defaultSleep2;
23595
23629
  const modelName = opts.model ?? MODEL_NAME_FALLBACK;
23596
23630
  let inputTokens = 0;
23597
23631
  let outputTokens = 0;
@@ -23607,7 +23641,7 @@ async function propose(model, system, user, opts = {}) {
23607
23641
  } catch (e) {
23608
23642
  lastErr = `parse: ${e.message}`;
23609
23643
  if (attempt < MAX_MODEL_ATTEMPTS - 1) {
23610
- await sleep3(attempt);
23644
+ await sleep4(attempt);
23611
23645
  continue;
23612
23646
  }
23613
23647
  break;
@@ -23616,7 +23650,7 @@ async function propose(model, system, user, opts = {}) {
23616
23650
  if (!Array.isArray(rawDeltas)) {
23617
23651
  lastErr = "schema: top-level { deltas: [] } missing";
23618
23652
  if (attempt < MAX_MODEL_ATTEMPTS - 1) {
23619
- await sleep3(attempt);
23653
+ await sleep4(attempt);
23620
23654
  continue;
23621
23655
  }
23622
23656
  break;
@@ -24183,6 +24217,7 @@ function narrowAdvance(src, kept) {
24183
24217
  watermarks,
24184
24218
  consumedRowsByPk,
24185
24219
  failedTsByPk: src.failedTsByPk,
24220
+ ...src.fetchFailures ? { fetchFailures: src.fetchFailures } : {},
24186
24221
  now,
24187
24222
  safetyLagSeconds,
24188
24223
  priorTsByPk
@@ -24200,7 +24235,13 @@ function filterAdvance(src, keptRowsByPk) {
24200
24235
  consumedRowsByPk[pk] = (src.consumedRowsByPk[pk] ?? []).filter((rk) => keep.has(rk));
24201
24236
  watermarks[pk] = { ts: wm.ts, consumedRowKeys: wm.consumedRowKeys.filter((rk) => keep.has(rk)) };
24202
24237
  }
24203
- return { worker: src.worker, watermarks, consumedRowsByPk, failedTsByPk: src.failedTsByPk };
24238
+ return {
24239
+ worker: src.worker,
24240
+ watermarks,
24241
+ consumedRowsByPk,
24242
+ failedTsByPk: src.failedTsByPk,
24243
+ ...src.fetchFailures ? { fetchFailures: src.fetchFailures } : {}
24244
+ };
24204
24245
  }
24205
24246
 
24206
24247
  // ../../packages/brain/engine/dist/esm/dream/index.js
@@ -24218,6 +24259,18 @@ async function runDream(input, deps, seams = defaultDreamSeams()) {
24218
24259
  });
24219
24260
  }
24220
24261
  input = capped;
24262
+ if (input.conversations.length === 0) {
24263
+ await commitCursorAdvance(input.advance, deps.cursor);
24264
+ deps.logger.info({
24265
+ at: "runDream",
24266
+ worker: input.worker,
24267
+ terminal: "no-corpus",
24268
+ skipped: input.stats.skipped,
24269
+ advanced: true,
24270
+ skipLedger: input.skipLedger.map((s) => `${s.reason}:${String(s.count)}`)
24271
+ });
24272
+ return { worker: input.worker, applied: [], digest, advanced: true };
24273
+ }
24221
24274
  const proposed = await seams.propose(input, deps);
24222
24275
  digest.push({ kind: "cost", model: proposed.cost.model, inputTokens: proposed.cost.inputTokens, outputTokens: proposed.cost.outputTokens, ms: proposed.cost.ms });
24223
24276
  if (!proposed.deltas) {
@@ -25722,7 +25775,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
25722
25775
  typeof this.output === "string" ? this.output : void 0,
25723
25776
  this.context.stdout
25724
25777
  );
25725
- const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
25778
+ const sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
25726
25779
  const watch = this.watch === true;
25727
25780
  for (; ; ) {
25728
25781
  let doc;
@@ -25732,7 +25785,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
25732
25785
  if (watch && e instanceof LocalCliError && e.code === "BOOTSTRAP_STATUS_UNREADABLE") {
25733
25786
  if (mode === "pretty") this.context.stderr.write(` ${colors.dim("waiting for the installer to start\u2026")}
25734
25787
  `);
25735
- await sleep3(1e4);
25788
+ await sleep4(1e4);
25736
25789
  continue;
25737
25790
  }
25738
25791
  throw e;
@@ -25757,7 +25810,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
25757
25810
  );
25758
25811
  return 1;
25759
25812
  }
25760
- await sleep3(1e4);
25813
+ await sleep4(1e4);
25761
25814
  }
25762
25815
  }
25763
25816
  };
@@ -26366,11 +26419,108 @@ import * as path35 from "path";
26366
26419
  import { spawn as spawn7 } from "child_process";
26367
26420
  init_errors();
26368
26421
  init_rbac();
26422
+
26423
+ // src/lib/simple-stacey.ts
26424
+ var SIMPLE_STACEY_PERSONA = "startup-advisor-intake";
26425
+ var SIMPLE_STACEY_AGENT = "stacey-intake";
26426
+ async function deploySimpleStacey(args) {
26427
+ try {
26428
+ return await deployPromptAdvisor({
26429
+ credential: args.credential,
26430
+ endpoint: args.endpoint,
26431
+ repoRoot: args.repoRoot,
26432
+ persona: SIMPLE_STACEY_PERSONA,
26433
+ agentName: SIMPLE_STACEY_AGENT,
26434
+ fieldOverrides: args.fieldOverrides
26435
+ });
26436
+ } catch (e) {
26437
+ if (e && typeof e === "object" && "code" in e) {
26438
+ const err = e;
26439
+ if (err.code === "ADVISOR_PERSONA_MISSING") {
26440
+ const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
26441
+ throw new LocalCliError2({
26442
+ code: "SIMPLE_STACEY_PERSONA_MISSING",
26443
+ message: err.message,
26444
+ hint: err.hint,
26445
+ cause: err.cause
26446
+ });
26447
+ }
26448
+ if (err.code === "ADVISOR_NO_MODEL") {
26449
+ const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
26450
+ throw new LocalCliError2({
26451
+ code: "SIMPLE_STACEY_NO_MODEL",
26452
+ message: err.message,
26453
+ hint: err.hint,
26454
+ cause: err.cause
26455
+ });
26456
+ }
26457
+ if (err.code === "ADVISOR_BAD_EFFORT") {
26458
+ const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
26459
+ throw new LocalCliError2({
26460
+ code: "SIMPLE_STACEY_BAD_EFFORT",
26461
+ message: err.message,
26462
+ hint: err.hint,
26463
+ cause: err.cause
26464
+ });
26465
+ }
26466
+ }
26467
+ throw e;
26468
+ }
26469
+ }
26470
+
26471
+ // src/lib/bootstrap-ui.ts
26472
+ function sleep3(ms) {
26473
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
26474
+ }
26475
+ function isAuthorizationShapedError(error) {
26476
+ const seen = /* @__PURE__ */ new Set();
26477
+ let current = error;
26478
+ while (current != null && !seen.has(current)) {
26479
+ seen.add(current);
26480
+ if (typeof current === "object") {
26481
+ const record = current;
26482
+ if (record.statusCode === 403) return true;
26483
+ if (typeof record.message === "string" && /403|Forbidden|PermissionDenied|not authorized/i.test(record.message)) return true;
26484
+ current = record.cause;
26485
+ continue;
26486
+ }
26487
+ if (typeof current === "string" && /403|Forbidden|PermissionDenied|not authorized/i.test(current)) return true;
26488
+ break;
26489
+ }
26490
+ return false;
26491
+ }
26492
+ async function deploySimpleStaceyWithRetry(args) {
26493
+ const maxWaitMs = args.maxWaitMs ?? 3e5;
26494
+ const intervalMs = args.intervalMs ?? 1e4;
26495
+ const deadline = Date.now() + maxWaitMs;
26496
+ for (; ; ) {
26497
+ try {
26498
+ return await deploySimpleStacey({
26499
+ credential: args.credential,
26500
+ endpoint: args.endpoint,
26501
+ repoRoot: args.repoRoot,
26502
+ fieldOverrides: args.fieldOverrides
26503
+ });
26504
+ } catch (error) {
26505
+ if (!isAuthorizationShapedError(error)) throw error;
26506
+ const remainingMs = deadline - Date.now();
26507
+ if (remainingMs <= 0) {
26508
+ throw new LocalCliError({
26509
+ code: "BOOTSTRAP_UI_ROLE_PROPAGATION_TIMEOUT",
26510
+ message: "Timed out waiting for Azure role propagation before deploying Simple Stacey.",
26511
+ hint: "Azure role propagation can take a few minutes - re-run 'm8t bootstrap ui' (idempotent).",
26512
+ cause: error
26513
+ });
26514
+ }
26515
+ args.onWait?.("waiting for Azure role propagation...");
26516
+ await sleep3(Math.min(intervalMs, remainingMs));
26517
+ }
26518
+ }
26519
+ }
26369
26520
  async function resolveFoundryEndpointWithWait(args, opts = {}) {
26370
26521
  const pollMs = opts.pollMs ?? 1e4;
26371
26522
  const timeoutMs = opts.timeoutMs ?? 15 * 6e4;
26372
26523
  const deadline = Date.now() + timeoutMs;
26373
- const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
26374
26524
  for (; ; ) {
26375
26525
  try {
26376
26526
  const p = await resolveFoundryProject({
@@ -26439,6 +26589,7 @@ function writeWebEnvLocal(args) {
26439
26589
  `AZURE_TENANT_ID=${args.tenantId}`,
26440
26590
  `AZURE_CLIENT_ID=${args.clientId}`,
26441
26591
  `FOUNDRY_PROJECT_ENDPOINT=${args.foundryEndpoint}`,
26592
+ "VOICE_RELAY_PUBLIC_URL=ws://localhost:8790",
26442
26593
  "STORAGE_ACCOUNT_NAME=",
26443
26594
  "M8T_DISABLE_WORKER=1",
26444
26595
  "M8T_ONBOARDING=1",
@@ -26493,11 +26644,16 @@ function onboardingUiPaths(home = os17.homedir()) {
26493
26644
  pidPath: path35.join(dir, "onboarding-ui.pid")
26494
26645
  };
26495
26646
  }
26496
- async function serveOnboardingUiDetached(args) {
26497
- const { logPath, pidPath } = onboardingUiPaths();
26498
- const portNum = Number(args.port);
26499
- const isPortOpen = () => new Promise((resolve3) => {
26500
- const s = net.createConnection({ host: "127.0.0.1", port: portNum });
26647
+ function onboardingRelayPaths(home = os17.homedir()) {
26648
+ const dir = path35.join(home, ".m8t-stack");
26649
+ return {
26650
+ logPath: path35.join(dir, "onboarding-relay.log"),
26651
+ pidPath: path35.join(dir, "onboarding-relay.pid")
26652
+ };
26653
+ }
26654
+ function isLocalPortOpen(port) {
26655
+ return new Promise((resolve3) => {
26656
+ const s = net.createConnection({ host: "127.0.0.1", port });
26501
26657
  s.setTimeout(1e3);
26502
26658
  s.on("connect", () => {
26503
26659
  s.destroy();
@@ -26511,6 +26667,11 @@ async function serveOnboardingUiDetached(args) {
26511
26667
  resolve3(false);
26512
26668
  });
26513
26669
  });
26670
+ }
26671
+ async function serveOnboardingUiDetached(args) {
26672
+ const { logPath, pidPath } = onboardingUiPaths();
26673
+ const portNum = Number(args.port);
26674
+ const isPortOpen = () => isLocalPortOpen(portNum);
26514
26675
  if (await isPortOpen()) {
26515
26676
  return { alreadyRunning: true, logPath };
26516
26677
  }
@@ -26534,13 +26695,53 @@ async function serveOnboardingUiDetached(args) {
26534
26695
  child.unref();
26535
26696
  fs32.writeFileSync(pidPath, String(child.pid), "utf8");
26536
26697
  const deadline = Date.now() + 45e3;
26537
- const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
26698
+ const sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
26538
26699
  while (Date.now() < deadline) {
26539
- await sleep3(500);
26700
+ await sleep4(500);
26540
26701
  if (await isPortOpen()) break;
26541
26702
  }
26542
26703
  return { alreadyRunning: false, logPath };
26543
26704
  }
26705
+ async function serveOnboardingRelayDetached(args) {
26706
+ const { logPath, pidPath } = onboardingRelayPaths();
26707
+ const portNum = 8790;
26708
+ if (await isLocalPortOpen(portNum)) {
26709
+ return { alreadyRunning: true, logPath };
26710
+ }
26711
+ fs32.mkdirSync(path35.dirname(logPath), { recursive: true });
26712
+ const fd = fs32.openSync(logPath, "a");
26713
+ const child = spawn7("pnpm", ["--filter", "web", "exec", "tsx", "voice-relay-entry.ts"], {
26714
+ cwd: args.repoRoot,
26715
+ detached: true,
26716
+ stdio: ["ignore", fd, fd],
26717
+ env: {
26718
+ ...process.env,
26719
+ FOUNDRY_PROJECT_ENDPOINT: args.foundryEndpoint,
26720
+ AZURE_TENANT_ID: args.tenantId,
26721
+ AZURE_CLIENT_ID: args.clientId,
26722
+ RELAY_ALLOWED_ORIGINS: "http://localhost:3000",
26723
+ RELAY_PORT: "8790"
26724
+ },
26725
+ shell: false
26726
+ });
26727
+ fs32.closeSync(fd);
26728
+ if (child.pid == null) {
26729
+ throw new LocalCliError({
26730
+ code: "BOOTSTRAP_UI_RELAY_SPAWN_FAILED",
26731
+ message: "Could not start the voice relay (pnpm did not spawn).",
26732
+ hint: "Check that pnpm + Node 20+ are installed; see the log at " + logPath + "."
26733
+ });
26734
+ }
26735
+ child.unref();
26736
+ fs32.writeFileSync(pidPath, String(child.pid), "utf8");
26737
+ const deadline = Date.now() + 45e3;
26738
+ const sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
26739
+ while (Date.now() < deadline) {
26740
+ await sleep4(500);
26741
+ if (await isLocalPortOpen(portNum)) break;
26742
+ }
26743
+ return { alreadyRunning: false, logPath };
26744
+ }
26544
26745
  function autoOpenOnboardingUi(url, open = tryOpenUrl) {
26545
26746
  try {
26546
26747
  const result = open(url);
@@ -26551,8 +26752,7 @@ function autoOpenOnboardingUi(url, open = tryOpenUrl) {
26551
26752
  } catch {
26552
26753
  }
26553
26754
  }
26554
- function stopOnboardingUi(home = os17.homedir()) {
26555
- const { pidPath } = onboardingUiPaths(home);
26755
+ function stopPidFile(pidPath) {
26556
26756
  let pidStr;
26557
26757
  try {
26558
26758
  pidStr = fs32.readFileSync(pidPath, "utf8").trim();
@@ -26573,56 +26773,27 @@ function stopOnboardingUi(home = os17.homedir()) {
26573
26773
  }
26574
26774
  return true;
26575
26775
  }
26576
-
26577
- // src/lib/simple-stacey.ts
26578
- var SIMPLE_STACEY_PERSONA = "startup-advisor-intake";
26579
- var SIMPLE_STACEY_AGENT = "stacey-intake";
26580
- async function deploySimpleStacey(args) {
26581
- try {
26582
- return await deployPromptAdvisor({
26583
- credential: args.credential,
26584
- endpoint: args.endpoint,
26585
- repoRoot: args.repoRoot,
26586
- persona: SIMPLE_STACEY_PERSONA,
26587
- agentName: SIMPLE_STACEY_AGENT,
26588
- fieldOverrides: args.fieldOverrides
26589
- });
26590
- } catch (e) {
26591
- if (e && typeof e === "object" && "code" in e) {
26592
- const err = e;
26593
- if (err.code === "ADVISOR_PERSONA_MISSING") {
26594
- const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
26595
- throw new LocalCliError2({
26596
- code: "SIMPLE_STACEY_PERSONA_MISSING",
26597
- message: err.message,
26598
- hint: err.hint,
26599
- cause: err.cause
26600
- });
26601
- }
26602
- if (err.code === "ADVISOR_NO_MODEL") {
26603
- const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
26604
- throw new LocalCliError2({
26605
- code: "SIMPLE_STACEY_NO_MODEL",
26606
- message: err.message,
26607
- hint: err.hint,
26608
- cause: err.cause
26609
- });
26610
- }
26611
- if (err.code === "ADVISOR_BAD_EFFORT") {
26612
- const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
26613
- throw new LocalCliError2({
26614
- code: "SIMPLE_STACEY_BAD_EFFORT",
26615
- message: err.message,
26616
- hint: err.hint,
26617
- cause: err.cause
26618
- });
26619
- }
26620
- }
26621
- throw e;
26622
- }
26776
+ function stopOnboardingUi(home = os17.homedir()) {
26777
+ const uiStopped = stopPidFile(onboardingUiPaths(home).pidPath);
26778
+ const relayStopped = stopPidFile(onboardingRelayPaths(home).pidPath);
26779
+ return uiStopped || relayStopped;
26623
26780
  }
26624
26781
 
26625
26782
  // src/commands/bootstrap/ui.ts
26783
+ function renderDeploySuccess(version, envPath) {
26784
+ return `${colors.success("\u2713")} Simple Stacey is live (stacey-intake v${version}).
26785
+ env: ${envPath}
26786
+ Opening ${colors.field("http://localhost:3000")} in your browser \u2192 Sign in with Microsoft \u2192 chat with Stacey.
26787
+ ${colors.dim("(If it doesn't open, browse to http://localhost:3000 yourself.)")}
26788
+ ${colors.dim("First turn may say 'warming up' for a few minutes while access propagates \u2014 that's expected.")}
26789
+ `;
26790
+ }
26791
+ function renderDeployFailure(error) {
26792
+ const hint = error instanceof LocalCliError && error.hint ? error.hint : "Re-run 'm8t bootstrap ui' (idempotent).";
26793
+ return `${colors.error("\u2717")} Simple Stacey deploy failed: ${error.message}
26794
+ ${colors.hint(hint)}
26795
+ `;
26796
+ }
26626
26797
  var BootstrapUiCommand = class extends M8tCommand {
26627
26798
  static paths = [["bootstrap", "ui"]];
26628
26799
  static usage = Command53.Usage({
@@ -26702,14 +26873,18 @@ var BootstrapUiCommand = class extends M8tCommand {
26702
26873
  out("granting you Foundry data-plane access\u2026");
26703
26874
  const oid = await getSignedInUserOid();
26704
26875
  await ensureFounderFoundryRole({ credential: credential2, subscriptionId: state.subscriptionId, principalId: oid, accountScope });
26705
- out("deploying Simple Stacey (stacey-intake)\u2026");
26706
26876
  const identity = await getSignedInUserIdentity();
26707
- const version = await deploySimpleStacey({
26877
+ out("deploying Simple Stacey (stacey-intake) in the background...");
26878
+ const deployOutcome = deploySimpleStaceyWithRetry({
26708
26879
  credential: credential2,
26709
26880
  endpoint,
26710
26881
  repoRoot,
26711
- fieldOverrides: { founder_identity_note: composeFounderIdentityNote(identity) }
26712
- });
26882
+ fieldOverrides: { founder_identity_note: composeFounderIdentityNote(identity) },
26883
+ onWait: out
26884
+ }).then(
26885
+ (version) => ({ ok: true, version }),
26886
+ (error) => ({ ok: false, error: error instanceof Error ? error : new Error(String(error)) })
26887
+ );
26713
26888
  const envPath = writeWebEnvLocal({
26714
26889
  repoRoot,
26715
26890
  tenantId: account.tenantId,
@@ -26720,24 +26895,41 @@ var BootstrapUiCommand = class extends M8tCommand {
26720
26895
  out("installing apps/web dependencies (pnpm)\u2026");
26721
26896
  await installWebDeps(repoRoot);
26722
26897
  }
26723
- this.context.stdout.write(
26724
- `${colors.success("\u2713")} Simple Stacey is live (stacey-intake v${version}).
26725
- env: ${envPath}
26726
- Opening ${colors.field("http://localhost:3000")} in your browser \u2192 Sign in with Microsoft \u2192 chat with Stacey.
26727
- ${colors.dim("(If it doesn't open, browse to http://localhost:3000 yourself.)")}
26728
- ${colors.dim("First turn may say 'warming up' for a few minutes while access propagates \u2014 that's expected.")}
26729
- `
26730
- );
26731
26898
  if (this.prepOnly === true) {
26899
+ const outcome2 = await deployOutcome;
26900
+ if (!outcome2.ok) throw outcome2.error;
26901
+ this.context.stdout.write(renderDeploySuccess(outcome2.version, envPath));
26732
26902
  this.context.stdout.write(` ${colors.hint("serve it yourself:")} pnpm --filter web dev
26733
26903
  `);
26734
26904
  return 0;
26735
26905
  }
26736
26906
  if (this.foreground === true) {
26907
+ out("starting the voice relay on :8790 in the background\u2026");
26908
+ await serveOnboardingRelayDetached({
26909
+ repoRoot,
26910
+ foundryEndpoint: endpoint,
26911
+ tenantId: account.tenantId,
26912
+ clientId: state.appRegClientId
26913
+ });
26914
+ out("voice relay on :8790");
26915
+ const outcome2 = await deployOutcome;
26916
+ if (!outcome2.ok) {
26917
+ this.context.stderr.write(renderDeployFailure(outcome2.error));
26918
+ return 1;
26919
+ }
26920
+ this.context.stdout.write(renderDeploySuccess(outcome2.version, envPath));
26737
26921
  out("starting the webapp on :3000 (Ctrl-C to stop)\u2026");
26738
26922
  await runInherit("pnpm", ["--filter", "web", "dev"], repoRoot, "BOOTSTRAP_UI_DEV_FAILED");
26739
26923
  return 0;
26740
26924
  }
26925
+ out("starting the voice relay on :8790 in the background\u2026");
26926
+ await serveOnboardingRelayDetached({
26927
+ repoRoot,
26928
+ foundryEndpoint: endpoint,
26929
+ tenantId: account.tenantId,
26930
+ clientId: state.appRegClientId
26931
+ });
26932
+ out("voice relay on :8790");
26741
26933
  out("starting the webapp on :3000 in the background\u2026");
26742
26934
  const { alreadyRunning, logPath } = await serveOnboardingUiDetached({ repoRoot, port: this.port });
26743
26935
  if (alreadyRunning) {
@@ -26753,6 +26945,12 @@ var BootstrapUiCommand = class extends M8tCommand {
26753
26945
  );
26754
26946
  }
26755
26947
  autoOpenOnboardingUi(`http://localhost:${this.port}`);
26948
+ const outcome = await deployOutcome;
26949
+ if (!outcome.ok) {
26950
+ this.context.stderr.write(renderDeployFailure(outcome.error));
26951
+ return 1;
26952
+ }
26953
+ this.context.stdout.write(renderDeploySuccess(outcome.version, envPath));
26756
26954
  return 0;
26757
26955
  }
26758
26956
  };
@@ -26790,7 +26988,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
26790
26988
  const parsedTimeout = Number(rawTimeout);
26791
26989
  const timeoutMin = rawTimeout !== "" && Number.isFinite(parsedTimeout) ? parsedTimeout : 20;
26792
26990
  const deadline = Date.now() + timeoutMin * 6e4;
26793
- const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
26991
+ const sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
26794
26992
  for (; ; ) {
26795
26993
  const token = await getFoundryToken();
26796
26994
  const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
@@ -26813,7 +27011,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
26813
27011
  }
26814
27012
  this.context.stderr.write(` ${colors.dim("waiting for the questionnaire to complete\u2026")}
26815
27013
  `);
26816
- await sleep3(2e4);
27014
+ await sleep4(2e4);
26817
27015
  }
26818
27016
  }
26819
27017
  };