@m8t-stack/cli 0.2.80 → 0.2.81

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
@@ -1400,7 +1400,7 @@ var init_enable_hosted_brain = __esm({
1400
1400
  import { Builtins, Cli } from "clipanion";
1401
1401
 
1402
1402
  // src/lib/package-version.ts
1403
- var CLI_VERSION = "0.2.80";
1403
+ var CLI_VERSION = "0.2.81";
1404
1404
 
1405
1405
  // src/lib/render-error.ts
1406
1406
  init_errors();
@@ -26631,13 +26631,66 @@ import { TableClient as TableClient7 } from "@azure/data-tables";
26631
26631
  import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
26632
26632
  import { LogsQueryClient as LogsQueryClient3, LogsQueryResultStatus as LogsQueryResultStatus3 } from "@azure/monitor-query";
26633
26633
 
26634
+ // ../../packages/agent-ledger/dist/esm/pii.js
26635
+ import { createHmac } from "crypto";
26636
+ var EMAIL_PLACEHOLDER = "[email]";
26637
+ var PHONE_PLACEHOLDER = "[phone]";
26638
+ var SECRET_PLACEHOLDER = "[secret]";
26639
+ var PEM_BLOCK_RE = /-----BEGIN [A-Z0-9 ]{0,32}PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]{0,32}PRIVATE KEY-----/g;
26640
+ var JWT_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{6,}(?:\.[A-Za-z0-9_-]{10,})?/g;
26641
+ var HTTP_CREDENTIAL_RE = /\b(Bearer|Basic)[ \t]+(?=[A-Za-z0-9._~+/=-]{0,80}[0-9+/=])[A-Za-z0-9._~+/=-]{16,}/gi;
26642
+ var KNOWN_PREFIX_RE = /(?<![A-Za-z0-9_-])(?:sk-[A-Za-z0-9_-]{16,}|rk-[A-Za-z0-9_-]{16,}|xox[a-z]-[A-Za-z0-9-]{10,}|gh[pousr]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|npm_[A-Za-z0-9]{28,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|ya29\.[A-Za-z0-9_-]{20,}|dckr_pat_[A-Za-z0-9_-]{20,}|shp(?:at|ca|pa|ss)_[a-fA-F0-9]{32,})(?![A-Za-z0-9_-])/g;
26643
+ var KEYED_VALUE = `("[^"\\n]{8,}"|'[^'\\n]{8,}'|(?=[A-Za-z0-9+/=_.~%-]{0,64}[0-9+/=])[A-Za-z0-9+/=_.~%-]{8,})`;
26644
+ var KEYED_STRONG_SECRET_RE = new RegExp(`(?<![A-Za-z0-9])(shared[_-]?access[_-]?key|account[_-]?key|access[_-]?key|storage[_-]?key|api[_-]?key|apikey|x[_-]?api[_-]?key|client[_-]?secret|sas[_-]?token|access[_-]?token|refresh[_-]?token|auth[_-]?token|id[_-]?token|bot[_-]?token|private[_-]?key|password|passwd|pwd)(["']?\\s*[:=]\\s*)("[^"\\n]{8,}"|'[^'\\n]{8,}'|[A-Za-z0-9+/=_.~%-]{8,})`, "gi");
26645
+ var KEYED_WEAK_SECRET_RE = new RegExp(`(?<![A-Za-z0-9])(authorization|secret|token|sig)(["']?\\s*[:=]\\s*)` + KEYED_VALUE, "gi");
26646
+ var TELEGRAM_BOT_TOKEN_RE = /(?<!\d)\d{8,10}:AA[A-Za-z0-9_-]{32,34}(?![A-Za-z0-9_-])/g;
26647
+ var SPOKEN_SECRET_RE = /\b((?:password|passcode|passphrase|api[ _-]?key|access code|pin(?: code)?)\s+is[ \t:]+)(?=\S{0,32}[0-9])\S{4,64}/gi;
26648
+ var SPOKEN_PASSWORD_RE = /\b((?:password|passcode|passphrase)\s+is[ \t:]+)(?!\[)(?!(?:wrong|incorrect|invalid|expired|required|missing|reset|unchanged|correct|secure|weak|strong|blank|empty|not|now|still|too|the|already|also|only|just|being|getting)\b)("[^"\n]{4,64}"|'[^'\n]{4,64}'|\S{4,64})/gi;
26649
+ var B64ISH_RE = /(?<![A-Za-z0-9+=_-])(?=[A-Za-z0-9+=_-]{0,64}[A-Z])(?=[A-Za-z0-9+=_-]{0,64}[a-z])(?=[A-Za-z0-9+=_-]{0,64}[0-9])[A-Za-z0-9+=_-]{40,}(?![A-Za-z0-9+=_-])/g;
26650
+ var EMAIL_RE = new RegExp("[\\p{L}\\p{N}._%+-]+@[\\p{L}\\p{N}](?:[\\p{L}\\p{N}.-]*[\\p{L}\\p{N}])?\\.\\p{L}{2,}(?![\\p{L}\\p{N}-])", "gu");
26651
+ var PHONE_INTL_RE = /(?<![\d/.-])\+\d{1,3}(?:[ .-]?\(\d{1,4}\))?(?:[ .-]?\d){6,12}(?!\d)/g;
26652
+ var PHONE_SEP_RE = /(?<![\d.-])(?:\d[ .-])?(?:\(\d{3}\)[ .-]?\d{3}[ .-]?\d{4}|\d{3}[ .-]\d{3}[ .-]\d{4}|0\d{1,2}[ .-]\d{7})(?![\d-])/g;
26653
+ var PHONE_CONTEXT_RE = /\b((?:call|text|dial|whatsapp)(?:\s+(?:me|us|him|her|them|back))?(?:\s+(?:at|on))?\s*[:-]?\s*|(?:phone|mobile|cell|tel)(?:\s*(?:number|no\.?))?\s*(?:is|:)?\s*)(?<![\d.(-])\d{7,15}(?![\d.])/gi;
26654
+ var PEM_OPEN_RE = /-----BEGIN [A-Z0-9 ]{0,32}PRIVATE KEY-----/;
26655
+ var UNCLOSED_QUOTED_SECRET_RE = /(?<![A-Za-z0-9])(?:shared[_-]?access[_-]?key|account[_-]?key|access[_-]?key|storage[_-]?key|api[_-]?key|apikey|x[_-]?api[_-]?key|client[_-]?secret|sas[_-]?token|access[_-]?token|refresh[_-]?token|auth[_-]?token|id[_-]?token|bot[_-]?token|private[_-]?key|password|passwd|pwd|authorization|secret|token|sig)["']?\s*[:=]\s*["'][^"'\n]*$/i;
26656
+ function failClosedOnCutConstructs(text) {
26657
+ const pem = PEM_OPEN_RE.exec(text);
26658
+ if (pem && !/-----END [A-Z0-9 ]{0,32}PRIVATE KEY-----/.test(text.slice(pem.index))) {
26659
+ return text.slice(0, pem.index) + SECRET_PLACEHOLDER;
26660
+ }
26661
+ const quoted = UNCLOSED_QUOTED_SECRET_RE.exec(text);
26662
+ if (quoted)
26663
+ return text.slice(0, quoted.index) + SECRET_PLACEHOLDER;
26664
+ return text;
26665
+ }
26666
+ function maskSecrets(text) {
26667
+ const masked = text.replace(PEM_BLOCK_RE, SECRET_PLACEHOLDER).replace(JWT_RE, SECRET_PLACEHOLDER).replace(HTTP_CREDENTIAL_RE, (_, scheme) => `${scheme} ${SECRET_PLACEHOLDER}`).replace(KNOWN_PREFIX_RE, SECRET_PLACEHOLDER).replace(TELEGRAM_BOT_TOKEN_RE, SECRET_PLACEHOLDER).replace(KEYED_STRONG_SECRET_RE, (_, key2, sep4) => `${key2}${sep4}${SECRET_PLACEHOLDER}`).replace(KEYED_WEAK_SECRET_RE, (_, key2, sep4) => `${key2}${sep4}${SECRET_PLACEHOLDER}`).replace(SPOKEN_SECRET_RE, (_, lead) => `${lead}${SECRET_PLACEHOLDER}`).replace(SPOKEN_PASSWORD_RE, (_, lead) => `${lead}${SECRET_PLACEHOLDER}`).replace(B64ISH_RE, SECRET_PLACEHOLDER);
26668
+ return failClosedOnCutConstructs(masked);
26669
+ }
26670
+ function maskContact(text) {
26671
+ return text.replace(EMAIL_RE, EMAIL_PLACEHOLDER).replace(PHONE_INTL_RE, PHONE_PLACEHOLDER).replace(PHONE_SEP_RE, PHONE_PLACEHOLDER).replace(PHONE_CONTEXT_RE, (_, lead) => `${lead}${PHONE_PLACEHOLDER}`);
26672
+ }
26673
+ var BARE_PHONE_RUN_RE = /(?<![\w.,-])\d{7,15}(?!\.?\d)(?![\w,-])/g;
26674
+ function maskContactStrict(text) {
26675
+ return maskContact(text).replace(BARE_PHONE_RUN_RE, PHONE_PLACEHOLDER);
26676
+ }
26677
+ var USERREF_DIGEST_VERSION = "1";
26678
+ function userRefHashKey() {
26679
+ return process.env.M8T_LEDGER_USERREF_KEY ?? (process.env.STORAGE_ACCOUNT_NAME ? `m8t-ledger-userref:${process.env.STORAGE_ACCOUNT_NAME}` : "m8t-agent-ledger-userref-dev");
26680
+ }
26681
+ function maskUserRef(ref) {
26682
+ if (ref == null)
26683
+ return void 0;
26684
+ return ref.replace(EMAIL_RE, (m) => `email#${USERREF_DIGEST_VERSION}:${createHmac("sha256", userRefHashKey()).update(m.toLowerCase()).digest("hex").slice(0, 32)}`);
26685
+ }
26686
+
26634
26687
  // ../../packages/agent-ledger/dist/esm/read/ledger-identity.js
26635
26688
  var CHANNEL_SOURCES = /* @__PURE__ */ new Set(["telegram", "slack"]);
26636
26689
  function resolveIdentity(source, userRef, members = []) {
26637
26690
  if (!userRef)
26638
26691
  return { source, ref: "unknown" };
26639
26692
  if (CHANNEL_SOURCES.has(source)) {
26640
- const m = members.find((x) => x.source === source && x.handle.toLowerCase() === userRef.toLowerCase());
26693
+ const m = members.find((x) => x.source === source && (x.handle.toLowerCase() === userRef.toLowerCase() || maskUserRef(x.handle.toLowerCase()) === userRef.toLowerCase()));
26641
26694
  if (m)
26642
26695
  return { source, ref: userRef, display: m.displayName };
26643
26696
  }
@@ -26947,6 +27000,10 @@ function makeRowKey(timestampIso, eventId) {
26947
27000
  return `${String(reverse).padStart(19, "0")}_${eventId}`;
26948
27001
  }
26949
27002
 
27003
+ // ../../packages/agent-ledger/dist/esm/entity.js
27004
+ var SNIPPET_MAX_LEN = 256;
27005
+ var PII_SCAN_WINDOW = SNIPPET_MAX_LEN * 16;
27006
+
26950
27007
  // ../../packages/brain/engine/dist/esm/cursor.js
26951
27008
  var CURSOR_PARTITION_KEY = "dreamcursor";
26952
27009
  function isAbsentCursorError(e) {
@@ -27175,7 +27232,7 @@ function mapItems(raw) {
27175
27232
  continue;
27176
27233
  if (item.role !== "user" && item.role !== "assistant")
27177
27234
  continue;
27178
- const text = Array.isArray(item.content) ? stripVoiceEnvelope(item.content.map((c) => typeof c.text === "string" && c.text.length > 0 ? c.text : typeof c.transcript === "string" && c.transcript.length > 0 ? c.transcript : "").filter(Boolean).join("\n\n")) : "";
27235
+ const text = Array.isArray(item.content) ? maskSecrets(stripVoiceEnvelope(item.content.map((c) => typeof c.text === "string" && c.text.length > 0 ? c.text : typeof c.transcript === "string" && c.transcript.length > 0 ? c.transcript : "").filter(Boolean).join("\n\n"))) : "";
27179
27236
  const at = typeof item.created_at === "number" ? new Date(item.created_at * 1e3).toISOString() : void 0;
27180
27237
  acc.push(at ? { role: item.role, text, at } : { role: item.role, text });
27181
27238
  }
@@ -27460,7 +27517,9 @@ async function runPipeline(args) {
27460
27517
  const grpRows = rowsByGroupKey.get(groupKeyOf(g)) ?? [];
27461
27518
  (consumedRowsByPk[pk] ??= []).push(...grpRows);
27462
27519
  const provRows = grpRows.map((r) => ({ rowKey: rowStorageKey(r), eventTimestamp: r.eventTimestamp }));
27463
- const conv = normalize(ng, items, enrichment);
27520
+ const trustedOwner = g.source !== "a2a" && g.user.ref !== "unknown" && !g.user.ref.startsWith("svc:");
27521
+ const dreamItems = trustedOwner ? items : items.map((i) => ({ ...i, text: maskContactStrict(i.text) }));
27522
+ const conv = normalize(ng, dreamItems, enrichment);
27464
27523
  conv.provenance = { pk, rows: provRows };
27465
27524
  conversations.push(conv);
27466
27525
  if (outcome === "consumed")
@@ -29217,14 +29276,278 @@ function defaultDeps(overrides) {
29217
29276
  return deps;
29218
29277
  }
29219
29278
 
29220
- // src/commands/foundry/create.ts
29279
+ // src/commands/conversations/sweep.ts
29280
+ import { createHash as createHash4 } from "crypto";
29221
29281
  import { Command as Command53, Option as Option50 } from "clipanion";
29282
+ import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
29283
+ import { TableClient as TableClient8 } from "@azure/data-tables";
29284
+ import { AIProjectClient as AIProjectClient4 } from "@azure/ai-projects";
29285
+ init_errors();
29286
+ var LEDGER_TABLE_NAME2 = "AgentLedger";
29287
+ var KEY_LIFETIME_DAYS = 30;
29288
+ var DAY_MS = 24 * 60 * 60 * 1e3;
29289
+ function ownerDigest(nativeUserKey) {
29290
+ return createHash4("sha256").update(nativeUserKey).digest("base64url");
29291
+ }
29292
+ function svcPrincipal(svcRef) {
29293
+ return svcRef.split(":")[1] ?? "";
29294
+ }
29295
+ function svcConversationFacts(rows) {
29296
+ const facts = /* @__PURE__ */ new Map();
29297
+ for (const r of rows) {
29298
+ if (!r.foundryConversationId) continue;
29299
+ if (!r.userRef?.startsWith("svc:")) continue;
29300
+ const prev = facts.get(r.foundryConversationId);
29301
+ if (!prev || r.eventTimestamp > prev.lastSeen) {
29302
+ facts.set(r.foundryConversationId, {
29303
+ conversationId: r.foundryConversationId,
29304
+ svcRef: r.userRef,
29305
+ principal: svcPrincipal(r.userRef),
29306
+ agent: r.agentName,
29307
+ lastSeen: r.eventTimestamp
29308
+ });
29309
+ }
29310
+ }
29311
+ return facts;
29312
+ }
29313
+ function selectStaleCandidates(facts, cutoffIso) {
29314
+ return [...facts.values()].filter((f) => f.lastSeen < cutoffIso).sort((a, b) => a.lastSeen < b.lastSeen ? -1 : 1);
29315
+ }
29316
+ function confirmCandidate(fact, meta, newestItemAt, cutoffIso) {
29317
+ if (meta.app !== "m8t-webapp") return { ok: false, reason: `unexpected app tag '${meta.app ?? ""}'` };
29318
+ const expectedOwner = `web:${ownerDigest(fact.svcRef)}`;
29319
+ if (meta.ownerKey !== expectedOwner) return { ok: false, reason: "owner mismatch" };
29320
+ if (newestItemAt === null) return { ok: false, reason: "no items to prove inactivity" };
29321
+ if (newestItemAt >= cutoffIso) return { ok: false, reason: "recent activity" };
29322
+ return { ok: true };
29323
+ }
29324
+ function statusOf2(e) {
29325
+ const err = e;
29326
+ return err?.statusCode ?? err?.status;
29327
+ }
29328
+ function defaultDeps2() {
29329
+ const credential2 = new AzureCliCredential2();
29330
+ const openaiFor = (ctx) => new AIProjectClient4(ctx.projectEndpoint, credential2).getOpenAIClient();
29331
+ return {
29332
+ async resolveContext(opts) {
29333
+ const account = await getAzAccount();
29334
+ const subscriptionId = opts.subscription ?? account.subscriptionId;
29335
+ const project = await resolveFoundryProject({
29336
+ credential: credential2,
29337
+ subscriptionId,
29338
+ interactive: false,
29339
+ endpoint: opts.endpoint
29340
+ });
29341
+ const m = /\/resourceGroups\/([^/]+)/i.exec(project.accountScope);
29342
+ if (!m) throw new LocalCliError({ code: "sweep_context", message: `Could not parse resource group from '${project.accountScope}'.` });
29343
+ const { ledgerTableEndpoint } = await discoverLedgerResources({
29344
+ credential: credential2,
29345
+ subscriptionId,
29346
+ resourceGroup: m[1]
29347
+ });
29348
+ return { projectEndpoint: project.endpoint, ledgerTableEndpoint };
29349
+ },
29350
+ async fetchRows(ctx, fromIso, toIso) {
29351
+ const client = new TableClient8(ctx.ledgerTableEndpoint, LEDGER_TABLE_NAME2, credential2);
29352
+ return fetchLedgerRows({ workers: [], source: "all", from: fromIso, to: toIso }, client);
29353
+ },
29354
+ async retrieveMetadata(ctx, conversationId) {
29355
+ try {
29356
+ const conv = await openaiFor(ctx).conversations.retrieve(conversationId);
29357
+ const md = conv.metadata;
29358
+ const s = (v) => typeof v === "string" ? v : void 0;
29359
+ if (!md || typeof md !== "object") return {};
29360
+ return { app: s(md.app), ownerKey: s(md.ownerKey), platform: s(md.platform) };
29361
+ } catch (e) {
29362
+ if (statusOf2(e) === 404) return "gone";
29363
+ throw e;
29364
+ }
29365
+ },
29366
+ async newestItemAt(ctx, conversationId) {
29367
+ try {
29368
+ for await (const item of openaiFor(ctx).conversations.items.list(conversationId)) {
29369
+ const created = item.created_at;
29370
+ if (typeof created !== "number") return { at: null };
29371
+ return { at: new Date(created * 1e3).toISOString() };
29372
+ }
29373
+ return { at: null };
29374
+ } catch (e) {
29375
+ if (statusOf2(e) === 404) return "gone";
29376
+ throw e;
29377
+ }
29378
+ },
29379
+ async deleteConversation(ctx, conversationId) {
29380
+ try {
29381
+ await openaiFor(ctx).conversations.delete(conversationId);
29382
+ return "deleted";
29383
+ } catch (e) {
29384
+ if (statusOf2(e) === 404) return "already-gone";
29385
+ throw e;
29386
+ }
29387
+ }
29388
+ };
29389
+ }
29390
+ var ConversationsSweepCommand = class extends M8tCommand {
29391
+ static paths = [["conversations", "sweep"]];
29392
+ static usage = Command53.Usage({
29393
+ category: "Conversations",
29394
+ description: "Delete expired public-visitor conversations (dry run by default)",
29395
+ details: `
29396
+ Finds conversations owned by delegated end users (public visitors) whose
29397
+ last activity predates the retention window (${String(KEY_LIFETIME_DAYS)}-day key life + grace),
29398
+ confirms each against its own metadata and newest item, and \u2014 only with
29399
+ --delete \u2014 permanently removes them. Team, channel, and delegated-work
29400
+ conversations are structurally out of scope. The masked ledger telemetry
29401
+ of swept conversations is kept.
29402
+ `,
29403
+ examples: [
29404
+ ["Report what would be deleted (nothing is)", "m8t conversations sweep"],
29405
+ ["Delete, capped at 50", "m8t conversations sweep --delete --max 50"]
29406
+ ]
29407
+ });
29408
+ doDelete = Option50.Boolean("--delete", false, {
29409
+ description: "Perform deletions (without this flag the command only reports)"
29410
+ });
29411
+ principal = Option50.String("--principal", {
29412
+ description: "Service-principal oid whose end-user keys are known-ephemeral (required with --delete; a dry run without it reports candidates grouped by principal)"
29413
+ });
29414
+ max = Option50.String("--max", "200", { description: "Maximum deletions per run" });
29415
+ graceDays = Option50.String("--grace-days", "14", {
29416
+ description: "Days past the 30-day key life before a conversation is eligible"
29417
+ });
29418
+ lookbackDays = Option50.String("--lookback-days", "180", {
29419
+ description: "How far back to scan ledger activity for candidates"
29420
+ });
29421
+ subscription = Option50.String("--subscription", { description: "Azure subscription id override" });
29422
+ endpoint = Option50.String("--endpoint", { description: "Foundry project endpoint override" });
29423
+ deps = defaultDeps2();
29424
+ async executeCommand() {
29425
+ const out = this.context.stdout;
29426
+ const max = Number.parseInt(this.max, 10);
29427
+ const grace = Number.parseInt(this.graceDays, 10);
29428
+ const lookback = Number.parseInt(this.lookbackDays, 10);
29429
+ if (!Number.isFinite(max) || max < 1) throw new LocalCliError({ code: "sweep_args", message: "--max must be a positive integer." });
29430
+ if (!Number.isFinite(grace) || grace < 0) throw new LocalCliError({ code: "sweep_args", message: "--grace-days must be a non-negative integer." });
29431
+ const retentionDays = KEY_LIFETIME_DAYS + grace;
29432
+ if (!Number.isFinite(lookback) || lookback <= retentionDays) {
29433
+ throw new LocalCliError({ code: "sweep_args", message: `--lookback-days must exceed the retention window (${String(retentionDays)} days).` });
29434
+ }
29435
+ const now = Date.now();
29436
+ const cutoffIso = new Date(now - retentionDays * DAY_MS).toISOString();
29437
+ const fromIso = new Date(now - lookback * DAY_MS).toISOString();
29438
+ const toIso = new Date(now).toISOString();
29439
+ const ctx = await this.deps.resolveContext({ subscription: this.subscription, endpoint: this.endpoint });
29440
+ out.write(`Scanning ledger activity ${fromIso} \u2192 ${toIso}; retention cutoff ${cutoffIso}.
29441
+ `);
29442
+ const rows = await this.deps.fetchRows(ctx, fromIso, toIso);
29443
+ const facts = svcConversationFacts(rows);
29444
+ const stale = selectStaleCandidates(facts, cutoffIso);
29445
+ if (this.doDelete && !this.principal) {
29446
+ throw new LocalCliError({
29447
+ code: "sweep_principal_required",
29448
+ message: "--delete requires --principal <service-principal oid>.",
29449
+ hint: "Run without --delete first: the dry run lists candidates grouped by principal so you can identify the visitor-minting one."
29450
+ });
29451
+ }
29452
+ const inScope = this.principal ? stale.filter((f) => f.principal === this.principal) : stale;
29453
+ const outOfScope = stale.length - inScope.length;
29454
+ out.write(
29455
+ `${String(facts.size)} delegated conversation(s) seen in window; ${String(stale.length)} past cutoff` + (this.principal ? ` (${String(inScope.length)} for principal ${this.principal}, ${String(outOfScope)} other-principal ignored)` : "") + ".\n"
29456
+ );
29457
+ if (!this.principal && stale.length > 0) {
29458
+ const byPrincipal = /* @__PURE__ */ new Map();
29459
+ for (const f of stale) byPrincipal.set(f.principal, (byPrincipal.get(f.principal) ?? 0) + 1);
29460
+ out.write("Stale candidates by principal (pass --principal to scope deletion):\n");
29461
+ for (const [pr, n] of [...byPrincipal.entries()].sort((a, b) => b[1] - a[1])) {
29462
+ out.write(` ${pr || "(malformed)"} ${String(n)}
29463
+ `);
29464
+ }
29465
+ }
29466
+ out.write("\n");
29467
+ let attempts = 0;
29468
+ let deleted = 0;
29469
+ let alreadyGone = 0;
29470
+ let skipped = 0;
29471
+ let failures = 0;
29472
+ let remaining = 0;
29473
+ for (const fact of inScope) {
29474
+ if (attempts >= max) {
29475
+ remaining += 1;
29476
+ continue;
29477
+ }
29478
+ const ageDays = Math.floor((now - Date.parse(fact.lastSeen)) / DAY_MS);
29479
+ const label = `${fact.conversationId} agent=${fact.agent} last-active=${fact.lastSeen} (${String(ageDays)}d)`;
29480
+ let meta;
29481
+ let newest;
29482
+ try {
29483
+ meta = await this.deps.retrieveMetadata(ctx, fact.conversationId);
29484
+ newest = meta === "gone" ? "gone" : await this.deps.newestItemAt(ctx, fact.conversationId);
29485
+ } catch (e) {
29486
+ failures += 1;
29487
+ out.write(`${colors.error("failed")} ${label} \u2014 ${e.message}
29488
+ `);
29489
+ continue;
29490
+ }
29491
+ if (meta === "gone" || newest === "gone") {
29492
+ alreadyGone += 1;
29493
+ out.write(`${colors.dim("already gone")} ${label}
29494
+ `);
29495
+ continue;
29496
+ }
29497
+ const verdict = confirmCandidate(fact, meta, newest.at, cutoffIso);
29498
+ if (!verdict.ok) {
29499
+ skipped += 1;
29500
+ out.write(`${colors.warn("skip")} ${label} \u2014 ${verdict.reason}
29501
+ `);
29502
+ continue;
29503
+ }
29504
+ attempts += 1;
29505
+ if (!this.doDelete) {
29506
+ out.write(`${colors.field("would delete")} ${label}
29507
+ `);
29508
+ continue;
29509
+ }
29510
+ try {
29511
+ const result2 = await this.deps.deleteConversation(ctx, fact.conversationId);
29512
+ if (result2 === "deleted") {
29513
+ deleted += 1;
29514
+ out.write(`${colors.error("deleted")} ${label}
29515
+ `);
29516
+ } else {
29517
+ alreadyGone += 1;
29518
+ out.write(`${colors.dim("already gone")} ${label}
29519
+ `);
29520
+ }
29521
+ } catch (e) {
29522
+ failures += 1;
29523
+ out.write(`${colors.error("failed")} ${label} \u2014 ${e.message}
29524
+ `);
29525
+ }
29526
+ }
29527
+ out.write("\n");
29528
+ const tail = `already gone ${String(alreadyGone)}, skipped ${String(skipped)} (fail-closed), failed ${String(failures)}` + (remaining > 0 ? `, ${String(remaining)} past the cap \u2014 re-run to continue` : "") + ".\n";
29529
+ if (this.doDelete) {
29530
+ out.write(`Deleted ${String(deleted)}, ${tail}`);
29531
+ } else {
29532
+ out.write(`DRY RUN \u2014 nothing deleted. ${String(attempts)} conversation(s) would be deleted, ${tail}`);
29533
+ }
29534
+ if (failures > 0) {
29535
+ out.write(`${colors.error("Run incomplete:")} ${String(failures)} verification/deletion failure(s) \u2014 fix access and re-run.
29536
+ `);
29537
+ return 1;
29538
+ }
29539
+ return 0;
29540
+ }
29541
+ };
29542
+
29543
+ // src/commands/foundry/create.ts
29544
+ import { Command as Command54, Option as Option51 } from "clipanion";
29222
29545
 
29223
29546
  // src/lib/foundry-create.ts
29224
29547
  init_errors();
29225
- import { createHash as createHash4 } from "crypto";
29548
+ import { createHash as createHash5 } from "crypto";
29226
29549
  function deriveAccountName(subscriptionId) {
29227
- const h = createHash4("sha256").update(subscriptionId).digest("hex").slice(0, 12);
29550
+ const h = createHash5("sha256").update(subscriptionId).digest("hex").slice(0, 12);
29228
29551
  return `m8t${h}`;
29229
29552
  }
29230
29553
  function assertHostedRegion(location) {
@@ -29456,7 +29779,7 @@ async function createFoundryProject(args) {
29456
29779
  init_errors();
29457
29780
  var FoundryCreateCommand = class extends M8tCommand {
29458
29781
  static paths = [["foundry", "create"]];
29459
- static usage = Command53.Usage({
29782
+ static usage = Command54.Usage({
29460
29783
  description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
29461
29784
  details: "Non-interactive and idempotent. Creates the AIServices account (custom subdomain + project management), a project, and a model deployment (default gpt-4.1-mini @ capacity 50). Region must be hosted-agent-eligible. Emits the project endpoint as structured output. Re-run is a clean no-op (account/project skipped if present; deployment capacity converges UP, never down).",
29462
29785
  examples: [
@@ -29465,16 +29788,16 @@ var FoundryCreateCommand = class extends M8tCommand {
29465
29788
  ["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
29466
29789
  ]
29467
29790
  });
29468
- resourceGroup = Option50.String("--resource-group");
29469
- location = Option50.String("--location");
29470
- account = Option50.String("--account");
29471
- project = Option50.String("--project", "m8t");
29472
- model = Option50.String("--model", "gpt-4.1-mini");
29473
- modelVersion = Option50.String("--model-version", "2025-04-14");
29474
- capacity = Option50.String("--capacity", "50");
29475
- subscription = Option50.String("--subscription");
29476
- skipQuotaCheck = Option50.Boolean("--skip-quota-check", false);
29477
- output = Option50.String("--output");
29791
+ resourceGroup = Option51.String("--resource-group");
29792
+ location = Option51.String("--location");
29793
+ account = Option51.String("--account");
29794
+ project = Option51.String("--project", "m8t");
29795
+ model = Option51.String("--model", "gpt-4.1-mini");
29796
+ modelVersion = Option51.String("--model-version", "2025-04-14");
29797
+ capacity = Option51.String("--capacity", "50");
29798
+ subscription = Option51.String("--subscription");
29799
+ skipQuotaCheck = Option51.Boolean("--skip-quota-check", false);
29800
+ output = Option51.String("--output");
29478
29801
  async executeCommand() {
29479
29802
  const mode = resolveOutputMode(
29480
29803
  this.output,
@@ -29546,22 +29869,22 @@ var FoundryCreateCommand = class extends M8tCommand {
29546
29869
  };
29547
29870
 
29548
29871
  // src/commands/foundry/await-ready.ts
29549
- import { Command as Command54, Option as Option51 } from "clipanion";
29550
- import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
29872
+ import { Command as Command55, Option as Option52 } from "clipanion";
29873
+ import { AzureCliCredential as AzureCliCredential3 } from "@azure/identity";
29551
29874
  init_errors();
29552
29875
  var FoundryAwaitReadyCommand = class extends M8tCommand {
29553
29876
  static paths = [["foundry", "await-ready"]];
29554
- static usage = Command54.Usage({
29877
+ static usage = Command55.Usage({
29555
29878
  description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
29556
29879
  details: "Probes the project (GET /agents) until it returns 200 on a few consecutive tries, or fails clearly after a bounded budget. A newly-created account can serve intermittent 404 'Project not found' for minutes; run this after 'foundry create' and before deploying agents so the worker phase doesn't catch the unstable window.",
29557
29880
  examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
29558
29881
  });
29559
- endpoint = Option51.String("--endpoint");
29560
- consecutive = Option51.String("--consecutive", "3");
29561
- attempts = Option51.String("--attempts", "60");
29562
- interval = Option51.String("--interval", "5");
29563
- subscription = Option51.String("--subscription");
29564
- output = Option51.String("--output");
29882
+ endpoint = Option52.String("--endpoint");
29883
+ consecutive = Option52.String("--consecutive", "3");
29884
+ attempts = Option52.String("--attempts", "60");
29885
+ interval = Option52.String("--interval", "5");
29886
+ subscription = Option52.String("--subscription");
29887
+ output = Option52.String("--output");
29565
29888
  async executeCommand() {
29566
29889
  const mode = resolveOutputMode(this.output, this.context.stdout);
29567
29890
  const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
@@ -29572,7 +29895,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
29572
29895
  const attempts = typeof this.attempts === "string" ? Number(this.attempts) : 60;
29573
29896
  const intervalMs = (typeof this.interval === "string" ? Number(this.interval) : 5) * 1e3;
29574
29897
  await getAzAccount();
29575
- const credential2 = new AzureCliCredential2();
29898
+ const credential2 = new AzureCliCredential3();
29576
29899
  const probe = makeAgentsProbe(credential2, endpoint);
29577
29900
  const onProgress = mode === "pretty" ? (m) => this.context.stderr.write(` ${colors.dim(m)}
29578
29901
  `) : void 0;
@@ -29595,7 +29918,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
29595
29918
  };
29596
29919
 
29597
29920
  // src/commands/bootstrap/preflight.ts
29598
- import { Command as Command55, Option as Option52 } from "clipanion";
29921
+ import { Command as Command56, Option as Option53 } from "clipanion";
29599
29922
 
29600
29923
  // ../../packages/telemetry-contract/artifact/tier-map.ts
29601
29924
  var EVENT_TIERS = {
@@ -29647,7 +29970,7 @@ function preflightRenderable(results) {
29647
29970
  }
29648
29971
  var BootstrapPreflightCommand = class extends M8tCommand {
29649
29972
  static paths = [["bootstrap", "preflight"]];
29650
- static usage = Command55.Usage({
29973
+ static usage = Command56.Usage({
29651
29974
  description: "Loudly verify you can install m8t (Owner/UAA + directory admin) and hard-stop if not.",
29652
29975
  details: "Step 1 of `m8t bootstrap`. Prints an unmissable admin-credentials notice, then checks: Owner or User Access Administrator at subscription scope (hard requirement), directory-admin capability to register the app (or pass --client-id), every Azure resource provider the install uses (registering any that are missing), soft-deleted Cognitive Services accounts (they keep their model quota until purged, so quota can read free and the model deployment still fail), and \u2014 with --location \u2014 model quota in the target region. Exits non-zero with the exact failing check + remedy. `m8t prereqs` runs the same substrate checks on their own.",
29653
29976
  examples: [
@@ -29656,9 +29979,9 @@ var BootstrapPreflightCommand = class extends M8tCommand {
29656
29979
  ["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
29657
29980
  ]
29658
29981
  });
29659
- clientId = Option52.String("--client-id");
29660
- subscription = Option52.String("--subscription");
29661
- location = Option52.String("--location", {
29982
+ clientId = Option53.String("--client-id");
29983
+ subscription = Option53.String("--subscription");
29984
+ location = Option53.String("--location", {
29662
29985
  description: "Target region. Enables the model-quota check \u2014 without it, quota is not verified."
29663
29986
  });
29664
29987
  async executeCommand() {
@@ -29758,7 +30081,7 @@ ${colors.error(" " + why)}
29758
30081
  import * as fs35 from "fs";
29759
30082
  import * as os15 from "os";
29760
30083
  import * as path38 from "path";
29761
- import { Command as Command56, Option as Option53 } from "clipanion";
30084
+ import { Command as Command57, Option as Option54 } from "clipanion";
29762
30085
  init_errors();
29763
30086
 
29764
30087
  // src/lib/bootstrap-mi.ts
@@ -29913,7 +30236,7 @@ async function kickInstaller(s) {
29913
30236
  }
29914
30237
 
29915
30238
  // src/lib/bootstrap-status.ts
29916
- import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
30239
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "crypto";
29917
30240
  import * as fs33 from "fs/promises";
29918
30241
  import * as os13 from "os";
29919
30242
  import * as path36 from "path";
@@ -29921,7 +30244,7 @@ init_errors();
29921
30244
  var STATUS_CONTAINER = "status";
29922
30245
  var STATUS_BLOB = "status.json";
29923
30246
  function deriveStatusSaName(resourceGroup, subscriptionId) {
29924
- const hex = createHash5("sha256").update(resourceGroup + subscriptionId).digest("hex").slice(0, 12);
30247
+ const hex = createHash6("sha256").update(resourceGroup + subscriptionId).digest("hex").slice(0, 12);
29925
30248
  return `m8tinst${hex}`;
29926
30249
  }
29927
30250
  function statusBlobUrl(saName) {
@@ -30128,7 +30451,7 @@ var ACI_NAME = "m8t-installer";
30128
30451
  var MI_NAME = "m8t-installer-mi";
30129
30452
  var BootstrapLaunchCommand = class extends M8tCommand {
30130
30453
  static paths = [["bootstrap", "launch"]];
30131
- static usage = Command56.Usage({
30454
+ static usage = Command57.Usage({
30132
30455
  description: "Create + authorize the installer managed identity, then kick the cloud installer.",
30133
30456
  details: "Step 2 of `m8t bootstrap` (run after `preflight`). Ensures the resource group, creates the m8t app registration (or uses --client-id), creates a user-assigned managed identity granted Owner at subscription scope, and launches the published m8t-installer image as an ACI run-to-completion job under that identity. Writes ~/.m8t/bootstrap.json for `status` and `reap`, and threads your Azure object id so the installer can grant you the platform's data-plane roles itself.",
30134
30457
  examples: [
@@ -30139,26 +30462,26 @@ var BootstrapLaunchCommand = class extends M8tCommand {
30139
30462
  ["Share a support contact", "$0 bootstrap launch --location eastus2 --contact-email you@example.com --company 'Acme'"]
30140
30463
  ]
30141
30464
  });
30142
- location = Option53.String("--location");
30143
- resourceGroup = Option53.String("--resource-group");
30144
- clientId = Option53.String("--client-id");
30145
- subscription = Option53.String("--subscription");
30146
- installerTag = Option53.String("--installer-tag");
30465
+ location = Option54.String("--location");
30466
+ resourceGroup = Option54.String("--resource-group");
30467
+ clientId = Option54.String("--client-id");
30468
+ subscription = Option54.String("--subscription");
30469
+ installerTag = Option54.String("--installer-tag");
30147
30470
  // Full image ref override (registry + repo + tag) — an escape hatch when the
30148
30471
  // default org/tag is wrong for the current CLI (e.g. a stale published build).
30149
30472
  // Wins over --installer-tag / the pinned default.
30150
- installerImage = Option53.String("--installer-image");
30151
- gatewayImageRef = Option53.String("--gateway-image-ref");
30152
- githubAppCreds = Option53.String("--github-app-creds");
30153
- contactEmail = Option53.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
30154
- company = Option53.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
30473
+ installerImage = Option54.String("--installer-image");
30474
+ gatewayImageRef = Option54.String("--gateway-image-ref");
30475
+ githubAppCreds = Option54.String("--github-app-creds");
30476
+ contactEmail = Option54.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
30477
+ company = Option54.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
30155
30478
  // Value-carrying on purpose: a bare --force would be cargo-culted into
30156
30479
  // runbooks and harness prompts and erode the protection, whereas a faithful
30157
30480
  // paste can never accidentally carry the victim group's name. It AUTHORIZES
30158
30481
  // the target; --resource-group is what CHOOSES it.
30159
- reinstallInto = Option53.String("--reinstall-into", { description: "Consent to installing into --resource-group even though it already holds resources. Must match the target group's name." });
30160
- org = Option53.String("--org", { description: "Assert the GitHub App on disk is installed on this org; refuses on a mismatch." });
30161
- noBrains = Option53.Boolean("--no-brains", false, { description: "Install without brain-backed workers. For test and CI rigs; the supported install creates brains." });
30482
+ reinstallInto = Option54.String("--reinstall-into", { description: "Consent to installing into --resource-group even though it already holds resources. Must match the target group's name." });
30483
+ org = Option54.String("--org", { description: "Assert the GitHub App on disk is installed on this org; refuses on a mismatch." });
30484
+ noBrains = Option54.Boolean("--no-brains", false, { description: "Install without brain-backed workers. For test and CI rigs; the supported install creates brains." });
30162
30485
  async executeCommand() {
30163
30486
  const location = typeof this.location === "string" ? this.location : void 0;
30164
30487
  if (!location) {
@@ -30319,7 +30642,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
30319
30642
  };
30320
30643
 
30321
30644
  // src/commands/bootstrap/status.ts
30322
- import { Command as Command58, Option as Option55 } from "clipanion";
30645
+ import { Command as Command59, Option as Option56 } from "clipanion";
30323
30646
  init_errors();
30324
30647
 
30325
30648
  // src/lib/bootstrap-aci-state.ts
@@ -31323,7 +31646,7 @@ import { randomUUID as randomUUID3 } from "crypto";
31323
31646
  import { execFile, spawn as spawn7 } from "child_process";
31324
31647
 
31325
31648
  // src/lib/companion-artifact.ts
31326
- import { createHash as createHash6 } from "crypto";
31649
+ import { createHash as createHash7 } from "crypto";
31327
31650
  import { constants } from "fs";
31328
31651
  import * as fs36 from "fs/promises";
31329
31652
  import * as path40 from "path";
@@ -31361,7 +31684,7 @@ function canonicalEntry(entry) {
31361
31684
  `;
31362
31685
  }
31363
31686
  function artifactTreeSha256(entries) {
31364
- const hash = createHash6("sha256");
31687
+ const hash = createHash7("sha256");
31365
31688
  const ordered = [...entries].sort(
31366
31689
  (left, right) => left.path.localeCompare(right.path)
31367
31690
  );
@@ -31431,7 +31754,7 @@ function parseArtifactManifest(value) {
31431
31754
  };
31432
31755
  }
31433
31756
  async function sha256File2(filePath) {
31434
- return createHash6("sha256").update(await fs36.readFile(filePath)).digest("hex");
31757
+ return createHash7("sha256").update(await fs36.readFile(filePath)).digest("hex");
31435
31758
  }
31436
31759
  async function walk2(root, relative4 = "") {
31437
31760
  const directory = path40.join(root, ...relative4.split("/").filter(Boolean));
@@ -32247,7 +32570,7 @@ async function uninstallCompanion(options) {
32247
32570
  // src/commands/companion/install.ts
32248
32571
  import * as os18 from "os";
32249
32572
  import * as path43 from "path";
32250
- import { Command as Command57, Option as Option54 } from "clipanion";
32573
+ import { Command as Command58, Option as Option55 } from "clipanion";
32251
32574
 
32252
32575
  // src/lib/companion-channel.ts
32253
32576
  async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps = {}) {
@@ -32262,7 +32585,7 @@ async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps =
32262
32585
  }
32263
32586
 
32264
32587
  // src/lib/companion-download.ts
32265
- import { createHash as createHash7 } from "crypto";
32588
+ import { createHash as createHash8 } from "crypto";
32266
32589
  import { execFile as execFile2 } from "child_process";
32267
32590
  import * as fs38 from "fs/promises";
32268
32591
  import * as os17 from "os";
@@ -32311,7 +32634,7 @@ async function downloadCompanionArtifact(component, deps = {}) {
32311
32634
  message: `${pinned.asset} is larger than this command will accept.`
32312
32635
  });
32313
32636
  }
32314
- const digest = createHash7("sha256").update(bytes).digest("hex");
32637
+ const digest = createHash8("sha256").update(bytes).digest("hex");
32315
32638
  if (digest !== pinned.sha256) {
32316
32639
  throw new LocalCliError({
32317
32640
  code: "COMPANION_ASSET_DIGEST_MISMATCH",
@@ -32405,7 +32728,7 @@ Version: ${state.version}
32405
32728
  }
32406
32729
  var CompanionInstallCommand = class extends M8tCommand {
32407
32730
  static paths = [["companion", "install"]];
32408
- static usage = Command57.Usage({
32731
+ static usage = Command58.Usage({
32409
32732
  description: "Install the desktop companions for this user from the release channel.",
32410
32733
  examples: [
32411
32734
  ["Install the released build", "$0 companion install"],
@@ -32415,10 +32738,10 @@ var CompanionInstallCommand = class extends M8tCommand {
32415
32738
  ]
32416
32739
  ]
32417
32740
  });
32418
- from = Option54.String("--from", {
32741
+ from = Option55.String("--from", {
32419
32742
  description: "A locally staged build directory instead of the released one."
32420
32743
  });
32421
- resourceGroup = Option54.String("--resource-group", {
32744
+ resourceGroup = Option55.String("--resource-group", {
32422
32745
  description: "Which deployment to bind to, when the subscription holds more than one."
32423
32746
  });
32424
32747
  async executeCommand() {
@@ -32446,7 +32769,7 @@ async function looksLikeCheckout(dir) {
32446
32769
  return false;
32447
32770
  }
32448
32771
  }
32449
- var defaultDeps2 = {
32772
+ var defaultDeps3 = {
32450
32773
  discoverGateway,
32451
32774
  writeConfig,
32452
32775
  ensureGatewayRedirectUri,
@@ -32459,7 +32782,7 @@ var defaultDeps2 = {
32459
32782
  }),
32460
32783
  homedir: () => os19.homedir()
32461
32784
  };
32462
- async function finalizeInstall(args, deps = defaultDeps2) {
32785
+ async function finalizeInstall(args, deps = defaultDeps3) {
32463
32786
  const markerDir = path44.join(deps.homedir(), ".m8t");
32464
32787
  const markerPath = path44.join(markerDir, "repo-root");
32465
32788
  const cwd = process.cwd();
@@ -32634,7 +32957,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
32634
32957
  // runbooks and shakedown recipes that a founder may already be part-way
32635
32958
  // through — it prints a deprecation notice and does the right thing.
32636
32959
  static paths = [["bootstrap", "status"], ["bootstrap", "finish"]];
32637
- static usage = Command58.Usage({
32960
+ static usage = Command59.Usage({
32638
32961
  description: "Show the cloud installer's live status (phase, progress, result) \u2014 and finish the local setup when it lands.",
32639
32962
  details: "Reads the durable status blob written by the installer. --watch polls until the install reaches done or failed.\n\nOn reaching done under --watch this also completes the local half of the install, which nothing else in the bootstrap path can do: it registers the webapp's sign-in redirect URI (the installer runs as a managed identity with no directory role, and at launch time the gateway FQDN did not exist yet), writes ~/.m8t/repo-root and the gateway discovery cache, and seeds your advisors' brains from the onboarding intake. Re-runnable \u2014 `m8t bootstrap finish` is a deprecated alias that does exactly this on an already-done install.",
32640
32963
  examples: [
@@ -32643,12 +32966,12 @@ var BootstrapStatusCommand = class extends M8tCommand {
32643
32966
  ["Redo the local setup on a finished install", "$0 bootstrap status --finalize --repo-root /path/to/m8t"]
32644
32967
  ]
32645
32968
  });
32646
- watch = Option55.Boolean("--watch", false);
32647
- output = Option55.String("--output");
32648
- repoRoot = Option55.String("--repo-root", { description: "The m8t clone to point local tools at (default: the current directory)." });
32649
- finalize = Option55.Boolean("--finalize", false, { description: "Complete the local setup against an already-done install, without watching." });
32650
- subscription = Option55.String("--subscription");
32651
- resourceGroup = Option55.String("--resource-group");
32969
+ watch = Option56.Boolean("--watch", false);
32970
+ output = Option56.String("--output");
32971
+ repoRoot = Option56.String("--repo-root", { description: "The m8t clone to point local tools at (default: the current directory)." });
32972
+ finalize = Option56.Boolean("--finalize", false, { description: "Complete the local setup against an already-done install, without watching." });
32973
+ subscription = Option56.String("--subscription");
32974
+ resourceGroup = Option56.String("--resource-group");
32652
32975
  async executeCommand() {
32653
32976
  const state = await readBootstrapState();
32654
32977
  if (!state) {
@@ -32781,7 +33104,7 @@ function formatStatus(d) {
32781
33104
  }
32782
33105
 
32783
33106
  // src/commands/bootstrap/reap.ts
32784
- import { Command as Command59, Option as Option56 } from "clipanion";
33107
+ import { Command as Command60, Option as Option57 } from "clipanion";
32785
33108
  init_errors();
32786
33109
 
32787
33110
  // src/lib/bootstrap-reap.ts
@@ -32875,7 +33198,7 @@ async function reapInstaller(opts) {
32875
33198
  // src/commands/bootstrap/reap.ts
32876
33199
  var BootstrapReapCommand = class extends M8tCommand {
32877
33200
  static paths = [["bootstrap", "reap"]];
32878
- static usage = Command59.Usage({
33201
+ static usage = Command60.Usage({
32879
33202
  description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
32880
33203
  details: "Runs locally on the 'done' signal (the installer can't delete its own identity). The platform RG and the gateway's own assignments persist. A failed install is left intact for diagnosis unless --force.\n\n--sweep-orphans is a DIFFERENT and much broader mode: instead of reaping this install, it scans the WHOLE SUBSCRIPTION for m8t role assignments left behind by installs whose resources are gone \u2014 orphaned installer Owner-at-subscription-scope grants and orphaned gateway subscription-scope roles. It is a dry run that only lists what it found unless you also pass --yes, which deletes them.",
32881
33204
  examples: [
@@ -32885,9 +33208,9 @@ var BootstrapReapCommand = class extends M8tCommand {
32885
33208
  ["\u2026and delete them", "$0 bootstrap reap --sweep-orphans --yes"]
32886
33209
  ]
32887
33210
  });
32888
- force = Option56.Boolean("--force", false, { description: "Reap even if the install failed or never reported a status." });
32889
- sweepOrphans = Option56.Boolean("--sweep-orphans", false, { description: "Subscription-wide: find m8t role assignments orphaned by earlier installs. Lists only, unless --yes." });
32890
- yes = Option56.Boolean("--yes", false, { description: "With --sweep-orphans, actually delete what the sweep found." });
33211
+ force = Option57.Boolean("--force", false, { description: "Reap even if the install failed or never reported a status." });
33212
+ sweepOrphans = Option57.Boolean("--sweep-orphans", false, { description: "Subscription-wide: find m8t role assignments orphaned by earlier installs. Lists only, unless --yes." });
33213
+ yes = Option57.Boolean("--yes", false, { description: "With --sweep-orphans, actually delete what the sweep found." });
32891
33214
  async executeCommand() {
32892
33215
  if (this.sweepOrphans === true) {
32893
33216
  const { subscriptionId: sub } = await getAzAccount();
@@ -32984,7 +33307,7 @@ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors
32984
33307
  import * as fs41 from "fs";
32985
33308
  import * as os21 from "os";
32986
33309
  import * as path46 from "path";
32987
- import { Command as Command60, Option as Option57 } from "clipanion";
33310
+ import { Command as Command61, Option as Option58 } from "clipanion";
32988
33311
  import { DefaultAzureCredential as DefaultAzureCredential24 } from "@azure/identity";
32989
33312
  init_errors();
32990
33313
 
@@ -33812,7 +34135,7 @@ function renderDeployFailure(error) {
33812
34135
  }
33813
34136
  var BootstrapUiCommand = class extends M8tCommand {
33814
34137
  static paths = [["bootstrap", "ui"]];
33815
- static usage = Command60.Usage({
34138
+ static usage = Command61.Usage({
33816
34139
  description: "Deploy Ezra + start the local onboarding chat UI in the background (returns immediately).",
33817
34140
  details: [
33818
34141
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
@@ -33833,16 +34156,16 @@ var BootstrapUiCommand = class extends M8tCommand {
33833
34156
  ["Experimental: start the voice relay (no effect on the text-only intake)", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
33834
34157
  ]
33835
34158
  });
33836
- repoRoot = Option57.String("--repo-root");
33837
- port = Option57.String("--port", "3000");
33838
- endpoint = Option57.String("--endpoint", {
34159
+ repoRoot = Option58.String("--repo-root");
34160
+ port = Option58.String("--port", "3000");
34161
+ endpoint = Option58.String("--endpoint", {
33839
34162
  description: "Foundry project endpoint to target \u2014 overrides discovery when this install's resource group holds more than one Foundry project."
33840
34163
  });
33841
- prepOnly = Option57.Boolean("--prep-only", false);
33842
- skipInstall = Option57.Boolean("--skip-install", false);
33843
- stop = Option57.Boolean("--stop", false);
33844
- foreground = Option57.Boolean("--foreground", false);
33845
- voice = Option57.Boolean("--voice", false, {
34164
+ prepOnly = Option58.Boolean("--prep-only", false);
34165
+ skipInstall = Option58.Boolean("--skip-install", false);
34166
+ stop = Option58.Boolean("--stop", false);
34167
+ foreground = Option58.Boolean("--foreground", false);
34168
+ voice = Option58.Boolean("--voice", false, {
33846
34169
  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."
33847
34170
  });
33848
34171
  async executeCommand() {
@@ -34014,10 +34337,10 @@ var BootstrapUiCommand = class extends M8tCommand {
34014
34337
  };
34015
34338
 
34016
34339
  // src/commands/bootstrap/seed-profile.ts
34017
- import { Command as Command61, Option as Option58 } from "clipanion";
34340
+ import { Command as Command62, Option as Option59 } from "clipanion";
34018
34341
  var BootstrapSeedProfileCommand = class extends M8tCommand {
34019
34342
  static paths = [["bootstrap", "seed-profile"]];
34020
- static usage = Command61.Usage({
34343
+ static usage = Command62.Usage({
34021
34344
  description: "Seed your advisors' brains with the founder + company profile from the onboarding intake.",
34022
34345
  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>/ezra-brain via the GitHub App. Idempotent. --watch polls until the founder finishes the intake.",
34023
34346
  examples: [
@@ -34025,11 +34348,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
34025
34348
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
34026
34349
  ]
34027
34350
  });
34028
- endpoint = Option58.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
34029
- brain = Option58.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/ezra-brain." });
34030
- watch = Option58.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
34031
- timeout = Option58.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
34032
- githubAppCreds = Option58.String("--github-app-creds");
34351
+ endpoint = Option59.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
34352
+ brain = Option59.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/ezra-brain." });
34353
+ watch = Option59.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
34354
+ timeout = Option59.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
34355
+ githubAppCreds = Option59.String("--github-app-creds");
34033
34356
  async executeCommand() {
34034
34357
  const ctx = await resolveSeedContext({
34035
34358
  endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
@@ -34092,7 +34415,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
34092
34415
  import * as fs42 from "fs";
34093
34416
  import * as os22 from "os";
34094
34417
  import * as path47 from "path";
34095
- import { Command as Command62, Option as Option59 } from "clipanion";
34418
+ import { Command as Command63, Option as Option60 } from "clipanion";
34096
34419
  init_errors();
34097
34420
 
34098
34421
  // src/lib/telemetry-enroll.ts
@@ -34174,7 +34497,7 @@ async function resolveKeyVaultName(containerAppResourceId) {
34174
34497
  }
34175
34498
  var TelemetryEnrollCommand = class extends M8tCommand {
34176
34499
  static paths = [["telemetry", "enroll"]];
34177
- static usage = Command62.Usage({
34500
+ static usage = Command63.Usage({
34178
34501
  description: "Enroll this installation for operational telemetry (pre-existing installs).",
34179
34502
  details: "Generates an instance id + ingest key from the m8t telemetry ingest and stores the key in your platform Key Vault, the same place the installer writes it on a fresh install. Your installation is identified only by that random instance id \u2014 no contact, company, or subscription details are sent unless you pass them explicitly. Idempotent: refuses if a key already exists.",
34180
34503
  examples: [
@@ -34182,11 +34505,11 @@ var TelemetryEnrollCommand = class extends M8tCommand {
34182
34505
  ["Enroll and share a support contact", "$0 telemetry enroll --contact-email you@example.com --company 'Acme'"]
34183
34506
  ]
34184
34507
  });
34185
- company = Option59.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
34186
- contactEmail = Option59.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
34187
- subscription = Option59.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
34188
- resourceGroup = Option59.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
34189
- force = Option59.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
34508
+ company = Option60.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
34509
+ contactEmail = Option60.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
34510
+ subscription = Option60.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
34511
+ resourceGroup = Option60.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
34512
+ force = Option60.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
34190
34513
  async executeCommand() {
34191
34514
  const account = await getAzAccount();
34192
34515
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
@@ -34236,7 +34559,7 @@ var TelemetryEnrollCommand = class extends M8tCommand {
34236
34559
  };
34237
34560
 
34238
34561
  // src/commands/companion/bridge.ts
34239
- import { Command as Command63, Option as Option60 } from "clipanion";
34562
+ import { Command as Command64, Option as Option61 } from "clipanion";
34240
34563
 
34241
34564
  // ../../packages/companion-bridge-contract/src/index.ts
34242
34565
  var COMPANION_MESSAGE_MAX_CODE_POINTS = 32768;
@@ -35289,7 +35612,7 @@ async function runCompanionBridgeServe(stdin, stdout, stderr, deps = {}) {
35289
35612
  }
35290
35613
 
35291
35614
  // src/commands/companion/bridge.ts
35292
- var defaultDeps3 = {
35615
+ var defaultDeps4 = {
35293
35616
  roster: rosterCompanions,
35294
35617
  send: sendCompanionMessage,
35295
35618
  converse: converseCompanionMessage,
@@ -35324,7 +35647,7 @@ function exitFor(terminal) {
35324
35647
  }
35325
35648
  return 2;
35326
35649
  }
35327
- async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
35650
+ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps4) {
35328
35651
  let request;
35329
35652
  try {
35330
35653
  request = parseRequestLine(await readSingleRequest(stdin));
@@ -35353,14 +35676,14 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
35353
35676
  return 3;
35354
35677
  }
35355
35678
  }
35356
- var CompanionBridgeCommand = class extends Command63 {
35679
+ var CompanionBridgeCommand = class extends Command64 {
35357
35680
  static paths = [["companion", "_bridge"]];
35358
35681
  /**
35359
35682
  * One process serving many requests instead of one per request, so the
35360
35683
  * session keeps its authenticated context between them. A CLI predating the
35361
35684
  * flag rejects it outright, which is how the app knows to fall back.
35362
35685
  */
35363
- serve = Option60.Boolean("--serve", false);
35686
+ serve = Option61.Boolean("--serve", false);
35364
35687
  async execute() {
35365
35688
  if (this.serve) {
35366
35689
  return runCompanionBridgeServe(
@@ -35378,7 +35701,7 @@ var CompanionBridgeCommand = class extends Command63 {
35378
35701
  };
35379
35702
 
35380
35703
  // src/commands/companion/status.ts
35381
- import { Command as Command64 } from "clipanion";
35704
+ import { Command as Command65 } from "clipanion";
35382
35705
  async function withTimeout(work, ms) {
35383
35706
  let timer;
35384
35707
  try {
@@ -35450,7 +35773,7 @@ Run: m8t companion install
35450
35773
  }
35451
35774
  var CompanionStatusCommand = class extends M8tCommand {
35452
35775
  static paths = [["companion", "status"]];
35453
- static usage = Command64.Usage({
35776
+ static usage = Command65.Usage({
35454
35777
  description: "Verify the installed desktop companion without launching it."
35455
35778
  });
35456
35779
  async executeCommand() {
@@ -35464,7 +35787,7 @@ var CompanionStatusCommand = class extends M8tCommand {
35464
35787
  };
35465
35788
 
35466
35789
  // src/commands/companion/repair.ts
35467
- import { Command as Command65, Option as Option61 } from "clipanion";
35790
+ import { Command as Command66, Option as Option62 } from "clipanion";
35468
35791
  async function runCompanionRepairCommand(stdout, repair) {
35469
35792
  const state = await repair();
35470
35793
  if (state.state === "not-released") {
@@ -35483,10 +35806,10 @@ async function runCompanionRepairCommand(stdout, repair) {
35483
35806
  }
35484
35807
  var CompanionRepairCommand = class extends M8tCommand {
35485
35808
  static paths = [["companion", "repair"]];
35486
- static usage = Command65.Usage({
35809
+ static usage = Command66.Usage({
35487
35810
  description: "Restore the desktop companions and start-at-login state."
35488
35811
  });
35489
- resourceGroup = Option61.String("--resource-group", {
35812
+ resourceGroup = Option62.String("--resource-group", {
35490
35813
  description: "Which deployment to bind to, when the subscription holds more than one."
35491
35814
  });
35492
35815
  async executeCommand() {
@@ -35500,7 +35823,7 @@ var CompanionRepairCommand = class extends M8tCommand {
35500
35823
  };
35501
35824
 
35502
35825
  // src/commands/companion/uninstall.ts
35503
- import { Command as Command66 } from "clipanion";
35826
+ import { Command as Command67 } from "clipanion";
35504
35827
  async function runCompanionUninstallCommand(stdout, uninstall) {
35505
35828
  const state = await uninstall();
35506
35829
  if (state.state !== "not-installed") {
@@ -35512,7 +35835,7 @@ async function runCompanionUninstallCommand(stdout, uninstall) {
35512
35835
  }
35513
35836
  var CompanionUninstallCommand = class extends M8tCommand {
35514
35837
  static paths = [["companion", "uninstall"]];
35515
- static usage = Command66.Usage({
35838
+ static usage = Command67.Usage({
35516
35839
  description: "Remove only this user's desktop companion installation."
35517
35840
  });
35518
35841
  async executeCommand() {
@@ -35582,6 +35905,7 @@ cli.register(A2aDisableCommand);
35582
35905
  cli.register(EvalSkillCommand);
35583
35906
  cli.register(EvalExamCommand);
35584
35907
  cli.register(DreamRunCommand);
35908
+ cli.register(ConversationsSweepCommand);
35585
35909
  cli.register(FoundryCreateCommand);
35586
35910
  cli.register(FoundryAwaitReadyCommand);
35587
35911
  cli.register(BootstrapPreflightCommand);