@m8t-stack/cli 0.2.65 → 0.2.67

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
@@ -1364,7 +1364,7 @@ var init_enable_hosted_brain = __esm({
1364
1364
  import { Builtins, Cli } from "clipanion";
1365
1365
 
1366
1366
  // src/lib/package-version.ts
1367
- var CLI_VERSION = "0.2.65";
1367
+ var CLI_VERSION = "0.2.67";
1368
1368
 
1369
1369
  // src/lib/render-error.ts
1370
1370
  init_errors();
@@ -21295,6 +21295,7 @@ function resolveHeadlessContextFromEnv(env) {
21295
21295
 
21296
21296
  // src/lib/apply-request-store.ts
21297
21297
  import { TableClient as TableClient4 } from "@azure/data-tables";
21298
+ var MAX_LEASE_TAKEOVERS = 2;
21298
21299
  async function openApplyTable(opts) {
21299
21300
  const { tableEndpoint } = await discoverStampStorage(opts);
21300
21301
  return new TableClient4(tableEndpoint, "Metadata", opts.credential);
@@ -21314,12 +21315,37 @@ async function readApplyRequest(client) {
21314
21315
  async function claimApplyRequest(client, executionId, nowIso, leaseMs) {
21315
21316
  const cur = await readApplyRequest(client);
21316
21317
  if (!cur) return null;
21317
- const leaseExpired = cur.status === "claimed" && cur.leaseUntil !== null && Date.parse(cur.leaseUntil) < Date.parse(nowIso);
21318
+ const leaseExpired = isInFlight(cur.status) && cur.leaseUntil !== null && Date.parse(cur.leaseUntil) < Date.parse(nowIso);
21318
21319
  const claimable = cur.status === "pending" || cur.status === "awaiting-engine-update" || leaseExpired;
21319
21320
  if (!claimable) return null;
21320
21321
  const nowMs = Date.parse(nowIso);
21321
21322
  const leaseUntil = new Date((Number.isNaN(nowMs) ? Date.now() : nowMs) + leaseMs).toISOString();
21322
- const claimed = { ...cur, status: "claimed", claimedBy: executionId, leaseUntil, updatedAt: nowIso };
21323
+ if (leaseExpired && cur.attempt >= MAX_LEASE_TAKEOVERS) {
21324
+ const held = {
21325
+ ...cur,
21326
+ status: "held",
21327
+ breaker: "held",
21328
+ result: {
21329
+ appliedVersion: cur.result?.appliedVersion ?? null,
21330
+ error: `the converge was abandoned mid-apply ${(cur.attempt + 1).toString()} times (last owner ${cur.claimedBy ?? "(unknown)"}, phase "${cur.phase ?? "(none)"}"); the updater stopped retrying to avoid re-rolling infra behind a converge that cannot finish`
21331
+ },
21332
+ updatedAt: nowIso
21333
+ };
21334
+ try {
21335
+ await client.updateEntity({ ...applyRequestToEntity(held), etag: cur.etag }, "Merge", { etag: cur.etag });
21336
+ } catch (e) {
21337
+ if (!hasStatus(e, 412)) throw e;
21338
+ }
21339
+ return null;
21340
+ }
21341
+ const claimed = {
21342
+ ...cur,
21343
+ status: "claimed",
21344
+ claimedBy: executionId,
21345
+ leaseUntil,
21346
+ ...leaseExpired ? { attempt: cur.attempt + 1 } : {},
21347
+ updatedAt: nowIso
21348
+ };
21323
21349
  try {
21324
21350
  await client.updateEntity({ ...applyRequestToEntity(claimed), etag: cur.etag }, "Merge", { etag: cur.etag });
21325
21351
  return claimed;
@@ -33147,6 +33173,10 @@ var COMPANION_REQUEST_ID_MAX_LENGTH = 128;
33147
33173
  var COMPANION_AGENT_NAME_MAX_LENGTH = 256;
33148
33174
  var COMPANION_DESKTOP_AFTER_MAX_LENGTH = 256;
33149
33175
  var COMPANION_BRIDGE_LINE_MAX_LENGTH = 14e4;
33176
+ var COMPANION_HISTORY_LIMIT_MAX = 40;
33177
+ var COMPANION_TURN_TEXT_MAX_CODE_POINTS = 32768;
33178
+ var COMPANION_REPLY_DELTA_MAX_CODE_POINTS = 4096;
33179
+ var COMPANION_TURN_ID_MAX_LENGTH = 256;
33150
33180
  var PERSONAS = /* @__PURE__ */ new Set([
33151
33181
  "startup-advisor",
33152
33182
  "azure-advisor"
@@ -33181,6 +33211,17 @@ function isBoundedPlainString(value, maxLength) {
33181
33211
  function isRequestId(value) {
33182
33212
  return isBoundedPlainString(value, COMPANION_REQUEST_ID_MAX_LENGTH);
33183
33213
  }
33214
+ var FORBIDDEN_TEXT_CHARACTERS = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/u;
33215
+ function isBoundedMessageText(value, maxCodePoints) {
33216
+ return typeof value === "string" && value.length > 0 && !FORBIDDEN_TEXT_CHARACTERS.test(value) && [...value].length <= maxCodePoints;
33217
+ }
33218
+ var INSTANT_SHAPE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
33219
+ function isInstantOrNull(value) {
33220
+ return value === null || typeof value === "string" && INSTANT_SHAPE.test(value) && Number.isFinite(Date.parse(value));
33221
+ }
33222
+ function isCompanionTurn(value) {
33223
+ 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);
33224
+ }
33184
33225
  function isCanonicalInstant(value) {
33185
33226
  if (typeof value !== "string") return false;
33186
33227
  const time = Date.parse(value);
@@ -33244,7 +33285,18 @@ function parseRequest(value) {
33244
33285
  if (!hasExactKeys(value, ["type"])) return requestError();
33245
33286
  return { type: "update" };
33246
33287
  }
33247
- if (value.type !== "send" || !hasExactKeys(value, [
33288
+ if (value.type === "history") {
33289
+ 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) {
33290
+ return requestError();
33291
+ }
33292
+ return {
33293
+ type: "history",
33294
+ requestId: value.requestId,
33295
+ personaKey: value.personaKey,
33296
+ limit: value.limit
33297
+ };
33298
+ }
33299
+ if (value.type !== "send" && value.type !== "converse" || !hasExactKeys(value, [
33248
33300
  "type",
33249
33301
  "requestId",
33250
33302
  "personaKey",
@@ -33254,7 +33306,7 @@ function parseRequest(value) {
33254
33306
  return requestError();
33255
33307
  }
33256
33308
  return {
33257
- type: "send",
33309
+ type: value.type,
33258
33310
  requestId: value.requestId,
33259
33311
  personaKey: value.personaKey,
33260
33312
  draftRevision: value.draftRevision,
@@ -33339,6 +33391,41 @@ function parseEvent(value) {
33339
33391
  }
33340
33392
  return { type: "completed", requestId: value.requestId };
33341
33393
  }
33394
+ if (value.type === "turn") {
33395
+ if (!hasExactKeys(value, ["type", "requestId", "turn"]) || !isRequestId(value.requestId) || !isCompanionTurn(value.turn)) {
33396
+ return eventError();
33397
+ }
33398
+ return {
33399
+ type: "turn",
33400
+ requestId: value.requestId,
33401
+ turn: {
33402
+ id: value.turn.id,
33403
+ role: value.turn.role,
33404
+ text: value.turn.text,
33405
+ at: value.turn.at
33406
+ }
33407
+ };
33408
+ }
33409
+ if (value.type === "history-end") {
33410
+ if (!hasExactKeys(value, ["type", "requestId", "count"]) || !isRequestId(value.requestId) || !Number.isSafeInteger(value.count) || value.count < 0 || value.count > COMPANION_HISTORY_LIMIT_MAX) {
33411
+ return eventError();
33412
+ }
33413
+ return {
33414
+ type: "history-end",
33415
+ requestId: value.requestId,
33416
+ count: value.count
33417
+ };
33418
+ }
33419
+ if (value.type === "reply-delta") {
33420
+ if (!hasExactKeys(value, ["type", "requestId", "text"]) || !isRequestId(value.requestId) || !isBoundedMessageText(value.text, COMPANION_REPLY_DELTA_MAX_CODE_POINTS)) {
33421
+ return eventError();
33422
+ }
33423
+ return {
33424
+ type: "reply-delta",
33425
+ requestId: value.requestId,
33426
+ text: value.text
33427
+ };
33428
+ }
33342
33429
  if (value.type === "dispatching" || value.type === "accepted") {
33343
33430
  if (!hasExactKeys(value, [
33344
33431
  "type",
@@ -33496,6 +33583,77 @@ function decodeLastMessageId(data) {
33496
33583
  }
33497
33584
  return last.id;
33498
33585
  }
33586
+ async function resolveConversationId(fetchImpl, origin, headers, agentName, signal) {
33587
+ let response;
33588
+ try {
33589
+ response = await fetchImpl(`${origin}/api/conversations`, {
33590
+ method: "POST",
33591
+ headers: { ...headers, "content-type": "application/json" },
33592
+ body: JSON.stringify({ agentName }),
33593
+ signal
33594
+ });
33595
+ } catch {
33596
+ throw new PredispatchFailure("not-connected");
33597
+ }
33598
+ return decodeConversationId(await readJsonEnvelope(response));
33599
+ }
33600
+ async function fetchConversationMessages(fetchImpl, origin, headers, conversationId, signal) {
33601
+ let response;
33602
+ try {
33603
+ response = await fetchImpl(
33604
+ `${origin}/api/conversations/${encodeURIComponent(conversationId)}/messages`,
33605
+ { headers, signal }
33606
+ );
33607
+ } catch {
33608
+ throw new PredispatchFailure("not-connected");
33609
+ }
33610
+ return readJsonEnvelope(response);
33611
+ }
33612
+ var EPOCH_SECONDS_LIMIT = 1e11;
33613
+ function sanitizeMessageText(value) {
33614
+ let out = "";
33615
+ for (const character of value) {
33616
+ const code = character.codePointAt(0) ?? 0;
33617
+ const forbidden = code === 127 || code <= 31 && code !== 9 && code !== 10 && code !== 13;
33618
+ if (!forbidden) out += character;
33619
+ }
33620
+ return out;
33621
+ }
33622
+ function boundCodePoints(value, maxCodePoints) {
33623
+ let count = 0;
33624
+ let out = "";
33625
+ for (const character of value) {
33626
+ if (count >= maxCodePoints) return out;
33627
+ out += character;
33628
+ count += 1;
33629
+ }
33630
+ return value;
33631
+ }
33632
+ function decodeTurns(data, limit2) {
33633
+ if (!isRecord3(data) || !Array.isArray(data.messages)) {
33634
+ throw new PredispatchFailure("not-connected");
33635
+ }
33636
+ const turns = [];
33637
+ for (const entry of data.messages) {
33638
+ if (!isRecord3(entry)) continue;
33639
+ if (entry.role !== "user" && entry.role !== "assistant") continue;
33640
+ if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
33641
+ if (typeof entry.content !== "string") continue;
33642
+ const text = boundCodePoints(
33643
+ sanitizeMessageText(entry.content),
33644
+ COMPANION_TURN_TEXT_MAX_CODE_POINTS
33645
+ );
33646
+ if (text.length === 0) continue;
33647
+ 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;
33648
+ turns.push({
33649
+ id: entry.id,
33650
+ role: entry.role === "user" ? "user" : "mate",
33651
+ text,
33652
+ at
33653
+ });
33654
+ }
33655
+ return turns.slice(-limit2);
33656
+ }
33499
33657
  function mateRoute(personaKey, desktopAfter) {
33500
33658
  const marker = desktopAfter === void 0 ? "" : `&desktopAfter=${desktopAfter}`;
33501
33659
  return `/mates/${personaKey}?desktopPending=1${marker}`;
@@ -33618,7 +33776,93 @@ async function drainAcceptedSse(response) {
33618
33776
  }
33619
33777
  if (!sawDone) throw new Error("stream ended without terminal marker");
33620
33778
  }
33621
- async function sendCompanionMessage(request, sink, deps = {}) {
33779
+ var REPLY_FLUSH_MIN_LENGTH = 64;
33780
+ var REPLY_FLUSH_MS = 150;
33781
+ function emitBoundedText(text, emit) {
33782
+ let slice = "";
33783
+ let count = 0;
33784
+ for (const character of text) {
33785
+ slice += character;
33786
+ count += 1;
33787
+ if (count === COMPANION_REPLY_DELTA_MAX_CODE_POINTS) {
33788
+ emit(slice);
33789
+ slice = "";
33790
+ count = 0;
33791
+ }
33792
+ }
33793
+ if (slice.length > 0) emit(slice);
33794
+ }
33795
+ async function streamAcceptedSse(response, now, onText) {
33796
+ if (!response.body) throw new Error("response body unavailable");
33797
+ const reader = response.body.getReader();
33798
+ const decoder = new TextDecoder("utf-8", { fatal: true });
33799
+ let buffered = "";
33800
+ let total = 0;
33801
+ const terminal = { sawDone: false };
33802
+ let pending = "";
33803
+ let lastFlush = now();
33804
+ const flush = () => {
33805
+ const text = sanitizeMessageText(pending);
33806
+ pending = "";
33807
+ lastFlush = now();
33808
+ if (text.length > 0) emitBoundedText(text, onText);
33809
+ };
33810
+ const handleFrame = (frame) => {
33811
+ if (frame.trim().length === 0) return;
33812
+ const dataLines = frame.split(/\r?\n/u).filter((line2) => line2.startsWith("data:")).map((line2) => line2.slice(5).trimStart());
33813
+ if (dataLines.length === 0) return;
33814
+ const data = dataLines.join("\n");
33815
+ if (terminal.sawDone) throw new Error("data after terminal stream marker");
33816
+ if (data === "[DONE]") {
33817
+ terminal.sawDone = true;
33818
+ return;
33819
+ }
33820
+ const parsed = JSON.parse(data);
33821
+ if (!isRecord3(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
33822
+ throw new Error("invalid stream event");
33823
+ }
33824
+ if (parsed.type === "text-delta" && typeof parsed.delta === "string" && parsed.delta.length > 0) {
33825
+ pending += parsed.delta;
33826
+ if (pending.length >= REPLY_FLUSH_MIN_LENGTH || now() - lastFlush >= REPLY_FLUSH_MS) {
33827
+ flush();
33828
+ }
33829
+ }
33830
+ };
33831
+ const drainFrames = () => {
33832
+ for (; ; ) {
33833
+ const boundary = /\r?\n\r?\n/u.exec(buffered);
33834
+ if (!boundary) return;
33835
+ const frame = buffered.slice(0, boundary.index);
33836
+ buffered = buffered.slice(boundary.index + boundary[0].length);
33837
+ handleFrame(frame);
33838
+ }
33839
+ };
33840
+ for (; ; ) {
33841
+ const part = await reader.read();
33842
+ if (part.done) break;
33843
+ total += part.value.byteLength;
33844
+ if (total > RESPONSE_MAX_BYTES) {
33845
+ await reader.cancel().catch(() => void 0);
33846
+ throw new Error("bounded response rejected");
33847
+ }
33848
+ buffered += decoder.decode(part.value, { stream: true });
33849
+ drainFrames();
33850
+ if (terminal.sawDone) {
33851
+ await reader.cancel().catch(() => void 0);
33852
+ break;
33853
+ }
33854
+ }
33855
+ if (!terminal.sawDone) {
33856
+ buffered += decoder.decode();
33857
+ drainFrames();
33858
+ if (buffered.trim().length > 0) handleFrame(buffered);
33859
+ }
33860
+ flush();
33861
+ if (!terminal.sawDone) {
33862
+ throw new Error("stream ended without terminal marker");
33863
+ }
33864
+ }
33865
+ async function dispatchConversation(request, sink, deps, consume, readDesktopMarker) {
33622
33866
  const { controller, timer } = createDeadline(deps);
33623
33867
  const fetchImpl = deps.fetch ?? fetch;
33624
33868
  let route;
@@ -33634,32 +33878,22 @@ async function sendCompanionMessage(request, sink, deps = {}) {
33634
33878
  controller.signal
33635
33879
  );
33636
33880
  const agentName = resolveAgentName(agents, request.personaKey);
33637
- let conversationResponse;
33638
- try {
33639
- conversationResponse = await fetchImpl(`${origin}/api/conversations`, {
33640
- method: "POST",
33641
- headers: { ...headers, "content-type": "application/json" },
33642
- body: JSON.stringify({ agentName }),
33643
- signal: controller.signal
33644
- });
33645
- } catch {
33646
- throw new PredispatchFailure("not-connected");
33647
- }
33648
- const conversationId = decodeConversationId(
33649
- await readJsonEnvelope(conversationResponse)
33650
- );
33651
- let messagesResponse;
33652
- try {
33653
- messagesResponse = await fetchImpl(
33654
- `${origin}/api/conversations/${encodeURIComponent(conversationId)}/messages`,
33655
- { headers, signal: controller.signal }
33656
- );
33657
- } catch {
33658
- throw new PredispatchFailure("not-connected");
33659
- }
33660
- const desktopAfter = decodeLastMessageId(
33661
- await readJsonEnvelope(messagesResponse)
33881
+ const conversationId = await resolveConversationId(
33882
+ fetchImpl,
33883
+ origin,
33884
+ headers,
33885
+ agentName,
33886
+ controller.signal
33662
33887
  );
33888
+ const desktopAfter = readDesktopMarker ? decodeLastMessageId(
33889
+ await fetchConversationMessages(
33890
+ fetchImpl,
33891
+ origin,
33892
+ headers,
33893
+ conversationId,
33894
+ controller.signal
33895
+ )
33896
+ ) : void 0;
33663
33897
  route = mateRoute(request.personaKey, desktopAfter);
33664
33898
  const dispatching = parseEvent({
33665
33899
  type: "dispatching",
@@ -33695,7 +33929,7 @@ async function sendCompanionMessage(request, sink, deps = {}) {
33695
33929
  });
33696
33930
  sink(acceptedEvent);
33697
33931
  accepted = true;
33698
- await drainAcceptedSse(chatResponse);
33932
+ await consume(chatResponse);
33699
33933
  const completed = parseEvent({
33700
33934
  type: "completed",
33701
33935
  requestId: request.requestId
@@ -33722,6 +33956,76 @@ async function sendCompanionMessage(request, sink, deps = {}) {
33722
33956
  clearTimeout(timer);
33723
33957
  }
33724
33958
  }
33959
+ async function sendCompanionMessage(request, sink, deps = {}) {
33960
+ return dispatchConversation(request, sink, deps, drainAcceptedSse, true);
33961
+ }
33962
+ async function converseCompanionMessage(request, sink, deps = {}) {
33963
+ return dispatchConversation(
33964
+ request,
33965
+ sink,
33966
+ deps,
33967
+ (response) => streamAcceptedSse(response, deps.now ?? Date.now, (text) => {
33968
+ sink(
33969
+ parseEvent({
33970
+ type: "reply-delta",
33971
+ requestId: request.requestId,
33972
+ text
33973
+ })
33974
+ );
33975
+ }),
33976
+ false
33977
+ );
33978
+ }
33979
+ async function historyCompanionMessages(request, sink, deps = {}) {
33980
+ const { controller, timer } = createDeadline(deps);
33981
+ const fetchImpl = deps.fetch ?? fetch;
33982
+ try {
33983
+ const { token, origin } = await acquireContext(deps);
33984
+ const headers = authenticatedHeaders(token);
33985
+ const agents = await fetchAgents(
33986
+ fetchImpl,
33987
+ origin,
33988
+ token,
33989
+ controller.signal
33990
+ );
33991
+ const agentName = resolveAgentName(agents, request.personaKey);
33992
+ const conversationId = await resolveConversationId(
33993
+ fetchImpl,
33994
+ origin,
33995
+ headers,
33996
+ agentName,
33997
+ controller.signal
33998
+ );
33999
+ const turns = decodeTurns(
34000
+ await fetchConversationMessages(
34001
+ fetchImpl,
34002
+ origin,
34003
+ headers,
34004
+ conversationId,
34005
+ controller.signal
34006
+ ),
34007
+ request.limit
34008
+ );
34009
+ for (const turn of turns) {
34010
+ sink(parseEvent({ type: "turn", requestId: request.requestId, turn }));
34011
+ }
34012
+ const end = parseEvent({
34013
+ type: "history-end",
34014
+ requestId: request.requestId,
34015
+ count: turns.length
34016
+ });
34017
+ sink(end);
34018
+ return end;
34019
+ } catch (error) {
34020
+ return emitFailure(
34021
+ sink,
34022
+ classifyUnknownPredispatch(error),
34023
+ request.requestId
34024
+ );
34025
+ } finally {
34026
+ clearTimeout(timer);
34027
+ }
34028
+ }
33725
34029
 
33726
34030
  // src/lib/companion-update-check.ts
33727
34031
  async function checkCompanionUpdate(options, deps = {}) {
@@ -33742,6 +34046,8 @@ function companionUpdateAvailable(update) {
33742
34046
  var defaultDeps3 = {
33743
34047
  roster: rosterCompanions,
33744
34048
  send: sendCompanionMessage,
34049
+ converse: converseCompanionMessage,
34050
+ history: historyCompanionMessages,
33745
34051
  update: () => checkCompanionUpdate(defaultLocalCompanionInstallOptions())
33746
34052
  };
33747
34053
  async function readSingleRequest(stdin) {
@@ -33764,6 +34070,7 @@ async function readSingleRequest(stdin) {
33764
34070
  }
33765
34071
  function exitFor(terminal) {
33766
34072
  if (terminal.type === "roster" || terminal.type === "completed") return 0;
34073
+ if (terminal.type === "history-end") return 0;
33767
34074
  if (terminal.type === "update") return 0;
33768
34075
  if (terminal.type === "indeterminate") return 4;
33769
34076
  if (terminal.type === "failed" && terminal.reason !== "bad-request") {
@@ -33789,6 +34096,10 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
33789
34096
  else if (request.type === "update") {
33790
34097
  terminal = await deps.update();
33791
34098
  sink(terminal);
34099
+ } else if (request.type === "history") {
34100
+ terminal = await deps.history(request, sink);
34101
+ } else if (request.type === "converse") {
34102
+ terminal = await deps.converse(request, sink);
33792
34103
  } else terminal = await deps.send(request, sink);
33793
34104
  return exitFor(terminal);
33794
34105
  } catch {