@m8t-stack/cli 0.2.81 → 0.2.82

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.82";
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;
@@ -30728,7 +30814,7 @@ var ONBOARDING_BLOCK_KEYS = [
30728
30814
  "advisor_name",
30729
30815
  "advisor_email"
30730
30816
  ];
30731
- function isRecord(value) {
30817
+ function isRecord2(value) {
30732
30818
  return typeof value === "object" && value !== null && !Array.isArray(value);
30733
30819
  }
30734
30820
  function hasValidUniqueJsonKeys(source) {
@@ -30836,7 +30922,7 @@ var V3_COMPANY_KEYS = ["name", "one_liner"];
30836
30922
  var V3_ADDRESS_KEYS = ["address", "city", "postal_code", "country"];
30837
30923
  var V3_REQUEST_KEYS = ["type", "model", "region", "consent", "company_address"];
30838
30924
  function exactStringRecord(value, keys) {
30839
- if (!isRecord(value)) return "non-string-value";
30925
+ if (!isRecord2(value)) return "non-string-value";
30840
30926
  const present = new Set(Object.keys(value));
30841
30927
  for (const key2 of keys) if (!present.has(key2)) return "missing-key";
30842
30928
  if (present.size !== keys.length) return "unexpected-key";
@@ -30848,7 +30934,7 @@ function canonicalPendingRequests(value) {
30848
30934
  if (value.length > 1) return { ok: false, reason: "too-many-pending-requests" };
30849
30935
  const requests = [];
30850
30936
  for (const entry of value) {
30851
- if (!isRecord(entry)) return { ok: false, reason: "non-string-value" };
30937
+ if (!isRecord2(entry)) return { ok: false, reason: "non-string-value" };
30852
30938
  const present = new Set(Object.keys(entry));
30853
30939
  for (const key2 of V3_REQUEST_KEYS) {
30854
30940
  if (!present.has(key2)) return { ok: false, reason: "missing-key" };
@@ -30898,7 +30984,7 @@ function canonicalV3Block(value) {
30898
30984
  };
30899
30985
  }
30900
30986
  function canonicalBlock(value) {
30901
- if (!isRecord(value)) return { ok: false, reason: "non-string-value" };
30987
+ if (!isRecord2(value)) return { ok: false, reason: "non-string-value" };
30902
30988
  if (value.schema_version === "3") return canonicalV3Block(value);
30903
30989
  if (value.schema_version !== "2") return { ok: false, reason: "unknown-schema-version" };
30904
30990
  const keys = new Set(Object.keys(value));
@@ -30943,7 +31029,7 @@ function parseOnboardingArtifactResult(machineText) {
30943
31029
  if (json.includes("m8t_onboarding")) return { ok: false, reason: "malformed-json" };
30944
31030
  continue;
30945
31031
  }
30946
- if (!isRecord(parsed) || !Object.hasOwn(parsed, "m8t_onboarding")) continue;
31032
+ if (!isRecord2(parsed) || !Object.hasOwn(parsed, "m8t_onboarding")) continue;
30947
31033
  if (Object.keys(parsed).length !== 1) return { ok: false, reason: "unexpected-key" };
30948
31034
  const outcome = canonicalBlock(parsed.m8t_onboarding);
30949
31035
  if (!outcome.ok) return outcome;
@@ -30989,7 +31075,7 @@ async function readCursorPages(args) {
30989
31075
  } catch {
30990
31076
  return null;
30991
31077
  }
30992
- if (!isRecord(body) || !Array.isArray(body.data)) return null;
31078
+ if (!isRecord2(body) || !Array.isArray(body.data)) return null;
30993
31079
  const page = body;
30994
31080
  all.push(...page.data);
30995
31081
  if (page.has_more !== true) {
@@ -31010,7 +31096,7 @@ function decodeFoundryUser(token) {
31010
31096
  if (parts.length !== 3 || parts.some((part) => part.length === 0) || !/^[A-Za-z0-9_-]+$/.test(parts[1] ?? "")) return null;
31011
31097
  try {
31012
31098
  const payload = JSON.parse(Buffer.from(parts[1] ?? "", "base64url").toString("utf8"));
31013
- if (!isRecord(payload)) return null;
31099
+ if (!isRecord2(payload)) return null;
31014
31100
  const claim = payload.upn ?? payload.preferred_username ?? payload.email;
31015
31101
  return typeof claim === "string" && claim.trim().length > 0 ? claim : null;
31016
31102
  } catch {
@@ -31041,7 +31127,7 @@ async function findOnboardingProfile(args) {
31041
31127
  });
31042
31128
  if (!listed) return { ...EMPTY_PROFILE_RESULT };
31043
31129
  const conversations = orderNewest(listed.flatMap((value, ordinal) => {
31044
- if (!isRecord(value) || typeof value.id !== "string" || value.id.length === 0 || !isRecord(value.metadata)) return [];
31130
+ if (!isRecord2(value) || typeof value.id !== "string" || value.id.length === 0 || !isRecord2(value.metadata)) return [];
31045
31131
  const metadata = value.metadata;
31046
31132
  if (metadata.app !== "m8t-webapp" || metadata.agent !== INTAKE_AGENT_NAME) return [];
31047
31133
  return [{
@@ -31059,7 +31145,7 @@ async function findOnboardingProfile(args) {
31059
31145
  });
31060
31146
  if (!items) return { hadIntake: true, block: null, machineText: null, speechText: null, rejection: null };
31061
31147
  const assistantItems = orderNewest(items.flatMap((value, ordinal) => {
31062
- if (!isRecord(value) || value.type !== "message" || value.role !== "assistant" || !Array.isArray(value.content)) return [];
31148
+ if (!isRecord2(value) || value.type !== "message" || value.role !== "assistant" || !Array.isArray(value.content)) return [];
31063
31149
  const id = typeof value.id === "string" ? value.id : "";
31064
31150
  return [{ value, id, createdAt: timestamp(value.created_at), ordinal }];
31065
31151
  }));
@@ -31068,7 +31154,7 @@ async function findOnboardingProfile(args) {
31068
31154
  for (const item of assistantItems) {
31069
31155
  const content = item.value.content;
31070
31156
  const machineText = content.flatMap((part) => {
31071
- if (!isRecord(part)) return [];
31157
+ if (!isRecord2(part)) return [];
31072
31158
  if (typeof part.text === "string" && part.text.length > 0) return [part.text];
31073
31159
  if (typeof part.transcript === "string") return [part.transcript];
31074
31160
  return [];
@@ -34571,6 +34657,14 @@ var COMPANION_HISTORY_LIMIT_MAX = 40;
34571
34657
  var COMPANION_TURN_TEXT_MAX_CODE_POINTS = 32768;
34572
34658
  var COMPANION_REPLY_DELTA_MAX_CODE_POINTS = 4096;
34573
34659
  var COMPANION_TURN_ID_MAX_LENGTH = 256;
34660
+ var COMPANION_DECISION_CALL_ID_MAX_LENGTH = 256;
34661
+ var COMPANION_DECISION_TITLE_MAX_LENGTH = 200;
34662
+ var COMPANION_DECISION_LABEL_MAX_LENGTH = 100;
34663
+ var COMPANION_DECISION_DETAIL_MAX_CODE_POINTS = 400;
34664
+ var COMPANION_DECISION_OPTIONS_MIN = 2;
34665
+ var COMPANION_DECISION_OPTIONS_MAX = 4;
34666
+ var COMPANION_ARTIFACT_NAME_MAX_LENGTH = 200;
34667
+ var COMPANION_ARTIFACTS_MAX = 16;
34574
34668
  var PERSONAS = /* @__PURE__ */ new Set([
34575
34669
  "startup-advisor",
34576
34670
  "ezra"
@@ -34580,7 +34674,8 @@ var FAILURE_REASONS = /* @__PURE__ */ new Set([
34580
34674
  "not-authorized",
34581
34675
  "mate-unavailable",
34582
34676
  "busy",
34583
- "bad-request"
34677
+ "bad-request",
34678
+ "decision-not-pending"
34584
34679
  ]);
34585
34680
  var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/u;
34586
34681
  var URL_SAFE_OPAQUE_ID = /^[A-Za-z0-9_-]+$/u;
@@ -34589,7 +34684,7 @@ var VERSION3 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/u;
34589
34684
  function isVersionOrNull(value) {
34590
34685
  return value === null || typeof value === "string" && VERSION3.test(value);
34591
34686
  }
34592
- function isRecord2(value) {
34687
+ function isRecord3(value) {
34593
34688
  return typeof value === "object" && value !== null && !Array.isArray(value);
34594
34689
  }
34595
34690
  function hasExactKeys(value, keys) {
@@ -34613,8 +34708,72 @@ var INSTANT_SHAPE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
34613
34708
  function isInstantOrNull(value) {
34614
34709
  return value === null || typeof value === "string" && INSTANT_SHAPE.test(value) && Number.isFinite(Date.parse(value));
34615
34710
  }
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);
34711
+ function parseDecision(value) {
34712
+ 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) {
34713
+ return eventError();
34714
+ }
34715
+ const options = value.options.map((option) => {
34716
+ if (!isRecord3(option) || !hasExactKeys(option, ["label", "detail"]) || !isBoundedPlainString(option.label, COMPANION_DECISION_LABEL_MAX_LENGTH) || !isBoundedMessageText(option.detail, COMPANION_DECISION_DETAIL_MAX_CODE_POINTS)) {
34717
+ return eventError();
34718
+ }
34719
+ return { label: option.label, detail: option.detail };
34720
+ });
34721
+ const base = { callId: value.callId, title: value.title, options };
34722
+ if (value.status === "pending" || value.status === "dismissed") {
34723
+ if (!hasExactKeys(value, ["callId", "title", "options", "status"])) {
34724
+ return eventError();
34725
+ }
34726
+ return { ...base, status: value.status };
34727
+ }
34728
+ if (value.status !== "selected" || !hasExactKeys(value, ["callId", "title", "options", "status", "optionIndex"]) || !Number.isSafeInteger(value.optionIndex) || value.optionIndex < 0 || value.optionIndex >= options.length) {
34729
+ return eventError();
34730
+ }
34731
+ return { ...base, status: "selected", optionIndex: value.optionIndex };
34732
+ }
34733
+ function parseArtifact(value) {
34734
+ if (!isRecord3(value) || !isBoundedPlainString(value.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH)) {
34735
+ return eventError();
34736
+ }
34737
+ if (value.sizeBytes === void 0) {
34738
+ if (!hasExactKeys(value, ["name"])) return eventError();
34739
+ return { name: value.name };
34740
+ }
34741
+ if (!hasExactKeys(value, ["name", "sizeBytes"]) || !Number.isSafeInteger(value.sizeBytes) || value.sizeBytes < 0) {
34742
+ return eventError();
34743
+ }
34744
+ return { name: value.name, sizeBytes: value.sizeBytes };
34745
+ }
34746
+ function parseTurnArtifacts(value) {
34747
+ if (!Array.isArray(value) || value.length === 0 || value.length > COMPANION_ARTIFACTS_MAX) {
34748
+ return eventError();
34749
+ }
34750
+ return value.map(parseArtifact);
34751
+ }
34752
+ function parseTurn(value) {
34753
+ if (!isRecord3(value) || !isBoundedPlainString(value.id, COMPANION_TURN_ID_MAX_LENGTH) || value.role !== "user" && value.role !== "mate" || !isInstantOrNull(value.at)) {
34754
+ return eventError();
34755
+ }
34756
+ const keys = [
34757
+ "id",
34758
+ "role",
34759
+ "text",
34760
+ "at",
34761
+ ...value.decision === void 0 ? [] : ["decision"],
34762
+ ...value.artifacts === void 0 ? [] : ["artifacts"]
34763
+ ];
34764
+ if (!hasExactKeys(value, keys)) return eventError();
34765
+ const allowEmptyText = value.decision !== void 0 || value.artifacts !== void 0;
34766
+ if (!(allowEmptyText && value.text === "" || isBoundedMessageText(value.text, COMPANION_TURN_TEXT_MAX_CODE_POINTS))) {
34767
+ return eventError();
34768
+ }
34769
+ return {
34770
+ id: value.id,
34771
+ role: value.role,
34772
+ text: value.text,
34773
+ at: value.at,
34774
+ ...value.decision === void 0 ? {} : { decision: parseDecision(value.decision) },
34775
+ ...value.artifacts === void 0 ? {} : { artifacts: parseTurnArtifacts(value.artifacts) }
34776
+ };
34618
34777
  }
34619
34778
  function isCanonicalInstant(value) {
34620
34779
  if (typeof value !== "string") return false;
@@ -34670,7 +34829,7 @@ function isValidMateRoute(value) {
34670
34829
  }
34671
34830
  }
34672
34831
  function parseRequest(value) {
34673
- if (!isRecord2(value)) return requestError();
34832
+ if (!isRecord3(value)) return requestError();
34674
34833
  if (value.type === "roster") {
34675
34834
  if (!hasExactKeys(value, ["type"])) return requestError();
34676
34835
  return { type: "roster" };
@@ -34679,18 +34838,36 @@ function parseRequest(value) {
34679
34838
  if (!hasExactKeys(value, ["type"])) return requestError();
34680
34839
  return { type: "update" };
34681
34840
  }
34682
- if (value.type === "history") {
34841
+ if (value.type === "history" || value.type === "recall") {
34683
34842
  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
34843
  return requestError();
34685
34844
  }
34686
34845
  return {
34687
- type: "history",
34846
+ type: value.type,
34688
34847
  requestId: value.requestId,
34689
34848
  personaKey: value.personaKey,
34690
34849
  limit: value.limit
34691
34850
  };
34692
34851
  }
34693
- if (value.type !== "send" && value.type !== "converse" || !hasExactKeys(value, [
34852
+ if (value.type === "decide") {
34853
+ if (!hasExactKeys(value, [
34854
+ "type",
34855
+ "requestId",
34856
+ "personaKey",
34857
+ "callId",
34858
+ "optionIndex"
34859
+ ]) || !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) {
34860
+ return requestError();
34861
+ }
34862
+ return {
34863
+ type: "decide",
34864
+ requestId: value.requestId,
34865
+ personaKey: value.personaKey,
34866
+ callId: value.callId,
34867
+ optionIndex: value.optionIndex
34868
+ };
34869
+ }
34870
+ if (value.type !== "send" && value.type !== "converse" && value.type !== "chat" || !hasExactKeys(value, [
34694
34871
  "type",
34695
34872
  "requestId",
34696
34873
  "personaKey",
@@ -34718,7 +34895,7 @@ function parseRequestLine(line2) {
34718
34895
  }
34719
34896
  }
34720
34897
  function parseMate(value) {
34721
- if (!isRecord2(value) || !hasExactKeys(value, [
34898
+ if (!isRecord3(value) || !hasExactKeys(value, [
34722
34899
  "personaKey",
34723
34900
  "agentName",
34724
34901
  "displayName",
@@ -34743,7 +34920,7 @@ function parseMate(value) {
34743
34920
  };
34744
34921
  }
34745
34922
  function parseEvent(value) {
34746
- if (!isRecord2(value)) return eventError();
34923
+ if (!isRecord3(value)) return eventError();
34747
34924
  if (value.type === "update") {
34748
34925
  if (!hasExactKeys(value, ["type", "installed", "available", "severity"]) || !isVersionOrNull(value.installed) || !isVersionOrNull(value.available) || !(value.severity === null || SEVERITIES2.has(value.severity))) {
34749
34926
  return eventError();
@@ -34786,18 +34963,33 @@ function parseEvent(value) {
34786
34963
  return { type: "completed", requestId: value.requestId };
34787
34964
  }
34788
34965
  if (value.type === "turn") {
34789
- if (!hasExactKeys(value, ["type", "requestId", "turn"]) || !isRequestId(value.requestId) || !isCompanionTurn(value.turn)) {
34966
+ if (!hasExactKeys(value, ["type", "requestId", "turn"]) || !isRequestId(value.requestId)) {
34790
34967
  return eventError();
34791
34968
  }
34792
34969
  return {
34793
34970
  type: "turn",
34794
34971
  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
- }
34972
+ turn: parseTurn(value.turn)
34973
+ };
34974
+ }
34975
+ if (value.type === "reply-decision") {
34976
+ if (!hasExactKeys(value, ["type", "requestId", "decision"]) || !isRequestId(value.requestId)) {
34977
+ return eventError();
34978
+ }
34979
+ return {
34980
+ type: "reply-decision",
34981
+ requestId: value.requestId,
34982
+ decision: parseDecision(value.decision)
34983
+ };
34984
+ }
34985
+ if (value.type === "reply-artifacts") {
34986
+ if (!hasExactKeys(value, ["type", "requestId", "artifacts"]) || !isRequestId(value.requestId) || !Array.isArray(value.artifacts) || value.artifacts.length === 0 || value.artifacts.length > COMPANION_ARTIFACTS_MAX) {
34987
+ return eventError();
34988
+ }
34989
+ return {
34990
+ type: "reply-artifacts",
34991
+ requestId: value.requestId,
34992
+ artifacts: value.artifacts.map(parseArtifact)
34801
34993
  };
34802
34994
  }
34803
34995
  if (value.type === "history-end") {
@@ -34854,6 +35046,7 @@ function serializeEvent(value) {
34854
35046
  }
34855
35047
 
34856
35048
  // src/lib/companion-chat-client.ts
35049
+ init_esm();
34857
35050
  import { stat as stat4 } from "fs/promises";
34858
35051
  var RESPONSE_MAX_BYTES = 1e6;
34859
35052
  var REQUEST_DEADLINE_MS = 12e4;
@@ -34866,7 +35059,7 @@ var PredispatchFailure = class extends Error {
34866
35059
  }
34867
35060
  reason;
34868
35061
  };
34869
- function isRecord3(value) {
35062
+ function isRecord4(value) {
34870
35063
  return typeof value === "object" && value !== null && !Array.isArray(value);
34871
35064
  }
34872
35065
  function hasControlCharacter(value) {
@@ -34936,16 +35129,16 @@ async function readJsonEnvelope(response) {
34936
35129
  if (error instanceof PredispatchFailure) throw error;
34937
35130
  throw new PredispatchFailure("not-connected");
34938
35131
  }
34939
- if (!isRecord3(envelope) || envelope.ok !== true || !("data" in envelope)) {
35132
+ if (!isRecord4(envelope) || envelope.ok !== true || !("data" in envelope)) {
34940
35133
  throw new PredispatchFailure("not-connected");
34941
35134
  }
34942
35135
  return envelope.data;
34943
35136
  }
34944
35137
  function decodeAgents(data) {
34945
- if (!isRecord3(data) || !Array.isArray(data.agents)) {
35138
+ if (!isRecord4(data) || !Array.isArray(data.agents)) {
34946
35139
  throw new PredispatchFailure("mate-unavailable");
34947
35140
  }
34948
- return data.agents.filter(isRecord3);
35141
+ return data.agents.filter(isRecord4);
34949
35142
  }
34950
35143
  function resolveAgentName(agents, personaKey) {
34951
35144
  const matches = agents.filter(
@@ -34967,18 +35160,18 @@ function requireAgentName(agents, personaKey) {
34967
35160
  return name;
34968
35161
  }
34969
35162
  function decodeConversationId(data) {
34970
- if (!isRecord3(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
35163
+ if (!isRecord4(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
34971
35164
  throw new PredispatchFailure("not-connected");
34972
35165
  }
34973
35166
  return data.id;
34974
35167
  }
34975
35168
  function decodeLastMessageId(data) {
34976
- if (!isRecord3(data) || !Array.isArray(data.messages)) {
35169
+ if (!isRecord4(data) || !Array.isArray(data.messages)) {
34977
35170
  throw new PredispatchFailure("not-connected");
34978
35171
  }
34979
35172
  const messages = data.messages;
34980
35173
  const last = messages.at(-1);
34981
- if (!isRecord3(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
35174
+ if (!isRecord4(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
34982
35175
  return void 0;
34983
35176
  }
34984
35177
  return last.id;
@@ -35029,13 +35222,61 @@ function boundCodePoints(value, maxCodePoints) {
35029
35222
  }
35030
35223
  return value;
35031
35224
  }
35225
+ function boundPlainLine(value, maxLength) {
35226
+ return value.replace(/[\u0000-\u001f\u007f]/gu, " ").trim().slice(0, maxLength);
35227
+ }
35228
+ var ARTIFACTS_PART_TYPE = "data-artifacts";
35229
+ function toCompanionDecision(directive) {
35230
+ const state = directive.state;
35231
+ if (state.status !== "pending" && state.status !== "selected" && state.status !== "dismissed") {
35232
+ return null;
35233
+ }
35234
+ const callId = boundPlainLine(
35235
+ directive.callId,
35236
+ COMPANION_DECISION_CALL_ID_MAX_LENGTH
35237
+ );
35238
+ const title = boundPlainLine(
35239
+ directive.args.title,
35240
+ COMPANION_DECISION_TITLE_MAX_LENGTH
35241
+ );
35242
+ if (callId.length === 0 || title.length === 0) return null;
35243
+ const options = [];
35244
+ for (const option of directive.args.options) {
35245
+ const label = boundPlainLine(option.label, COMPANION_DECISION_LABEL_MAX_LENGTH);
35246
+ const detail = boundCodePoints(
35247
+ sanitizeMessageText(option.detail).trim(),
35248
+ COMPANION_DECISION_DETAIL_MAX_CODE_POINTS
35249
+ );
35250
+ if (label.length === 0 || detail.length === 0) return null;
35251
+ options.push({ label, detail });
35252
+ }
35253
+ const base = { callId, title, options };
35254
+ return state.status === "selected" ? { ...base, status: "selected", optionIndex: state.optionIndex } : { ...base, status: state.status };
35255
+ }
35256
+ function toCompanionArtifacts(data) {
35257
+ const artifacts = [];
35258
+ for (const entry of data) {
35259
+ if (artifacts.length >= COMPANION_ARTIFACTS_MAX) break;
35260
+ if (!isRecord4(entry) || typeof entry.name !== "string") continue;
35261
+ const name = boundPlainLine(entry.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH);
35262
+ if (name.length === 0) continue;
35263
+ const size = entry.size_bytes;
35264
+ artifacts.push(
35265
+ typeof size === "number" && Number.isSafeInteger(size) && size >= 0 ? { name, sizeBytes: size } : { name }
35266
+ );
35267
+ }
35268
+ return artifacts;
35269
+ }
35270
+ function decodeMessageInstant(value) {
35271
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value < EPOCH_SECONDS_LIMIT ? new Date(Math.round(value) * 1e3).toISOString() : null;
35272
+ }
35032
35273
  function decodeTurns(data, limit2) {
35033
- if (!isRecord3(data) || !Array.isArray(data.messages)) {
35274
+ if (!isRecord4(data) || !Array.isArray(data.messages)) {
35034
35275
  throw new PredispatchFailure("not-connected");
35035
35276
  }
35036
35277
  const turns = [];
35037
35278
  for (const entry of data.messages) {
35038
- if (!isRecord3(entry)) continue;
35279
+ if (!isRecord4(entry)) continue;
35039
35280
  if (entry.role !== "user" && entry.role !== "assistant") continue;
35040
35281
  if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
35041
35282
  if (typeof entry.content !== "string") continue;
@@ -35044,12 +35285,52 @@ function decodeTurns(data, limit2) {
35044
35285
  COMPANION_TURN_TEXT_MAX_CODE_POINTS
35045
35286
  );
35046
35287
  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
35288
  turns.push({
35049
35289
  id: entry.id,
35050
35290
  role: entry.role === "user" ? "user" : "mate",
35051
35291
  text,
35052
- at
35292
+ at: decodeMessageInstant(entry.createdAt)
35293
+ });
35294
+ }
35295
+ return turns.slice(-limit2);
35296
+ }
35297
+ function decodeDurableDecision(entry) {
35298
+ const frame = parseUiDirectiveFrame({
35299
+ directive: entry.uiDirective,
35300
+ source: entry.uiDirectiveSource === "voice" ? "voice" : "text"
35301
+ });
35302
+ if (!frame) return null;
35303
+ return toCompanionDecision(frame.directive);
35304
+ }
35305
+ function decodeRichTurns(data, limit2) {
35306
+ if (!isRecord4(data) || !Array.isArray(data.messages)) {
35307
+ throw new PredispatchFailure("not-connected");
35308
+ }
35309
+ const turns = [];
35310
+ for (const entry of data.messages) {
35311
+ if (!isRecord4(entry)) continue;
35312
+ if (entry.role !== "user" && entry.role !== "assistant") continue;
35313
+ if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
35314
+ if (typeof entry.content !== "string") continue;
35315
+ const parsed = parseArtifacts(entry.content);
35316
+ const artifacts = toCompanionArtifacts(parsed.artifacts);
35317
+ const text = boundCodePoints(
35318
+ sanitizeMessageText(parsed.text),
35319
+ COMPANION_TURN_TEXT_MAX_CODE_POINTS
35320
+ );
35321
+ const at = decodeMessageInstant(entry.createdAt);
35322
+ const decision = entry.uiDirective === void 0 ? null : decodeDurableDecision(entry);
35323
+ if (decision !== null) {
35324
+ turns.push({ id: entry.id, role: "mate", text, at, decision });
35325
+ continue;
35326
+ }
35327
+ if (text.length === 0 && artifacts.length === 0) continue;
35328
+ turns.push({
35329
+ id: entry.id,
35330
+ role: entry.role === "user" ? "user" : "mate",
35331
+ text,
35332
+ at,
35333
+ ...artifacts.length > 0 ? { artifacts } : {}
35053
35334
  });
35054
35335
  }
35055
35336
  return turns.slice(-limit2);
@@ -35163,7 +35444,7 @@ async function drainAcceptedSse(response) {
35163
35444
  continue;
35164
35445
  }
35165
35446
  const parsed = JSON.parse(data);
35166
- if (!isRecord3(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35447
+ if (!isRecord4(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35167
35448
  throw new Error("invalid stream event");
35168
35449
  }
35169
35450
  }
@@ -35185,7 +35466,7 @@ function emitBoundedText(text, emit) {
35185
35466
  }
35186
35467
  if (slice.length > 0) emit(slice);
35187
35468
  }
35188
- async function streamAcceptedSse(response, now, onText) {
35469
+ async function streamAcceptedSse(response, now, onText, onData) {
35189
35470
  if (!response.body) throw new Error("response body unavailable");
35190
35471
  const reader = response.body.getReader();
35191
35472
  const decoder = new TextDecoder("utf-8", { fatal: true });
@@ -35211,7 +35492,7 @@ async function streamAcceptedSse(response, now, onText) {
35211
35492
  return;
35212
35493
  }
35213
35494
  const parsed = JSON.parse(data);
35214
- if (!isRecord3(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35495
+ if (!isRecord4(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35215
35496
  throw new Error("invalid stream event");
35216
35497
  }
35217
35498
  if (parsed.type === "text-delta" && typeof parsed.delta === "string" && parsed.delta.length > 0) {
@@ -35220,6 +35501,10 @@ async function streamAcceptedSse(response, now, onText) {
35220
35501
  flush();
35221
35502
  }
35222
35503
  }
35504
+ if (onData && parsed.type.startsWith("data-")) {
35505
+ flush();
35506
+ onData(parsed);
35507
+ }
35223
35508
  };
35224
35509
  const drainFrames = () => {
35225
35510
  for (; ; ) {
@@ -35255,7 +35540,7 @@ async function streamAcceptedSse(response, now, onText) {
35255
35540
  throw new Error("stream ended without terminal marker");
35256
35541
  }
35257
35542
  }
35258
- async function dispatchConversation(request, sink, deps, consume, readDesktopMarker) {
35543
+ async function dispatchConversation(request, sink, deps, consume, readDesktopMarker, chatBody, classifyRefusal) {
35259
35544
  const { controller, timer } = createDeadline(deps);
35260
35545
  const fetchImpl = deps.fetch ?? fetch;
35261
35546
  let route;
@@ -35301,17 +35586,19 @@ async function dispatchConversation(request, sink, deps, consume, readDesktopMar
35301
35586
  chatResponse = await fetchImpl(`${origin}/api/chat`, {
35302
35587
  method: "POST",
35303
35588
  headers: { ...headers, "content-type": "application/json" },
35304
- body: JSON.stringify({
35305
- messages: [{ role: "user", content: request.text }],
35306
- agentName,
35307
- conversationId
35308
- }),
35589
+ body: JSON.stringify(chatBody(agentName, conversationId)),
35309
35590
  signal: controller.signal
35310
35591
  });
35311
35592
  } catch {
35312
35593
  throw new Error("chat dispatch outcome unavailable");
35313
35594
  }
35314
35595
  if (!chatResponse.ok || !chatResponse.body || !(chatResponse.headers.get("content-type") ?? "").toLowerCase().includes("text/event-stream")) {
35596
+ if (!chatResponse.ok && classifyRefusal) {
35597
+ const reason = await classifyRefusal(chatResponse);
35598
+ if (reason !== null) {
35599
+ return emitFailure(sink, reason, request.requestId);
35600
+ }
35601
+ }
35315
35602
  throw new Error("chat response not accepted");
35316
35603
  }
35317
35604
  const acceptedEvent = parseEvent({
@@ -35349,8 +35636,40 @@ async function dispatchConversation(request, sink, deps, consume, readDesktopMar
35349
35636
  clearTimeout(timer);
35350
35637
  }
35351
35638
  }
35639
+ function textTurnBody(text) {
35640
+ return (agentName, conversationId) => ({
35641
+ messages: [{ role: "user", content: text }],
35642
+ agentName,
35643
+ conversationId
35644
+ });
35645
+ }
35646
+ function replyDataForwarder(requestId, sink) {
35647
+ return (part) => {
35648
+ if (part.type === UI_DIRECTIVE_PART_TYPE) {
35649
+ const frame = parseUiDirectiveFrame(part.data);
35650
+ if (!frame) return;
35651
+ const decision = toCompanionDecision(frame.directive);
35652
+ if (decision === null) return;
35653
+ sink(parseEvent({ type: "reply-decision", requestId, decision }));
35654
+ return;
35655
+ }
35656
+ if (part.type === ARTIFACTS_PART_TYPE && Array.isArray(part.data)) {
35657
+ const artifacts = toCompanionArtifacts(part.data);
35658
+ if (artifacts.length > 0) {
35659
+ sink(parseEvent({ type: "reply-artifacts", requestId, artifacts }));
35660
+ }
35661
+ }
35662
+ };
35663
+ }
35352
35664
  async function sendCompanionMessage(request, sink, deps = {}) {
35353
- return dispatchConversation(request, sink, deps, drainAcceptedSse, true);
35665
+ return dispatchConversation(
35666
+ request,
35667
+ sink,
35668
+ deps,
35669
+ drainAcceptedSse,
35670
+ true,
35671
+ textTurnBody(request.text)
35672
+ );
35354
35673
  }
35355
35674
  async function converseCompanionMessage(request, sink, deps = {}) {
35356
35675
  return dispatchConversation(
@@ -35366,10 +35685,101 @@ async function converseCompanionMessage(request, sink, deps = {}) {
35366
35685
  })
35367
35686
  );
35368
35687
  }),
35369
- false
35688
+ false,
35689
+ textTurnBody(request.text)
35690
+ );
35691
+ }
35692
+ async function chatCompanionMessage(request, sink, deps = {}) {
35693
+ return dispatchConversation(
35694
+ request,
35695
+ sink,
35696
+ deps,
35697
+ (response) => streamAcceptedSse(
35698
+ response,
35699
+ deps.now ?? Date.now,
35700
+ (text) => {
35701
+ sink(
35702
+ parseEvent({
35703
+ type: "reply-delta",
35704
+ requestId: request.requestId,
35705
+ text
35706
+ })
35707
+ );
35708
+ },
35709
+ replyDataForwarder(request.requestId, sink)
35710
+ ),
35711
+ false,
35712
+ textTurnBody(request.text)
35713
+ );
35714
+ }
35715
+ async function classifyDecideRefusal(response) {
35716
+ let reason;
35717
+ try {
35718
+ const envelope = JSON.parse(await readBoundedText(response));
35719
+ if (isRecord4(envelope) && isRecord4(envelope.error) && isRecord4(envelope.error.details)) {
35720
+ reason = envelope.error.details.reason;
35721
+ }
35722
+ } catch {
35723
+ }
35724
+ if (isDecisionRefusalReason(reason)) {
35725
+ return reason === DECISION_REFUSAL_REASONS.notPending ? "decision-not-pending" : "bad-request";
35726
+ }
35727
+ if (response.status === 401 || response.status === 403) {
35728
+ return "not-authorized";
35729
+ }
35730
+ if (response.status === 400) return "bad-request";
35731
+ if (response.status === 409) return "busy";
35732
+ return null;
35733
+ }
35734
+ async function decideCompanionMessage(request, sink, deps = {}) {
35735
+ const message = {
35736
+ role: "user",
35737
+ parts: [
35738
+ {
35739
+ type: UI_DIRECTIVE_RESULT_PART_TYPE,
35740
+ data: {
35741
+ version: 1,
35742
+ name: "present_decision",
35743
+ callId: request.callId,
35744
+ result: { status: "selected", optionIndex: request.optionIndex }
35745
+ }
35746
+ }
35747
+ ]
35748
+ };
35749
+ return dispatchConversation(
35750
+ request,
35751
+ sink,
35752
+ deps,
35753
+ (response) => streamAcceptedSse(
35754
+ response,
35755
+ deps.now ?? Date.now,
35756
+ (text) => {
35757
+ sink(
35758
+ parseEvent({
35759
+ type: "reply-delta",
35760
+ requestId: request.requestId,
35761
+ text
35762
+ })
35763
+ );
35764
+ },
35765
+ replyDataForwarder(request.requestId, sink)
35766
+ ),
35767
+ false,
35768
+ (agentName, conversationId) => ({
35769
+ messages: [message],
35770
+ agentName,
35771
+ conversationId
35772
+ }),
35773
+ classifyDecideRefusal
35370
35774
  );
35371
35775
  }
35372
35776
  async function historyCompanionMessages(request, sink, deps = {}) {
35777
+ return fetchConversationTurns(request, sink, deps, decodeTurns);
35778
+ }
35779
+ async function recallCompanionMessages(request, sink, deps = {}) {
35780
+ return fetchConversationTurns(request, sink, deps, decodeRichTurns);
35781
+ }
35782
+ async function fetchConversationTurns(request, sink, deps, decode) {
35373
35783
  const { controller, timer } = createDeadline(deps);
35374
35784
  const fetchImpl = deps.fetch ?? fetch;
35375
35785
  try {
@@ -35389,7 +35799,7 @@ async function historyCompanionMessages(request, sink, deps = {}) {
35389
35799
  agentName,
35390
35800
  controller.signal
35391
35801
  );
35392
- const turns = decodeTurns(
35802
+ const turns = decode(
35393
35803
  await fetchConversationMessages(
35394
35804
  fetchImpl,
35395
35805
  origin,
@@ -35545,7 +35955,10 @@ async function dispatchRequest(request, sink, handlers) {
35545
35955
  return terminal;
35546
35956
  }
35547
35957
  if (request.type === "history") return handlers.history(request, sink);
35958
+ if (request.type === "recall") return handlers.recall(request, sink);
35548
35959
  if (request.type === "converse") return handlers.converse(request, sink);
35960
+ if (request.type === "chat") return handlers.chat(request, sink);
35961
+ if (request.type === "decide") return handlers.decide(request, sink);
35549
35962
  return handlers.send(request, sink);
35550
35963
  }
35551
35964
  async function handleWithContext(request, sink, handlers, context) {
@@ -35581,7 +35994,10 @@ async function runCompanionBridgeServe(stdin, stdout, stderr, deps = {}) {
35581
35994
  roster: (sink2) => rosterCompanions(sink2, chat),
35582
35995
  send: (request, sink2) => sendCompanionMessage(request, sink2, chat),
35583
35996
  converse: (request, sink2) => converseCompanionMessage(request, sink2, chat),
35997
+ chat: (request, sink2) => chatCompanionMessage(request, sink2, chat),
35998
+ decide: (request, sink2) => decideCompanionMessage(request, sink2, chat),
35584
35999
  history: (request, sink2) => historyCompanionMessages(request, sink2, chat),
36000
+ recall: (request, sink2) => recallCompanionMessages(request, sink2, chat),
35585
36001
  update: deps.update ?? (() => checkCompanionUpdate(defaultLocalCompanionInstallOptions()))
35586
36002
  };
35587
36003
  const sink = (event) => {
@@ -35616,7 +36032,10 @@ var defaultDeps4 = {
35616
36032
  roster: rosterCompanions,
35617
36033
  send: sendCompanionMessage,
35618
36034
  converse: converseCompanionMessage,
36035
+ chat: chatCompanionMessage,
36036
+ decide: decideCompanionMessage,
35619
36037
  history: historyCompanionMessages,
36038
+ recall: recallCompanionMessages,
35620
36039
  update: () => checkCompanionUpdate(defaultLocalCompanionInstallOptions())
35621
36040
  };
35622
36041
  async function readSingleRequest(stdin) {
@@ -35667,8 +36086,14 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps4) {
35667
36086
  sink(terminal);
35668
36087
  } else if (request.type === "history") {
35669
36088
  terminal = await deps.history(request, sink);
36089
+ } else if (request.type === "recall") {
36090
+ terminal = await deps.recall(request, sink);
35670
36091
  } else if (request.type === "converse") {
35671
36092
  terminal = await deps.converse(request, sink);
36093
+ } else if (request.type === "chat") {
36094
+ terminal = await deps.chat(request, sink);
36095
+ } else if (request.type === "decide") {
36096
+ terminal = await deps.decide(request, sink);
35672
36097
  } else terminal = await deps.send(request, sink);
35673
36098
  return exitFor(terminal);
35674
36099
  } catch {