@usecontextlayer/ctxs 0.5.23 → 0.5.25

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.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5c792d56-554a-5d58-8c1b-518f322339f4")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3205b84e-0fd0-5843-a715-326779c52b1a")}catch(e){}}();
4
4
  import { createRequire } from "node:module";
5
5
  import * as Sentry from "@sentry/node";
6
6
  import * as fs$2 from "node:fs/promises";
7
7
  import { access, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
8
8
  import path, { sep } from "node:path";
9
- import childProcess, { execFile } from "node:child_process";
9
+ import childProcess, { execFile, spawn } from "node:child_process";
10
10
  import { formatWithOptions, promisify, stripVTControlCharacters } from "node:util";
11
- import { randomUUID } from "node:crypto";
11
+ import { randomBytes, randomInt, randomUUID } from "node:crypto";
12
12
  import { serve, upgradeWebSocket } from "@hono/node-server";
13
13
  import { Hono } from "hono";
14
14
  import { cors } from "hono/cors";
@@ -642,7 +642,7 @@ const _safeParseAsync$1 = (_Err) => async (schema, value, _ctx) => {
642
642
  };
643
643
  };
644
644
  const safeParseAsync$4 = /* @__PURE__*/ _safeParseAsync$1($ZodRealError$1);
645
- const _encode$1 = (_Err) => (schema, value, _ctx) => {
645
+ const _encode$2 = (_Err) => (schema, value, _ctx) => {
646
646
  const ctx = _ctx ? {
647
647
  ..._ctx,
648
648
  direction: "backward"
@@ -3851,7 +3851,7 @@ const parse$3 = /* @__PURE__ */ _parse$1(ZodRealError$1);
3851
3851
  const parseAsync$1 = /* @__PURE__ */ _parseAsync$1(ZodRealError$1);
3852
3852
  const safeParse$3 = /* @__PURE__ */ _safeParse$1(ZodRealError$1);
3853
3853
  const safeParseAsync$3 = /* @__PURE__ */ _safeParseAsync$1(ZodRealError$1);
3854
- const encode$2 = /* @__PURE__ */ _encode$1(ZodRealError$1);
3854
+ const encode$3 = /* @__PURE__ */ _encode$2(ZodRealError$1);
3855
3855
  const decode$3 = /* @__PURE__ */ _decode$1(ZodRealError$1);
3856
3856
  const encodeAsync$1 = /* @__PURE__ */ _encodeAsync$1(ZodRealError$1);
3857
3857
  const decodeAsync$1 = /* @__PURE__ */ _decodeAsync$1(ZodRealError$1);
@@ -3913,7 +3913,7 @@ const ZodType$2 = /*@__PURE__*/ $constructor$1("ZodType", (inst, def) => {
3913
3913
  inst.parseAsync = async (data, params) => parseAsync$1(inst, data, params, { callee: inst.parseAsync });
3914
3914
  inst.safeParseAsync = async (data, params) => safeParseAsync$3(inst, data, params);
3915
3915
  inst.spa = inst.safeParseAsync;
3916
- inst.encode = (data, params) => encode$2(inst, data, params);
3916
+ inst.encode = (data, params) => encode$3(inst, data, params);
3917
3917
  inst.decode = (data, params) => decode$3(inst, data, params);
3918
3918
  inst.encodeAsync = async (data, params) => encodeAsync$1(inst, data, params);
3919
3919
  inst.decodeAsync = async (data, params) => decodeAsync$1(inst, data, params);
@@ -4418,7 +4418,7 @@ function strictObject$1(shape, params) {
4418
4418
  ...normalizeParams$1(params)
4419
4419
  });
4420
4420
  }
4421
- function looseObject(shape, params) {
4421
+ function looseObject$1(shape, params) {
4422
4422
  return new ZodObject$2({
4423
4423
  type: "object",
4424
4424
  shape,
@@ -4788,7 +4788,7 @@ function normalizedKey(event) {
4788
4788
 
4789
4789
  //#endregion
4790
4790
  //#region package.json
4791
- var version$1 = "0.5.23";
4791
+ var version$1 = "0.5.25";
4792
4792
 
4793
4793
  //#endregion
4794
4794
  //#region sentry.ts
@@ -4969,6 +4969,16 @@ function createGuestMemoryProbe(readMetrics) {
4969
4969
  function isNodeError(error) {
4970
4970
  return error instanceof Error && "code" in error;
4971
4971
  }
4972
+ function createSandboxDisposer(input) {
4973
+ let disposePromise = null;
4974
+ return () => {
4975
+ if (disposePromise === null) {
4976
+ input.stopMemoryProbe();
4977
+ disposePromise = input.stopSandbox();
4978
+ }
4979
+ return disposePromise;
4980
+ };
4981
+ }
4972
4982
  async function ensureHostFilesystemReady(bindMounts) {
4973
4983
  for (const mount of bindMounts) {
4974
4984
  if (mount.hostPathBehavior === "createDir") {
@@ -5008,8 +5018,13 @@ async function bootSandbox(microsandboxLib, config) {
5008
5018
  });
5009
5019
  builder = builder.network((nb) => nb.policy(microsandboxLib.NetworkPolicy.allowAll()));
5010
5020
  const sandbox = await builder.create();
5021
+ const guestMemory = createGuestMemoryProbe(() => sandbox.metrics());
5011
5022
  return {
5012
- guestMemory: createGuestMemoryProbe(() => sandbox.metrics()),
5023
+ dispose: createSandboxDisposer({
5024
+ stopMemoryProbe: guestMemory.stop,
5025
+ stopSandbox: () => sandbox.stopWithTimeout(SANDBOX_STOP_TIMEOUT_MS)
5026
+ }),
5027
+ guestMemory,
5013
5028
  sandbox
5014
5029
  };
5015
5030
  }
@@ -5118,7 +5133,7 @@ function guestGitEnv(input) {
5118
5133
  name: input.sub
5119
5134
  }), entries);
5120
5135
  }
5121
- const execFileAsync = promisify(execFile);
5136
+ const execFileAsync$1 = promisify(execFile);
5122
5137
  const CAPTURE_AUTHOR_NAME = "contextlayer";
5123
5138
  const CAPTURE_AUTHOR_EMAIL = "noreply@usecontextlayer.com";
5124
5139
  const BRANCH = "main";
@@ -5281,7 +5296,7 @@ async function runGit(args, options = {}) {
5281
5296
  })
5282
5297
  };
5283
5298
  try {
5284
- const { stdout, stderr } = await execFileAsync("git", args, {
5299
+ const { stdout, stderr } = await execFileAsync$1("git", args, {
5285
5300
  cwd: options.cwd,
5286
5301
  env
5287
5302
  });
@@ -6439,7 +6454,7 @@ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
6439
6454
  };
6440
6455
  };
6441
6456
  const safeParseAsync$1 = /* @__PURE__*/ _safeParseAsync($ZodRealError);
6442
- const _encode = (_Err) => (schema, value, _ctx) => {
6457
+ const _encode$1 = (_Err) => (schema, value, _ctx) => {
6443
6458
  const ctx = _ctx ? {
6444
6459
  ..._ctx,
6445
6460
  direction: "backward"
@@ -9025,7 +9040,7 @@ const parse$2 = /* @__PURE__ */ _parse(ZodRealError);
9025
9040
  const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
9026
9041
  const safeParse$2 = /* @__PURE__ */ _safeParse(ZodRealError);
9027
9042
  const safeParseAsync$2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
9028
- const encode$1 = /* @__PURE__ */ _encode(ZodRealError);
9043
+ const encode$2 = /* @__PURE__ */ _encode$1(ZodRealError);
9029
9044
  const decode$2 = /* @__PURE__ */ _decode(ZodRealError);
9030
9045
  const encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);
9031
9046
  const decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);
@@ -9084,7 +9099,7 @@ const ZodType$1 = /*@__PURE__*/ $constructor("ZodType", (inst, def) => {
9084
9099
  inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
9085
9100
  inst.safeParseAsync = async (data, params) => safeParseAsync$2(inst, data, params);
9086
9101
  inst.spa = inst.safeParseAsync;
9087
- inst.encode = (data, params) => encode$1(inst, data, params);
9102
+ inst.encode = (data, params) => encode$2(inst, data, params);
9088
9103
  inst.decode = (data, params) => decode$2(inst, data, params);
9089
9104
  inst.encodeAsync = async (data, params) => encodeAsync(inst, data, params);
9090
9105
  inst.decodeAsync = async (data, params) => decodeAsync(inst, data, params);
@@ -9499,6 +9514,14 @@ function strictObject(shape, params) {
9499
9514
  ...normalizeParams(params)
9500
9515
  });
9501
9516
  }
9517
+ function looseObject(shape, params) {
9518
+ return new ZodObject$1({
9519
+ type: "object",
9520
+ shape,
9521
+ catchall: unknown$2(),
9522
+ ...normalizeParams(params)
9523
+ });
9524
+ }
9502
9525
  const ZodUnion$1 = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
9503
9526
  $ZodUnion.init(inst, def);
9504
9527
  ZodType$1.init(inst, def);
@@ -9831,7 +9854,7 @@ function superRefine(fn, params) {
9831
9854
  freshness: {
9832
9855
  props: {
9833
9856
  text: { $concat: ["Updated ", { $relativeDay: {
9834
- now: { $state: "/now" },
9857
+ today: { $state: "/today" },
9835
9858
  value: { $maxBy: {
9836
9859
  field: "last_updated",
9837
9860
  items: { $state: "/items" }
@@ -9938,6 +9961,11 @@ const sessionMetaSchema$1 = object$3({
9938
9961
  sessionId: string$4()
9939
9962
  });
9940
9963
  record$1(string$4(), sessionMetaSchema$1);
9964
+ looseObject({
9965
+ timestamp: string$4().optional(),
9966
+ type: string$4(),
9967
+ uuid: string$4().optional()
9968
+ });
9941
9969
  async function ensureWorkspaceCheckout(workspaceId, ctx) {
9942
9970
  const repo = await resolveRepo({
9943
9971
  platformUrl: ctx.platformUrl,
@@ -13300,7 +13328,7 @@ function concat(...buffers) {
13300
13328
  }
13301
13329
  return buf;
13302
13330
  }
13303
- function encode(string) {
13331
+ function encode$1(string) {
13304
13332
  const bytes = new Uint8Array(string.length);
13305
13333
  for (let i = 0; i < string.length; i++) {
13306
13334
  const code = string.charCodeAt(i);
@@ -14021,7 +14049,7 @@ async function flattenedVerify(jws, key, options) {
14021
14049
  resolvedKey = true;
14022
14050
  }
14023
14051
  checkKeyType(alg, key, "verify");
14024
- const data = concat(jws.protected !== void 0 ? encode(jws.protected) : new Uint8Array(), encode("."), typeof jws.payload === "string" ? b64 ? encode(jws.payload) : encoder.encode(jws.payload) : jws.payload);
14052
+ const data = concat(jws.protected !== void 0 ? encode$1(jws.protected) : new Uint8Array(), encode$1("."), typeof jws.payload === "string" ? b64 ? encode$1(jws.payload) : encoder.encode(jws.payload) : jws.payload);
14025
14053
  const signature = decodeBase64url(jws.signature, "signature", JWSInvalid);
14026
14054
  const k = await normalizeKey$1(key, alg);
14027
14055
  if (!await verify(alg, k, signature, data)) throw new JWSSignatureVerificationFailed();
@@ -14636,6 +14664,99 @@ function createDevJwtVerifier() {
14636
14664
  };
14637
14665
  }
14638
14666
 
14667
+ //#endregion
14668
+ //#region ../../node_modules/.pnpm/js-base64@3.9.1/node_modules/js-base64/base64.mjs
14669
+ const _TD = typeof TextDecoder === "function" ? new TextDecoder("utf-8", { ignoreBOM: true }) : void 0;
14670
+ const _TE = typeof TextEncoder === "function" ? new TextEncoder() : void 0;
14671
+ const b64chs = Array.prototype.slice.call("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
14672
+ const b64tab = ((a) => {
14673
+ let tab = {};
14674
+ a.forEach((c, i) => tab[c] = i);
14675
+ return tab;
14676
+ })(b64chs);
14677
+ const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
14678
+ const _fromCC = String.fromCharCode.bind(String);
14679
+ const _U8Afrom = typeof Uint8Array.from === "function" ? Uint8Array.from.bind(Uint8Array) : (it) => new Uint8Array(Array.prototype.slice.call(it, 0));
14680
+ const _mkUriSafe = (src) => src.replace(/=/g, "").replace(/[+\/]/g, (m0) => m0 == "+" ? "-" : "_");
14681
+ const _tidyB64 = (s) => s.replace(/[^A-Za-z0-9\+\/]/g, "");
14682
+ /**
14683
+ * polyfill version of `btoa`
14684
+ */
14685
+ const btoaPolyfill = (bin) => {
14686
+ let u32, c0, c1, c2, asc = "";
14687
+ const pad = bin.length % 3;
14688
+ for (let i = 0; i < bin.length;) {
14689
+ if ((c0 = bin.charCodeAt(i++)) > 255 || (c1 = bin.charCodeAt(i++)) > 255 || (c2 = bin.charCodeAt(i++)) > 255) throw new TypeError("invalid character found");
14690
+ u32 = c0 << 16 | c1 << 8 | c2;
14691
+ asc += b64chs[u32 >> 18 & 63] + b64chs[u32 >> 12 & 63] + b64chs[u32 >> 6 & 63] + b64chs[u32 & 63];
14692
+ }
14693
+ return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc;
14694
+ };
14695
+ /**
14696
+ * does what `window.btoa` of web browsers do.
14697
+ * @param {String} bin binary string
14698
+ * @returns {string} Base64-encoded string
14699
+ */
14700
+ const _btoa = typeof btoa === "function" ? (bin) => btoa(bin) : btoaPolyfill;
14701
+ const _fromUint8Array = typeof Uint8Array.prototype.toBase64 === "function" ? (u8a) => u8a.toBase64() : (u8a) => {
14702
+ const maxargs = 4096;
14703
+ let strs = [];
14704
+ for (let i = 0, l = u8a.length; i < l; i += maxargs) strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs)));
14705
+ return _btoa(strs.join(""));
14706
+ };
14707
+ const cb_utob = (c) => {
14708
+ if (c.length < 2) {
14709
+ var cc = c.charCodeAt(0);
14710
+ return cc < 128 ? c : cc < 2048 ? _fromCC(192 | cc >>> 6) + _fromCC(128 | cc & 63) : _fromCC(224 | cc >>> 12 & 15) + _fromCC(128 | cc >>> 6 & 63) + _fromCC(128 | cc & 63);
14711
+ } else {
14712
+ var cc = 65536 + (c.charCodeAt(0) - 55296) * 1024 + (c.charCodeAt(1) - 56320);
14713
+ return _fromCC(240 | cc >>> 18 & 7) + _fromCC(128 | cc >>> 12 & 63) + _fromCC(128 | cc >>> 6 & 63) + _fromCC(128 | cc & 63);
14714
+ }
14715
+ };
14716
+ const re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
14717
+ /**
14718
+ * @deprecated should have been internal use only.
14719
+ * @param {string} src UTF-8 string
14720
+ * @returns {string} UTF-16 string
14721
+ */
14722
+ const utob = (u) => u.replace(re_utob, cb_utob);
14723
+ const _encode = _TE ? (s) => _fromUint8Array(_TE.encode(s)) : (s) => _btoa(utob(s));
14724
+ /**
14725
+ * converts a UTF-8-encoded string to a Base64 string.
14726
+ * @param {boolean} [urlsafe] if `true` make the result URL-safe
14727
+ * @returns {string} Base64 string
14728
+ */
14729
+ const encode = (src, urlsafe = false) => urlsafe ? _mkUriSafe(_encode(src)) : _encode(src);
14730
+ /**
14731
+ * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5.
14732
+ * @returns {string} Base64 string
14733
+ */
14734
+ const encodeURI$1 = (src) => encode(src, true);
14735
+ /**
14736
+ * polyfill version of `atob`
14737
+ */
14738
+ const atobPolyfill = (asc) => {
14739
+ asc = asc.replace(/\s+/g, "");
14740
+ if (!b64re.test(asc)) throw new TypeError("malformed base64.");
14741
+ asc += "==".slice(2 - (asc.length & 3));
14742
+ let u24, r1, r2;
14743
+ let binArray = [];
14744
+ for (let i = 0; i < asc.length;) {
14745
+ u24 = b64tab[asc.charAt(i++)] << 18 | b64tab[asc.charAt(i++)] << 12 | (r1 = b64tab[asc.charAt(i++)]) << 6 | (r2 = b64tab[asc.charAt(i++)]);
14746
+ if (r1 === 64) binArray.push(_fromCC(u24 >> 16 & 255));
14747
+ else if (r2 === 64) binArray.push(_fromCC(u24 >> 16 & 255, u24 >> 8 & 255));
14748
+ else binArray.push(_fromCC(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255));
14749
+ }
14750
+ return binArray.join("");
14751
+ };
14752
+ /**
14753
+ * does what `window.atob` of web browsers do.
14754
+ * @param {String} asc Base64-encoded string
14755
+ * @returns {string} binary string
14756
+ */
14757
+ const _atob = typeof atob === "function" ? (asc) => atob(_tidyB64(asc)) : atobPolyfill;
14758
+ const _toUint8Array = typeof Uint8Array.fromBase64 === "function" ? (a) => Uint8Array.fromBase64(a) : (a) => _U8Afrom(_atob(a).split("").map((c) => c.charCodeAt(0)));
14759
+
14639
14760
  //#endregion
14640
14761
  //#region ../slate-shared/dist/index.mjs
14641
14762
  const INBOX_DOCS_TREE = {
@@ -14781,6 +14902,8 @@ export default async ({ docs }) => {
14781
14902
  },
14782
14903
  { sort },
14783
14904
  )
14905
+ // This neutral dev fixture has no customer timezone, so its calendar day is UTC.
14906
+ const today = new Date().toISOString().slice(0, 10)
14784
14907
  const items = rows.map((u) => {
14785
14908
  const proposedTitle = u.proposed_title ?? ""
14786
14909
  const separatorIndex = proposedTitle.indexOf(" - ")
@@ -14798,7 +14921,7 @@ export default async ({ docs }) => {
14798
14921
  : proposedTitle || u.path,
14799
14922
  }
14800
14923
  })
14801
- return { items }
14924
+ return { items, today }
14802
14925
  }
14803
14926
  `,
14804
14927
  "docs/views/inbox.render-spec.json": `${JSON.stringify({
@@ -14808,7 +14931,7 @@ export default async ({ docs }) => {
14808
14931
  freshness: {
14809
14932
  props: {
14810
14933
  text: { $concat: ["Updated ", { $relativeDay: {
14811
- now: { $state: "/now" },
14934
+ today: { $state: "/today" },
14812
14935
  value: { $maxBy: {
14813
14936
  field: "last_updated",
14814
14937
  items: { $state: "/items" }
@@ -14892,7 +15015,24 @@ export default async ({ docs }) => {
14892
15015
  }
14893
15016
  }, null, " ")}\n`
14894
15017
  };
15018
+ const DEV_IDENTITY = {
15019
+ email: "joeblack@gmail.com",
15020
+ name: "Joe Black",
15021
+ sub: "user-dev"
15022
+ };
14895
15023
  const docPathSchema = string$5().min(1, "a doc path must be non-empty").refine((docPath) => !docPath.startsWith("/"), { message: "a doc path must be root-relative, not absolute" }).refine((docPath) => !docPath.split("/").includes(".."), { message: "a doc path must not contain a '..' segment" });
15024
+ function encodeBase64UrlJson(value) {
15025
+ return encodeURI$1(JSON.stringify(value));
15026
+ }
15027
+ function mintUnsignedCtxToken(sub) {
15028
+ return `${encodeBase64UrlJson({
15029
+ alg: "none",
15030
+ typ: "JWT"
15031
+ })}.${encodeBase64UrlJson({
15032
+ aud: "ctx",
15033
+ sub
15034
+ })}.`;
15035
+ }
14896
15036
  const renderSpecSchema = strictObject$1({
14897
15037
  params: record$2(string$5().min(1), strictObject$1({ default: string$5().min(1) })).optional().describe("URL search params this surface accepts, each with its default. Resolution is `URL value ?? default`; the default is stripped back out of the URL, and an undeclared URL key is ignored. What a value MEANS — which are legal, what each expands to — is the loader's job, in JS."),
14898
15038
  render: unknown$3().describe("The render tree — a @json-render Spec. Its elements name the components and directives published under `components` and `directives` in this artifact, and those contracts govern what each element may carry. This envelope does not constrain the tree's contents.")
@@ -14917,6 +15057,40 @@ const sessionMetaSchema = object$4({
14917
15057
  sessionId: string$5()
14918
15058
  });
14919
15059
  const claudeIndexSchema = record$2(string$5(), sessionMetaSchema);
15060
+ const sessionStoreEntrySchema = looseObject$1({
15061
+ timestamp: string$5().optional(),
15062
+ type: string$5(),
15063
+ uuid: string$5().optional()
15064
+ });
15065
+ function parseSessionStoreJsonl(jsonl, malformedEntryPolicy) {
15066
+ const entries = [];
15067
+ for (const [index, line] of jsonl.split("\n").entries()) {
15068
+ if (line.trim() === "") continue;
15069
+ try {
15070
+ const parsed = JSON.parse(line);
15071
+ entries.push(malformedEntryPolicy === "throw" ? sessionStoreEntrySchema.parse(parsed) : parsed);
15072
+ } catch (cause) {
15073
+ if (malformedEntryPolicy === "skip") continue;
15074
+ throw new Error(`invalid session transcript entry at line ${index + 1}`, { cause });
15075
+ }
15076
+ }
15077
+ return entries;
15078
+ }
15079
+ function singleSessionStore(sessionId, entries) {
15080
+ return {
15081
+ append: () => {
15082
+ throw new Error("singleSessionStore is read-only; append() is not supported");
15083
+ },
15084
+ listSessions: async () => [{
15085
+ mtime: 0,
15086
+ sessionId
15087
+ }],
15088
+ load: async (key) => {
15089
+ if (key.sessionId !== sessionId || key.subpath) return null;
15090
+ return entries;
15091
+ }
15092
+ };
15093
+ }
14920
15094
 
14921
15095
  //#endregion
14922
15096
  //#region ../../node_modules/.pnpm/undici@8.8.0/node_modules/undici/lib/core/symbols.js
@@ -40450,7 +40624,7 @@ const SafeUrlSchema = url().superRefine((val, ctx) => {
40450
40624
  /**
40451
40625
  * RFC 9728 OAuth Protected Resource Metadata
40452
40626
  */
40453
- const OAuthProtectedResourceMetadataSchema = looseObject({
40627
+ const OAuthProtectedResourceMetadataSchema = looseObject$1({
40454
40628
  resource: string$5().url(),
40455
40629
  authorization_servers: array$1(SafeUrlSchema).optional(),
40456
40630
  jwks_uri: string$5().url().optional(),
@@ -40469,7 +40643,7 @@ const OAuthProtectedResourceMetadataSchema = looseObject({
40469
40643
  /**
40470
40644
  * RFC 8414 OAuth 2.0 Authorization Server Metadata
40471
40645
  */
40472
- const OAuthMetadataSchema = looseObject({
40646
+ const OAuthMetadataSchema = looseObject$1({
40473
40647
  issuer: string$5(),
40474
40648
  authorization_endpoint: SafeUrlSchema,
40475
40649
  token_endpoint: SafeUrlSchema,
@@ -40494,7 +40668,7 @@ const OAuthMetadataSchema = looseObject({
40494
40668
  * OpenID Connect Discovery 1.0 Provider Metadata
40495
40669
  * see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
40496
40670
  */
40497
- const OpenIdProviderMetadataSchema = looseObject({
40671
+ const OpenIdProviderMetadataSchema = looseObject$1({
40498
40672
  issuer: string$5(),
40499
40673
  authorization_endpoint: SafeUrlSchema,
40500
40674
  token_endpoint: SafeUrlSchema,
@@ -40729,7 +40903,7 @@ const CursorSchema = string$5();
40729
40903
  /**
40730
40904
  * Task creation parameters, used to ask that the server create a task to represent a request.
40731
40905
  */
40732
- const TaskCreationParamsSchema = looseObject({
40906
+ const TaskCreationParamsSchema = looseObject$1({
40733
40907
  /**
40734
40908
  * Requested duration in milliseconds to retain task from creation.
40735
40909
  */
@@ -40745,7 +40919,7 @@ const TaskMetadataSchema = object$4({ ttl: number$4().optional() });
40745
40919
  * Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.
40746
40920
  */
40747
40921
  const RelatedTaskMetadataSchema = object$4({ taskId: string$5() });
40748
- const RequestMetaSchema = looseObject({
40922
+ const RequestMetaSchema = looseObject$1({
40749
40923
  /**
40750
40924
  * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.
40751
40925
  */
@@ -40797,7 +40971,7 @@ const NotificationSchema = object$4({
40797
40971
  method: string$5(),
40798
40972
  params: NotificationsParamsSchema.loose().optional()
40799
40973
  });
40800
- const ResultSchema = looseObject({
40974
+ const ResultSchema = looseObject$1({
40801
40975
  /**
40802
40976
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
40803
40977
  * for notes on _meta usage.
@@ -41011,7 +41185,7 @@ const ElicitationCapabilitySchema = preprocess$1((value) => {
41011
41185
  /**
41012
41186
  * Task capabilities for clients, indicating which request types support task creation.
41013
41187
  */
41014
- const ClientTasksCapabilitySchema = looseObject({
41188
+ const ClientTasksCapabilitySchema = looseObject$1({
41015
41189
  /**
41016
41190
  * Present if the client supports listing tasks.
41017
41191
  */
@@ -41023,21 +41197,21 @@ const ClientTasksCapabilitySchema = looseObject({
41023
41197
  /**
41024
41198
  * Capabilities for task creation on specific request types.
41025
41199
  */
41026
- requests: looseObject({
41200
+ requests: looseObject$1({
41027
41201
  /**
41028
41202
  * Task support for sampling requests.
41029
41203
  */
41030
- sampling: looseObject({ createMessage: AssertObjectSchema.optional() }).optional(),
41204
+ sampling: looseObject$1({ createMessage: AssertObjectSchema.optional() }).optional(),
41031
41205
  /**
41032
41206
  * Task support for elicitation requests.
41033
41207
  */
41034
- elicitation: looseObject({ create: AssertObjectSchema.optional() }).optional()
41208
+ elicitation: looseObject$1({ create: AssertObjectSchema.optional() }).optional()
41035
41209
  }).optional()
41036
41210
  });
41037
41211
  /**
41038
41212
  * Task capabilities for servers, indicating which request types support task creation.
41039
41213
  */
41040
- const ServerTasksCapabilitySchema = looseObject({
41214
+ const ServerTasksCapabilitySchema = looseObject$1({
41041
41215
  /**
41042
41216
  * Present if the server supports listing tasks.
41043
41217
  */
@@ -41049,11 +41223,11 @@ const ServerTasksCapabilitySchema = looseObject({
41049
41223
  /**
41050
41224
  * Capabilities for task creation on specific request types.
41051
41225
  */
41052
- requests: looseObject({
41226
+ requests: looseObject$1({
41053
41227
  /**
41054
41228
  * Task support for tool requests.
41055
41229
  */
41056
- tools: looseObject({ call: AssertObjectSchema.optional() }).optional() }).optional()
41230
+ tools: looseObject$1({ call: AssertObjectSchema.optional() }).optional() }).optional()
41057
41231
  });
41058
41232
  /**
41059
41233
  * Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.
@@ -41434,7 +41608,7 @@ const ResourceSchema = object$4({
41434
41608
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
41435
41609
  * for notes on _meta usage.
41436
41610
  */
41437
- _meta: optional$3(looseObject({}))
41611
+ _meta: optional$3(looseObject$1({}))
41438
41612
  });
41439
41613
  /**
41440
41614
  * A template description for resources available on the server.
@@ -41464,7 +41638,7 @@ const ResourceTemplateSchema = object$4({
41464
41638
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
41465
41639
  * for notes on _meta usage.
41466
41640
  */
41467
- _meta: optional$3(looseObject({}))
41641
+ _meta: optional$3(looseObject$1({}))
41468
41642
  });
41469
41643
  /**
41470
41644
  * Sent from the client to request a list of resources the server has.
@@ -41577,7 +41751,7 @@ const PromptSchema = object$4({
41577
41751
  * See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
41578
41752
  * for notes on _meta usage.
41579
41753
  */
41580
- _meta: optional$3(looseObject({}))
41754
+ _meta: optional$3(looseObject$1({}))
41581
41755
  });
41582
41756
  /**
41583
41757
  * Sent from the client to request a list of prompts and prompt templates the server has.
@@ -42513,7 +42687,7 @@ function assertCompleteRequestResourceTemplate(request) {
42513
42687
  /**
42514
42688
  * The server's response to a completion/complete request
42515
42689
  */
42516
- const CompleteResultSchema = ResultSchema.extend({ completion: looseObject({
42690
+ const CompleteResultSchema = ResultSchema.extend({ completion: looseObject$1({
42517
42691
  /**
42518
42692
  * An array of completion values. Must not exceed 100 items.
42519
42693
  */
@@ -58964,7 +59138,7 @@ function createScheduleApiClient(config) {
58964
59138
  })
58965
59139
  };
58966
59140
  }
58967
- const SERVER_VERSION = "0.5.23";
59141
+ const SERVER_VERSION = "0.5.25";
58968
59142
  /** The most operations one OperationSet will carry. The service's own ceiling. */
58969
59143
  const MAX_OPERATIONS = 200;
58970
59144
  /**
@@ -95483,6 +95657,9 @@ function stemOf(relPath) {
95483
95657
  function isMarkdownPath(relPath) {
95484
95658
  return normalizeRelativePath(relPath).endsWith(".md");
95485
95659
  }
95660
+ function isVisibleIndexPath(relPath) {
95661
+ return normalizeRelativePath(relPath).split("/").every((segment) => !segment.startsWith("."));
95662
+ }
95486
95663
  function escapesIndexRoot(target) {
95487
95664
  return normalizedPathEscapesRoot(normalizeAuthoredPath(target));
95488
95665
  }
@@ -95559,26 +95736,6 @@ function unique(values) {
95559
95736
  function uniqueSorted(values) {
95560
95737
  return unique(values).sort(comparePath);
95561
95738
  }
95562
- function computeBacklinks(docs) {
95563
- const backlinks = /* @__PURE__ */ new Map();
95564
- for (const doc of docs) backlinks.set(normalizeLinkTarget(doc.path), backlinks.get(normalizeLinkTarget(doc.path)) ?? []);
95565
- for (const doc of docs) {
95566
- const targets = new Set([...doc.outgoingLinks, ...Object.values(doc.relationships).flat()]);
95567
- for (const target of targets) {
95568
- const normalized = normalizeLinkTarget(target);
95569
- addBacklink(backlinks, normalized, doc.path);
95570
- const resolved = resolveWikilink(normalized, docs);
95571
- if (resolved) addBacklink(backlinks, normalizeLinkTarget(resolved.path), doc.path);
95572
- }
95573
- }
95574
- for (const [target, sources] of backlinks) backlinks.set(target, sources.sort(comparePath));
95575
- return backlinks;
95576
- }
95577
- function addBacklink(backlinks, target, source) {
95578
- const sources = backlinks.get(target) ?? [];
95579
- if (!sources.includes(source)) sources.push(source);
95580
- backlinks.set(target, sources);
95581
- }
95582
95739
  const MDAST_OPTIONS = {
95583
95740
  extensions: [frontmatter()],
95584
95741
  mdastExtensions: [frontmatterFromMarkdown()]
@@ -95662,6 +95819,26 @@ function extractRelationships(frontmatter) {
95662
95819
  }
95663
95820
  return relationships;
95664
95821
  }
95822
+ function computeBacklinks(docs) {
95823
+ const backlinks = /* @__PURE__ */ new Map();
95824
+ for (const doc of docs) backlinks.set(normalizeLinkTarget(doc.path), backlinks.get(normalizeLinkTarget(doc.path)) ?? []);
95825
+ for (const doc of docs) {
95826
+ const targets = new Set([...doc.outgoingLinks, ...Object.values(doc.relationships).flat()]);
95827
+ for (const target of targets) {
95828
+ const normalized = normalizeLinkTarget(target);
95829
+ addBacklink(backlinks, normalized, doc.path);
95830
+ const resolved = resolveWikilink(normalized, docs);
95831
+ if (resolved) addBacklink(backlinks, normalizeLinkTarget(resolved.path), doc.path);
95832
+ }
95833
+ }
95834
+ for (const [target, sources] of backlinks) backlinks.set(target, sources.sort(comparePath));
95835
+ return backlinks;
95836
+ }
95837
+ function addBacklink(backlinks, target, source) {
95838
+ const sources = backlinks.get(target) ?? [];
95839
+ if (!sources.includes(source)) sources.push(source);
95840
+ backlinks.set(target, sources);
95841
+ }
95665
95842
  const RESERVED = new Set([
95666
95843
  "type",
95667
95844
  "title",
@@ -95964,7 +96141,7 @@ var Docs = class {
95964
96141
  const dirents = await this.fs.readdir(absDir, { withFileTypes: true });
95965
96142
  for (const dirent of dirents) {
95966
96143
  const relPath = normalizeRelativePath(posixJoin(relDir, dirent.name));
95967
- if (dirent.name.startsWith(".")) continue;
96144
+ if (!isVisibleIndexPath(relPath)) continue;
95968
96145
  if (dirent.isDirectory()) {
95969
96146
  const child = await this.walk(this.abs(relPath), relPath);
95970
96147
  markdownFiles.push(...child.markdownFiles);
@@ -97934,7 +98111,82 @@ function osUsername() {
97934
98111
  }
97935
98112
 
97936
98113
  //#endregion
97937
- //#region ../slate-bridge/dist/goldens-CkMPmJl8.mjs
98114
+ //#region ../db-infra/dist/postgres-DrlDW_Ri.mjs
98115
+ const env$2 = { NODE_ENV: process.env.NODE_ENV };
98116
+
98117
+ //#endregion
98118
+ //#region ../db-infra/dist/ephemeral.mjs
98119
+ const PG_HOST = "postgres://postgres:postgres@127.0.0.1:6489";
98120
+ const ADMIN_URL = `${PG_HOST}/postgres`;
98121
+ async function createEphemeralDatabase(baseName) {
98122
+ const name = `${baseName}_${randomBytes(4).toString("hex")}`;
98123
+ const admin = src_default(ADMIN_URL, { max: 1 });
98124
+ try {
98125
+ await admin.unsafe(`CREATE DATABASE ${name}`);
98126
+ } finally {
98127
+ await admin.end();
98128
+ }
98129
+ return {
98130
+ async drop() {
98131
+ const admin2 = src_default(ADMIN_URL, { max: 1 });
98132
+ try {
98133
+ await admin2`SELECT pg_terminate_backend(pid) FROM pg_stat_activity
98134
+ WHERE datname = ${name} AND pid <> pg_backend_pid()`;
98135
+ await admin2.unsafe(`DROP DATABASE IF EXISTS ${name}`);
98136
+ } finally {
98137
+ await admin2.end();
98138
+ }
98139
+ },
98140
+ url: `${PG_HOST}/${name}`
98141
+ };
98142
+ }
98143
+ function isPerKeyMode(opts) {
98144
+ return opts.migrators !== void 0;
98145
+ }
98146
+ /**
98147
+ * A composition's whole database dance in one call: CREATE a fresh database per
98148
+ * base name, run the migrate step, and hand back the urls + one `dropAll`. If
98149
+ * ANY create or migrate fails, every database already created is dropped before
98150
+ * the error rethrows — a failed boot must not leak `ctx_*_<hex8>` litter.
98151
+ */
98152
+ async function provisionEphemeralDatabases(opts) {
98153
+ let baseNames;
98154
+ let migrate;
98155
+ if (isPerKeyMode(opts)) {
98156
+ const { migrators } = opts;
98157
+ baseNames = Object.keys(migrators);
98158
+ migrate = async (urls) => {
98159
+ await Promise.all(Object.keys(migrators).map((baseName) => migrators[baseName](urls[baseName])));
98160
+ };
98161
+ } else ({migrate, baseNames} = opts);
98162
+ const settled = await Promise.allSettled(baseNames.map(async (baseName) => ({
98163
+ baseName,
98164
+ db: await createEphemeralDatabase(baseName)
98165
+ })));
98166
+ const created = settled.filter((r) => r.status === "fulfilled").map((r) => r.value);
98167
+ const dropAll = async () => {
98168
+ await Promise.all(created.map(({ db }) => db.drop()));
98169
+ };
98170
+ const failed = settled.find((r) => r.status === "rejected");
98171
+ if (failed) {
98172
+ await dropAll();
98173
+ throw failed.reason;
98174
+ }
98175
+ const urls = Object.fromEntries(created.map(({ baseName, db }) => [baseName, db.url]));
98176
+ try {
98177
+ await migrate(urls);
98178
+ } catch (err) {
98179
+ await dropAll();
98180
+ throw err;
98181
+ }
98182
+ return {
98183
+ dropAll,
98184
+ urls
98185
+ };
98186
+ }
98187
+
98188
+ //#endregion
98189
+ //#region ../slate-bridge/dist/platform-harness-CL-GGo8J.mjs
97938
98190
  const nonEmptyStringSchema = string$5().trim().min(1);
97939
98191
  const portSchema = number$3().int().min(0).max(65535).default(7777);
97940
98192
  const envSchema = object$4({
@@ -97943,7 +98195,7 @@ const envSchema = object$4({
97943
98195
  CTX_PLATFORM_URL: url().default("http://127.0.0.1:3010").transform((url) => url.replace(/\/+$/, "")),
97944
98196
  CTX_WEB_URL: url().default("http://localhost:3000").transform((url) => url.replace(/\/+$/, "")),
97945
98197
  CTXS_BRIDGE_PORT: portSchema,
97946
- CTXS_CLAUDE_IMAGE: nonEmptyStringSchema.default("ghcr.io/usecontextlayer/ctx-sandbox:0.5.23"),
98198
+ CTXS_CLAUDE_IMAGE: nonEmptyStringSchema.default("ghcr.io/usecontextlayer/ctx-sandbox:0.5.25"),
97947
98199
  CTXS_CLAUDE_MODEL: nonEmptyStringSchema.default("opus"),
97948
98200
  CTXS_HOST_CLAUDE_JSON_PATH: nonEmptyStringSchema.default(() => path.join(os.homedir(), ".claude.json")),
97949
98201
  NODE_ENV: _enum$1([
@@ -97982,7 +98234,6 @@ Do this bookkeeping quietly. Don't narrate routine git operations — mention gi
97982
98234
  The workspace's synced source data — email, meetings, tasks, whatever the user connected — lives in a Postgres database called the base. Query it with the \`ctxb\` CLI, following the contextbase skill. \`CTXB_DATABASE_URL\` is already set when this workspace has a base; if it is unset, no base is connected. Answer data questions from the base, not from memory.`;
97983
98235
  const BUILTIN_ALLOWED_TOOLS = [
97984
98236
  "Agent",
97985
- "AskUserQuestion",
97986
98237
  "Bash",
97987
98238
  "BashOutput",
97988
98239
  "Edit",
@@ -98004,6 +98255,7 @@ const BUILTIN_ALLOWED_TOOLS = [
98004
98255
  "WebSearch",
98005
98256
  "Write"
98006
98257
  ];
98258
+ const BUILTIN_DISALLOWED_TOOLS = ["AskUserQuestion"];
98007
98259
  const CLAUDE_ARGS = [
98008
98260
  "--input-format",
98009
98261
  "stream-json",
@@ -98029,12 +98281,13 @@ function resolveChatBaseId(baseIds, workspaceId) {
98029
98281
  }
98030
98282
  function buildChatSpawnPlan(input) {
98031
98283
  const { allowedTools, disallowedTools } = input.mcpToolRules;
98284
+ const deniedTools = [...BUILTIN_DISALLOWED_TOOLS, ...disallowedTools];
98032
98285
  return {
98033
98286
  args: [
98034
98287
  ...CLAUDE_ARGS,
98035
98288
  "--allowedTools",
98036
98289
  [...BUILTIN_ALLOWED_TOOLS, ...allowedTools].join(","),
98037
- ...disallowedTools.length > 0 ? ["--disallowedTools", disallowedTools.join(",")] : [],
98290
+ ...deniedTools.length > 0 ? ["--disallowedTools", deniedTools.join(",")] : [],
98038
98291
  ...input.mcpConfigArgs ?? [],
98039
98292
  "--model",
98040
98293
  input.model,
@@ -98361,16 +98614,9 @@ async function createClaudeMicrosandbox(options) {
98361
98614
  workdir: options.hostProjectDir
98362
98615
  });
98363
98616
  await ensureHostFilesystemReady(bootConfig.bindMounts);
98364
- const { guestMemory, sandbox } = await bootSandbox(await import("microsandbox"), bootConfig);
98365
- let disposePromise = null;
98617
+ const { dispose, guestMemory, sandbox } = await bootSandbox(await import("microsandbox"), bootConfig);
98366
98618
  return {
98367
- dispose: () => {
98368
- if (disposePromise === null) {
98369
- guestMemory.stop();
98370
- disposePromise = sandbox.stopWithTimeout(SANDBOX_STOP_TIMEOUT_MS);
98371
- }
98372
- return disposePromise;
98373
- },
98619
+ dispose,
98374
98620
  guestMemory: () => guestMemory.read(),
98375
98621
  spawn: (spawnOptions) => spawnClaudeProcess(sandbox, spawnOptions)
98376
98622
  };
@@ -98487,11 +98733,7 @@ function generateSandboxName() {
98487
98733
  }
98488
98734
  const VIEW_NAME = /^[\w-]+$/;
98489
98735
  const EVAL_TIMEOUT_MS = 1e4;
98490
- const RESERVED_STATE_KEYS = [
98491
- "now",
98492
- "params",
98493
- "workspaceId"
98494
- ];
98736
+ const RESERVED_STATE_KEYS = ["params", "workspaceId"];
98495
98737
  async function runWorkspaceView(opts) {
98496
98738
  if (!VIEW_NAME.test(opts.view)) throw new Error(`views/run: invalid view name "${opts.view}"`);
98497
98739
  return runSurfacePair({
@@ -99056,6 +99298,190 @@ async function createBridgeServer(opts) {
99056
99298
  port: boundPort
99057
99299
  };
99058
99300
  }
99301
+ const REPO_ROOT = path.resolve(fileURLToPath(new URL(".", import.meta.url)), "..", "..", "..");
99302
+ const EPHEMERAL_SECRETS_ENCRYPTION_KEY_HEX = "0".repeat(64);
99303
+ function platformEnv(extra) {
99304
+ const env = {
99305
+ ...process.env,
99306
+ ...extra
99307
+ };
99308
+ delete env.CTX_AUTH_SUB_ID;
99309
+ return env;
99310
+ }
99311
+ const PLATFORM_DATABASES = [
99312
+ "ctx_base_platform",
99313
+ "ctx_pggit",
99314
+ "ctx_secrets",
99315
+ "ctx_slate_platform"
99316
+ ];
99317
+ function toPlatformDatabaseEnv(urls) {
99318
+ return {
99319
+ CTX_BASE_DATABASE_URL: urls.ctx_base_platform,
99320
+ CTX_PGGIT_DATABASE_URL: urls.ctx_pggit,
99321
+ CTX_SECRETS_DATABASE_URL: urls.ctx_secrets,
99322
+ CTX_SECRETS_ENCRYPTION_KEY: EPHEMERAL_SECRETS_ENCRYPTION_KEY_HEX,
99323
+ CTX_SLATE_DATABASE_URL: urls.ctx_slate_platform
99324
+ };
99325
+ }
99326
+ async function provisionPlatformDatabases() {
99327
+ const provisioned = await provisionEphemeralDatabases({
99328
+ baseNames: PLATFORM_DATABASES,
99329
+ migrate: (urls) => migrateAll(toPlatformDatabaseEnv(urls))
99330
+ });
99331
+ return {
99332
+ databaseEnv: toPlatformDatabaseEnv(provisioned.urls),
99333
+ dropAll: provisioned.dropAll
99334
+ };
99335
+ }
99336
+ async function migrateAll(databaseEnv) {
99337
+ await runToCompletion([
99338
+ "--filter",
99339
+ "@usecontextlayer/platform",
99340
+ "run",
99341
+ "db.latest"
99342
+ ], platformEnv(databaseEnv));
99343
+ }
99344
+ function runToCompletion(args, env) {
99345
+ return new Promise((resolve, reject) => {
99346
+ const proc = spawn("pnpm", args, {
99347
+ cwd: REPO_ROOT,
99348
+ env,
99349
+ stdio: [
99350
+ "ignore",
99351
+ "pipe",
99352
+ "pipe"
99353
+ ]
99354
+ });
99355
+ let err = "";
99356
+ proc.stderr?.on("data", (c) => {
99357
+ err += c.toString("utf-8");
99358
+ });
99359
+ proc.once("error", reject);
99360
+ proc.once("exit", (code) => code === 0 ? resolve() : reject(/* @__PURE__ */ new Error(`pnpm ${args.join(" ")} exited ${code}\n${err}`)));
99361
+ });
99362
+ }
99363
+ async function bootPlatform(databaseEnv) {
99364
+ const port = randomInt(2e4, 6e4);
99365
+ const proc = spawn("pnpm", [
99366
+ "--filter",
99367
+ "@usecontextlayer/platform",
99368
+ "run",
99369
+ "start"
99370
+ ], {
99371
+ cwd: REPO_ROOT,
99372
+ detached: true,
99373
+ env: platformEnv({
99374
+ ...databaseEnv,
99375
+ CTX_AUTH_SKIP_VERIFY_JWT: "true",
99376
+ CTX_PLATFORM_PORT: String(port)
99377
+ }),
99378
+ stdio: [
99379
+ "ignore",
99380
+ "pipe",
99381
+ "pipe"
99382
+ ]
99383
+ });
99384
+ proc.stderr?.on("data", (c) => process.stderr.write(c));
99385
+ const exited = new Promise((resolve, reject) => {
99386
+ proc.once("exit", (code) => resolve(code));
99387
+ proc.once("error", reject);
99388
+ });
99389
+ const platformUrl = `http://127.0.0.1:${port}`;
99390
+ const exitedBeforeReady = exited.then((code) => {
99391
+ throw new Error(`platform exited before ready (code ${code})`);
99392
+ });
99393
+ await Promise.race([exitedBeforeReady, waitForHttpReady(`${platformUrl}/doc`, 3e4)]);
99394
+ return {
99395
+ close: async () => {
99396
+ proc.kill("SIGTERM");
99397
+ await exited;
99398
+ },
99399
+ platformUrl
99400
+ };
99401
+ }
99402
+ async function waitForHttpReady(url, timeoutMs) {
99403
+ const deadline = Date.now() + timeoutMs;
99404
+ let last;
99405
+ while (Date.now() < deadline) {
99406
+ try {
99407
+ const res = await fetch(url, { signal: AbortSignal.timeout(2e3) });
99408
+ if (res.status < 500) return;
99409
+ last = /* @__PURE__ */ new Error(`HTTP ${res.status}`);
99410
+ } catch (e) {
99411
+ last = e;
99412
+ }
99413
+ await new Promise((r) => setTimeout(r, 250));
99414
+ }
99415
+ throw new Error(`${url} not ready in ${timeoutMs}ms (last: ${last instanceof Error ? last.message : String(last)})`);
99416
+ }
99417
+ async function seedWorkspaceViaSdk(opts) {
99418
+ return (await new SlateAPIClient({
99419
+ environment: opts.platformUrl,
99420
+ ...ctxAuthHeaders(() => Promise.resolve(opts.token))
99421
+ }).workspaces.createWorkspace({
99422
+ baseIds: opts.baseIds ?? [],
99423
+ model: opts.model ?? "haiku",
99424
+ name: opts.name ?? "capstone-test",
99425
+ ...opts.claudeToken ? { claudeOauthTokenSecret: opts.claudeToken } : {}
99426
+ })).workspaceId;
99427
+ }
99428
+ strictObject$1({
99429
+ bases: array$1(strictObject$1({
99430
+ base_id: string$5().uuid(),
99431
+ dsn: string$5().min(1),
99432
+ name: string$5().min(1)
99433
+ })),
99434
+ model: string$5().min(1).nullable(),
99435
+ name: string$5().min(1),
99436
+ platform_origin: string$5().min(1),
99437
+ pulled_at: string$5().min(1),
99438
+ slug: string$5().min(1),
99439
+ workspace_id: string$5().uuid()
99440
+ });
99441
+ async function pushRepo(platformUrl, token, repoId, files) {
99442
+ const base = await mkdtemp(path.join(os.tmpdir(), "seed-push-"));
99443
+ try {
99444
+ const repo = await resolveRepo({
99445
+ platformUrl,
99446
+ repoId,
99447
+ token
99448
+ }, base);
99449
+ if (await remoteHead(repo.remote) !== null) await cloneRepo(repo);
99450
+ for (const [rel, content] of Object.entries(files)) {
99451
+ await mkdir(path.join(repo.hostDir, path.dirname(rel)), { recursive: true });
99452
+ await writeFile(path.join(repo.hostDir, rel), content);
99453
+ }
99454
+ await pushSnapshot(repo, { message: `seed ${repoId}` });
99455
+ } finally {
99456
+ await rm(base, {
99457
+ force: true,
99458
+ recursive: true
99459
+ });
99460
+ }
99461
+ }
99462
+ async function pushDocsTree(opts) {
99463
+ const repoId = buildRepoId({
99464
+ kind: "workspace",
99465
+ owner: "slate",
99466
+ workspaceId: opts.workspaceId
99467
+ });
99468
+ await pushRepo(opts.platformUrl, opts.token, repoId, opts.files);
99469
+ }
99470
+ async function pushClaudeHome(opts) {
99471
+ const repoId = buildRepoId({
99472
+ kind: "claude",
99473
+ owner: "slate",
99474
+ userId: opts.userId,
99475
+ workspaceId: opts.workspaceId
99476
+ });
99477
+ await pushRepo(opts.platformUrl, opts.token, repoId, opts.files);
99478
+ }
99479
+ function turnPrompt(turn) {
99480
+ return typeof turn === "string" ? turn : turn.prompt;
99481
+ }
99482
+ function turnInterruptPhase(turn) {
99483
+ return typeof turn === "string" ? void 0 : turn.interrupt;
99484
+ }
99059
99485
  const PARITY_SCENARIOS = [
99060
99486
  {
99061
99487
  name: "text-only",
@@ -99107,6 +99533,13 @@ const PARITY_SCENARIOS = [
99107
99533
  "Summarize this conversation in one short sentence.",
99108
99534
  "Reply with exactly one word: bye"
99109
99535
  ]
99536
+ },
99537
+ {
99538
+ name: "interrupted-text",
99539
+ turns: [{
99540
+ interrupt: "text",
99541
+ prompt: "Write a detailed 1500-word essay on the history of the bicycle. Plain prose only, no tools, no lists, no headings."
99542
+ }, "Reply with exactly one word: continued"]
99110
99543
  }
99111
99544
  ];
99112
99545
  function resolveGoldensDir() {
@@ -99119,45 +99552,96 @@ function resolveGoldensDir() {
99119
99552
  dir = parent;
99120
99553
  }
99121
99554
  }
99555
+ const keychainClaudeCredentialsSchema = object$4({ claudeAiOauth: object$4({
99556
+ accessToken: string$5().min(1),
99557
+ expiresAt: number$4()
99558
+ }) });
99559
+ async function resolveClaudeHostCredential() {
99560
+ const exported = process.env.CLAUDE_CODE_OAUTH_TOKEN;
99561
+ if (exported) return {
99562
+ source: "env",
99563
+ token: exported
99564
+ };
99565
+ const raw = await readClaudeCodeCredentialsFromKeychain({ platform: process.platform });
99566
+ if (raw === null) throw new Error("no Claude credentials for the claude tier: export CLAUDE_CODE_OAUTH_TOKEN, or log in to Claude Code on this Mac (the keychain fallback found nothing).");
99567
+ const creds = keychainClaudeCredentialsSchema.parse(JSON.parse(raw));
99568
+ if (creds.claudeAiOauth.expiresAt <= Date.now()) throw new Error("the keychain's Claude Code token has expired: run any claude command to refresh it, or export CLAUDE_CODE_OAUTH_TOKEN.");
99569
+ return {
99570
+ expiresAt: creds.claudeAiOauth.expiresAt,
99571
+ source: "keychain",
99572
+ token: creds.claudeAiOauth.accessToken
99573
+ };
99574
+ }
99122
99575
 
99123
99576
  //#endregion
99124
99577
  //#region ../slate-bridge/dist/index.mjs
99578
+ const execFileAsync = promisify(execFile);
99125
99579
  async function writeJsonAtomic(filePath, value) {
99126
99580
  const tmp = `${filePath}.tmp`;
99127
99581
  await writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`);
99128
99582
  await rename(tmp, filePath);
99129
99583
  }
99130
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
99131
- function generatorCtxToken() {
99132
- throw new Error("parity generator: aud=ctx `?token=` source not implemented — the browser-side token mint is unbuilt");
99584
+ async function formatScenarioGoldens(scenarioDir) {
99585
+ await execFileAsync(path.join(REPO_ROOT, "node_modules", ".bin", "biome"), [
99586
+ "check",
99587
+ "--write",
99588
+ scenarioDir
99589
+ ], { cwd: REPO_ROOT });
99590
+ }
99591
+ const FIRST_FRAME_BUDGET_MS = 3e5;
99592
+ const TURN_SILENCE_BUDGET_MS = 6e4;
99593
+ const KNOWN_SCENARIO_NAMES = new Set(PARITY_SCENARIOS.map((scenario) => scenario.name));
99594
+ const generateParityGoldensOptionsSchema = strictObject$1({ only: array$1(string$5().refine((name) => KNOWN_SCENARIO_NAMES.has(name), { error: `unknown parity scenario; expected one of: ${[...KNOWN_SCENARIO_NAMES].join(", ")}` })) });
99595
+ async function readFinalizedTranscript(sessionId, claudeHomeDir) {
99596
+ const transcriptPath = await findSessionFile(claudeHomeDir, sessionId);
99597
+ if (transcriptPath === null) throw new Error(`[parity] session ${sessionId}: finalized transcript is missing`);
99598
+ return readFile(transcriptPath, "utf-8");
99599
+ }
99600
+ async function readFinalizedHistory(sessionId, claudeHomeDir) {
99601
+ return getSessionMessages(sessionId, { sessionStore: singleSessionStore(sessionId, parseSessionStoreJsonl(await readFinalizedTranscript(sessionId, claudeHomeDir), "throw")) });
99602
+ }
99603
+ function transcriptRowText(row) {
99604
+ const message = row.message;
99605
+ if (typeof message !== "object" || message === null) return "";
99606
+ const content = message.content;
99607
+ if (typeof content === "string") return content;
99608
+ if (!Array.isArray(content)) return "";
99609
+ return content.map((block) => typeof block === "object" && block !== null && "text" in block ? String(block.text ?? "") : "").join("\n");
99133
99610
  }
99134
- async function pollHistoryStable(sessionId, rootDir) {
99135
- const deadline = Date.now() + 3e4;
99136
- let prev = -1;
99137
- let stableReads = 0;
99138
- let history = [];
99139
- while (Date.now() < deadline) {
99140
- history = await getSessionMessages(sessionId, { dir: rootDir });
99141
- if (history.length > 0 && history.length === prev) {
99142
- stableReads += 1;
99143
- if (stableReads >= 2) break;
99144
- } else stableReads = 0;
99145
- prev = history.length;
99146
- await sleep(400);
99611
+ const INTERRUPT_MARKER = {
99612
+ text: "[Request interrupted by user]",
99613
+ tool: "[Request interrupted by user for tool use]"
99614
+ };
99615
+ function assertInterruptPersisted(scenario, history) {
99616
+ const phases = new Set(scenario.turns.map(turnInterruptPhase).filter((phase) => phase !== void 0));
99617
+ for (const phase of phases) {
99618
+ const marker = INTERRUPT_MARKER[phase];
99619
+ if (!history.some((row) => transcriptRowText(row).includes(marker))) throw new Error(`[parity] scenario "${scenario.name}": declared interrupt:"${phase}" but the captured transcript carries no ${marker} row — the stream aborted without the transcript recording it`);
99620
+ }
99621
+ }
99622
+ function isInterruptTrigger(message, phase) {
99623
+ if (phase === "text") return message.type === "stream_event" && message.event.type === "content_block_delta" && message.event.delta.type === "text_delta";
99624
+ return message.type === "user";
99625
+ }
99626
+ function assertTurnInterruptionOutcome(scenario, turnNumber, result, expectedInterruptPhase) {
99627
+ const where = `[parity] scenario "${scenario.name}" turn ${turnNumber}/${scenario.turns.length}`;
99628
+ if (expectedInterruptPhase === void 0) {
99629
+ if (result.subtype !== "success") throw new Error(`${where} ended with ${result.subtype}: ${result.errors.join("; ")}`);
99630
+ return;
99147
99631
  }
99148
- return history;
99632
+ if (result.subtype === "success") throw new Error(`${where} declared interrupt:"${expectedInterruptPhase}" but ended success — the interrupt did not land`);
99633
+ if (result.terminal_reason !== "aborted_streaming" && result.terminal_reason !== "aborted_tools") throw new Error(`${where} declared interrupt:"${expectedInterruptPhase}" but ended ${result.subtype} with terminal_reason ${String(result.terminal_reason)} — that is a failure, not an interrupt`);
99149
99634
  }
99150
- async function captureScenario(server, scenario, rootDir) {
99635
+ async function captureScenarioStream(server, scenario, options) {
99151
99636
  const ac = new AbortController();
99152
99637
  let iter;
99153
99638
  try {
99154
- const workspaceId = crypto.randomUUID();
99155
99639
  const sessionId = crypto.randomUUID();
99156
- const url = `ws://127.0.0.1:${server.port}/bridge/workspaces/${workspaceId}/sessions/${sessionId}/wss?token=${encodeURIComponent(generatorCtxToken())}`;
99640
+ const url = `ws://127.0.0.1:${server.port}/bridge/workspaces/${options.workspaceId}/sessions/${sessionId}/wss?token=${encodeURIComponent(options.ctxToken)}`;
99157
99641
  const queue = [];
99158
99642
  let resolveNext = null;
99159
99643
  const pushTurn = (text) => {
99160
- const msg = {
99644
+ const message = {
99161
99645
  message: {
99162
99646
  content: text,
99163
99647
  role: "user"
@@ -99168,8 +99652,8 @@ async function captureScenario(server, scenario, rootDir) {
99168
99652
  if (resolveNext) {
99169
99653
  const resolve = resolveNext;
99170
99654
  resolveNext = null;
99171
- resolve(msg);
99172
- } else queue.push(msg);
99655
+ resolve(message);
99656
+ } else queue.push(message);
99173
99657
  };
99174
99658
  async function* promptGen() {
99175
99659
  while (true) {
@@ -99180,27 +99664,33 @@ async function captureScenario(server, scenario, rootDir) {
99180
99664
  });
99181
99665
  }
99182
99666
  }
99183
- iter = query({
99667
+ const claudeQuery = query({
99184
99668
  abortController: ac,
99185
99669
  prompt: promptGen(),
99186
99670
  websocket: { url }
99187
99671
  });
99672
+ iter = claudeQuery;
99188
99673
  const stream = [];
99189
- const STALL_MS = 6e4;
99190
- let nextTurn = 0;
99191
- const advance = () => {
99192
- const turn = scenario.turns[nextTurn];
99674
+ let expectedInterruptPhase;
99675
+ let interruptFired = false;
99676
+ let sentTurnCount = 0;
99677
+ const sendNextTurn = () => {
99678
+ const turn = scenario.turns[sentTurnCount];
99193
99679
  if (turn === void 0) return false;
99194
- pushTurn(turn);
99195
- nextTurn += 1;
99680
+ pushTurn(turnPrompt(turn));
99681
+ expectedInterruptPhase = turnInterruptPhase(turn);
99682
+ interruptFired = false;
99683
+ sentTurnCount += 1;
99196
99684
  return true;
99197
99685
  };
99198
- advance();
99686
+ sendNextTurn();
99199
99687
  let finished = false;
99200
99688
  while (!finished) {
99689
+ const awaitingFirstFrame = stream.length === 0;
99690
+ const budgetMs = awaitingFirstFrame ? FIRST_FRAME_BUDGET_MS : TURN_SILENCE_BUDGET_MS;
99201
99691
  let timer;
99202
99692
  const stalled = new Promise((_resolve, reject) => {
99203
- timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`[parity] scenario "${scenario.name}": no SDK message for ${STALL_MS}ms awaiting turn ${nextTurn}/${scenario.turns.length} — turn stalled or emitted no result`)), STALL_MS);
99693
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(awaitingFirstFrame ? `[parity] scenario "${scenario.name}": no first SDK frame for ${FIRST_FRAME_BUDGET_MS}ms during guest acquisition and boot for turn ${sentTurnCount}/${scenario.turns.length} — a cold OCI image pull is a likely cause` : `[parity] scenario "${scenario.name}": no SDK frame for ${TURN_SILENCE_BUDGET_MS}ms while streaming turn ${sentTurnCount}/${scenario.turns.length} — running turn stalled or emitted no result`)), budgetMs);
99204
99694
  });
99205
99695
  let next;
99206
99696
  try {
@@ -99208,55 +99698,113 @@ async function captureScenario(server, scenario, rootDir) {
99208
99698
  } finally {
99209
99699
  if (timer) clearTimeout(timer);
99210
99700
  }
99211
- if (next.done) break;
99701
+ if (next.done) throw new Error(`[parity] scenario "${scenario.name}": SDK stream ended while awaiting turn ${sentTurnCount}/${scenario.turns.length}`);
99212
99702
  stream.push(next.value);
99213
- if (next.value.type === "result" && !advance()) finished = true;
99703
+ if (expectedInterruptPhase !== void 0 && !interruptFired && isInterruptTrigger(next.value, expectedInterruptPhase)) {
99704
+ interruptFired = true;
99705
+ claudeQuery.interrupt();
99706
+ }
99707
+ if (next.value.type === "result") {
99708
+ assertTurnInterruptionOutcome(scenario, sentTurnCount, next.value, expectedInterruptPhase);
99709
+ if (!sendNextTurn()) finished = true;
99710
+ }
99214
99711
  }
99215
99712
  return {
99216
- history: await pollHistoryStable(sessionId, rootDir),
99713
+ sessionId,
99217
99714
  stream
99218
99715
  };
99219
99716
  } finally {
99220
- await iter?.return?.(void 0);
99221
- ac.abort();
99717
+ try {
99718
+ await iter?.return?.(void 0);
99719
+ } finally {
99720
+ ac.abort();
99721
+ }
99222
99722
  }
99223
99723
  }
99224
- async function generateParityGoldens(opts) {
99225
- const filter = opts.only?.filter((n) => n.length > 0) ?? [];
99226
- if (filter.length > 0) {
99227
- const known = new Set(PARITY_SCENARIOS.map((s) => s.name));
99228
- const unknown = filter.filter((n) => !known.has(n));
99229
- if (unknown.length > 0) throw new Error(`[parity] unknown scenario(s): ${unknown.join(", ")}. Known: ${[...known].join(", ")}`);
99230
- }
99231
- const selected = filter.length > 0 ? PARITY_SCENARIOS.filter((s) => filter.includes(s.name)) : PARITY_SCENARIOS;
99232
- for (const scenario of selected) if (scenario.turns.length === 0 || scenario.turns.some((t) => t.trim() === "")) throw new Error(`[parity] scenario "${scenario.name}" has an empty or missing turn`);
99233
- const goldensDir = resolveGoldensDir();
99234
- const scenarioRoots = await Promise.all(selected.map(async (scenario) => ({
99235
- rootDir: await mkdtemp(path.join(os.tmpdir(), `parity-${scenario.name}-`)),
99236
- scenario
99237
- })));
99238
- const server = await createBridgeServer({
99239
- gitHostBaseDir: env$1.CTX_GIT_HOST_BASE_DIR,
99240
- hostClaudeJsonPath: env$1.CTXS_HOST_CLAUDE_JSON_PATH,
99241
- image: env$1.CTXS_CLAUDE_IMAGE,
99242
- port: 0
99724
+ async function seedChatWorkspace(options) {
99725
+ const workspaceId = await seedWorkspaceViaSdk({
99726
+ claudeToken: options.claudeToken,
99727
+ name: options.name,
99728
+ platformUrl: options.platformUrl,
99729
+ token: options.ctxToken
99730
+ });
99731
+ await pushDocsTree({
99732
+ files: { "README.md": `parity scenario workspace: ${options.name}\n` },
99733
+ platformUrl: options.platformUrl,
99734
+ token: options.ctxToken,
99735
+ workspaceId
99243
99736
  });
99737
+ await pushClaudeHome({
99738
+ files: { ".gitkeep": "" },
99739
+ platformUrl: options.platformUrl,
99740
+ token: options.ctxToken,
99741
+ userId: DEV_IDENTITY.sub,
99742
+ workspaceId
99743
+ });
99744
+ return workspaceId;
99745
+ }
99746
+ async function generateParityGoldens(options) {
99747
+ const { only: requestedScenarioNames } = generateParityGoldensOptionsSchema.parse(options);
99748
+ const selectedScenarios = requestedScenarioNames.length > 0 ? PARITY_SCENARIOS.filter((scenario) => requestedScenarioNames.includes(scenario.name)) : PARITY_SCENARIOS;
99749
+ for (const scenario of selectedScenarios) if (scenario.turns.length === 0 || scenario.turns.some((turn) => turnPrompt(turn).trim() === "")) throw new Error(`[parity] scenario "${scenario.name}" has an empty or missing turn`);
99750
+ const goldensDir = resolveGoldensDir();
99751
+ const claudeToken = (await resolveClaudeHostCredential()).token;
99752
+ const ctxToken = mintUnsignedCtxToken(DEV_IDENTITY.sub);
99753
+ const { databaseEnv, dropAll } = await provisionPlatformDatabases();
99244
99754
  try {
99245
- for (const { rootDir, scenario } of scenarioRoots) {
99246
- console.log(`[parity] capturing "${scenario.name}" (${scenario.turns.length} turns)…`);
99247
- const { stream, history } = await captureScenario(server, scenario, rootDir);
99248
- const dir = path.join(goldensDir, scenario.name);
99249
- await mkdir(dir, { recursive: true });
99250
- await writeJsonAtomic(path.join(dir, "stream.json"), stream);
99251
- await writeJsonAtomic(path.join(dir, "history.json"), history);
99252
- console.log(`[parity] ${scenario.name}: stream=${stream.length} msgs, history=${history.length} rows → ${dir}`);
99755
+ const platform = await bootPlatform(databaseEnv);
99756
+ try {
99757
+ const gitHostBaseDir = await mkdtemp(path.join(os.tmpdir(), "parity-git-host-"));
99758
+ try {
99759
+ for (const scenario of selectedScenarios) {
99760
+ console.log(`[parity] capturing "${scenario.name}" (${scenario.turns.length} turns)…`);
99761
+ const workspaceId = await seedChatWorkspace({
99762
+ claudeToken,
99763
+ ctxToken,
99764
+ name: `parity-${scenario.name}`,
99765
+ platformUrl: platform.platformUrl
99766
+ });
99767
+ const server = await createBridgeServer({
99768
+ gitHostBaseDir,
99769
+ hostClaudeJsonPath: env$1.CTXS_HOST_CLAUDE_JSON_PATH,
99770
+ image: env$1.CTXS_CLAUDE_IMAGE,
99771
+ platformUrl: platform.platformUrl,
99772
+ port: 0,
99773
+ verify: createDevJwtVerifier()
99774
+ });
99775
+ const { sessionId, stream } = await captureScenarioStream(server, scenario, {
99776
+ ctxToken,
99777
+ workspaceId
99778
+ }).finally(() => server.close());
99779
+ const history = await readFinalizedHistory(sessionId, (await resolveRepo({
99780
+ platformUrl: platform.platformUrl,
99781
+ repoId: buildRepoId({
99782
+ kind: "claude",
99783
+ owner: "slate",
99784
+ userId: DEV_IDENTITY.sub,
99785
+ workspaceId
99786
+ }),
99787
+ token: ctxToken
99788
+ }, gitHostBaseDir)).hostDir);
99789
+ assertInterruptPersisted(scenario, history);
99790
+ const scenarioDir = path.join(goldensDir, scenario.name);
99791
+ await mkdir(scenarioDir, { recursive: true });
99792
+ await writeJsonAtomic(path.join(scenarioDir, "stream.json"), stream);
99793
+ await writeJsonAtomic(path.join(scenarioDir, "history.json"), history);
99794
+ await formatScenarioGoldens(scenarioDir);
99795
+ console.log(`[parity] ${scenario.name}: stream=${stream.length} msgs, history=${history.length} rows → ${scenarioDir}`);
99796
+ }
99797
+ } finally {
99798
+ await rm(gitHostBaseDir, {
99799
+ force: true,
99800
+ recursive: true
99801
+ });
99802
+ }
99803
+ } finally {
99804
+ await platform.close();
99253
99805
  }
99254
99806
  } finally {
99255
- await server.close();
99256
- await Promise.all(scenarioRoots.map(({ rootDir }) => rm(rootDir, {
99257
- force: true,
99258
- recursive: true
99259
- })));
99807
+ await dropAll();
99260
99808
  }
99261
99809
  }
99262
99810
 
@@ -103177,7 +103725,7 @@ function createProgram() {
103177
103725
  program.command("bridge").description("Slate bridge commands").command("start").description("Start the slate bridge server in the foreground, bound to localhost on CTXS_BRIDGE_PORT (default 7777). Authenticates each connection's aud=ctx user token and resolves its workspace + credentials from the platform (CTX_WEB_URL verifies the token, CTX_PLATFORM_URL serves the plan + base DSN); serves the render endpoints + the per-session WebSocket that spawns claude inside a microsandbox sandbox. Blocks until SIGINT/SIGTERM.").action(async () => {
103178
103726
  await createBridgeServer({});
103179
103727
  });
103180
- program.command("parity").description("Translator parity golden tooling (dev/maintainer)").command("generate [scenarios...]").description("Drive scripted multi-turn conversations through the real bridge + microsandbox + claude and (re)write the parity goldens consumed by slate-shared's parity test. Pass scenario names to capture only those (others keep their existing goldens); omit to regenerate all. Dev tool: needs local claude auth and the microsandbox runtime. Run from source ('node --import tsx cli.ts parity generate [names...]').").action(async (scenarios) => {
103728
+ program.command("parity").description("Translator parity golden tooling (dev/maintainer)").command("generate [scenarios...]").description("Drive scripted multi-turn conversations through the real bridge + microsandbox + claude and (re)write the parity goldens consumed by slate-shared's parity test. Pass scenario names to capture only those (others keep their existing goldens); omit to regenerate all. Consumes real Claude usage and requires local Claude auth, the microsandbox runtime, a pullable guest image, the host Claude trust file, and local dev Postgres. Never run from a gate.").action(async (scenarios) => {
103181
103729
  await generateParityGoldens({ only: scenarios });
103182
103730
  });
103183
103731
  program.command("sandbox").allowUnknownOption(true).allowExcessArguments(true).argument("[command...]", "Command + args to run in the guest (default: uname -a)").requiredOption("--image <ref>", "Guest image to boot in the microVM").description("Boot a microsandbox microVM from --image, run a command inside it, print its output, and tear it down. A DB-free check that the host can boot guest microVMs (/dev/kvm in a container; HVF on macOS).").action(async (command, options) => {
@@ -103203,4 +103751,4 @@ runCli().catch((error) => {
103203
103751
  //#endregion
103204
103752
  export { createProgram, runCli };
103205
103753
  //# sourceMappingURL=cli.mjs.map
103206
- //# debugId=5c792d56-554a-5d58-8c1b-518f322339f4
103754
+ //# debugId=3205b84e-0fd0-5843-a715-326779c52b1a