@m8t-stack/cli 0.2.81 → 0.2.83

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,10 +229,77 @@ 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
+ function parseArgs(input) {
233
+ if (!isRecord(input) || !nonEmpty(input.title) || !Array.isArray(input.options))
234
+ return null;
235
+ const { options } = input;
236
+ if (options.length < MIN_OPTIONS || options.length > MAX_OPTIONS)
237
+ return null;
238
+ const parsed = [];
239
+ for (const option of options) {
240
+ if (!isRecord(option) || !nonEmpty(option.label) || !nonEmpty(option.detail))
241
+ return null;
242
+ parsed.push({ label: option.label, detail: option.detail });
243
+ }
244
+ return { title: input.title, options: parsed };
245
+ }
246
+ function parseState(input, optionCount) {
247
+ if (!isRecord(input))
248
+ return null;
249
+ switch (input.status) {
250
+ case "pending":
251
+ return { status: "pending" };
252
+ case "dismissed":
253
+ return { status: "dismissed" };
254
+ case "submitting":
255
+ return inRange(input.optionIndex, optionCount) ? { status: "submitting", optionIndex: input.optionIndex } : null;
256
+ case "selected":
257
+ if (!inRange(input.optionIndex, optionCount))
258
+ return null;
259
+ return input.acknowledgement === "pending" || input.acknowledgement === "complete" || input.acknowledgement === "error" ? { status: "selected", optionIndex: input.optionIndex, acknowledgement: input.acknowledgement } : null;
260
+ case "error":
261
+ return nonEmpty(input.message) ? { status: "error", message: input.message } : null;
262
+ default:
263
+ return null;
264
+ }
265
+ }
266
+ function parseUiDirectiveFrame(input) {
267
+ if (!isRecord(input))
268
+ return null;
269
+ if (input.source !== "voice" && input.source !== "text")
270
+ return null;
271
+ const directive = input.directive;
272
+ if (!isRecord(directive))
273
+ return null;
274
+ if (directive.version !== 1 || directive.name !== "present_decision")
275
+ return null;
276
+ if (!nonEmpty(directive.callId))
277
+ return null;
278
+ const args = parseArgs(directive.args);
279
+ if (!args)
280
+ return null;
281
+ const state = parseState(directive.state, args.options.length);
282
+ if (!state)
283
+ return null;
284
+ return {
285
+ directive: { version: 1, name: "present_decision", callId: directive.callId, args, state },
286
+ source: input.source
287
+ };
288
+ }
289
+ function isDecisionRefusalReason(value) {
290
+ return typeof value === "string" && decisionRefusalReasons.includes(value);
291
+ }
292
+ var UI_DIRECTIVE_PART_TYPE, MIN_OPTIONS, MAX_OPTIONS, isRecord, nonEmpty, inRange, UI_DIRECTIVE_RESULT_PART_TYPE, DECISION_REFUSAL_REASONS, decisionRefusalReasons;
233
293
  var init_ui_directive = __esm({
234
294
  "../../packages/api-contract/dist/esm/ui-directive.js"() {
235
295
  "use strict";
296
+ UI_DIRECTIVE_PART_TYPE = "data-ui-directive";
297
+ MIN_OPTIONS = 2;
298
+ MAX_OPTIONS = 4;
299
+ isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
300
+ nonEmpty = (value) => typeof value === "string" && value.trim().length > 0;
301
+ inRange = (value, count) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value < count;
302
+ UI_DIRECTIVE_RESULT_PART_TYPE = "data-ui-result";
236
303
  DECISION_REFUSAL_REASONS = {
237
304
  /** 409 — no live call with that `callId`. Answered twice, or talked past and dismissed. */
238
305
  notPending: "decision_not_pending",
@@ -1400,7 +1467,7 @@ var init_enable_hosted_brain = __esm({
1400
1467
  import { Builtins, Cli } from "clipanion";
1401
1468
 
1402
1469
  // src/lib/package-version.ts
1403
- var CLI_VERSION = "0.2.81";
1470
+ var CLI_VERSION = "0.2.83";
1404
1471
 
1405
1472
  // src/lib/render-error.ts
1406
1473
  init_errors();
@@ -12728,6 +12795,25 @@ function classifyFoundryError(err) {
12728
12795
  return { category: "unknown", retryable: false, status, message };
12729
12796
  }
12730
12797
 
12798
+ // ../../packages/foundry-invoke/dist/esm/artifacts.js
12799
+ var FENCE_RE = /```json m8t:artifacts\s*\n([\s\S]*?)\n```/;
12800
+ function parseArtifacts(raw) {
12801
+ const text = raw;
12802
+ const m = FENCE_RE.exec(text);
12803
+ if (!m)
12804
+ return { text: text.trimEnd(), artifacts: [] };
12805
+ let artifacts = [];
12806
+ try {
12807
+ const parsed = JSON.parse(m[1]);
12808
+ if (Array.isArray(parsed))
12809
+ artifacts = parsed;
12810
+ } catch {
12811
+ artifacts = [];
12812
+ }
12813
+ const clean2 = text.slice(0, m.index).trimEnd();
12814
+ return { text: clean2, artifacts };
12815
+ }
12816
+
12731
12817
  // src/lib/render-error.ts
12732
12818
  function renderHint(hint) {
12733
12819
  return hint.split("\n").join("\n ");
@@ -26708,7 +26794,7 @@ var str3 = (v) => {
26708
26794
  return "";
26709
26795
  };
26710
26796
  var req = (v, fallback = "") => str3(v) ?? fallback;
26711
- function parseArtifacts(v) {
26797
+ function parseArtifacts2(v) {
26712
26798
  if (typeof v !== "string" || v === "")
26713
26799
  return void 0;
26714
26800
  try {
@@ -26742,7 +26828,7 @@ function mapEntity(e) {
26742
26828
  delegationId: str3(e.delegationId),
26743
26829
  parentEventId: str3(e.parentEventId),
26744
26830
  depth: e.depth == null ? void 0 : Number(e.depth),
26745
- artifacts: parseArtifacts(e.artifacts)
26831
+ artifacts: parseArtifacts2(e.artifacts)
26746
26832
  };
26747
26833
  }
26748
26834
  var rank = (o) => o === "ok" ? 0 : 1;
@@ -28806,21 +28892,28 @@ var ARM4 = "https://management.azure.com";
28806
28892
  var ARM_SCOPE8 = "https://management.azure.com/.default";
28807
28893
  var STORAGE_API2 = "2023-05-01";
28808
28894
  var LA_API = "2023-09-01";
28895
+ var tagOf = (a, key2) => a.tags?.[key2] ?? a.properties?.tags?.[key2];
28896
+ function selectLedgerAccount(accounts, storageAccount) {
28897
+ if (storageAccount) return accounts.find((a) => a.name === storageAccount);
28898
+ const managed = accounts.filter((a) => tagOf(a, "managedBy") === "m8t");
28899
+ return managed.find((a) => tagOf(a, "m8t") === "storage") ?? managed.at(0) ?? accounts.find((a) => tagOf(a, "m8t:role") === "ledger") ?? (accounts.length === 1 ? accounts[0] : void 0);
28900
+ }
28809
28901
  async function discoverLedgerResources(opts) {
28810
- const { credential: credential2, subscriptionId, resourceGroup } = opts;
28902
+ const { credential: credential2, subscriptionId, resourceGroup, storageAccount } = opts;
28811
28903
  const storage = await authedJsonPagedValues({
28812
28904
  credential: credential2,
28813
28905
  scope: ARM_SCOPE8,
28814
28906
  url: `${ARM4}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Storage/storageAccounts?api-version=${STORAGE_API2}`
28815
28907
  });
28816
28908
  const accounts = storage.filter((a) => a.properties?.primaryEndpoints?.table);
28817
- const ledgerAcct = accounts.find((a) => (a.tags?.["m8t:role"] ?? a.properties?.tags?.["m8t:role"]) === "ledger") ?? (accounts.length === 1 ? accounts[0] : void 0);
28909
+ const ledgerAcct = selectLedgerAccount(accounts, storageAccount);
28818
28910
  const ledgerTableEndpoint = ledgerAcct?.properties?.primaryEndpoints?.table?.replace(/\/$/, "");
28819
28911
  if (!ledgerTableEndpoint) {
28912
+ const found = accounts.map((a) => a.name).join(", ") || "(none)";
28820
28913
  throw new LocalCliError({
28821
28914
  code: "DREAM_LEDGER_STORAGE_NOT_FOUND",
28822
- message: `No AgentLedger storage account found in resource group ${resourceGroup}.`,
28823
- hint: `Found: ${accounts.map((a) => a.name).join(", ") || "(none)"}. Tag the ledger account 'm8t:role=ledger'.`
28915
+ message: storageAccount ? `Storage account '${storageAccount}' was not found in resource group ${resourceGroup}, or has no table endpoint.` : `No AgentLedger storage account found in resource group ${resourceGroup}.`,
28916
+ hint: `Found: ${found}. Pass --storage-account <name> to select one explicitly.`
28824
28917
  });
28825
28918
  }
28826
28919
  const ws = await authedJsonPagedValues({
@@ -28862,6 +28955,7 @@ async function resolveDreamContext(opts) {
28862
28955
  });
28863
28956
  const resourceGroup = rgFromScope(project.accountScope);
28864
28957
  const { ledgerTableEndpoint, workspaceId } = await discoverLedgerResources({
28958
+ storageAccount: opts.storageAccount,
28865
28959
  credential: credential2,
28866
28960
  subscriptionId,
28867
28961
  resourceGroup
@@ -28929,6 +29023,9 @@ var DreamRunCommand = class extends M8tCommand {
28929
29023
  });
28930
29024
  subscription = Option49.String("--subscription");
28931
29025
  endpoint = Option49.String("--endpoint");
29026
+ storageAccount = Option49.String("--storage-account", {
29027
+ description: "Ledger storage account name (skips tag-based discovery)"
29028
+ });
28932
29029
  output = Option49.String("--output");
28933
29030
  // Resolution seam — overridden by tests; built lazily at runtime otherwise.
28934
29031
  deps;
@@ -28946,7 +29043,8 @@ var DreamRunCommand = class extends M8tCommand {
28946
29043
  const liveContext = await liveDeps.resolveContext({
28947
29044
  worker,
28948
29045
  subscription: typeof this.subscription === "string" ? this.subscription : void 0,
28949
- endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0
29046
+ endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0,
29047
+ storageAccount: typeof this.storageAccount === "string" ? this.storageAccount : void 0
28950
29048
  });
28951
29049
  const liveCursor = await liveDeps.readCursor(liveContext.worker);
28952
29050
  const result2 = await liveDeps.runDream({
@@ -28993,7 +29091,8 @@ var DreamRunCommand = class extends M8tCommand {
28993
29091
  const context = await deps.resolveContext({
28994
29092
  worker,
28995
29093
  subscription: typeof this.subscription === "string" ? this.subscription : void 0,
28996
- endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0
29094
+ endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0,
29095
+ storageAccount: typeof this.storageAccount === "string" ? this.storageAccount : void 0
28997
29096
  });
28998
29097
  const now = /* @__PURE__ */ new Date();
28999
29098
  const cursor = await deps.readCursor(context.worker);
@@ -29343,13 +29442,28 @@ function defaultDeps2() {
29343
29442
  const { ledgerTableEndpoint } = await discoverLedgerResources({
29344
29443
  credential: credential2,
29345
29444
  subscriptionId,
29346
- resourceGroup: m[1]
29445
+ resourceGroup: m[1],
29446
+ storageAccount: opts.storageAccount
29347
29447
  });
29348
29448
  return { projectEndpoint: project.endpoint, ledgerTableEndpoint };
29349
29449
  },
29350
29450
  async fetchRows(ctx, fromIso, toIso) {
29351
29451
  const client = new TableClient8(ctx.ledgerTableEndpoint, LEDGER_TABLE_NAME2, credential2);
29352
- return fetchLedgerRows({ workers: [], source: "all", from: fromIso, to: toIso }, client);
29452
+ try {
29453
+ return await fetchLedgerRows({ workers: [], source: "all", from: fromIso, to: toIso }, client);
29454
+ } catch (e) {
29455
+ const msg = e instanceof Error ? e.message : String(e);
29456
+ if (statusOf2(e) === 403 || msg.includes("AuthorizationPermissionMismatch")) {
29457
+ const account = /https:\/\/([^.]+)\./.exec(ctx.ledgerTableEndpoint)?.[1] ?? "the ledger account";
29458
+ throw new LocalCliError({
29459
+ code: "sweep_ledger_forbidden",
29460
+ message: `Not authorized to read the ledger table on '${account}'.`,
29461
+ hint: `Reading table rows needs a data-plane role: grant 'Storage Table Data Reader' on that storage account to the signed-in identity (owning the account is not enough).`,
29462
+ cause: e
29463
+ });
29464
+ }
29465
+ throw e;
29466
+ }
29353
29467
  },
29354
29468
  async retrieveMetadata(ctx, conversationId) {
29355
29469
  try {
@@ -29420,6 +29534,9 @@ var ConversationsSweepCommand = class extends M8tCommand {
29420
29534
  });
29421
29535
  subscription = Option50.String("--subscription", { description: "Azure subscription id override" });
29422
29536
  endpoint = Option50.String("--endpoint", { description: "Foundry project endpoint override" });
29537
+ storageAccount = Option50.String("--storage-account", {
29538
+ description: "Ledger storage account name (skips tag-based discovery)"
29539
+ });
29423
29540
  deps = defaultDeps2();
29424
29541
  async executeCommand() {
29425
29542
  const out = this.context.stdout;
@@ -29436,7 +29553,11 @@ var ConversationsSweepCommand = class extends M8tCommand {
29436
29553
  const cutoffIso = new Date(now - retentionDays * DAY_MS).toISOString();
29437
29554
  const fromIso = new Date(now - lookback * DAY_MS).toISOString();
29438
29555
  const toIso = new Date(now).toISOString();
29439
- const ctx = await this.deps.resolveContext({ subscription: this.subscription, endpoint: this.endpoint });
29556
+ const ctx = await this.deps.resolveContext({
29557
+ subscription: this.subscription,
29558
+ endpoint: this.endpoint,
29559
+ storageAccount: this.storageAccount
29560
+ });
29440
29561
  out.write(`Scanning ledger activity ${fromIso} \u2192 ${toIso}; retention cutoff ${cutoffIso}.
29441
29562
  `);
29442
29563
  const rows = await this.deps.fetchRows(ctx, fromIso, toIso);
@@ -30728,7 +30849,7 @@ var ONBOARDING_BLOCK_KEYS = [
30728
30849
  "advisor_name",
30729
30850
  "advisor_email"
30730
30851
  ];
30731
- function isRecord(value) {
30852
+ function isRecord2(value) {
30732
30853
  return typeof value === "object" && value !== null && !Array.isArray(value);
30733
30854
  }
30734
30855
  function hasValidUniqueJsonKeys(source) {
@@ -30836,7 +30957,7 @@ var V3_COMPANY_KEYS = ["name", "one_liner"];
30836
30957
  var V3_ADDRESS_KEYS = ["address", "city", "postal_code", "country"];
30837
30958
  var V3_REQUEST_KEYS = ["type", "model", "region", "consent", "company_address"];
30838
30959
  function exactStringRecord(value, keys) {
30839
- if (!isRecord(value)) return "non-string-value";
30960
+ if (!isRecord2(value)) return "non-string-value";
30840
30961
  const present = new Set(Object.keys(value));
30841
30962
  for (const key2 of keys) if (!present.has(key2)) return "missing-key";
30842
30963
  if (present.size !== keys.length) return "unexpected-key";
@@ -30848,7 +30969,7 @@ function canonicalPendingRequests(value) {
30848
30969
  if (value.length > 1) return { ok: false, reason: "too-many-pending-requests" };
30849
30970
  const requests = [];
30850
30971
  for (const entry of value) {
30851
- if (!isRecord(entry)) return { ok: false, reason: "non-string-value" };
30972
+ if (!isRecord2(entry)) return { ok: false, reason: "non-string-value" };
30852
30973
  const present = new Set(Object.keys(entry));
30853
30974
  for (const key2 of V3_REQUEST_KEYS) {
30854
30975
  if (!present.has(key2)) return { ok: false, reason: "missing-key" };
@@ -30898,7 +31019,7 @@ function canonicalV3Block(value) {
30898
31019
  };
30899
31020
  }
30900
31021
  function canonicalBlock(value) {
30901
- if (!isRecord(value)) return { ok: false, reason: "non-string-value" };
31022
+ if (!isRecord2(value)) return { ok: false, reason: "non-string-value" };
30902
31023
  if (value.schema_version === "3") return canonicalV3Block(value);
30903
31024
  if (value.schema_version !== "2") return { ok: false, reason: "unknown-schema-version" };
30904
31025
  const keys = new Set(Object.keys(value));
@@ -30943,7 +31064,7 @@ function parseOnboardingArtifactResult(machineText) {
30943
31064
  if (json.includes("m8t_onboarding")) return { ok: false, reason: "malformed-json" };
30944
31065
  continue;
30945
31066
  }
30946
- if (!isRecord(parsed) || !Object.hasOwn(parsed, "m8t_onboarding")) continue;
31067
+ if (!isRecord2(parsed) || !Object.hasOwn(parsed, "m8t_onboarding")) continue;
30947
31068
  if (Object.keys(parsed).length !== 1) return { ok: false, reason: "unexpected-key" };
30948
31069
  const outcome = canonicalBlock(parsed.m8t_onboarding);
30949
31070
  if (!outcome.ok) return outcome;
@@ -30989,7 +31110,7 @@ async function readCursorPages(args) {
30989
31110
  } catch {
30990
31111
  return null;
30991
31112
  }
30992
- if (!isRecord(body) || !Array.isArray(body.data)) return null;
31113
+ if (!isRecord2(body) || !Array.isArray(body.data)) return null;
30993
31114
  const page = body;
30994
31115
  all.push(...page.data);
30995
31116
  if (page.has_more !== true) {
@@ -31010,7 +31131,7 @@ function decodeFoundryUser(token) {
31010
31131
  if (parts.length !== 3 || parts.some((part) => part.length === 0) || !/^[A-Za-z0-9_-]+$/.test(parts[1] ?? "")) return null;
31011
31132
  try {
31012
31133
  const payload = JSON.parse(Buffer.from(parts[1] ?? "", "base64url").toString("utf8"));
31013
- if (!isRecord(payload)) return null;
31134
+ if (!isRecord2(payload)) return null;
31014
31135
  const claim = payload.upn ?? payload.preferred_username ?? payload.email;
31015
31136
  return typeof claim === "string" && claim.trim().length > 0 ? claim : null;
31016
31137
  } catch {
@@ -31041,7 +31162,7 @@ async function findOnboardingProfile(args) {
31041
31162
  });
31042
31163
  if (!listed) return { ...EMPTY_PROFILE_RESULT };
31043
31164
  const conversations = orderNewest(listed.flatMap((value, ordinal) => {
31044
- if (!isRecord(value) || typeof value.id !== "string" || value.id.length === 0 || !isRecord(value.metadata)) return [];
31165
+ if (!isRecord2(value) || typeof value.id !== "string" || value.id.length === 0 || !isRecord2(value.metadata)) return [];
31045
31166
  const metadata = value.metadata;
31046
31167
  if (metadata.app !== "m8t-webapp" || metadata.agent !== INTAKE_AGENT_NAME) return [];
31047
31168
  return [{
@@ -31059,7 +31180,7 @@ async function findOnboardingProfile(args) {
31059
31180
  });
31060
31181
  if (!items) return { hadIntake: true, block: null, machineText: null, speechText: null, rejection: null };
31061
31182
  const assistantItems = orderNewest(items.flatMap((value, ordinal) => {
31062
- if (!isRecord(value) || value.type !== "message" || value.role !== "assistant" || !Array.isArray(value.content)) return [];
31183
+ if (!isRecord2(value) || value.type !== "message" || value.role !== "assistant" || !Array.isArray(value.content)) return [];
31063
31184
  const id = typeof value.id === "string" ? value.id : "";
31064
31185
  return [{ value, id, createdAt: timestamp(value.created_at), ordinal }];
31065
31186
  }));
@@ -31068,7 +31189,7 @@ async function findOnboardingProfile(args) {
31068
31189
  for (const item of assistantItems) {
31069
31190
  const content = item.value.content;
31070
31191
  const machineText = content.flatMap((part) => {
31071
- if (!isRecord(part)) return [];
31192
+ if (!isRecord2(part)) return [];
31072
31193
  if (typeof part.text === "string" && part.text.length > 0) return [part.text];
31073
31194
  if (typeof part.transcript === "string") return [part.transcript];
31074
31195
  return [];
@@ -34571,6 +34692,14 @@ var COMPANION_HISTORY_LIMIT_MAX = 40;
34571
34692
  var COMPANION_TURN_TEXT_MAX_CODE_POINTS = 32768;
34572
34693
  var COMPANION_REPLY_DELTA_MAX_CODE_POINTS = 4096;
34573
34694
  var COMPANION_TURN_ID_MAX_LENGTH = 256;
34695
+ var COMPANION_DECISION_CALL_ID_MAX_LENGTH = 256;
34696
+ var COMPANION_DECISION_TITLE_MAX_LENGTH = 200;
34697
+ var COMPANION_DECISION_LABEL_MAX_LENGTH = 100;
34698
+ var COMPANION_DECISION_DETAIL_MAX_CODE_POINTS = 400;
34699
+ var COMPANION_DECISION_OPTIONS_MIN = 2;
34700
+ var COMPANION_DECISION_OPTIONS_MAX = 4;
34701
+ var COMPANION_ARTIFACT_NAME_MAX_LENGTH = 200;
34702
+ var COMPANION_ARTIFACTS_MAX = 16;
34574
34703
  var PERSONAS = /* @__PURE__ */ new Set([
34575
34704
  "startup-advisor",
34576
34705
  "ezra"
@@ -34580,7 +34709,8 @@ var FAILURE_REASONS = /* @__PURE__ */ new Set([
34580
34709
  "not-authorized",
34581
34710
  "mate-unavailable",
34582
34711
  "busy",
34583
- "bad-request"
34712
+ "bad-request",
34713
+ "decision-not-pending"
34584
34714
  ]);
34585
34715
  var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/u;
34586
34716
  var URL_SAFE_OPAQUE_ID = /^[A-Za-z0-9_-]+$/u;
@@ -34589,7 +34719,7 @@ var VERSION3 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/u;
34589
34719
  function isVersionOrNull(value) {
34590
34720
  return value === null || typeof value === "string" && VERSION3.test(value);
34591
34721
  }
34592
- function isRecord2(value) {
34722
+ function isRecord3(value) {
34593
34723
  return typeof value === "object" && value !== null && !Array.isArray(value);
34594
34724
  }
34595
34725
  function hasExactKeys(value, keys) {
@@ -34613,8 +34743,72 @@ var INSTANT_SHAPE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
34613
34743
  function isInstantOrNull(value) {
34614
34744
  return value === null || typeof value === "string" && INSTANT_SHAPE.test(value) && Number.isFinite(Date.parse(value));
34615
34745
  }
34616
- function isCompanionTurn(value) {
34617
- return isRecord2(value) && hasExactKeys(value, ["id", "role", "text", "at"]) && isBoundedPlainString(value.id, COMPANION_TURN_ID_MAX_LENGTH) && (value.role === "user" || value.role === "mate") && isBoundedMessageText(value.text, COMPANION_TURN_TEXT_MAX_CODE_POINTS) && isInstantOrNull(value.at);
34746
+ function parseDecision(value) {
34747
+ if (!isRecord3(value) || !isBoundedPlainString(value.callId, COMPANION_DECISION_CALL_ID_MAX_LENGTH) || !isBoundedPlainString(value.title, COMPANION_DECISION_TITLE_MAX_LENGTH) || !Array.isArray(value.options) || value.options.length < COMPANION_DECISION_OPTIONS_MIN || value.options.length > COMPANION_DECISION_OPTIONS_MAX) {
34748
+ return eventError();
34749
+ }
34750
+ const options = value.options.map((option) => {
34751
+ if (!isRecord3(option) || !hasExactKeys(option, ["label", "detail"]) || !isBoundedPlainString(option.label, COMPANION_DECISION_LABEL_MAX_LENGTH) || !isBoundedMessageText(option.detail, COMPANION_DECISION_DETAIL_MAX_CODE_POINTS)) {
34752
+ return eventError();
34753
+ }
34754
+ return { label: option.label, detail: option.detail };
34755
+ });
34756
+ const base = { callId: value.callId, title: value.title, options };
34757
+ if (value.status === "pending" || value.status === "dismissed") {
34758
+ if (!hasExactKeys(value, ["callId", "title", "options", "status"])) {
34759
+ return eventError();
34760
+ }
34761
+ return { ...base, status: value.status };
34762
+ }
34763
+ if (value.status !== "selected" || !hasExactKeys(value, ["callId", "title", "options", "status", "optionIndex"]) || !Number.isSafeInteger(value.optionIndex) || value.optionIndex < 0 || value.optionIndex >= options.length) {
34764
+ return eventError();
34765
+ }
34766
+ return { ...base, status: "selected", optionIndex: value.optionIndex };
34767
+ }
34768
+ function parseArtifact(value) {
34769
+ if (!isRecord3(value) || !isBoundedPlainString(value.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH)) {
34770
+ return eventError();
34771
+ }
34772
+ if (value.sizeBytes === void 0) {
34773
+ if (!hasExactKeys(value, ["name"])) return eventError();
34774
+ return { name: value.name };
34775
+ }
34776
+ if (!hasExactKeys(value, ["name", "sizeBytes"]) || !Number.isSafeInteger(value.sizeBytes) || value.sizeBytes < 0) {
34777
+ return eventError();
34778
+ }
34779
+ return { name: value.name, sizeBytes: value.sizeBytes };
34780
+ }
34781
+ function parseTurnArtifacts(value) {
34782
+ if (!Array.isArray(value) || value.length === 0 || value.length > COMPANION_ARTIFACTS_MAX) {
34783
+ return eventError();
34784
+ }
34785
+ return value.map(parseArtifact);
34786
+ }
34787
+ function parseTurn(value) {
34788
+ if (!isRecord3(value) || !isBoundedPlainString(value.id, COMPANION_TURN_ID_MAX_LENGTH) || value.role !== "user" && value.role !== "mate" || !isInstantOrNull(value.at)) {
34789
+ return eventError();
34790
+ }
34791
+ const keys = [
34792
+ "id",
34793
+ "role",
34794
+ "text",
34795
+ "at",
34796
+ ...value.decision === void 0 ? [] : ["decision"],
34797
+ ...value.artifacts === void 0 ? [] : ["artifacts"]
34798
+ ];
34799
+ if (!hasExactKeys(value, keys)) return eventError();
34800
+ const allowEmptyText = value.decision !== void 0 || value.artifacts !== void 0;
34801
+ if (!(allowEmptyText && value.text === "" || isBoundedMessageText(value.text, COMPANION_TURN_TEXT_MAX_CODE_POINTS))) {
34802
+ return eventError();
34803
+ }
34804
+ return {
34805
+ id: value.id,
34806
+ role: value.role,
34807
+ text: value.text,
34808
+ at: value.at,
34809
+ ...value.decision === void 0 ? {} : { decision: parseDecision(value.decision) },
34810
+ ...value.artifacts === void 0 ? {} : { artifacts: parseTurnArtifacts(value.artifacts) }
34811
+ };
34618
34812
  }
34619
34813
  function isCanonicalInstant(value) {
34620
34814
  if (typeof value !== "string") return false;
@@ -34670,7 +34864,7 @@ function isValidMateRoute(value) {
34670
34864
  }
34671
34865
  }
34672
34866
  function parseRequest(value) {
34673
- if (!isRecord2(value)) return requestError();
34867
+ if (!isRecord3(value)) return requestError();
34674
34868
  if (value.type === "roster") {
34675
34869
  if (!hasExactKeys(value, ["type"])) return requestError();
34676
34870
  return { type: "roster" };
@@ -34679,18 +34873,36 @@ function parseRequest(value) {
34679
34873
  if (!hasExactKeys(value, ["type"])) return requestError();
34680
34874
  return { type: "update" };
34681
34875
  }
34682
- if (value.type === "history") {
34876
+ if (value.type === "history" || value.type === "recall") {
34683
34877
  if (!hasExactKeys(value, ["type", "requestId", "personaKey", "limit"]) || !isRequestId(value.requestId) || !isPersonaKey(value.personaKey) || !Number.isSafeInteger(value.limit) || value.limit < 1 || value.limit > COMPANION_HISTORY_LIMIT_MAX) {
34684
34878
  return requestError();
34685
34879
  }
34686
34880
  return {
34687
- type: "history",
34881
+ type: value.type,
34688
34882
  requestId: value.requestId,
34689
34883
  personaKey: value.personaKey,
34690
34884
  limit: value.limit
34691
34885
  };
34692
34886
  }
34693
- if (value.type !== "send" && value.type !== "converse" || !hasExactKeys(value, [
34887
+ if (value.type === "decide") {
34888
+ if (!hasExactKeys(value, [
34889
+ "type",
34890
+ "requestId",
34891
+ "personaKey",
34892
+ "callId",
34893
+ "optionIndex"
34894
+ ]) || !isRequestId(value.requestId) || !isPersonaKey(value.personaKey) || !isBoundedPlainString(value.callId, COMPANION_DECISION_CALL_ID_MAX_LENGTH) || !Number.isSafeInteger(value.optionIndex) || value.optionIndex < 0 || value.optionIndex >= COMPANION_DECISION_OPTIONS_MAX) {
34895
+ return requestError();
34896
+ }
34897
+ return {
34898
+ type: "decide",
34899
+ requestId: value.requestId,
34900
+ personaKey: value.personaKey,
34901
+ callId: value.callId,
34902
+ optionIndex: value.optionIndex
34903
+ };
34904
+ }
34905
+ if (value.type !== "send" && value.type !== "converse" && value.type !== "chat" || !hasExactKeys(value, [
34694
34906
  "type",
34695
34907
  "requestId",
34696
34908
  "personaKey",
@@ -34718,7 +34930,7 @@ function parseRequestLine(line2) {
34718
34930
  }
34719
34931
  }
34720
34932
  function parseMate(value) {
34721
- if (!isRecord2(value) || !hasExactKeys(value, [
34933
+ if (!isRecord3(value) || !hasExactKeys(value, [
34722
34934
  "personaKey",
34723
34935
  "agentName",
34724
34936
  "displayName",
@@ -34743,7 +34955,7 @@ function parseMate(value) {
34743
34955
  };
34744
34956
  }
34745
34957
  function parseEvent(value) {
34746
- if (!isRecord2(value)) return eventError();
34958
+ if (!isRecord3(value)) return eventError();
34747
34959
  if (value.type === "update") {
34748
34960
  if (!hasExactKeys(value, ["type", "installed", "available", "severity"]) || !isVersionOrNull(value.installed) || !isVersionOrNull(value.available) || !(value.severity === null || SEVERITIES2.has(value.severity))) {
34749
34961
  return eventError();
@@ -34786,18 +34998,33 @@ function parseEvent(value) {
34786
34998
  return { type: "completed", requestId: value.requestId };
34787
34999
  }
34788
35000
  if (value.type === "turn") {
34789
- if (!hasExactKeys(value, ["type", "requestId", "turn"]) || !isRequestId(value.requestId) || !isCompanionTurn(value.turn)) {
35001
+ if (!hasExactKeys(value, ["type", "requestId", "turn"]) || !isRequestId(value.requestId)) {
34790
35002
  return eventError();
34791
35003
  }
34792
35004
  return {
34793
35005
  type: "turn",
34794
35006
  requestId: value.requestId,
34795
- turn: {
34796
- id: value.turn.id,
34797
- role: value.turn.role,
34798
- text: value.turn.text,
34799
- at: value.turn.at
34800
- }
35007
+ turn: parseTurn(value.turn)
35008
+ };
35009
+ }
35010
+ if (value.type === "reply-decision") {
35011
+ if (!hasExactKeys(value, ["type", "requestId", "decision"]) || !isRequestId(value.requestId)) {
35012
+ return eventError();
35013
+ }
35014
+ return {
35015
+ type: "reply-decision",
35016
+ requestId: value.requestId,
35017
+ decision: parseDecision(value.decision)
35018
+ };
35019
+ }
35020
+ if (value.type === "reply-artifacts") {
35021
+ if (!hasExactKeys(value, ["type", "requestId", "artifacts"]) || !isRequestId(value.requestId) || !Array.isArray(value.artifacts) || value.artifacts.length === 0 || value.artifacts.length > COMPANION_ARTIFACTS_MAX) {
35022
+ return eventError();
35023
+ }
35024
+ return {
35025
+ type: "reply-artifacts",
35026
+ requestId: value.requestId,
35027
+ artifacts: value.artifacts.map(parseArtifact)
34801
35028
  };
34802
35029
  }
34803
35030
  if (value.type === "history-end") {
@@ -34854,6 +35081,7 @@ function serializeEvent(value) {
34854
35081
  }
34855
35082
 
34856
35083
  // src/lib/companion-chat-client.ts
35084
+ init_esm();
34857
35085
  import { stat as stat4 } from "fs/promises";
34858
35086
  var RESPONSE_MAX_BYTES = 1e6;
34859
35087
  var REQUEST_DEADLINE_MS = 12e4;
@@ -34866,7 +35094,7 @@ var PredispatchFailure = class extends Error {
34866
35094
  }
34867
35095
  reason;
34868
35096
  };
34869
- function isRecord3(value) {
35097
+ function isRecord4(value) {
34870
35098
  return typeof value === "object" && value !== null && !Array.isArray(value);
34871
35099
  }
34872
35100
  function hasControlCharacter(value) {
@@ -34936,16 +35164,16 @@ async function readJsonEnvelope(response) {
34936
35164
  if (error instanceof PredispatchFailure) throw error;
34937
35165
  throw new PredispatchFailure("not-connected");
34938
35166
  }
34939
- if (!isRecord3(envelope) || envelope.ok !== true || !("data" in envelope)) {
35167
+ if (!isRecord4(envelope) || envelope.ok !== true || !("data" in envelope)) {
34940
35168
  throw new PredispatchFailure("not-connected");
34941
35169
  }
34942
35170
  return envelope.data;
34943
35171
  }
34944
35172
  function decodeAgents(data) {
34945
- if (!isRecord3(data) || !Array.isArray(data.agents)) {
35173
+ if (!isRecord4(data) || !Array.isArray(data.agents)) {
34946
35174
  throw new PredispatchFailure("mate-unavailable");
34947
35175
  }
34948
- return data.agents.filter(isRecord3);
35176
+ return data.agents.filter(isRecord4);
34949
35177
  }
34950
35178
  function resolveAgentName(agents, personaKey) {
34951
35179
  const matches = agents.filter(
@@ -34967,18 +35195,18 @@ function requireAgentName(agents, personaKey) {
34967
35195
  return name;
34968
35196
  }
34969
35197
  function decodeConversationId(data) {
34970
- if (!isRecord3(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
35198
+ if (!isRecord4(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
34971
35199
  throw new PredispatchFailure("not-connected");
34972
35200
  }
34973
35201
  return data.id;
34974
35202
  }
34975
35203
  function decodeLastMessageId(data) {
34976
- if (!isRecord3(data) || !Array.isArray(data.messages)) {
35204
+ if (!isRecord4(data) || !Array.isArray(data.messages)) {
34977
35205
  throw new PredispatchFailure("not-connected");
34978
35206
  }
34979
35207
  const messages = data.messages;
34980
35208
  const last = messages.at(-1);
34981
- if (!isRecord3(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
35209
+ if (!isRecord4(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
34982
35210
  return void 0;
34983
35211
  }
34984
35212
  return last.id;
@@ -35029,13 +35257,61 @@ function boundCodePoints(value, maxCodePoints) {
35029
35257
  }
35030
35258
  return value;
35031
35259
  }
35260
+ function boundPlainLine(value, maxLength) {
35261
+ return value.replace(/[\u0000-\u001f\u007f]/gu, " ").trim().slice(0, maxLength);
35262
+ }
35263
+ var ARTIFACTS_PART_TYPE = "data-artifacts";
35264
+ function toCompanionDecision(directive) {
35265
+ const state = directive.state;
35266
+ if (state.status !== "pending" && state.status !== "selected" && state.status !== "dismissed") {
35267
+ return null;
35268
+ }
35269
+ const callId = boundPlainLine(
35270
+ directive.callId,
35271
+ COMPANION_DECISION_CALL_ID_MAX_LENGTH
35272
+ );
35273
+ const title = boundPlainLine(
35274
+ directive.args.title,
35275
+ COMPANION_DECISION_TITLE_MAX_LENGTH
35276
+ );
35277
+ if (callId.length === 0 || title.length === 0) return null;
35278
+ const options = [];
35279
+ for (const option of directive.args.options) {
35280
+ const label = boundPlainLine(option.label, COMPANION_DECISION_LABEL_MAX_LENGTH);
35281
+ const detail = boundCodePoints(
35282
+ sanitizeMessageText(option.detail).trim(),
35283
+ COMPANION_DECISION_DETAIL_MAX_CODE_POINTS
35284
+ );
35285
+ if (label.length === 0 || detail.length === 0) return null;
35286
+ options.push({ label, detail });
35287
+ }
35288
+ const base = { callId, title, options };
35289
+ return state.status === "selected" ? { ...base, status: "selected", optionIndex: state.optionIndex } : { ...base, status: state.status };
35290
+ }
35291
+ function toCompanionArtifacts(data) {
35292
+ const artifacts = [];
35293
+ for (const entry of data) {
35294
+ if (artifacts.length >= COMPANION_ARTIFACTS_MAX) break;
35295
+ if (!isRecord4(entry) || typeof entry.name !== "string") continue;
35296
+ const name = boundPlainLine(entry.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH);
35297
+ if (name.length === 0) continue;
35298
+ const size = entry.size_bytes;
35299
+ artifacts.push(
35300
+ typeof size === "number" && Number.isSafeInteger(size) && size >= 0 ? { name, sizeBytes: size } : { name }
35301
+ );
35302
+ }
35303
+ return artifacts;
35304
+ }
35305
+ function decodeMessageInstant(value) {
35306
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value < EPOCH_SECONDS_LIMIT ? new Date(Math.round(value) * 1e3).toISOString() : null;
35307
+ }
35032
35308
  function decodeTurns(data, limit2) {
35033
- if (!isRecord3(data) || !Array.isArray(data.messages)) {
35309
+ if (!isRecord4(data) || !Array.isArray(data.messages)) {
35034
35310
  throw new PredispatchFailure("not-connected");
35035
35311
  }
35036
35312
  const turns = [];
35037
35313
  for (const entry of data.messages) {
35038
- if (!isRecord3(entry)) continue;
35314
+ if (!isRecord4(entry)) continue;
35039
35315
  if (entry.role !== "user" && entry.role !== "assistant") continue;
35040
35316
  if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
35041
35317
  if (typeof entry.content !== "string") continue;
@@ -35044,12 +35320,52 @@ function decodeTurns(data, limit2) {
35044
35320
  COMPANION_TURN_TEXT_MAX_CODE_POINTS
35045
35321
  );
35046
35322
  if (text.length === 0) continue;
35047
- const at = typeof entry.createdAt === "number" && Number.isFinite(entry.createdAt) && entry.createdAt > 0 && entry.createdAt < EPOCH_SECONDS_LIMIT ? new Date(Math.round(entry.createdAt) * 1e3).toISOString() : null;
35048
35323
  turns.push({
35049
35324
  id: entry.id,
35050
35325
  role: entry.role === "user" ? "user" : "mate",
35051
35326
  text,
35052
- at
35327
+ at: decodeMessageInstant(entry.createdAt)
35328
+ });
35329
+ }
35330
+ return turns.slice(-limit2);
35331
+ }
35332
+ function decodeDurableDecision(entry) {
35333
+ const frame = parseUiDirectiveFrame({
35334
+ directive: entry.uiDirective,
35335
+ source: entry.uiDirectiveSource === "voice" ? "voice" : "text"
35336
+ });
35337
+ if (!frame) return null;
35338
+ return toCompanionDecision(frame.directive);
35339
+ }
35340
+ function decodeRichTurns(data, limit2) {
35341
+ if (!isRecord4(data) || !Array.isArray(data.messages)) {
35342
+ throw new PredispatchFailure("not-connected");
35343
+ }
35344
+ const turns = [];
35345
+ for (const entry of data.messages) {
35346
+ if (!isRecord4(entry)) continue;
35347
+ if (entry.role !== "user" && entry.role !== "assistant") continue;
35348
+ if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
35349
+ if (typeof entry.content !== "string") continue;
35350
+ const parsed = parseArtifacts(entry.content);
35351
+ const artifacts = toCompanionArtifacts(parsed.artifacts);
35352
+ const text = boundCodePoints(
35353
+ sanitizeMessageText(parsed.text),
35354
+ COMPANION_TURN_TEXT_MAX_CODE_POINTS
35355
+ );
35356
+ const at = decodeMessageInstant(entry.createdAt);
35357
+ const decision = entry.uiDirective === void 0 ? null : decodeDurableDecision(entry);
35358
+ if (decision !== null) {
35359
+ turns.push({ id: entry.id, role: "mate", text, at, decision });
35360
+ continue;
35361
+ }
35362
+ if (text.length === 0 && artifacts.length === 0) continue;
35363
+ turns.push({
35364
+ id: entry.id,
35365
+ role: entry.role === "user" ? "user" : "mate",
35366
+ text,
35367
+ at,
35368
+ ...artifacts.length > 0 ? { artifacts } : {}
35053
35369
  });
35054
35370
  }
35055
35371
  return turns.slice(-limit2);
@@ -35163,7 +35479,7 @@ async function drainAcceptedSse(response) {
35163
35479
  continue;
35164
35480
  }
35165
35481
  const parsed = JSON.parse(data);
35166
- if (!isRecord3(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35482
+ if (!isRecord4(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35167
35483
  throw new Error("invalid stream event");
35168
35484
  }
35169
35485
  }
@@ -35185,7 +35501,7 @@ function emitBoundedText(text, emit) {
35185
35501
  }
35186
35502
  if (slice.length > 0) emit(slice);
35187
35503
  }
35188
- async function streamAcceptedSse(response, now, onText) {
35504
+ async function streamAcceptedSse(response, now, onText, onData) {
35189
35505
  if (!response.body) throw new Error("response body unavailable");
35190
35506
  const reader = response.body.getReader();
35191
35507
  const decoder = new TextDecoder("utf-8", { fatal: true });
@@ -35211,7 +35527,7 @@ async function streamAcceptedSse(response, now, onText) {
35211
35527
  return;
35212
35528
  }
35213
35529
  const parsed = JSON.parse(data);
35214
- if (!isRecord3(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35530
+ if (!isRecord4(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35215
35531
  throw new Error("invalid stream event");
35216
35532
  }
35217
35533
  if (parsed.type === "text-delta" && typeof parsed.delta === "string" && parsed.delta.length > 0) {
@@ -35220,6 +35536,10 @@ async function streamAcceptedSse(response, now, onText) {
35220
35536
  flush();
35221
35537
  }
35222
35538
  }
35539
+ if (onData && parsed.type.startsWith("data-")) {
35540
+ flush();
35541
+ onData(parsed);
35542
+ }
35223
35543
  };
35224
35544
  const drainFrames = () => {
35225
35545
  for (; ; ) {
@@ -35255,7 +35575,7 @@ async function streamAcceptedSse(response, now, onText) {
35255
35575
  throw new Error("stream ended without terminal marker");
35256
35576
  }
35257
35577
  }
35258
- async function dispatchConversation(request, sink, deps, consume, readDesktopMarker) {
35578
+ async function dispatchConversation(request, sink, deps, consume, readDesktopMarker, chatBody, classifyRefusal) {
35259
35579
  const { controller, timer } = createDeadline(deps);
35260
35580
  const fetchImpl = deps.fetch ?? fetch;
35261
35581
  let route;
@@ -35301,17 +35621,19 @@ async function dispatchConversation(request, sink, deps, consume, readDesktopMar
35301
35621
  chatResponse = await fetchImpl(`${origin}/api/chat`, {
35302
35622
  method: "POST",
35303
35623
  headers: { ...headers, "content-type": "application/json" },
35304
- body: JSON.stringify({
35305
- messages: [{ role: "user", content: request.text }],
35306
- agentName,
35307
- conversationId
35308
- }),
35624
+ body: JSON.stringify(chatBody(agentName, conversationId)),
35309
35625
  signal: controller.signal
35310
35626
  });
35311
35627
  } catch {
35312
35628
  throw new Error("chat dispatch outcome unavailable");
35313
35629
  }
35314
35630
  if (!chatResponse.ok || !chatResponse.body || !(chatResponse.headers.get("content-type") ?? "").toLowerCase().includes("text/event-stream")) {
35631
+ if (!chatResponse.ok && classifyRefusal) {
35632
+ const reason = await classifyRefusal(chatResponse);
35633
+ if (reason !== null) {
35634
+ return emitFailure(sink, reason, request.requestId);
35635
+ }
35636
+ }
35315
35637
  throw new Error("chat response not accepted");
35316
35638
  }
35317
35639
  const acceptedEvent = parseEvent({
@@ -35349,8 +35671,40 @@ async function dispatchConversation(request, sink, deps, consume, readDesktopMar
35349
35671
  clearTimeout(timer);
35350
35672
  }
35351
35673
  }
35674
+ function textTurnBody(text) {
35675
+ return (agentName, conversationId) => ({
35676
+ messages: [{ role: "user", content: text }],
35677
+ agentName,
35678
+ conversationId
35679
+ });
35680
+ }
35681
+ function replyDataForwarder(requestId, sink) {
35682
+ return (part) => {
35683
+ if (part.type === UI_DIRECTIVE_PART_TYPE) {
35684
+ const frame = parseUiDirectiveFrame(part.data);
35685
+ if (!frame) return;
35686
+ const decision = toCompanionDecision(frame.directive);
35687
+ if (decision === null) return;
35688
+ sink(parseEvent({ type: "reply-decision", requestId, decision }));
35689
+ return;
35690
+ }
35691
+ if (part.type === ARTIFACTS_PART_TYPE && Array.isArray(part.data)) {
35692
+ const artifacts = toCompanionArtifacts(part.data);
35693
+ if (artifacts.length > 0) {
35694
+ sink(parseEvent({ type: "reply-artifacts", requestId, artifacts }));
35695
+ }
35696
+ }
35697
+ };
35698
+ }
35352
35699
  async function sendCompanionMessage(request, sink, deps = {}) {
35353
- return dispatchConversation(request, sink, deps, drainAcceptedSse, true);
35700
+ return dispatchConversation(
35701
+ request,
35702
+ sink,
35703
+ deps,
35704
+ drainAcceptedSse,
35705
+ true,
35706
+ textTurnBody(request.text)
35707
+ );
35354
35708
  }
35355
35709
  async function converseCompanionMessage(request, sink, deps = {}) {
35356
35710
  return dispatchConversation(
@@ -35366,10 +35720,101 @@ async function converseCompanionMessage(request, sink, deps = {}) {
35366
35720
  })
35367
35721
  );
35368
35722
  }),
35369
- false
35723
+ false,
35724
+ textTurnBody(request.text)
35725
+ );
35726
+ }
35727
+ async function chatCompanionMessage(request, sink, deps = {}) {
35728
+ return dispatchConversation(
35729
+ request,
35730
+ sink,
35731
+ deps,
35732
+ (response) => streamAcceptedSse(
35733
+ response,
35734
+ deps.now ?? Date.now,
35735
+ (text) => {
35736
+ sink(
35737
+ parseEvent({
35738
+ type: "reply-delta",
35739
+ requestId: request.requestId,
35740
+ text
35741
+ })
35742
+ );
35743
+ },
35744
+ replyDataForwarder(request.requestId, sink)
35745
+ ),
35746
+ false,
35747
+ textTurnBody(request.text)
35748
+ );
35749
+ }
35750
+ async function classifyDecideRefusal(response) {
35751
+ let reason;
35752
+ try {
35753
+ const envelope = JSON.parse(await readBoundedText(response));
35754
+ if (isRecord4(envelope) && isRecord4(envelope.error) && isRecord4(envelope.error.details)) {
35755
+ reason = envelope.error.details.reason;
35756
+ }
35757
+ } catch {
35758
+ }
35759
+ if (isDecisionRefusalReason(reason)) {
35760
+ return reason === DECISION_REFUSAL_REASONS.notPending ? "decision-not-pending" : "bad-request";
35761
+ }
35762
+ if (response.status === 401 || response.status === 403) {
35763
+ return "not-authorized";
35764
+ }
35765
+ if (response.status === 400) return "bad-request";
35766
+ if (response.status === 409) return "busy";
35767
+ return null;
35768
+ }
35769
+ async function decideCompanionMessage(request, sink, deps = {}) {
35770
+ const message = {
35771
+ role: "user",
35772
+ parts: [
35773
+ {
35774
+ type: UI_DIRECTIVE_RESULT_PART_TYPE,
35775
+ data: {
35776
+ version: 1,
35777
+ name: "present_decision",
35778
+ callId: request.callId,
35779
+ result: { status: "selected", optionIndex: request.optionIndex }
35780
+ }
35781
+ }
35782
+ ]
35783
+ };
35784
+ return dispatchConversation(
35785
+ request,
35786
+ sink,
35787
+ deps,
35788
+ (response) => streamAcceptedSse(
35789
+ response,
35790
+ deps.now ?? Date.now,
35791
+ (text) => {
35792
+ sink(
35793
+ parseEvent({
35794
+ type: "reply-delta",
35795
+ requestId: request.requestId,
35796
+ text
35797
+ })
35798
+ );
35799
+ },
35800
+ replyDataForwarder(request.requestId, sink)
35801
+ ),
35802
+ false,
35803
+ (agentName, conversationId) => ({
35804
+ messages: [message],
35805
+ agentName,
35806
+ conversationId
35807
+ }),
35808
+ classifyDecideRefusal
35370
35809
  );
35371
35810
  }
35372
35811
  async function historyCompanionMessages(request, sink, deps = {}) {
35812
+ return fetchConversationTurns(request, sink, deps, decodeTurns);
35813
+ }
35814
+ async function recallCompanionMessages(request, sink, deps = {}) {
35815
+ return fetchConversationTurns(request, sink, deps, decodeRichTurns);
35816
+ }
35817
+ async function fetchConversationTurns(request, sink, deps, decode) {
35373
35818
  const { controller, timer } = createDeadline(deps);
35374
35819
  const fetchImpl = deps.fetch ?? fetch;
35375
35820
  try {
@@ -35389,7 +35834,7 @@ async function historyCompanionMessages(request, sink, deps = {}) {
35389
35834
  agentName,
35390
35835
  controller.signal
35391
35836
  );
35392
- const turns = decodeTurns(
35837
+ const turns = decode(
35393
35838
  await fetchConversationMessages(
35394
35839
  fetchImpl,
35395
35840
  origin,
@@ -35545,7 +35990,10 @@ async function dispatchRequest(request, sink, handlers) {
35545
35990
  return terminal;
35546
35991
  }
35547
35992
  if (request.type === "history") return handlers.history(request, sink);
35993
+ if (request.type === "recall") return handlers.recall(request, sink);
35548
35994
  if (request.type === "converse") return handlers.converse(request, sink);
35995
+ if (request.type === "chat") return handlers.chat(request, sink);
35996
+ if (request.type === "decide") return handlers.decide(request, sink);
35549
35997
  return handlers.send(request, sink);
35550
35998
  }
35551
35999
  async function handleWithContext(request, sink, handlers, context) {
@@ -35581,7 +36029,10 @@ async function runCompanionBridgeServe(stdin, stdout, stderr, deps = {}) {
35581
36029
  roster: (sink2) => rosterCompanions(sink2, chat),
35582
36030
  send: (request, sink2) => sendCompanionMessage(request, sink2, chat),
35583
36031
  converse: (request, sink2) => converseCompanionMessage(request, sink2, chat),
36032
+ chat: (request, sink2) => chatCompanionMessage(request, sink2, chat),
36033
+ decide: (request, sink2) => decideCompanionMessage(request, sink2, chat),
35584
36034
  history: (request, sink2) => historyCompanionMessages(request, sink2, chat),
36035
+ recall: (request, sink2) => recallCompanionMessages(request, sink2, chat),
35585
36036
  update: deps.update ?? (() => checkCompanionUpdate(defaultLocalCompanionInstallOptions()))
35586
36037
  };
35587
36038
  const sink = (event) => {
@@ -35616,7 +36067,10 @@ var defaultDeps4 = {
35616
36067
  roster: rosterCompanions,
35617
36068
  send: sendCompanionMessage,
35618
36069
  converse: converseCompanionMessage,
36070
+ chat: chatCompanionMessage,
36071
+ decide: decideCompanionMessage,
35619
36072
  history: historyCompanionMessages,
36073
+ recall: recallCompanionMessages,
35620
36074
  update: () => checkCompanionUpdate(defaultLocalCompanionInstallOptions())
35621
36075
  };
35622
36076
  async function readSingleRequest(stdin) {
@@ -35667,8 +36121,14 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps4) {
35667
36121
  sink(terminal);
35668
36122
  } else if (request.type === "history") {
35669
36123
  terminal = await deps.history(request, sink);
36124
+ } else if (request.type === "recall") {
36125
+ terminal = await deps.recall(request, sink);
35670
36126
  } else if (request.type === "converse") {
35671
36127
  terminal = await deps.converse(request, sink);
36128
+ } else if (request.type === "chat") {
36129
+ terminal = await deps.chat(request, sink);
36130
+ } else if (request.type === "decide") {
36131
+ terminal = await deps.decide(request, sink);
35672
36132
  } else terminal = await deps.send(request, sink);
35673
36133
  return exitFor(terminal);
35674
36134
  } catch {