@m8t-stack/cli 0.2.79 → 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
@@ -229,9 +229,19 @@ var init_agent_roster = __esm({
229
229
  });
230
230
 
231
231
  // ../../packages/api-contract/dist/esm/ui-directive.js
232
+ var DECISION_REFUSAL_REASONS, decisionRefusalReasons;
232
233
  var init_ui_directive = __esm({
233
234
  "../../packages/api-contract/dist/esm/ui-directive.js"() {
234
235
  "use strict";
236
+ DECISION_REFUSAL_REASONS = {
237
+ /** 409 — no live call with that `callId`. Answered twice, or talked past and dismissed. */
238
+ notPending: "decision_not_pending",
239
+ /** 400 — decisions are a prompt-worker surface; a hosted worker raises none. */
240
+ invalidAgent: "invalid_decision_agent",
241
+ /** 400 — the `data-ui-result` payload is malformed or its option is out of range. */
242
+ invalidResult: "invalid_decision_result"
243
+ };
244
+ decisionRefusalReasons = Object.values(DECISION_REFUSAL_REASONS);
235
245
  }
236
246
  });
237
247
 
@@ -1390,7 +1400,7 @@ var init_enable_hosted_brain = __esm({
1390
1400
  import { Builtins, Cli } from "clipanion";
1391
1401
 
1392
1402
  // src/lib/package-version.ts
1393
- var CLI_VERSION = "0.2.79";
1403
+ var CLI_VERSION = "0.2.81";
1394
1404
 
1395
1405
  // src/lib/render-error.ts
1396
1406
  init_errors();
@@ -15005,14 +15015,14 @@ function tryResolveRepoRoot() {
15005
15015
  const root = fs5.readFileSync(marker, "utf8").trim();
15006
15016
  return root.length > 0 ? root : null;
15007
15017
  }
15008
- function assertPlatformCheckout(repoRoot, operation, needs = ["personas"]) {
15018
+ function assertPlatformCheckout(repoRoot, operation, needs = ["personas"], overrides = {}) {
15009
15019
  const missing = needs.filter((d) => !fs5.existsSync(path6.join(repoRoot, d)));
15010
15020
  if (missing.length === 0) return;
15011
15021
  const isAgentRepo = fs5.existsSync(path6.join(repoRoot, "agent", "persona.md"));
15012
15022
  throw new LocalCliError({
15013
15023
  code: "PLATFORM_CHECKOUT_REQUIRED",
15014
- message: `${operation} needs a platform checkout, but ~/.m8t/repo-root points at '${repoRoot}', which has no ${missing.join("/ or ")}/.` + (isAgentRepo ? " That path is the agent repository \u2014 it carries the install runbook, not the platform." : ""),
15015
- hint: "This operation reads platform content that is not published today, so it is maintainer-side for now. Your installation still updates itself: the auto-update rail applies published releases without needing any checkout."
15024
+ message: `${operation} needs a platform checkout, but ${overrides.source ?? "~/.m8t/repo-root"} points at '${repoRoot}', which has no ${missing.join("/ or ")}/.` + (isAgentRepo ? " That path is the agent repository \u2014 it carries the install runbook, not the platform." : ""),
15025
+ hint: overrides.hint ?? "This operation reads platform content that is not published today, so it is maintainer-side for now. Your installation still updates itself: the auto-update rail applies published releases without needing any checkout."
15016
15026
  });
15017
15027
  }
15018
15028
  function resolvePlatformRepoRoot(operation, needs) {
@@ -26621,13 +26631,66 @@ import { TableClient as TableClient7 } from "@azure/data-tables";
26621
26631
  import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
26622
26632
  import { LogsQueryClient as LogsQueryClient3, LogsQueryResultStatus as LogsQueryResultStatus3 } from "@azure/monitor-query";
26623
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
+
26624
26687
  // ../../packages/agent-ledger/dist/esm/read/ledger-identity.js
26625
26688
  var CHANNEL_SOURCES = /* @__PURE__ */ new Set(["telegram", "slack"]);
26626
26689
  function resolveIdentity(source, userRef, members = []) {
26627
26690
  if (!userRef)
26628
26691
  return { source, ref: "unknown" };
26629
26692
  if (CHANNEL_SOURCES.has(source)) {
26630
- 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()));
26631
26694
  if (m)
26632
26695
  return { source, ref: userRef, display: m.displayName };
26633
26696
  }
@@ -26937,6 +27000,10 @@ function makeRowKey(timestampIso, eventId) {
26937
27000
  return `${String(reverse).padStart(19, "0")}_${eventId}`;
26938
27001
  }
26939
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
+
26940
27007
  // ../../packages/brain/engine/dist/esm/cursor.js
26941
27008
  var CURSOR_PARTITION_KEY = "dreamcursor";
26942
27009
  function isAbsentCursorError(e) {
@@ -27165,7 +27232,7 @@ function mapItems(raw) {
27165
27232
  continue;
27166
27233
  if (item.role !== "user" && item.role !== "assistant")
27167
27234
  continue;
27168
- 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"))) : "";
27169
27236
  const at = typeof item.created_at === "number" ? new Date(item.created_at * 1e3).toISOString() : void 0;
27170
27237
  acc.push(at ? { role: item.role, text, at } : { role: item.role, text });
27171
27238
  }
@@ -27450,7 +27517,9 @@ async function runPipeline(args) {
27450
27517
  const grpRows = rowsByGroupKey.get(groupKeyOf(g)) ?? [];
27451
27518
  (consumedRowsByPk[pk] ??= []).push(...grpRows);
27452
27519
  const provRows = grpRows.map((r) => ({ rowKey: rowStorageKey(r), eventTimestamp: r.eventTimestamp }));
27453
- 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);
27454
27523
  conv.provenance = { pk, rows: provRows };
27455
27524
  conversations.push(conv);
27456
27525
  if (outcome === "consumed")
@@ -29207,14 +29276,278 @@ function defaultDeps(overrides) {
29207
29276
  return deps;
29208
29277
  }
29209
29278
 
29210
- // src/commands/foundry/create.ts
29279
+ // src/commands/conversations/sweep.ts
29280
+ import { createHash as createHash4 } from "crypto";
29211
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";
29212
29545
 
29213
29546
  // src/lib/foundry-create.ts
29214
29547
  init_errors();
29215
- import { createHash as createHash4 } from "crypto";
29548
+ import { createHash as createHash5 } from "crypto";
29216
29549
  function deriveAccountName(subscriptionId) {
29217
- const h = createHash4("sha256").update(subscriptionId).digest("hex").slice(0, 12);
29550
+ const h = createHash5("sha256").update(subscriptionId).digest("hex").slice(0, 12);
29218
29551
  return `m8t${h}`;
29219
29552
  }
29220
29553
  function assertHostedRegion(location) {
@@ -29446,7 +29779,7 @@ async function createFoundryProject(args) {
29446
29779
  init_errors();
29447
29780
  var FoundryCreateCommand = class extends M8tCommand {
29448
29781
  static paths = [["foundry", "create"]];
29449
- static usage = Command53.Usage({
29782
+ static usage = Command54.Usage({
29450
29783
  description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
29451
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).",
29452
29785
  examples: [
@@ -29455,16 +29788,16 @@ var FoundryCreateCommand = class extends M8tCommand {
29455
29788
  ["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
29456
29789
  ]
29457
29790
  });
29458
- resourceGroup = Option50.String("--resource-group");
29459
- location = Option50.String("--location");
29460
- account = Option50.String("--account");
29461
- project = Option50.String("--project", "m8t");
29462
- model = Option50.String("--model", "gpt-4.1-mini");
29463
- modelVersion = Option50.String("--model-version", "2025-04-14");
29464
- capacity = Option50.String("--capacity", "50");
29465
- subscription = Option50.String("--subscription");
29466
- skipQuotaCheck = Option50.Boolean("--skip-quota-check", false);
29467
- 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");
29468
29801
  async executeCommand() {
29469
29802
  const mode = resolveOutputMode(
29470
29803
  this.output,
@@ -29536,22 +29869,22 @@ var FoundryCreateCommand = class extends M8tCommand {
29536
29869
  };
29537
29870
 
29538
29871
  // src/commands/foundry/await-ready.ts
29539
- import { Command as Command54, Option as Option51 } from "clipanion";
29540
- 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";
29541
29874
  init_errors();
29542
29875
  var FoundryAwaitReadyCommand = class extends M8tCommand {
29543
29876
  static paths = [["foundry", "await-ready"]];
29544
- static usage = Command54.Usage({
29877
+ static usage = Command55.Usage({
29545
29878
  description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
29546
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.",
29547
29880
  examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
29548
29881
  });
29549
- endpoint = Option51.String("--endpoint");
29550
- consecutive = Option51.String("--consecutive", "3");
29551
- attempts = Option51.String("--attempts", "60");
29552
- interval = Option51.String("--interval", "5");
29553
- subscription = Option51.String("--subscription");
29554
- 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");
29555
29888
  async executeCommand() {
29556
29889
  const mode = resolveOutputMode(this.output, this.context.stdout);
29557
29890
  const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
@@ -29562,7 +29895,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
29562
29895
  const attempts = typeof this.attempts === "string" ? Number(this.attempts) : 60;
29563
29896
  const intervalMs = (typeof this.interval === "string" ? Number(this.interval) : 5) * 1e3;
29564
29897
  await getAzAccount();
29565
- const credential2 = new AzureCliCredential2();
29898
+ const credential2 = new AzureCliCredential3();
29566
29899
  const probe = makeAgentsProbe(credential2, endpoint);
29567
29900
  const onProgress = mode === "pretty" ? (m) => this.context.stderr.write(` ${colors.dim(m)}
29568
29901
  `) : void 0;
@@ -29585,7 +29918,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
29585
29918
  };
29586
29919
 
29587
29920
  // src/commands/bootstrap/preflight.ts
29588
- import { Command as Command55, Option as Option52 } from "clipanion";
29921
+ import { Command as Command56, Option as Option53 } from "clipanion";
29589
29922
 
29590
29923
  // ../../packages/telemetry-contract/artifact/tier-map.ts
29591
29924
  var EVENT_TIERS = {
@@ -29637,7 +29970,7 @@ function preflightRenderable(results) {
29637
29970
  }
29638
29971
  var BootstrapPreflightCommand = class extends M8tCommand {
29639
29972
  static paths = [["bootstrap", "preflight"]];
29640
- static usage = Command55.Usage({
29973
+ static usage = Command56.Usage({
29641
29974
  description: "Loudly verify you can install m8t (Owner/UAA + directory admin) and hard-stop if not.",
29642
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.",
29643
29976
  examples: [
@@ -29646,9 +29979,9 @@ var BootstrapPreflightCommand = class extends M8tCommand {
29646
29979
  ["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
29647
29980
  ]
29648
29981
  });
29649
- clientId = Option52.String("--client-id");
29650
- subscription = Option52.String("--subscription");
29651
- location = Option52.String("--location", {
29982
+ clientId = Option53.String("--client-id");
29983
+ subscription = Option53.String("--subscription");
29984
+ location = Option53.String("--location", {
29652
29985
  description: "Target region. Enables the model-quota check \u2014 without it, quota is not verified."
29653
29986
  });
29654
29987
  async executeCommand() {
@@ -29748,7 +30081,7 @@ ${colors.error(" " + why)}
29748
30081
  import * as fs35 from "fs";
29749
30082
  import * as os15 from "os";
29750
30083
  import * as path38 from "path";
29751
- import { Command as Command56, Option as Option53 } from "clipanion";
30084
+ import { Command as Command57, Option as Option54 } from "clipanion";
29752
30085
  init_errors();
29753
30086
 
29754
30087
  // src/lib/bootstrap-mi.ts
@@ -29903,7 +30236,7 @@ async function kickInstaller(s) {
29903
30236
  }
29904
30237
 
29905
30238
  // src/lib/bootstrap-status.ts
29906
- import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
30239
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "crypto";
29907
30240
  import * as fs33 from "fs/promises";
29908
30241
  import * as os13 from "os";
29909
30242
  import * as path36 from "path";
@@ -29911,7 +30244,7 @@ init_errors();
29911
30244
  var STATUS_CONTAINER = "status";
29912
30245
  var STATUS_BLOB = "status.json";
29913
30246
  function deriveStatusSaName(resourceGroup, subscriptionId) {
29914
- 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);
29915
30248
  return `m8tinst${hex}`;
29916
30249
  }
29917
30250
  function statusBlobUrl(saName) {
@@ -30118,7 +30451,7 @@ var ACI_NAME = "m8t-installer";
30118
30451
  var MI_NAME = "m8t-installer-mi";
30119
30452
  var BootstrapLaunchCommand = class extends M8tCommand {
30120
30453
  static paths = [["bootstrap", "launch"]];
30121
- static usage = Command56.Usage({
30454
+ static usage = Command57.Usage({
30122
30455
  description: "Create + authorize the installer managed identity, then kick the cloud installer.",
30123
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.",
30124
30457
  examples: [
@@ -30129,26 +30462,26 @@ var BootstrapLaunchCommand = class extends M8tCommand {
30129
30462
  ["Share a support contact", "$0 bootstrap launch --location eastus2 --contact-email you@example.com --company 'Acme'"]
30130
30463
  ]
30131
30464
  });
30132
- location = Option53.String("--location");
30133
- resourceGroup = Option53.String("--resource-group");
30134
- clientId = Option53.String("--client-id");
30135
- subscription = Option53.String("--subscription");
30136
- 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");
30137
30470
  // Full image ref override (registry + repo + tag) — an escape hatch when the
30138
30471
  // default org/tag is wrong for the current CLI (e.g. a stale published build).
30139
30472
  // Wins over --installer-tag / the pinned default.
30140
- installerImage = Option53.String("--installer-image");
30141
- gatewayImageRef = Option53.String("--gateway-image-ref");
30142
- githubAppCreds = Option53.String("--github-app-creds");
30143
- contactEmail = Option53.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
30144
- 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." });
30145
30478
  // Value-carrying on purpose: a bare --force would be cargo-culted into
30146
30479
  // runbooks and harness prompts and erode the protection, whereas a faithful
30147
30480
  // paste can never accidentally carry the victim group's name. It AUTHORIZES
30148
30481
  // the target; --resource-group is what CHOOSES it.
30149
- 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." });
30150
- org = Option53.String("--org", { description: "Assert the GitHub App on disk is installed on this org; refuses on a mismatch." });
30151
- 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." });
30152
30485
  async executeCommand() {
30153
30486
  const location = typeof this.location === "string" ? this.location : void 0;
30154
30487
  if (!location) {
@@ -30309,7 +30642,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
30309
30642
  };
30310
30643
 
30311
30644
  // src/commands/bootstrap/status.ts
30312
- import { Command as Command58, Option as Option55 } from "clipanion";
30645
+ import { Command as Command59, Option as Option56 } from "clipanion";
30313
30646
  init_errors();
30314
30647
 
30315
30648
  // src/lib/bootstrap-aci-state.ts
@@ -31313,7 +31646,7 @@ import { randomUUID as randomUUID3 } from "crypto";
31313
31646
  import { execFile, spawn as spawn7 } from "child_process";
31314
31647
 
31315
31648
  // src/lib/companion-artifact.ts
31316
- import { createHash as createHash6 } from "crypto";
31649
+ import { createHash as createHash7 } from "crypto";
31317
31650
  import { constants } from "fs";
31318
31651
  import * as fs36 from "fs/promises";
31319
31652
  import * as path40 from "path";
@@ -31351,7 +31684,7 @@ function canonicalEntry(entry) {
31351
31684
  `;
31352
31685
  }
31353
31686
  function artifactTreeSha256(entries) {
31354
- const hash = createHash6("sha256");
31687
+ const hash = createHash7("sha256");
31355
31688
  const ordered = [...entries].sort(
31356
31689
  (left, right) => left.path.localeCompare(right.path)
31357
31690
  );
@@ -31421,7 +31754,7 @@ function parseArtifactManifest(value) {
31421
31754
  };
31422
31755
  }
31423
31756
  async function sha256File2(filePath) {
31424
- return createHash6("sha256").update(await fs36.readFile(filePath)).digest("hex");
31757
+ return createHash7("sha256").update(await fs36.readFile(filePath)).digest("hex");
31425
31758
  }
31426
31759
  async function walk2(root, relative4 = "") {
31427
31760
  const directory = path40.join(root, ...relative4.split("/").filter(Boolean));
@@ -32237,7 +32570,7 @@ async function uninstallCompanion(options) {
32237
32570
  // src/commands/companion/install.ts
32238
32571
  import * as os18 from "os";
32239
32572
  import * as path43 from "path";
32240
- import { Command as Command57, Option as Option54 } from "clipanion";
32573
+ import { Command as Command58, Option as Option55 } from "clipanion";
32241
32574
 
32242
32575
  // src/lib/companion-channel.ts
32243
32576
  async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps = {}) {
@@ -32252,7 +32585,7 @@ async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps =
32252
32585
  }
32253
32586
 
32254
32587
  // src/lib/companion-download.ts
32255
- import { createHash as createHash7 } from "crypto";
32588
+ import { createHash as createHash8 } from "crypto";
32256
32589
  import { execFile as execFile2 } from "child_process";
32257
32590
  import * as fs38 from "fs/promises";
32258
32591
  import * as os17 from "os";
@@ -32301,7 +32634,7 @@ async function downloadCompanionArtifact(component, deps = {}) {
32301
32634
  message: `${pinned.asset} is larger than this command will accept.`
32302
32635
  });
32303
32636
  }
32304
- const digest = createHash7("sha256").update(bytes).digest("hex");
32637
+ const digest = createHash8("sha256").update(bytes).digest("hex");
32305
32638
  if (digest !== pinned.sha256) {
32306
32639
  throw new LocalCliError({
32307
32640
  code: "COMPANION_ASSET_DIGEST_MISMATCH",
@@ -32395,7 +32728,7 @@ Version: ${state.version}
32395
32728
  }
32396
32729
  var CompanionInstallCommand = class extends M8tCommand {
32397
32730
  static paths = [["companion", "install"]];
32398
- static usage = Command57.Usage({
32731
+ static usage = Command58.Usage({
32399
32732
  description: "Install the desktop companions for this user from the release channel.",
32400
32733
  examples: [
32401
32734
  ["Install the released build", "$0 companion install"],
@@ -32405,10 +32738,10 @@ var CompanionInstallCommand = class extends M8tCommand {
32405
32738
  ]
32406
32739
  ]
32407
32740
  });
32408
- from = Option54.String("--from", {
32741
+ from = Option55.String("--from", {
32409
32742
  description: "A locally staged build directory instead of the released one."
32410
32743
  });
32411
- resourceGroup = Option54.String("--resource-group", {
32744
+ resourceGroup = Option55.String("--resource-group", {
32412
32745
  description: "Which deployment to bind to, when the subscription holds more than one."
32413
32746
  });
32414
32747
  async executeCommand() {
@@ -32436,7 +32769,7 @@ async function looksLikeCheckout(dir) {
32436
32769
  return false;
32437
32770
  }
32438
32771
  }
32439
- var defaultDeps2 = {
32772
+ var defaultDeps3 = {
32440
32773
  discoverGateway,
32441
32774
  writeConfig,
32442
32775
  ensureGatewayRedirectUri,
@@ -32449,7 +32782,7 @@ var defaultDeps2 = {
32449
32782
  }),
32450
32783
  homedir: () => os19.homedir()
32451
32784
  };
32452
- async function finalizeInstall(args, deps = defaultDeps2) {
32785
+ async function finalizeInstall(args, deps = defaultDeps3) {
32453
32786
  const markerDir = path44.join(deps.homedir(), ".m8t");
32454
32787
  const markerPath = path44.join(markerDir, "repo-root");
32455
32788
  const cwd = process.cwd();
@@ -32624,7 +32957,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
32624
32957
  // runbooks and shakedown recipes that a founder may already be part-way
32625
32958
  // through — it prints a deprecation notice and does the right thing.
32626
32959
  static paths = [["bootstrap", "status"], ["bootstrap", "finish"]];
32627
- static usage = Command58.Usage({
32960
+ static usage = Command59.Usage({
32628
32961
  description: "Show the cloud installer's live status (phase, progress, result) \u2014 and finish the local setup when it lands.",
32629
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.",
32630
32963
  examples: [
@@ -32633,12 +32966,12 @@ var BootstrapStatusCommand = class extends M8tCommand {
32633
32966
  ["Redo the local setup on a finished install", "$0 bootstrap status --finalize --repo-root /path/to/m8t"]
32634
32967
  ]
32635
32968
  });
32636
- watch = Option55.Boolean("--watch", false);
32637
- output = Option55.String("--output");
32638
- repoRoot = Option55.String("--repo-root", { description: "The m8t clone to point local tools at (default: the current directory)." });
32639
- finalize = Option55.Boolean("--finalize", false, { description: "Complete the local setup against an already-done install, without watching." });
32640
- subscription = Option55.String("--subscription");
32641
- 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");
32642
32975
  async executeCommand() {
32643
32976
  const state = await readBootstrapState();
32644
32977
  if (!state) {
@@ -32771,7 +33104,7 @@ function formatStatus(d) {
32771
33104
  }
32772
33105
 
32773
33106
  // src/commands/bootstrap/reap.ts
32774
- import { Command as Command59, Option as Option56 } from "clipanion";
33107
+ import { Command as Command60, Option as Option57 } from "clipanion";
32775
33108
  init_errors();
32776
33109
 
32777
33110
  // src/lib/bootstrap-reap.ts
@@ -32865,7 +33198,7 @@ async function reapInstaller(opts) {
32865
33198
  // src/commands/bootstrap/reap.ts
32866
33199
  var BootstrapReapCommand = class extends M8tCommand {
32867
33200
  static paths = [["bootstrap", "reap"]];
32868
- static usage = Command59.Usage({
33201
+ static usage = Command60.Usage({
32869
33202
  description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
32870
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.",
32871
33204
  examples: [
@@ -32875,9 +33208,9 @@ var BootstrapReapCommand = class extends M8tCommand {
32875
33208
  ["\u2026and delete them", "$0 bootstrap reap --sweep-orphans --yes"]
32876
33209
  ]
32877
33210
  });
32878
- force = Option56.Boolean("--force", false, { description: "Reap even if the install failed or never reported a status." });
32879
- sweepOrphans = Option56.Boolean("--sweep-orphans", false, { description: "Subscription-wide: find m8t role assignments orphaned by earlier installs. Lists only, unless --yes." });
32880
- 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." });
32881
33214
  async executeCommand() {
32882
33215
  if (this.sweepOrphans === true) {
32883
33216
  const { subscriptionId: sub } = await getAzAccount();
@@ -32974,7 +33307,7 @@ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors
32974
33307
  import * as fs41 from "fs";
32975
33308
  import * as os21 from "os";
32976
33309
  import * as path46 from "path";
32977
- import { Command as Command60, Option as Option57 } from "clipanion";
33310
+ import { Command as Command61, Option as Option58 } from "clipanion";
32978
33311
  import { DefaultAzureCredential as DefaultAzureCredential24 } from "@azure/identity";
32979
33312
  init_errors();
32980
33313
 
@@ -33802,7 +34135,7 @@ function renderDeployFailure(error) {
33802
34135
  }
33803
34136
  var BootstrapUiCommand = class extends M8tCommand {
33804
34137
  static paths = [["bootstrap", "ui"]];
33805
- static usage = Command60.Usage({
34138
+ static usage = Command61.Usage({
33806
34139
  description: "Deploy Ezra + start the local onboarding chat UI in the background (returns immediately).",
33807
34140
  details: [
33808
34141
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
@@ -33823,16 +34156,16 @@ var BootstrapUiCommand = class extends M8tCommand {
33823
34156
  ["Experimental: start the voice relay (no effect on the text-only intake)", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
33824
34157
  ]
33825
34158
  });
33826
- repoRoot = Option57.String("--repo-root");
33827
- port = Option57.String("--port", "3000");
33828
- endpoint = Option57.String("--endpoint", {
34159
+ repoRoot = Option58.String("--repo-root");
34160
+ port = Option58.String("--port", "3000");
34161
+ endpoint = Option58.String("--endpoint", {
33829
34162
  description: "Foundry project endpoint to target \u2014 overrides discovery when this install's resource group holds more than one Foundry project."
33830
34163
  });
33831
- prepOnly = Option57.Boolean("--prep-only", false);
33832
- skipInstall = Option57.Boolean("--skip-install", false);
33833
- stop = Option57.Boolean("--stop", false);
33834
- foreground = Option57.Boolean("--foreground", false);
33835
- 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, {
33836
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."
33837
34170
  });
33838
34171
  async executeCommand() {
@@ -33855,7 +34188,12 @@ var BootstrapUiCommand = class extends M8tCommand {
33855
34188
  hint: "Run 'm8t bootstrap launch --location <region>' first."
33856
34189
  });
33857
34190
  }
33858
- const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
34191
+ const explicitRoot = typeof this.repoRoot === "string" ? this.repoRoot : void 0;
34192
+ const repoRoot = explicitRoot ?? process.cwd();
34193
+ assertPlatformCheckout(repoRoot, "'m8t bootstrap ui'", ["apps/web"], {
34194
+ source: explicitRoot === void 0 ? "the working directory" : "--repo-root",
34195
+ hint: "The local onboarding chat is part of the platform's web app, which is not published, so this step is unavailable today. Nothing else is blocked: let the install finish, then run 'm8t open' to meet Ezra in your deployed web app."
34196
+ });
33859
34197
  if (this.port !== "3000") {
33860
34198
  throw new LocalCliError({
33861
34199
  code: "BOOTSTRAP_UI_PORT_MSAL_LOCKED",
@@ -33999,10 +34337,10 @@ var BootstrapUiCommand = class extends M8tCommand {
33999
34337
  };
34000
34338
 
34001
34339
  // src/commands/bootstrap/seed-profile.ts
34002
- import { Command as Command61, Option as Option58 } from "clipanion";
34340
+ import { Command as Command62, Option as Option59 } from "clipanion";
34003
34341
  var BootstrapSeedProfileCommand = class extends M8tCommand {
34004
34342
  static paths = [["bootstrap", "seed-profile"]];
34005
- static usage = Command61.Usage({
34343
+ static usage = Command62.Usage({
34006
34344
  description: "Seed your advisors' brains with the founder + company profile from the onboarding intake.",
34007
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.",
34008
34346
  examples: [
@@ -34010,11 +34348,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
34010
34348
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
34011
34349
  ]
34012
34350
  });
34013
- endpoint = Option58.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
34014
- brain = Option58.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/ezra-brain." });
34015
- watch = Option58.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
34016
- timeout = Option58.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
34017
- 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");
34018
34356
  async executeCommand() {
34019
34357
  const ctx = await resolveSeedContext({
34020
34358
  endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
@@ -34077,7 +34415,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
34077
34415
  import * as fs42 from "fs";
34078
34416
  import * as os22 from "os";
34079
34417
  import * as path47 from "path";
34080
- import { Command as Command62, Option as Option59 } from "clipanion";
34418
+ import { Command as Command63, Option as Option60 } from "clipanion";
34081
34419
  init_errors();
34082
34420
 
34083
34421
  // src/lib/telemetry-enroll.ts
@@ -34159,7 +34497,7 @@ async function resolveKeyVaultName(containerAppResourceId) {
34159
34497
  }
34160
34498
  var TelemetryEnrollCommand = class extends M8tCommand {
34161
34499
  static paths = [["telemetry", "enroll"]];
34162
- static usage = Command62.Usage({
34500
+ static usage = Command63.Usage({
34163
34501
  description: "Enroll this installation for operational telemetry (pre-existing installs).",
34164
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.",
34165
34503
  examples: [
@@ -34167,11 +34505,11 @@ var TelemetryEnrollCommand = class extends M8tCommand {
34167
34505
  ["Enroll and share a support contact", "$0 telemetry enroll --contact-email you@example.com --company 'Acme'"]
34168
34506
  ]
34169
34507
  });
34170
- company = Option59.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
34171
- contactEmail = Option59.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
34172
- subscription = Option59.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
34173
- resourceGroup = Option59.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
34174
- 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." });
34175
34513
  async executeCommand() {
34176
34514
  const account = await getAzAccount();
34177
34515
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
@@ -34221,7 +34559,7 @@ var TelemetryEnrollCommand = class extends M8tCommand {
34221
34559
  };
34222
34560
 
34223
34561
  // src/commands/companion/bridge.ts
34224
- import { Command as Command63, Option as Option60 } from "clipanion";
34562
+ import { Command as Command64, Option as Option61 } from "clipanion";
34225
34563
 
34226
34564
  // ../../packages/companion-bridge-contract/src/index.ts
34227
34565
  var COMPANION_MESSAGE_MAX_CODE_POINTS = 32768;
@@ -35274,7 +35612,7 @@ async function runCompanionBridgeServe(stdin, stdout, stderr, deps = {}) {
35274
35612
  }
35275
35613
 
35276
35614
  // src/commands/companion/bridge.ts
35277
- var defaultDeps3 = {
35615
+ var defaultDeps4 = {
35278
35616
  roster: rosterCompanions,
35279
35617
  send: sendCompanionMessage,
35280
35618
  converse: converseCompanionMessage,
@@ -35309,7 +35647,7 @@ function exitFor(terminal) {
35309
35647
  }
35310
35648
  return 2;
35311
35649
  }
35312
- async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
35650
+ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps4) {
35313
35651
  let request;
35314
35652
  try {
35315
35653
  request = parseRequestLine(await readSingleRequest(stdin));
@@ -35338,14 +35676,14 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
35338
35676
  return 3;
35339
35677
  }
35340
35678
  }
35341
- var CompanionBridgeCommand = class extends Command63 {
35679
+ var CompanionBridgeCommand = class extends Command64 {
35342
35680
  static paths = [["companion", "_bridge"]];
35343
35681
  /**
35344
35682
  * One process serving many requests instead of one per request, so the
35345
35683
  * session keeps its authenticated context between them. A CLI predating the
35346
35684
  * flag rejects it outright, which is how the app knows to fall back.
35347
35685
  */
35348
- serve = Option60.Boolean("--serve", false);
35686
+ serve = Option61.Boolean("--serve", false);
35349
35687
  async execute() {
35350
35688
  if (this.serve) {
35351
35689
  return runCompanionBridgeServe(
@@ -35363,7 +35701,7 @@ var CompanionBridgeCommand = class extends Command63 {
35363
35701
  };
35364
35702
 
35365
35703
  // src/commands/companion/status.ts
35366
- import { Command as Command64 } from "clipanion";
35704
+ import { Command as Command65 } from "clipanion";
35367
35705
  async function withTimeout(work, ms) {
35368
35706
  let timer;
35369
35707
  try {
@@ -35435,7 +35773,7 @@ Run: m8t companion install
35435
35773
  }
35436
35774
  var CompanionStatusCommand = class extends M8tCommand {
35437
35775
  static paths = [["companion", "status"]];
35438
- static usage = Command64.Usage({
35776
+ static usage = Command65.Usage({
35439
35777
  description: "Verify the installed desktop companion without launching it."
35440
35778
  });
35441
35779
  async executeCommand() {
@@ -35449,7 +35787,7 @@ var CompanionStatusCommand = class extends M8tCommand {
35449
35787
  };
35450
35788
 
35451
35789
  // src/commands/companion/repair.ts
35452
- import { Command as Command65, Option as Option61 } from "clipanion";
35790
+ import { Command as Command66, Option as Option62 } from "clipanion";
35453
35791
  async function runCompanionRepairCommand(stdout, repair) {
35454
35792
  const state = await repair();
35455
35793
  if (state.state === "not-released") {
@@ -35468,10 +35806,10 @@ async function runCompanionRepairCommand(stdout, repair) {
35468
35806
  }
35469
35807
  var CompanionRepairCommand = class extends M8tCommand {
35470
35808
  static paths = [["companion", "repair"]];
35471
- static usage = Command65.Usage({
35809
+ static usage = Command66.Usage({
35472
35810
  description: "Restore the desktop companions and start-at-login state."
35473
35811
  });
35474
- resourceGroup = Option61.String("--resource-group", {
35812
+ resourceGroup = Option62.String("--resource-group", {
35475
35813
  description: "Which deployment to bind to, when the subscription holds more than one."
35476
35814
  });
35477
35815
  async executeCommand() {
@@ -35485,7 +35823,7 @@ var CompanionRepairCommand = class extends M8tCommand {
35485
35823
  };
35486
35824
 
35487
35825
  // src/commands/companion/uninstall.ts
35488
- import { Command as Command66 } from "clipanion";
35826
+ import { Command as Command67 } from "clipanion";
35489
35827
  async function runCompanionUninstallCommand(stdout, uninstall) {
35490
35828
  const state = await uninstall();
35491
35829
  if (state.state !== "not-installed") {
@@ -35497,7 +35835,7 @@ async function runCompanionUninstallCommand(stdout, uninstall) {
35497
35835
  }
35498
35836
  var CompanionUninstallCommand = class extends M8tCommand {
35499
35837
  static paths = [["companion", "uninstall"]];
35500
- static usage = Command66.Usage({
35838
+ static usage = Command67.Usage({
35501
35839
  description: "Remove only this user's desktop companion installation."
35502
35840
  });
35503
35841
  async executeCommand() {
@@ -35567,6 +35905,7 @@ cli.register(A2aDisableCommand);
35567
35905
  cli.register(EvalSkillCommand);
35568
35906
  cli.register(EvalExamCommand);
35569
35907
  cli.register(DreamRunCommand);
35908
+ cli.register(ConversationsSweepCommand);
35570
35909
  cli.register(FoundryCreateCommand);
35571
35910
  cli.register(FoundryAwaitReadyCommand);
35572
35911
  cli.register(BootstrapPreflightCommand);