@gatherthread/dsh-host 0.1.0-alpha.8 → 0.1.0-beta.2

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.
@@ -729,9 +729,9 @@ function floatSafeRemainder(val, step) {
729
729
  return ratio - roundedRatio;
730
730
  }
731
731
  var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
732
- function defineLazy(object2, key, getter) {
732
+ function defineLazy(object3, key, getter) {
733
733
  let value = void 0;
734
- Object.defineProperty(object2, key, {
734
+ Object.defineProperty(object3, key, {
735
735
  get() {
736
736
  if (value === EVALUATING) {
737
737
  return void 0;
@@ -743,7 +743,7 @@ function defineLazy(object2, key, getter) {
743
743
  return value;
744
744
  },
745
745
  set(v) {
746
- Object.defineProperty(object2, key, {
746
+ Object.defineProperty(object3, key, {
747
747
  value: v
748
748
  // configurable: true,
749
749
  });
@@ -823,10 +823,10 @@ function mergeDefs(...defs) {
823
823
  function cloneDef(schema) {
824
824
  return mergeDefs(schema._zod.def);
825
825
  }
826
- function getElementAtPath(obj, path10) {
827
- if (!path10)
826
+ function getElementAtPath(obj, path11) {
827
+ if (!path11)
828
828
  return obj;
829
- return path10.reduce((acc, key) => acc?.[key], obj);
829
+ return path11.reduce((acc, key) => acc?.[key], obj);
830
830
  }
831
831
  function promiseAllObject(promisesObj) {
832
832
  const keys = Object.keys(promisesObj);
@@ -1166,11 +1166,11 @@ function explicitlyAborted(x, startIndex = 0) {
1166
1166
  }
1167
1167
  return false;
1168
1168
  }
1169
- function prefixIssues(path10, issues) {
1169
+ function prefixIssues(path11, issues) {
1170
1170
  return issues.map((iss) => {
1171
1171
  var _a3;
1172
1172
  (_a3 = iss).path ?? (_a3.path = []);
1173
- iss.path.unshift(path10);
1173
+ iss.path.unshift(path11);
1174
1174
  return iss;
1175
1175
  });
1176
1176
  }
@@ -1277,16 +1277,16 @@ function cleanEnum(obj) {
1277
1277
  }
1278
1278
  function base64ToUint8Array(base643) {
1279
1279
  const binaryString = atob(base643);
1280
- const bytes = new Uint8Array(binaryString.length);
1280
+ const bytes2 = new Uint8Array(binaryString.length);
1281
1281
  for (let i = 0; i < binaryString.length; i++) {
1282
- bytes[i] = binaryString.charCodeAt(i);
1282
+ bytes2[i] = binaryString.charCodeAt(i);
1283
1283
  }
1284
- return bytes;
1284
+ return bytes2;
1285
1285
  }
1286
- function uint8ArrayToBase64(bytes) {
1286
+ function uint8ArrayToBase64(bytes2) {
1287
1287
  let binaryString = "";
1288
- for (let i = 0; i < bytes.length; i++) {
1289
- binaryString += String.fromCharCode(bytes[i]);
1288
+ for (let i = 0; i < bytes2.length; i++) {
1289
+ binaryString += String.fromCharCode(bytes2[i]);
1290
1290
  }
1291
1291
  return btoa(binaryString);
1292
1292
  }
@@ -1295,22 +1295,22 @@ function base64urlToUint8Array(base64url3) {
1295
1295
  const padding = "=".repeat((4 - base643.length % 4) % 4);
1296
1296
  return base64ToUint8Array(base643 + padding);
1297
1297
  }
1298
- function uint8ArrayToBase64url(bytes) {
1299
- return uint8ArrayToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1298
+ function uint8ArrayToBase64url(bytes2) {
1299
+ return uint8ArrayToBase64(bytes2).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
1300
1300
  }
1301
1301
  function hexToUint8Array(hex3) {
1302
1302
  const cleanHex = hex3.replace(/^0x/, "");
1303
1303
  if (cleanHex.length % 2 !== 0) {
1304
1304
  throw new Error("Invalid hex string length");
1305
1305
  }
1306
- const bytes = new Uint8Array(cleanHex.length / 2);
1306
+ const bytes2 = new Uint8Array(cleanHex.length / 2);
1307
1307
  for (let i = 0; i < cleanHex.length; i += 2) {
1308
- bytes[i / 2] = Number.parseInt(cleanHex.slice(i, i + 2), 16);
1308
+ bytes2[i / 2] = Number.parseInt(cleanHex.slice(i, i + 2), 16);
1309
1309
  }
1310
- return bytes;
1310
+ return bytes2;
1311
1311
  }
1312
- function uint8ArrayToHex(bytes) {
1313
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1312
+ function uint8ArrayToHex(bytes2) {
1313
+ return Array.from(bytes2).map((b) => b.toString(16).padStart(2, "0")).join("");
1314
1314
  }
1315
1315
  var Class = class {
1316
1316
  constructor(..._args) {
@@ -1620,16 +1620,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
1620
1620
  }
1621
1621
  function formatError(error62, mapper = (issue2) => issue2.message) {
1622
1622
  const fieldErrors = { _errors: [] };
1623
- const processError = (error63, path10 = []) => {
1623
+ const processError = (error63, path11 = []) => {
1624
1624
  for (const issue2 of error63.issues) {
1625
1625
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1626
- issue2.errors.map((issues) => processError({ issues }, [...path10, ...issue2.path]));
1626
+ issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
1627
1627
  } else if (issue2.code === "invalid_key") {
1628
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
1628
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
1629
1629
  } else if (issue2.code === "invalid_element") {
1630
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
1630
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
1631
1631
  } else {
1632
- const fullpath = [...path10, ...issue2.path];
1632
+ const fullpath = [...path11, ...issue2.path];
1633
1633
  if (fullpath.length === 0) {
1634
1634
  fieldErrors._errors.push(mapper(issue2));
1635
1635
  } else {
@@ -1668,17 +1668,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
1668
1668
  }
1669
1669
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
1670
1670
  const result = { errors: [] };
1671
- const processError = (error63, path10 = []) => {
1671
+ const processError = (error63, path11 = []) => {
1672
1672
  var _a3;
1673
1673
  for (const issue2 of error63.issues) {
1674
1674
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1675
- issue2.errors.map((issues) => processError({ issues }, [...path10, ...issue2.path]));
1675
+ issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
1676
1676
  } else if (issue2.code === "invalid_key") {
1677
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
1677
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
1678
1678
  } else if (issue2.code === "invalid_element") {
1679
- processError({ issues: issue2.issues }, [...path10, ...issue2.path]);
1679
+ processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
1680
1680
  } else {
1681
- const fullpath = [...path10, ...issue2.path];
1681
+ const fullpath = [...path11, ...issue2.path];
1682
1682
  if (fullpath.length === 0) {
1683
1683
  result.errors.push(mapper(issue2));
1684
1684
  continue;
@@ -1717,8 +1717,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
1717
1717
  }
1718
1718
  function toDotPath(_path) {
1719
1719
  const segs = [];
1720
- const path10 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1721
- for (const seg of path10) {
1720
+ const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1721
+ for (const seg of path11) {
1722
1722
  if (typeof seg === "number")
1723
1723
  segs.push(`[${seg}]`);
1724
1724
  else if (typeof seg === "symbol")
@@ -15810,8 +15810,8 @@ function foldObjects(members2) {
15810
15810
  }
15811
15811
  const properties = {};
15812
15812
  const required2 = /* @__PURE__ */ new Set();
15813
- for (const object2 of objects) {
15814
- for (const key in object2.properties) {
15813
+ for (const object3 of objects) {
15814
+ for (const key in object3.properties) {
15815
15815
  if (Object.prototype.hasOwnProperty.call(properties, key))
15816
15816
  continue;
15817
15817
  const parts = [];
@@ -15825,18 +15825,18 @@ function foldObjects(members2) {
15825
15825
  const merged = parts.length === 1 ? parts[0] : foldObjects(parts) ?? { allOf: parts };
15826
15826
  assignProp(properties, key, merged);
15827
15827
  }
15828
- for (const key of object2.required ?? [])
15828
+ for (const key of object3.required ?? [])
15829
15829
  required2.add(key);
15830
15830
  }
15831
15831
  const folded = { type: "object", properties };
15832
15832
  if (required2.size)
15833
15833
  folded.required = [...required2];
15834
- if (objects.every((object2) => object2.additionalProperties === false)) {
15834
+ if (objects.every((object3) => object3.additionalProperties === false)) {
15835
15835
  folded.additionalProperties = false;
15836
15836
  } else {
15837
15837
  const constraints = [];
15838
- for (const object2 of objects) {
15839
- const constraint = undeclaredConstraint(object2);
15838
+ for (const object3 of objects) {
15839
+ const constraint = undeclaredConstraint(object3);
15840
15840
  if (constraint && !constraints.some((seen) => JSON.stringify(seen) === JSON.stringify(constraint)))
15841
15841
  constraints.push(constraint);
15842
15842
  }
@@ -18820,13 +18820,13 @@ function resolveRef(ref, ctx) {
18820
18820
  if (!ref.startsWith("#")) {
18821
18821
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
18822
18822
  }
18823
- const path10 = ref.slice(1).split("/").filter(Boolean);
18824
- if (path10.length === 0) {
18823
+ const path11 = ref.slice(1).split("/").filter(Boolean);
18824
+ if (path11.length === 0) {
18825
18825
  return ctx.rootSchema;
18826
18826
  }
18827
18827
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
18828
- if (path10[0] === defsKey) {
18829
- const key = path10[1] === void 0 ? void 0 : decodeJSONPointerSegment(path10[1]);
18828
+ if (path11[0] === defsKey) {
18829
+ const key = path11[1] === void 0 ? void 0 : decodeJSONPointerSegment(path11[1]);
18830
18830
  if (!key || !ctx.defs[key]) {
18831
18831
  throw new Error(`Reference not found: ${ref}`);
18832
18832
  }
@@ -19675,6 +19675,28 @@ function date4(params) {
19675
19675
  return _coercedDate(ZodDate, params);
19676
19676
  }
19677
19677
 
19678
+ // ../protocol/dist/src/live-output.js
19679
+ var utf8 = (maximum) => external_exports.string().refine((value) => new TextEncoder().encode(value).length <= maximum, "Live output is too large");
19680
+ var LiveOutputPayloadSchema = external_exports.object({
19681
+ content: utf8(24 * 1024),
19682
+ phase: external_exports.literal("stream"),
19683
+ stream_id: external_exports.string().uuid(),
19684
+ stream_mode: external_exports.literal("snapshot"),
19685
+ stream_kind: external_exports.enum(["answer", "reasoning", "tool", "plan"]),
19686
+ stream_revision: external_exports.number().int().min(1).max(1200),
19687
+ stream_state: external_exports.enum(["running", "completed", "failed"]),
19688
+ title: utf8(256).optional(),
19689
+ plan: external_exports.array(external_exports.object({ step: utf8(512), status: external_exports.enum(["pending", "in_progress", "completed"]) }).strict()).max(64).optional(),
19690
+ status: external_exports.enum(["thinking", "running"]).optional(),
19691
+ occurred_at: external_exports.string().datetime().optional(),
19692
+ capture_fidelity: external_exports.literal("harness_transcript").optional(),
19693
+ source_harness: external_exports.string().min(1).max(80).optional()
19694
+ }).strict().superRefine((value, context) => {
19695
+ if (value.stream_kind === "plan" !== (value.plan !== void 0)) {
19696
+ context.addIssue({ code: "custom", message: "Only plan snapshots carry plan steps" });
19697
+ }
19698
+ });
19699
+
19678
19700
  // ../protocol/dist/src/code-sync.js
19679
19701
  var CODE_SYNC_MAX_FILES = 1e3;
19680
19702
  var CODE_SYNC_MAX_FILE_BYTES = 2 * 1024 * 1024;
@@ -19685,16 +19707,16 @@ var Commit = external_exports.string().regex(/^[a-f0-9]{40}$/);
19685
19707
  var BranchId = external_exports.string().regex(/^branch-[a-f0-9]{24}$/);
19686
19708
  var pathEncoder = new TextEncoder();
19687
19709
  var hfsIgnoredCharacters = /[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/gu;
19688
- function portablePathKey(path10) {
19689
- return path10.normalize("NFC").replace(hfsIgnoredCharacters, "").toLowerCase();
19710
+ function portablePathKey(path11) {
19711
+ return path11.normalize("NFC").replace(hfsIgnoredCharacters, "").toLowerCase();
19690
19712
  }
19691
19713
  function containsCodeSyncSecret(text) {
19692
- return /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----|\b(?:gh[pousr]|github_pat|glpat|sk|xox[baprs])[-_][A-Za-z0-9_-]{16,}|\b(?:gt[abidp]|acp(?:i|d)?)_[A-Za-z0-9_-]{20,}|\bAKIA[A-Z0-9]{16}\b/u.test(text);
19714
+ return /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----|\b(?:gh[pousr]|github_pat|glpat|sk|xox[baprs])[-_][A-Za-z0-9_-]{16,}|\b(?:(?:gt[abidp]|gteg?)|acp(?:i|d)?)_[A-Za-z0-9_-]{20,}|\bAKIA[A-Z0-9]{16}\b/u.test(text);
19693
19715
  }
19694
- function isCodeSyncPathAllowed(path10) {
19695
- if (!path10 || path10.length > 500 || path10.startsWith("/") || /[\\\u0000-\u001f\u007f\ud800-\udfff:<>"|?*]/u.test(path10))
19716
+ function isCodeSyncPathAllowed(path11) {
19717
+ if (!path11 || path11.length > 500 || path11.startsWith("/") || /[\\\u0000-\u001f\u007f\ud800-\udfff:<>"|?*]/u.test(path11))
19696
19718
  return false;
19697
- const components = path10.split("/");
19719
+ const components = path11.split("/");
19698
19720
  return components.every((part) => {
19699
19721
  const lower = portablePathKey(part);
19700
19722
  if (!part || pathEncoder.encode(part).byteLength > 255 || part === "." || part === ".." || part.endsWith(".") || part.endsWith(" "))
@@ -19714,34 +19736,42 @@ function isCodeSyncPathAllowed(path10) {
19714
19736
  return !/\.(?:pem|p12|pfx|key)$/iu.test(lower) && !/^(?:id_rsa|id_ed25519)(?:\.|$)/iu.test(lower);
19715
19737
  });
19716
19738
  }
19717
- var CodeFileSchema = external_exports.object({
19718
- path: external_exports.string().refine(isCodeSyncPathAllowed, "Unsafe or private code path"),
19719
- content_base64: external_exports.string().max(4 * Math.ceil(CODE_SYNC_MAX_FILE_BYTES / 3)).regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/),
19720
- executable: external_exports.boolean()
19721
- }).strict();
19722
- var CodeFilesSchema = external_exports.array(CodeFileSchema).max(CODE_SYNC_MAX_FILES).superRefine((files, context) => {
19723
- let size = 0;
19724
- const names = /* @__PURE__ */ new Set();
19725
- for (const [index, file2] of files.entries()) {
19726
- const bytes = file2.content_base64.length * 3 / 4 - (file2.content_base64.endsWith("==") ? 2 : file2.content_base64.endsWith("=") ? 1 : 0);
19727
- size += bytes;
19728
- const normalized = portablePathKey(file2.path);
19729
- if (bytes > CODE_SYNC_MAX_FILE_BYTES || names.has(normalized))
19730
- context.addIssue({ code: "custom", path: [index], message: "File limit or duplicate portable path" });
19731
- names.add(normalized);
19732
- }
19733
- if (size > CODE_SYNC_MAX_SNAPSHOT_BYTES)
19734
- context.addIssue({ code: "custom", message: "Code snapshot exceeds 8 MiB" });
19735
- for (const name2 of names) {
19736
- const parts = name2.split("/");
19737
- parts.pop();
19738
- while (parts.length) {
19739
- if (names.has(parts.join("/")))
19740
- context.addIssue({ code: "custom", message: "File and directory paths overlap" });
19739
+ var CLOUD_CODE_SYNC_LIMITS = { maxFiles: CODE_SYNC_MAX_FILES, maxFileBytes: CODE_SYNC_MAX_FILE_BYTES, maxSnapshotBytes: CODE_SYNC_MAX_SNAPSHOT_BYTES };
19740
+ var GITHUB_CODE_SYNC_LIMITS = { maxFiles: 1e4, maxFileBytes: 20 * 1024 * 1024, maxSnapshotBytes: 128 * 1024 * 1024 };
19741
+ function createCodeFileSchema(limits) {
19742
+ return external_exports.object({
19743
+ path: external_exports.string().refine(isCodeSyncPathAllowed, "Unsafe or private code path"),
19744
+ content_base64: external_exports.string().max(4 * Math.ceil(limits.maxFileBytes / 3)).regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/),
19745
+ executable: external_exports.boolean()
19746
+ }).strict();
19747
+ }
19748
+ var CodeFileSchema = createCodeFileSchema(CLOUD_CODE_SYNC_LIMITS);
19749
+ function createCodeFilesSchema(limits) {
19750
+ return external_exports.array(createCodeFileSchema(limits)).max(limits.maxFiles).superRefine((files, context) => {
19751
+ let size = 0;
19752
+ const names = /* @__PURE__ */ new Set();
19753
+ for (const [index, file2] of files.entries()) {
19754
+ const bytes2 = file2.content_base64.length * 3 / 4 - (file2.content_base64.endsWith("==") ? 2 : file2.content_base64.endsWith("=") ? 1 : 0);
19755
+ size += bytes2;
19756
+ const normalized = portablePathKey(file2.path);
19757
+ if (bytes2 > limits.maxFileBytes || names.has(normalized))
19758
+ context.addIssue({ code: "custom", path: [index], message: "File limit or duplicate portable path" });
19759
+ names.add(normalized);
19760
+ }
19761
+ if (size > limits.maxSnapshotBytes)
19762
+ context.addIssue({ code: "custom", message: "Code snapshot exceeds provider limit" });
19763
+ for (const name2 of names) {
19764
+ const parts = name2.split("/");
19741
19765
  parts.pop();
19766
+ while (parts.length) {
19767
+ if (names.has(parts.join("/")))
19768
+ context.addIssue({ code: "custom", message: "File and directory paths overlap" });
19769
+ parts.pop();
19770
+ }
19742
19771
  }
19743
- }
19744
- });
19772
+ });
19773
+ }
19774
+ var CodeFilesSchema = createCodeFilesSchema(CLOUD_CODE_SYNC_LIMITS);
19745
19775
  var CodeEnableInputSchema = external_exports.object({ idempotency_key: Key }).strict();
19746
19776
  var CodeDisableInputSchema = external_exports.object({ idempotency_key: Key }).strict();
19747
19777
  var CodeCheckpointInputSchema = external_exports.object({
@@ -19803,6 +19833,159 @@ var CodeSnapshotResultSchema = external_exports.object({ snapshot: external_expo
19803
19833
  }).strict() }).strict();
19804
19834
  var codeSyncRequestKinds = ["code_sync_status", "code_upload", "code_download", "code_recover", "code_auto_upload_enable", "code_auto_upload_disable"];
19805
19835
 
19836
+ // ../protocol/dist/src/github-sync.js
19837
+ var GitHubRepositorySchema = external_exports.string().max(140).regex(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?\/[A-Za-z0-9_.-]{1,100}$/).refine((value) => {
19838
+ const repository = value.split("/")[1];
19839
+ return repository !== "." && repository !== ".." && !repository.toLowerCase().endsWith(".git");
19840
+ }, "Use owner/repository without a .git suffix");
19841
+ function isGitHubBranchAllowed(value) {
19842
+ return value.length > 0 && value.length <= 200 && /^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(value) && !value.includes("..") && value.split("/").every((part) => !!part && !part.startsWith(".") && !part.endsWith(".") && !part.toLowerCase().endsWith(".lock")) && value !== "HEAD";
19843
+ }
19844
+ var GitHubBranchSchema = external_exports.string().refine(isGitHubBranchAllowed, "Unsafe GitHub branch name");
19845
+ var GitHubBaseBranchSchema = GitHubBranchSchema.refine((value) => !value.startsWith("gatherthread/"), "The shared base cannot use the GatherThread personal branch namespace");
19846
+ var GitHubPersonalBranchSchema = external_exports.string().regex(/^gatherthread\/[a-f0-9]{24}\/[a-f0-9]{24}$/);
19847
+ var GitHubConnectionSchema = external_exports.object({
19848
+ repository: GitHubRepositorySchema,
19849
+ base_branch: GitHubBaseBranchSchema,
19850
+ enabled: external_exports.boolean(),
19851
+ revision: external_exports.string().uuid()
19852
+ }).strict();
19853
+ var GitHubProjectStatusSchema = external_exports.object({
19854
+ connection: GitHubConnectionSchema.nullable(),
19855
+ branch: GitHubPersonalBranchSchema,
19856
+ can_configure: external_exports.boolean(),
19857
+ // GatherThread membership only. GitHub permissions must be checked locally.
19858
+ can_write: external_exports.boolean()
19859
+ }).strict();
19860
+ var GitHubConnectionInputSchema = external_exports.object({
19861
+ repository: GitHubRepositorySchema,
19862
+ base_branch: GitHubBaseBranchSchema,
19863
+ enabled: external_exports.boolean(),
19864
+ expected_revision: external_exports.string().uuid().nullable()
19865
+ }).strict();
19866
+ var githubCodeSyncRequestKinds = [
19867
+ "github_auth_connect",
19868
+ "github_code_sync_status",
19869
+ "github_code_upload",
19870
+ "github_code_download",
19871
+ "github_code_recover",
19872
+ "github_code_auto_upload_enable",
19873
+ "github_code_auto_upload_disable",
19874
+ "github_code_update"
19875
+ ];
19876
+
19877
+ // ../protocol/dist/src/registration.js
19878
+ var RegistrationEmailSchema = external_exports.string().max(254).trim().toLowerCase().regex(/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/).refine((value) => value.split("@")[0].length <= 64 && value.split("@")[1].split(".").every((part) => part.length <= 63));
19879
+ var Label = external_exports.string().regex(/^[^\u0000-\u001f\u007f-\u009f]*$/u).trim().min(1).max(120);
19880
+ var SendRegistrationInputSchema = external_exports.object({
19881
+ email: RegistrationEmailSchema,
19882
+ challenge_token: external_exports.string().min(1).max(2048),
19883
+ idempotency_key: external_exports.string().uuid(),
19884
+ locale: external_exports.enum(["en", "zh-CN"])
19885
+ }).strict();
19886
+ var AccountPasswordSchema = external_exports.string().min(8).max(128).refine((value) => new TextEncoder().encode(value).byteLength <= 512);
19887
+ var EmailLoginInputSchema = external_exports.object({
19888
+ email: RegistrationEmailSchema,
19889
+ password: AccountPasswordSchema,
19890
+ device_name: Label,
19891
+ remember_device: external_exports.boolean().default(false)
19892
+ }).strict();
19893
+ var LauncherProjectId = external_exports.string().min(1).max(128).refine((value) => /^[A-Za-z0-9]/u.test(value) && !/[^A-Za-z0-9._:-]/u.test(value));
19894
+ var CreateCodexLauncherAuthorizationInputSchema = external_exports.object({
19895
+ project_id: LauncherProjectId,
19896
+ email: RegistrationEmailSchema
19897
+ }).strict();
19898
+ var ClaimCodexLauncherAuthorizationInputSchema = external_exports.object({
19899
+ authorization_id: external_exports.string().uuid().toLowerCase(),
19900
+ project_id: LauncherProjectId,
19901
+ email: RegistrationEmailSchema,
19902
+ password: AccountPasswordSchema,
19903
+ device_name: Label
19904
+ }).strict();
19905
+ var VerifyRegistrationInputSchema = external_exports.object({
19906
+ registration_id: external_exports.string().uuid(),
19907
+ code: external_exports.string().regex(/^\d{8}$/),
19908
+ display_name: Label,
19909
+ device_name: Label,
19910
+ remember_device: external_exports.boolean().default(false),
19911
+ privacy_acknowledged: external_exports.literal(true),
19912
+ password: AccountPasswordSchema
19913
+ }).strict();
19914
+ var RegistrationStatusSchema = external_exports.object({
19915
+ enabled: external_exports.boolean(),
19916
+ site_key: external_exports.string().nullable(),
19917
+ challenge_binding: external_exports.string().nullable()
19918
+ }).strict();
19919
+ var RegistrationSentSchema = external_exports.object({
19920
+ registration_id: external_exports.string().uuid(),
19921
+ expires_in_seconds: external_exports.literal(600),
19922
+ resend_after_seconds: external_exports.literal(60)
19923
+ }).strict();
19924
+ var PasswordResetSentSchema = external_exports.object({
19925
+ reset_id: external_exports.string().uuid(),
19926
+ expires_in_seconds: external_exports.literal(600),
19927
+ resend_after_seconds: external_exports.literal(60)
19928
+ }).strict();
19929
+ var VerifyPasswordResetInputSchema = external_exports.object({
19930
+ reset_id: external_exports.string().uuid(),
19931
+ email: RegistrationEmailSchema,
19932
+ code: external_exports.string().regex(/^\d{8}$/),
19933
+ password: AccountPasswordSchema,
19934
+ password_confirmation: AccountPasswordSchema,
19935
+ locale: external_exports.enum(["en", "zh-CN"])
19936
+ }).strict().refine((value) => value.password === value.password_confirmation);
19937
+ var EmailAccountSessionSchema = external_exports.object({
19938
+ actor: external_exports.object({ user_id: external_exports.string(), display_name: external_exports.string(), device_id: external_exports.string(), can_create_projects: external_exports.literal(true) }).strict(),
19939
+ expires_at: external_exports.string().datetime()
19940
+ }).strict();
19941
+
19942
+ // ../protocol/dist/src/cloud-api.js
19943
+ var CloudApiProviderIdSchema = external_exports.enum(["openai", "anthropic", "deepseek", "siliconflow-cn", "siliconflow-global", "openrouter", "qwen", "glm", "doubao"]);
19944
+ var CloudApiModelSchema = external_exports.string().trim().min(1).max(160).regex(/^[^\u0000-\u0020\u007f-\u009f]+$/u);
19945
+ var Label2 = external_exports.string().trim().min(1).max(80).regex(/^[^\u0000-\u001f\u007f-\u009f]+$/u);
19946
+ var CloudApiPutInputSchema = external_exports.object({
19947
+ expected_revision: external_exports.string().uuid().nullable(),
19948
+ provider_id: CloudApiProviderIdSchema,
19949
+ label: Label2,
19950
+ model: CloudApiModelSchema,
19951
+ enabled: external_exports.boolean(),
19952
+ api_key: external_exports.string().min(8).max(4096).regex(/^[\x21-\x7e]+$/u).optional()
19953
+ }).strict();
19954
+ var CloudApiDeleteInputSchema = external_exports.object({ expected_revision: external_exports.string().uuid() }).strict();
19955
+ var CloudApiConfigurationSchema = external_exports.object({
19956
+ id: external_exports.string().uuid(),
19957
+ revision: external_exports.string().uuid(),
19958
+ provider_id: CloudApiProviderIdSchema,
19959
+ profile_id: external_exports.string().regex(/^api-[0-9a-f-]{36}$/u),
19960
+ label: Label2,
19961
+ model: CloudApiModelSchema,
19962
+ enabled: external_exports.boolean(),
19963
+ has_key: external_exports.literal(true),
19964
+ updated_at: external_exports.string().datetime()
19965
+ }).strict();
19966
+ var CloudApiProviderSchema = external_exports.object({
19967
+ id: CloudApiProviderIdSchema,
19968
+ label: external_exports.string().max(80),
19969
+ transport: external_exports.enum(["openai-chat", "anthropic-messages"]),
19970
+ base_url: external_exports.string().url().max(200),
19971
+ docs_url: external_exports.string().url().max(300),
19972
+ default_model: CloudApiModelSchema.optional()
19973
+ }).strict();
19974
+ var CloudApiStatusSchema = external_exports.object({
19975
+ enabled: external_exports.boolean(),
19976
+ providers: external_exports.array(CloudApiProviderSchema).max(16),
19977
+ configurations: external_exports.array(CloudApiConfigurationSchema).max(8)
19978
+ }).strict();
19979
+ var CloudApiSavedSchema = external_exports.object({ configuration: CloudApiConfigurationSchema }).strict();
19980
+ var PersonalCloudRevisionSchema = external_exports.string().uuid().optional();
19981
+ function hasValidPersonalCloudRevision(value) {
19982
+ return value.profile_id.startsWith("api-") ? /^api-[0-9a-f-]{36}$/u.test(value.profile_id) && value.api_config_revision !== void 0 : value.api_config_revision === void 0;
19983
+ }
19984
+ var HostedAgentSelectionSchema = external_exports.object({
19985
+ profile_id: external_exports.string().min(1).max(64),
19986
+ api_config_revision: PersonalCloudRevisionSchema
19987
+ }).strict().refine(hasValidPersonalCloudRevision, "Personal Cloud Agent selection requires its exact revision");
19988
+
19806
19989
  // ../protocol/dist/src/history-summary.js
19807
19990
  var HISTORY_SUMMARY_MAX_SOURCE_BYTES = 20 * 1024;
19808
19991
  var HISTORY_SUMMARY_MAX_PROMPT_BYTES = 30 * 1024;
@@ -19818,7 +20001,7 @@ function record2(value) {
19818
20001
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
19819
20002
  }
19820
20003
  function historySummaryMarker(event) {
19821
- if (event.type !== "agent_request")
20004
+ if (event.deleted || event.type !== "agent_request")
19822
20005
  return void 0;
19823
20006
  const marker = record2(record2(event.payload)?.history_summary);
19824
20007
  const ids = marker?.source_event_ids;
@@ -19827,6 +20010,105 @@ function historySummaryMarker(event) {
19827
20010
  return { version: 1, source_event_ids: ids, source_digest: marker.source_digest };
19828
20011
  }
19829
20012
 
20013
+ // ../protocol/dist/src/test-gate.js
20014
+ var TestGateExchangeInputSchema = external_exports.object({
20015
+ admission_code: external_exports.string().regex(/^gte_[A-Za-z0-9_-]{43}$/)
20016
+ }).strict();
20017
+ var TestGateStatusSchema = external_exports.object({
20018
+ enabled: external_exports.boolean(),
20019
+ admitted: external_exports.boolean(),
20020
+ environment: external_exports.enum(["production", "test"])
20021
+ }).strict();
20022
+
20023
+ // ../protocol/dist/src/hosted-github.js
20024
+ var Key2 = external_exports.string().min(8).max(200);
20025
+ var HostedGithubRepositoryInputSchema = external_exports.object({
20026
+ repository: external_exports.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u).max(140),
20027
+ base_branch: external_exports.string().min(1).max(200).refine((v) => !/[\s~^:?*\[\\]/u.test(v) && !v.includes("..") && !v.startsWith("-") && !v.endsWith("/") && !v.endsWith(".lock"))
20028
+ }).strict();
20029
+ var HostedGithubTaskInputSchema = external_exports.object({
20030
+ content: external_exports.string().trim().min(1).max(6e3),
20031
+ profile_id: external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/u),
20032
+ idempotency_key: Key2,
20033
+ continue_task_id: external_exports.string().regex(/^gh-task-[a-f0-9]{32}$/u).optional(),
20034
+ reply_to_event_id: external_exports.string().min(1).max(128).nullable().optional(),
20035
+ api_config_revision: PersonalCloudRevisionSchema,
20036
+ content_revision: external_exports.number().int().nonnegative().optional()
20037
+ }).strict().refine(hasValidPersonalCloudRevision, { message: "Personal cloud profiles require their exact configuration revision" });
20038
+ var HostedGithubCompleteInputSchema = external_exports.object({
20039
+ state: external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/u),
20040
+ code: external_exports.string().min(1).max(500)
20041
+ }).strict();
20042
+ var HostedGithubPrInputSchema = external_exports.object({
20043
+ title: external_exports.string().trim().min(1).max(200),
20044
+ body: external_exports.string().max(6e3),
20045
+ expected_revision: external_exports.string().regex(/^[a-f0-9]{64}$/u)
20046
+ }).strict();
20047
+ var Login = external_exports.string().regex(/^[A-Za-z0-9-]{1,39}$/u);
20048
+ var TaskId = external_exports.string().regex(/^gh-task-[a-f0-9]{32}$/u);
20049
+ var Sha = external_exports.string().regex(/^[a-f0-9]{40}$/u).nullable();
20050
+ var Revision = external_exports.string().regex(/^[a-f0-9]{64}$/u).nullable();
20051
+ var PullUrl = external_exports.string().max(2048).regex(/^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/[1-9][0-9]*$/u).nullable();
20052
+ var HostedGithubBindingSchema = HostedGithubRepositoryInputSchema;
20053
+ var HostedGithubStatusSchema = external_exports.union([
20054
+ external_exports.object({ enabled: external_exports.literal(false) }).strict(),
20055
+ external_exports.object({
20056
+ enabled: external_exports.literal(true),
20057
+ connected: external_exports.boolean(),
20058
+ login: Login.nullable(),
20059
+ binding: HostedGithubBindingSchema.nullable(),
20060
+ installation_url: external_exports.string().max(2048).regex(/^https:\/\/github\.com\/apps\/[A-Za-z0-9_.-]+\/installations\/new$/u)
20061
+ }).strict().refine((v) => v.connected ? v.login !== null : v.login === null && v.binding === null)
20062
+ ]);
20063
+ var HostedGithubAuthorizationSchema = external_exports.object({ authorization_url: external_exports.string().max(4096).refine((value) => {
20064
+ try {
20065
+ const url2 = new URL(value);
20066
+ return url2.origin === "https://github.com" && url2.pathname === "/login/oauth/authorize" && !url2.username && !url2.password && !url2.hash && /^[A-Za-z0-9_-]{43}$/u.test(url2.searchParams.get("state") ?? "") && [...url2.searchParams.keys()].every((key) => ["client_id", "redirect_uri", "state", "code_challenge", "code_challenge_method", "prompt"].includes(key));
20067
+ } catch {
20068
+ return false;
20069
+ }
20070
+ }) }).strict();
20071
+ var HostedGithubConnectionSchema = external_exports.object({ connected: external_exports.literal(true), login: Login }).strict();
20072
+ var HostedGithubDisconnectionSchema = external_exports.object({ connected: external_exports.literal(false) }).strict();
20073
+ var HostedGithubTaskSummarySchema = external_exports.object({
20074
+ id: TaskId,
20075
+ session_id: external_exports.string().min(1).max(128),
20076
+ profile_id: HostedGithubTaskInputSchema.shape.profile_id,
20077
+ resumable: external_exports.boolean(),
20078
+ state: external_exports.enum(["running", "completed", "failed", "interrupted"]),
20079
+ repository: HostedGithubRepositoryInputSchema.shape.repository,
20080
+ base_branch: HostedGithubRepositoryInputSchema.shape.base_branch,
20081
+ base_sha: Sha,
20082
+ revision: Revision,
20083
+ error_code: external_exports.string().regex(/^[a-z][a-z0-9_]{0,63}$/u).nullable(),
20084
+ pull_request_url: PullUrl,
20085
+ expires_at: external_exports.number().int().positive().max(Number.MAX_SAFE_INTEGER),
20086
+ api_config_revision: external_exports.string().uuid().optional()
20087
+ }).strict();
20088
+ var bytes = (value) => value.length / 4 * 3 - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0);
20089
+ var Source = external_exports.string().max(2796204).regex(/^[A-Za-z0-9+/]*={0,2}$/u).refine((value) => value.length % 4 === 0 && bytes(value) <= 2 * 1024 * 1024).nullable();
20090
+ var HostedGithubChangeSchema = external_exports.object({
20091
+ path: external_exports.string().max(4096).refine(isCodeSyncPathAllowed),
20092
+ before_base64: Source,
20093
+ after_base64: Source,
20094
+ before_executable: external_exports.boolean().nullable(),
20095
+ after_executable: external_exports.boolean().nullable()
20096
+ }).strict().refine((v) => v.before_base64 === null === (v.before_executable === null) && v.after_base64 === null === (v.after_executable === null) && (v.before_base64 !== null || v.after_base64 !== null));
20097
+ var HostedGithubTaskSchema = HostedGithubTaskSummarySchema.extend({
20098
+ answer: external_exports.string().max(14e3).nullable(),
20099
+ changes: external_exports.array(HostedGithubChangeSchema).max(2e3)
20100
+ }).strict().refine((v) => {
20101
+ if (new Set(v.changes.map((c) => c.path)).size !== v.changes.length)
20102
+ return false;
20103
+ for (const side of ["before_base64", "after_base64"]) {
20104
+ const values = v.changes.map((c) => c[side]).filter((s) => s !== null);
20105
+ if (values.length > 1e3 || values.reduce((sum, s) => sum + bytes(s), 0) > 8 * 1024 * 1024)
20106
+ return false;
20107
+ }
20108
+ return !v.resumable || v.state !== "running" && v.pull_request_url === null;
20109
+ });
20110
+ var HostedGithubTaskListSchema = external_exports.object({ tasks: external_exports.array(HostedGithubTaskSummarySchema).max(10) }).strict();
20111
+
19830
20112
  // ../protocol/dist/src/index.js
19831
20113
  var sessionModes = ["solo", "multi"];
19832
20114
  var membershipRoles = ["owner", "participant", "viewer"];
@@ -19841,7 +20123,8 @@ var eventTypes = [
19841
20123
  "attachment",
19842
20124
  "context_snapshot",
19843
20125
  "membership_change",
19844
- "session_state_change"
20126
+ "session_state_change",
20127
+ "message_deleted"
19845
20128
  ];
19846
20129
  var eventVisibilities = ["session", "owner_only"];
19847
20130
  var captureFidelities = [
@@ -19938,8 +20221,18 @@ var CanonicalEventSchema = external_exports.object({
19938
20221
  visibility: EventVisibilitySchema,
19939
20222
  reply_to_event_id: IdSchema.nullable(),
19940
20223
  payload: JsonValueSchema,
19941
- runtime_provenance: RuntimeProvenanceSchema.nullable()
20224
+ runtime_provenance: RuntimeProvenanceSchema.nullable(),
20225
+ content_revision: external_exports.number().int().nonnegative().optional(),
20226
+ deleted: external_exports.object({ deleted_at: external_exports.string().datetime(), reason: external_exports.enum(["author", "request", "summary_invalidated"]) }).strict().optional()
19942
20227
  });
20228
+ var DeleteMessageInputSchema = external_exports.object({ idempotency_key: IdempotencyKeySchema }).strict();
20229
+ var DeleteMessageResultSchema = external_exports.object({
20230
+ event: CanonicalEventSchema,
20231
+ target_event_id: IdSchema,
20232
+ content_revision: external_exports.number().int().nonnegative(),
20233
+ affected_event_ids: external_exports.array(IdSchema).max(256),
20234
+ affected_truncated: external_exports.boolean()
20235
+ }).strict();
19943
20236
  var MessageMentionsSchema = external_exports.array(external_exports.object({
19944
20237
  user_id: IdSchema,
19945
20238
  start: external_exports.number().int().nonnegative().max(1e6),
@@ -19968,9 +20261,70 @@ var AppendEventInputSchema = external_exports.object({
19968
20261
  payload: JsonValueSchema,
19969
20262
  runtime_id: IdSchema.optional(),
19970
20263
  claim_attempt: external_exports.number().int().positive().optional(),
20264
+ content_revision: external_exports.number().int().nonnegative().optional(),
19971
20265
  observed_model: external_exports.string().trim().min(1).max(160).regex(/^[^\u0000-\u001f\u007f-\u009f]+$/u).optional(),
19972
20266
  observed_reasoning_effort: external_exports.string().trim().min(1).max(80).regex(/^[^\u0000-\u001f\u007f-\u009f]+$/u).optional()
19973
20267
  });
20268
+ var HostedAgentRequestInputSchema = external_exports.object({
20269
+ profile_id: external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/u).default("default"),
20270
+ content: external_exports.string().trim().min(1).max(6e3),
20271
+ include_code: external_exports.boolean().default(false),
20272
+ idempotency_key: IdempotencyKeySchema,
20273
+ reply_to_event_id: IdSchema.nullable().optional(),
20274
+ github_task_id: external_exports.string().regex(/^gh-task-[a-f0-9]{32}$/u).optional(),
20275
+ continue_from_event_id: IdSchema.optional(),
20276
+ api_config_revision: PersonalCloudRevisionSchema,
20277
+ content_revision: external_exports.number().int().nonnegative().optional()
20278
+ }).strict().refine(hasValidPersonalCloudRevision, { message: "Personal cloud profiles require their exact configuration revision" });
20279
+ var HostedAgentFailurePayloadSchema = external_exports.object({
20280
+ content: external_exports.string().min(1).max(512),
20281
+ status: external_exports.literal("failed"),
20282
+ outcome: external_exports.enum(["timed_out", "failed"]),
20283
+ error: external_exports.object({
20284
+ code: external_exports.string().regex(/^[a-z][a-z0-9_]{0,63}$/u),
20285
+ provider_attempts: external_exports.number().int().min(0).max(64).optional()
20286
+ }).strict()
20287
+ }).strict();
20288
+ var HostedHistorySummaryInputSchema = CreateHistorySummaryInputSchema.omit({ execution_profile: true }).extend({ profile_id: HostedAgentRequestInputSchema.shape.profile_id.unwrap(), api_config_revision: PersonalCloudRevisionSchema, continue_from_event_id: IdSchema.optional() }).strict().refine(hasValidPersonalCloudRevision, { message: "Personal cloud profiles require their exact configuration revision" });
20289
+ var HostedAgentAcceptedSchema = external_exports.object({
20290
+ request_event: CanonicalEventSchema,
20291
+ replayed: external_exports.boolean()
20292
+ }).strict();
20293
+ var HostedAgentPausedSchema = external_exports.object({
20294
+ request_event_id: IdSchema,
20295
+ status: external_exports.literal("paused")
20296
+ }).strict();
20297
+ var HostedAgentStatusSchema = external_exports.discriminatedUnion("enabled", [
20298
+ external_exports.object({ enabled: external_exports.literal(false) }).strict(),
20299
+ external_exports.object({
20300
+ enabled: external_exports.literal(true),
20301
+ harness: external_exports.literal("opencode"),
20302
+ utc_day: external_exports.string(),
20303
+ user_limit_runs: external_exports.number().int().nonnegative().nullable(),
20304
+ user_used_runs: external_exports.number().int().nonnegative(),
20305
+ global_limit_runs: external_exports.number().int().nonnegative().nullable(),
20306
+ global_used_runs: external_exports.number().int().nonnegative(),
20307
+ rate_limits: external_exports.object({
20308
+ user_min_interval_seconds: external_exports.number().int().min(1).max(3600),
20309
+ user_max_concurrent: external_exports.number().int().min(1).max(8),
20310
+ user_active_runs: external_exports.number().int().nonnegative(),
20311
+ retry_after_seconds: external_exports.number().int().nonnegative()
20312
+ }).strict().optional(),
20313
+ profiles: external_exports.array(external_exports.object({
20314
+ id: external_exports.string(),
20315
+ label: external_exports.string(),
20316
+ provider: external_exports.string(),
20317
+ model: external_exports.string(),
20318
+ available: external_exports.boolean(),
20319
+ capacity: external_exports.number().int().nonnegative(),
20320
+ status: external_exports.enum(["available", "busy", "daily_limit", "cooldown", "user_busy", "rate_limit"]),
20321
+ source: external_exports.enum(["service", "personal"]).optional(),
20322
+ api_config_revision: external_exports.string().uuid().optional()
20323
+ }).strict()),
20324
+ capabilities: external_exports.array(external_exports.string()),
20325
+ privacy: external_exports.string()
20326
+ }).strict()
20327
+ ]);
19974
20328
  var SingleLineTitleSchema = external_exports.string().regex(/^[^\u0000-\u001f\u007f-\u009f]*$/u, "Title must not contain control characters").trim().min(1).max(200);
19975
20329
  var SessionTitleSchema = SingleLineTitleSchema;
19976
20330
  var ProjectTitleSchema = SingleLineTitleSchema;
@@ -20017,7 +20371,12 @@ var ProjectListItemSchema = ProjectRecordSchema.omit({ owner_user_id: true }).ex
20017
20371
  role: MembershipRoleSchema,
20018
20372
  session_count: external_exports.number().int().nonnegative()
20019
20373
  });
20374
+ var AvatarIdSchema = external_exports.enum(["cat", "fox", "bear", "rabbit", "penguin", "owl"]);
20375
+ var SetAccountAvatarInputSchema = external_exports.object({ avatar_id: AvatarIdSchema.nullable() }).strict();
20376
+ var AccountAvatarProfileSchema = external_exports.object({ user_id: IdSchema, avatar_id: AvatarIdSchema.nullable() });
20377
+ var SessionAvatarProfilesSchema = external_exports.object({ profiles: external_exports.array(AccountAvatarProfileSchema) });
20020
20378
  var ProjectMemberRecordSchema = external_exports.object({
20379
+ avatar_id: AvatarIdSchema.nullable().optional(),
20021
20380
  user_id: IdSchema,
20022
20381
  display_name: external_exports.string().trim().min(1).max(120),
20023
20382
  role: MembershipRoleSchema
@@ -20072,6 +20431,17 @@ var ClaimInvitationInputSchema = external_exports.object({
20072
20431
  var AcceptInvitationInputSchema = external_exports.object({
20073
20432
  invite_token: external_exports.string().min(32).max(512)
20074
20433
  });
20434
+ var PreviewProjectInvitationInputSchema = external_exports.object({
20435
+ invite_token: external_exports.string().regex(/^gti_[A-Za-z0-9_-]{32,508}$/)
20436
+ }).strict();
20437
+ var ProjectInvitationPreviewSchema = external_exports.object({
20438
+ project: external_exports.object({ id: IdSchema, title: external_exports.string().min(1).max(200) }).strict(),
20439
+ inviter_display_name: external_exports.string().min(1).max(120),
20440
+ role: InvitationRoleSchema,
20441
+ created_at: external_exports.string().datetime(),
20442
+ expires_at: external_exports.string().datetime(),
20443
+ already_member: external_exports.boolean()
20444
+ }).strict();
20075
20445
  var InvitationRecordSchema = external_exports.object({
20076
20446
  id: IdSchema,
20077
20447
  session_id: IdSchema,
@@ -20146,6 +20516,10 @@ var DeviceAuthorizationRecordSchema = external_exports.object({
20146
20516
  claimed_at: external_exports.string().datetime().nullable(),
20147
20517
  claimed_by_device_id: IdSchema.nullable()
20148
20518
  });
20519
+ var CodexLauncherAuthorizationSchema = external_exports.object({
20520
+ authorization: DeviceAuthorizationRecordSchema.strict(),
20521
+ email: RegistrationEmailSchema
20522
+ }).strict();
20149
20523
  var RotateDeviceTokenInputSchema = external_exports.object({
20150
20524
  expires_at: external_exports.string().datetime().nullable().optional()
20151
20525
  });
@@ -20180,6 +20554,7 @@ var RegisterRuntimeInputSchema = external_exports.object({
20180
20554
  provider: external_exports.string().trim().min(1).max(80),
20181
20555
  model: external_exports.string().trim().min(1).max(160),
20182
20556
  execution_profiles: RuntimeExecutionProfilesSchema.optional(),
20557
+ context_deletion_version: external_exports.literal(1).optional(),
20183
20558
  local_session_id: external_exports.string().trim().min(1).max(512),
20184
20559
  capture_fidelity: CaptureFidelitySchema
20185
20560
  });
@@ -20193,11 +20568,13 @@ var AgentRequestClaimSchema = external_exports.object({
20193
20568
  // `paused` is a decision by the requester, not a claim a runtime can execute.
20194
20569
  // It is reported so a connector can skip the request without advancing past it.
20195
20570
  status: external_exports.enum(["claimed", "completed", "paused"]),
20196
- attempt_count: external_exports.number().int().positive().optional()
20571
+ attempt_count: external_exports.number().int().positive().optional(),
20572
+ content_revision: external_exports.number().int().nonnegative().optional()
20197
20573
  }).strict();
20198
20574
  var CompleteAgentRequestInputSchema = external_exports.object({
20199
20575
  runtime_id: IdSchema,
20200
20576
  claim_attempt: external_exports.number().int().positive().optional(),
20577
+ content_revision: external_exports.number().int().nonnegative().optional(),
20201
20578
  idempotency_key: IdempotencyKeySchema,
20202
20579
  payload: JsonValueSchema,
20203
20580
  observed_model: external_exports.string().trim().min(1).max(160).regex(/^[^\u0000-\u001f\u007f-\u009f]+$/u).optional(),
@@ -20212,6 +20589,7 @@ var CommitLocalTurnInputSchema = external_exports.object({
20212
20589
  local_turn_id: IdSchema,
20213
20590
  runtime_id: IdSchema,
20214
20591
  based_on_sequence: external_exports.number().int().nonnegative(),
20592
+ content_revision: external_exports.number().int().nonnegative().optional(),
20215
20593
  occurred_at: external_exports.string().datetime(),
20216
20594
  observed_model: external_exports.string().trim().min(1).max(160).regex(/^[^\u0000-\u001f\u007f-\u009f]+$/u).optional(),
20217
20595
  observed_reasoning_effort: external_exports.string().trim().min(1).max(80).regex(/^[^\u0000-\u001f\u007f-\u009f]+$/u).optional(),
@@ -20232,6 +20610,7 @@ var snapshotRequestStatuses = ["pending", "claimed", "completed", "failed"];
20232
20610
  var SnapshotRequestStatusSchema = external_exports.enum(snapshotRequestStatuses);
20233
20611
  var snapshotRequestKinds = [
20234
20612
  ...codeSyncRequestKinds,
20613
+ ...githubCodeSyncRequestKinds,
20235
20614
  "immutable",
20236
20615
  "visible_history_replace",
20237
20616
  "local_sync_status",
@@ -20264,6 +20643,7 @@ var SnapshotRequestRecordSchema = external_exports.object({
20264
20643
  kind: SnapshotRequestKindSchema,
20265
20644
  through_sequence: external_exports.number().int().nonnegative(),
20266
20645
  status: SnapshotRequestStatusSchema,
20646
+ content_revision: external_exports.number().int().nonnegative().optional(),
20267
20647
  target_runtime_id: IdSchema.nullable(),
20268
20648
  claimed_by_runtime_id: IdSchema.nullable(),
20269
20649
  created_at: external_exports.string().datetime(),
@@ -20289,7 +20669,8 @@ var SubscribeMessageSchema = external_exports.object({
20289
20669
  var ReplayResponseSchema = external_exports.object({
20290
20670
  events: external_exports.array(CanonicalEventSchema),
20291
20671
  cursor: external_exports.number().int().nonnegative(),
20292
- has_more: external_exports.boolean()
20672
+ has_more: external_exports.boolean(),
20673
+ content_revision: external_exports.number().int().nonnegative().optional()
20293
20674
  });
20294
20675
  var SessionListItemSchema = external_exports.object({
20295
20676
  id: IdSchema,
@@ -20300,6 +20681,7 @@ var SessionListItemSchema = external_exports.object({
20300
20681
  state: external_exports.enum(["active", "archived"]),
20301
20682
  role: MembershipRoleSchema,
20302
20683
  current_sequence: external_exports.number().int().nonnegative(),
20684
+ content_revision: external_exports.number().int().nonnegative().optional(),
20303
20685
  member_count: external_exports.number().int().nonnegative().optional(),
20304
20686
  updated_at: external_exports.string().datetime()
20305
20687
  });
@@ -20403,7 +20785,8 @@ var HttpCollaborationClient = class {
20403
20785
  return {
20404
20786
  events,
20405
20787
  nextSequence: requiredNumber(body.cursor, "cursor"),
20406
- hasMore: body.has_more === true
20788
+ hasMore: body.has_more === true,
20789
+ ...body.content_revision === void 0 ? {} : { contentRevision: requiredRevision(body.content_revision, "events.content_revision") }
20407
20790
  };
20408
20791
  }
20409
20792
  async readContext(sessionId, view, throughSequence) {
@@ -20461,7 +20844,8 @@ var HttpCollaborationClient = class {
20461
20844
  status,
20462
20845
  requestId: requiredString(body.request_event_id, "claim.request_event_id"),
20463
20846
  runtimeId: requiredString(body.runtime_id, "claim.runtime_id"),
20464
- attemptCount
20847
+ attemptCount,
20848
+ ...body.content_revision === void 0 ? {} : { contentRevision: requiredRevision(body.content_revision, "claim.content_revision") }
20465
20849
  };
20466
20850
  }
20467
20851
  async completeAgentRequest(sessionId, requestId, input2) {
@@ -20470,6 +20854,7 @@ var HttpCollaborationClient = class {
20470
20854
  body: JSON.stringify({
20471
20855
  runtime_id: input2.runtimeId,
20472
20856
  ...input2.claimAttempt === void 0 ? {} : { claim_attempt: input2.claimAttempt },
20857
+ ...input2.contentRevision === void 0 ? {} : { content_revision: input2.contentRevision },
20473
20858
  idempotency_key: input2.idempotencyKey,
20474
20859
  payload: truncateJsonValue(input2.payload, 160 * 1024),
20475
20860
  ...input2.observedModel === void 0 ? {} : { observed_model: input2.observedModel },
@@ -20484,6 +20869,7 @@ var HttpCollaborationClient = class {
20484
20869
  body: JSON.stringify({
20485
20870
  runtime_id: input2.runtimeId,
20486
20871
  ...input2.claimAttempt === void 0 ? {} : { claim_attempt: input2.claimAttempt },
20872
+ ...input2.contentRevision === void 0 ? {} : { content_revision: input2.contentRevision },
20487
20873
  idempotency_key: input2.idempotencyKey,
20488
20874
  payload: truncateJsonValue(input2.payload, 32 * 1024),
20489
20875
  ...input2.observedModel === void 0 ? {} : { observed_model: input2.observedModel },
@@ -20500,6 +20886,7 @@ var HttpCollaborationClient = class {
20500
20886
  local_turn_id: input2.localTurnId,
20501
20887
  runtime_id: input2.runtimeId,
20502
20888
  based_on_sequence: input2.basedOnSequence,
20889
+ ...input2.contentRevision === void 0 ? {} : { content_revision: input2.contentRevision },
20503
20890
  occurred_at: input2.occurredAt,
20504
20891
  ...input2.observedModel === void 0 ? {} : { observed_model: input2.observedModel },
20505
20892
  ...input2.observedReasoningEffort === void 0 ? {} : { observed_reasoning_effort: input2.observedReasoningEffort },
@@ -20553,9 +20940,9 @@ var HttpCollaborationClient = class {
20553
20940
  }));
20554
20941
  return fromWireSnapshotRequest(body.snapshot_request ?? body);
20555
20942
  }
20556
- async #request(path10, init = {}) {
20943
+ async #request(path11, init = {}) {
20557
20944
  const signal = combineSignals(init.signal ?? void 0, this.#signal, AbortSignal.timeout(this.#requestTimeoutMs));
20558
- const response = await this.#fetch(`${this.#baseUrl}${path10}`, {
20945
+ const response = await this.#fetch(`${this.#baseUrl}${path11}`, {
20559
20946
  ...init,
20560
20947
  redirect: "error",
20561
20948
  signal,
@@ -20606,6 +20993,7 @@ function toWireEvent(event) {
20606
20993
  visibility: event.visibility ?? "session",
20607
20994
  runtime_id: event.runtimeId ?? event.runtime?.runtimeId,
20608
20995
  claim_attempt: event.claimAttempt,
20996
+ ...event.contentRevision === void 0 ? {} : { content_revision: event.contentRevision },
20609
20997
  observed_model: event.observedModel,
20610
20998
  observed_reasoning_effort: event.observedReasoningEffort
20611
20999
  };
@@ -20631,7 +21019,8 @@ function toWireRuntimeRegistration(runtime) {
20631
21019
  local_session_id: runtime.localSessionId,
20632
21020
  capture_fidelity: runtime.captureFidelity,
20633
21021
  ...runtime.capabilities === void 0 ? {} : { capabilities: [...runtime.capabilities] },
20634
- ...runtime.purpose === void 0 ? {} : { purpose: runtime.purpose }
21022
+ ...runtime.purpose === void 0 ? {} : { purpose: runtime.purpose },
21023
+ ...runtime.contextDeletionVersion === void 0 ? {} : { context_deletion_version: runtime.contextDeletionVersion }
20635
21024
  };
20636
21025
  }
20637
21026
  function validateContextReadInput(sessionId, view, throughSequence) {
@@ -20692,7 +21081,17 @@ function parseHistoryContext(value, expectedView, throughSequence) {
20692
21081
  if (items.some((item) => sourceIds.has(item.event_id))) {
20693
21082
  throw new Error("Collaboration API context repeats summarized sources");
20694
21083
  }
20695
- return { view: value.view, through_sequence: Number(value.through_sequence), items };
21084
+ return {
21085
+ view: value.view,
21086
+ through_sequence: Number(value.through_sequence),
21087
+ items,
21088
+ ...value.content_revision === void 0 ? {} : { content_revision: requiredRevision(value.content_revision, "context.content_revision") }
21089
+ };
21090
+ }
21091
+ function requiredRevision(value, label) {
21092
+ if (!Number.isSafeInteger(value) || Number(value) < 0)
21093
+ throw new Error(`Collaboration API returned invalid ${label}`);
21094
+ return Number(value);
20696
21095
  }
20697
21096
  function isContextId(value) {
20698
21097
  return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value);
@@ -20711,9 +21110,17 @@ function fromWireEvent(value) {
20711
21110
  ...typeof input2.reply_to_event_id === "string" ? { replyTo: input2.reply_to_event_id } : {},
20712
21111
  timestamp: requiredString(input2.created_at, "event.created_at"),
20713
21112
  payload: input2.payload,
21113
+ ...input2.content_revision === void 0 ? {} : { contentRevision: requiredRevision(input2.content_revision, "event.content_revision") },
21114
+ ...input2.deleted === void 0 ? {} : { deleted: fromWireDeletion(input2.deleted) },
20714
21115
  ...runtime === void 0 ? {} : { runtime }
20715
21116
  };
20716
21117
  }
21118
+ function fromWireDeletion(value) {
21119
+ const input2 = requiredObject(value);
21120
+ if (input2.reason !== "author" && input2.reason !== "request" && input2.reason !== "summary_invalidated")
21121
+ throw new Error("Collaboration API returned invalid event.deleted reason");
21122
+ return { deletedAt: requiredString(input2.deleted_at, "event.deleted.deleted_at"), reason: input2.reason };
21123
+ }
20717
21124
  function fromWireSnapshotRequest(value) {
20718
21125
  const input2 = requiredObject(value);
20719
21126
  const status = requiredString(input2.status, "snapshot_request.status");
@@ -20749,6 +21156,7 @@ function fromWireSnapshotRequest(value) {
20749
21156
  kind,
20750
21157
  ...typeof input2.target_runtime_id === "string" ? { targetRuntimeId: input2.target_runtime_id } : {},
20751
21158
  throughSequence: requiredNumber(input2.through_sequence, "snapshot_request.through_sequence"),
21159
+ ...input2.content_revision === void 0 ? {} : { contentRevision: requiredRevision(input2.content_revision, "snapshot_request.content_revision") },
20752
21160
  status,
20753
21161
  ...createdAt === void 0 ? {} : { createdAt },
20754
21162
  ..."result" in input2 ? { result: input2.result } : {},
@@ -20768,7 +21176,8 @@ function fromWireSession(value) {
20768
21176
  ...input2.state === "active" || input2.state === "archived" ? { state: input2.state } : {},
20769
21177
  ...name2 === void 0 ? {} : { name: name2 },
20770
21178
  ...role === void 0 ? {} : { role },
20771
- ...latestSequence === void 0 ? {} : { latestSequence }
21179
+ ...latestSequence === void 0 ? {} : { latestSequence },
21180
+ ...input2.content_revision === void 0 ? {} : { contentRevision: requiredRevision(input2.content_revision, "session.content_revision") }
20772
21181
  };
20773
21182
  }
20774
21183
  function fromWireProject(value) {
@@ -20815,7 +21224,8 @@ function fromWireRuntime(value) {
20815
21224
  ...input2.execution_profiles === void 0 || input2.execution_profiles === null ? {} : { executionProfiles: fromWireExecutionProfiles(input2.execution_profiles) },
20816
21225
  localSessionId: requiredString(input2.local_session_id, "runtime.local_session_id"),
20817
21226
  captureFidelity: requiredString(input2.capture_fidelity, "runtime.capture_fidelity"),
20818
- purpose
21227
+ purpose,
21228
+ ...input2.context_deletion_version === 1 ? { contextDeletionVersion: 1 } : {}
20819
21229
  };
20820
21230
  }
20821
21231
  function fromWireExecutionProfiles(value) {
@@ -20946,8 +21356,8 @@ function isObject2(value) {
20946
21356
 
20947
21357
  // ../adapters/dist/src/redaction.js
20948
21358
  var REDACTED = "[REDACTED]";
21359
+ var PRIVATE_KEY_PATTERN = /-----BEGIN (?:ENCRYPTED |RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----[\s\S]*?(?:-----END (?:ENCRYPTED |RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----|$)/g;
20949
21360
  var DEFAULT_SECRET_PATTERNS = [
20950
- /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/g,
20951
21361
  /\bAKIA[0-9A-Z]{16}\b/g,
20952
21362
  /\b(?:ghp|github_pat|glpat|sk|xox[baprs])[-_][A-Za-z0-9_-]{16,}\b/g,
20953
21363
  /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}\b/gi,
@@ -20960,6 +21370,8 @@ var SENSITIVE_KEY_NAMES = /* @__PURE__ */ new Set([
20960
21370
  "accesstoken",
20961
21371
  "authtoken",
20962
21372
  "authorization",
21373
+ "cookie",
21374
+ "setcookie",
20963
21375
  "bearer",
20964
21376
  "clientsecret",
20965
21377
  "password",
@@ -20976,7 +21388,83 @@ var SENSITIVE_KEY_NAMES = /* @__PURE__ */ new Set([
20976
21388
  ]);
20977
21389
  function redactText(value, options = {}) {
20978
21390
  const replacement = options.replacement ?? REDACTED;
20979
- return [...DEFAULT_SECRET_PATTERNS, ...options.extraPatterns ?? []].reduce((redacted, pattern) => redacted.replace(cloneGlobal(pattern), (match, prefix) => typeof prefix === "string" && prefix.length < match.length ? `${prefix}${replacement}` : replacement), value);
21391
+ return [...DEFAULT_SECRET_PATTERNS, ...options.extraPatterns ?? []].reduce(
21392
+ (redacted, pattern) => redacted.replace(cloneGlobal(pattern), (match, prefix) => typeof prefix === "string" && prefix.length < match.length ? `${prefix}${replacement}` : replacement),
21393
+ // Mask PEM first: scanning `private_key: -----BEGIN ...` as an ordinary
21394
+ // assignment must not break its header and leave the following body public.
21395
+ redactSensitiveFields(value.replace(cloneGlobal(PRIVATE_KEY_PATTERN), () => replacement), replacement)
21396
+ );
21397
+ }
21398
+ function redactSensitiveFields(text, replacement) {
21399
+ const fields = /("(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'|\b[A-Za-z_][A-Za-z0-9_.-]*)(\s*[:=]\s*)/gu;
21400
+ const parts = [];
21401
+ let retainedFrom = 0;
21402
+ let match;
21403
+ while ((match = fields.exec(text)) !== null) {
21404
+ const encodedKey = match[1];
21405
+ let key = encodedKey;
21406
+ if (encodedKey.startsWith('"')) {
21407
+ try {
21408
+ key = JSON.parse(encodedKey);
21409
+ } catch {
21410
+ continue;
21411
+ }
21412
+ } else if (encodedKey.startsWith("'"))
21413
+ key = encodedKey.slice(1, -1);
21414
+ if (!isSensitiveKey(key))
21415
+ continue;
21416
+ const start = fields.lastIndex;
21417
+ const end = sensitiveValueEnd(text, start, encodedKey[0] !== '"' && encodedKey[0] !== "'" && /^(?:authorization|cookie|set-cookie)$/iu.test(key));
21418
+ const quote = text[start];
21419
+ const masked = quote === "'" ? `'${replacement.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'` : quote === '"' || encodedKey.startsWith('"') ? JSON.stringify(replacement) : replacement;
21420
+ parts.push(text.slice(retainedFrom, start), masked);
21421
+ retainedFrom = end;
21422
+ fields.lastIndex = end;
21423
+ }
21424
+ return parts.length === 0 ? text : parts.join("") + text.slice(retainedFrom);
21425
+ }
21426
+ function sensitiveValueEnd(text, start, header) {
21427
+ if (header) {
21428
+ const end = text.indexOf("\n", start);
21429
+ return end < 0 ? text.length : end;
21430
+ }
21431
+ const first = text[start];
21432
+ if (first === '"' || first === "'") {
21433
+ for (let index2 = start + 1; index2 < text.length; index2++) {
21434
+ if (text[index2] === "\\")
21435
+ index2++;
21436
+ else if (text[index2] === first)
21437
+ return index2 + 1;
21438
+ }
21439
+ return text.length;
21440
+ }
21441
+ if (first === "{" || first === "[") {
21442
+ const closing = [first === "{" ? "}" : "]"];
21443
+ let quote;
21444
+ for (let index2 = start + 1; index2 < text.length; index2++) {
21445
+ const character = text[index2];
21446
+ if (quote !== void 0) {
21447
+ if (character === "\\")
21448
+ index2++;
21449
+ else if (character === quote)
21450
+ quote = void 0;
21451
+ } else if (character === '"' || character === "'")
21452
+ quote = character;
21453
+ else if (character === "{" || character === "[")
21454
+ closing.push(character === "{" ? "}" : "]");
21455
+ else if (character === "}" || character === "]") {
21456
+ if (character !== closing.pop())
21457
+ return text.length;
21458
+ if (closing.length === 0)
21459
+ return index2 + 1;
21460
+ }
21461
+ }
21462
+ return text.length;
21463
+ }
21464
+ let index = start;
21465
+ while (index < text.length && !/[\s,;}\]]/u.test(text[index]))
21466
+ index++;
21467
+ return index;
20980
21468
  }
20981
21469
  function redactValue(value, options = {}) {
20982
21470
  if (typeof value === "string")
@@ -20998,6 +21486,208 @@ function cloneGlobal(pattern) {
20998
21486
  return new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
20999
21487
  }
21000
21488
 
21489
+ // ../adapters/dist/src/live-output.js
21490
+ import { randomUUID } from "node:crypto";
21491
+ var MAX_ITEMS = 128;
21492
+ var MAX_INPUT_BYTES = 96 * 1024;
21493
+ var MAX_PUBLIC_BYTES = 24 * 1024;
21494
+ var MAX_PUBLICATIONS = 1200;
21495
+ function createLiveOutputPublisher(options) {
21496
+ const items = /* @__PURE__ */ new Map();
21497
+ const now = options.now ?? Date.now;
21498
+ const intervalMs = options.intervalMs ?? 1500;
21499
+ let lastPublished, publications = 0, closed = false;
21500
+ let timer;
21501
+ let queue = Promise.resolve();
21502
+ let lastVisited;
21503
+ const serial = (operation) => {
21504
+ queue = queue.then(operation, operation);
21505
+ return queue;
21506
+ };
21507
+ const itemFor = (key, kind, title) => {
21508
+ let item = items.get(key);
21509
+ if (!item && items.size < MAX_ITEMS) {
21510
+ item = {
21511
+ streamId: randomUUID(),
21512
+ raw: "",
21513
+ kind,
21514
+ state: "running",
21515
+ revision: 0,
21516
+ acknowledged: "",
21517
+ dirty: true,
21518
+ truncated: false
21519
+ };
21520
+ items.set(key, item);
21521
+ }
21522
+ if (item && title !== void 0)
21523
+ item.title = bound(redactText(title, options.redaction), 256);
21524
+ return item;
21525
+ };
21526
+ const safe = (item) => {
21527
+ let content = item.raw;
21528
+ if (item.state === "running") {
21529
+ content = content.replace(/(?:\bBearer\s+|\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|password|passwd|secret|token)["']?\s*[=:]\s*["']?)[^\n,;]*$/giu, "");
21530
+ let end = content.length;
21531
+ while (end > 0 && /[A-Za-z0-9_./~+=:@%\-]/u.test(content[end - 1]))
21532
+ end--;
21533
+ content = content.slice(0, end);
21534
+ }
21535
+ content = redactText(content, options.redaction);
21536
+ return bound(content, MAX_PUBLIC_BYTES, item.truncated);
21537
+ };
21538
+ const signatureFor = (item) => JSON.stringify([safe(item), item.state, item.title, item.plan]);
21539
+ const schedule = () => {
21540
+ if (closed || options.schedule === false || timer || ![...items.values()].some((x) => x.dirty))
21541
+ return;
21542
+ timer = setTimeout(() => {
21543
+ timer = void 0;
21544
+ void flush().catch(() => void 0);
21545
+ }, intervalMs);
21546
+ timer.unref();
21547
+ };
21548
+ const publishOne = async (item) => {
21549
+ if (!item.dirty || publications >= MAX_PUBLICATIONS)
21550
+ return;
21551
+ const content = safe(item);
21552
+ const signature = JSON.stringify([content, item.state, item.title, item.plan]);
21553
+ if (!item.pending && signature === item.acknowledged) {
21554
+ item.dirty = false;
21555
+ return;
21556
+ }
21557
+ if (!item.pending && !content.trim() && item.plan === void 0)
21558
+ return;
21559
+ const revision = item.revision + 1;
21560
+ const update = {
21561
+ id: `${item.streamId}:${revision}`,
21562
+ content,
21563
+ phase: "stream",
21564
+ streamId: item.streamId,
21565
+ streamMode: "snapshot",
21566
+ streamKind: item.kind,
21567
+ streamRevision: revision,
21568
+ streamState: item.state,
21569
+ ...item.title === void 0 ? {} : { title: item.title },
21570
+ ...item.plan === void 0 ? {} : { plan: item.plan }
21571
+ };
21572
+ item.pending ??= { update, signature };
21573
+ const pending = item.pending;
21574
+ try {
21575
+ if (await options.publish(pending.update) === false)
21576
+ return;
21577
+ item.revision = pending.update.streamRevision;
21578
+ item.acknowledged = pending.signature;
21579
+ delete item.pending;
21580
+ item.dirty = signatureFor(item) !== item.acknowledged;
21581
+ lastPublished = now();
21582
+ publications++;
21583
+ } catch {
21584
+ }
21585
+ };
21586
+ const flush = () => serial(async () => {
21587
+ if (closed || lastPublished !== void 0 && now() - lastPublished < intervalMs) {
21588
+ schedule();
21589
+ return;
21590
+ }
21591
+ const ordered = [...items.values()];
21592
+ const start = lastVisited === void 0 ? 0 : (ordered.indexOf(lastVisited) + 1) % ordered.length;
21593
+ for (let offset = 0; offset < ordered.length; offset++) {
21594
+ const item = ordered[(start + offset) % ordered.length];
21595
+ if (!item.dirty)
21596
+ continue;
21597
+ lastVisited = item;
21598
+ await publishOne(item);
21599
+ if (lastPublished !== void 0 && now() - lastPublished < intervalMs)
21600
+ break;
21601
+ }
21602
+ schedule();
21603
+ });
21604
+ return {
21605
+ async onDelta(key, delta, kind = "answer", title) {
21606
+ if (closed || !delta)
21607
+ return;
21608
+ const item = itemFor(key, kind, title);
21609
+ if (!item || item.state !== "running" || item.truncated)
21610
+ return;
21611
+ const raw = item.raw + delta;
21612
+ item.raw = bound(raw, MAX_INPUT_BYTES);
21613
+ item.truncated = Buffer.byteLength(raw) > MAX_INPUT_BYTES;
21614
+ item.dirty = true;
21615
+ await flush();
21616
+ },
21617
+ async onSnapshot(key, text, kind = "answer", title, state = "running") {
21618
+ if (closed)
21619
+ return;
21620
+ const item = itemFor(key, kind, title);
21621
+ if (!item)
21622
+ return;
21623
+ item.raw = bound(text, MAX_INPUT_BYTES);
21624
+ item.truncated = Buffer.byteLength(text) > MAX_INPUT_BYTES;
21625
+ item.state = state;
21626
+ item.dirty = true;
21627
+ await flush();
21628
+ },
21629
+ async onPlan(key, steps) {
21630
+ if (closed)
21631
+ return;
21632
+ const item = itemFor(key, "plan");
21633
+ if (!item)
21634
+ return;
21635
+ item.plan = steps.slice(0, 64).map((step) => ({ step: bound(redactText(step.step, options.redaction), 512), status: step.status }));
21636
+ item.raw = item.plan.map((step) => `${step.status}: ${step.step}`).join("\n");
21637
+ item.dirty = true;
21638
+ await flush();
21639
+ },
21640
+ flush,
21641
+ async finish(success2 = true) {
21642
+ if (timer) {
21643
+ clearTimeout(timer);
21644
+ timer = void 0;
21645
+ }
21646
+ await serial(async () => {
21647
+ if (closed)
21648
+ return;
21649
+ for (const item of [...items.values()].slice(0, 64)) {
21650
+ if (item.state === "running") {
21651
+ item.state = success2 ? "completed" : "failed";
21652
+ item.dirty = true;
21653
+ }
21654
+ await publishOne(item);
21655
+ if (item.dirty && !item.pending)
21656
+ await publishOne(item);
21657
+ }
21658
+ closed = true;
21659
+ items.clear();
21660
+ });
21661
+ },
21662
+ lastPublishedAt: () => lastPublished
21663
+ };
21664
+ }
21665
+ function liveOutputPayload(update) {
21666
+ return {
21667
+ content: update.content,
21668
+ phase: update.phase,
21669
+ stream_id: update.streamId,
21670
+ stream_mode: update.streamMode,
21671
+ stream_kind: update.streamKind,
21672
+ stream_revision: update.streamRevision,
21673
+ stream_state: update.streamState,
21674
+ ...update.title === void 0 ? {} : { title: update.title },
21675
+ ...update.plan === void 0 ? {} : { plan: update.plan.map((step) => ({ step: step.step, status: step.status })) }
21676
+ };
21677
+ }
21678
+ function bound(text, maxBytes, alreadyTruncated = false) {
21679
+ if (!alreadyTruncated && Buffer.byteLength(text) <= maxBytes)
21680
+ return text;
21681
+ const suffix = "\n[TRUNCATED]";
21682
+ const bytes2 = Buffer.from(text).subarray(0, maxBytes - Buffer.byteLength(suffix));
21683
+ let end = bytes2.length;
21684
+ while (end > 0 && (bytes2[end - 1] & 192) === 128)
21685
+ end--;
21686
+ if (end < bytes2.length || (bytes2[end - 1] & 128) !== 0)
21687
+ end = Math.max(0, end - 1);
21688
+ return bytes2.subarray(0, end).toString("utf8") + suffix;
21689
+ }
21690
+
21001
21691
  // ../adapters/dist/src/jsonl.js
21002
21692
  var DEFAULT_MAX_READ_BYTES = 4 * 1024 * 1024;
21003
21693
 
@@ -21204,7 +21894,7 @@ function isNodeError(error62) {
21204
21894
  var MAX_FRAME_BYTES = 1024 * 1024;
21205
21895
 
21206
21896
  // ../bridge/dist/src/code-sync.js
21207
- import { createHash as createHash2, randomUUID } from "node:crypto";
21897
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
21208
21898
  import { execFile } from "node:child_process";
21209
21899
  import { constants } from "node:fs";
21210
21900
  import { chmod as chmod2, lstat as lstat2, mkdir as mkdir2, open as open3, readFile as readFile2, realpath, rename, unlink } from "node:fs/promises";
@@ -21228,10 +21918,14 @@ var ProjectCodeSync = class _ProjectCodeSync {
21228
21918
  #apiUrl;
21229
21919
  #root = "";
21230
21920
  #stateRoot = "";
21921
+ #lockRoot = "";
21231
21922
  #initialized;
21232
21923
  #stableDigest = "";
21233
21924
  #stableSince = 0;
21234
21925
  #lastAutomaticAttempt = 0;
21926
+ get #limits() {
21927
+ return this.#options.storage?.limits ?? CLOUD_CODE_SYNC_LIMITS;
21928
+ }
21235
21929
  constructor(options) {
21236
21930
  const url2 = new URL(options.apiUrl);
21237
21931
  if (url2.username || url2.password || url2.search || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname))) {
@@ -21265,6 +21959,9 @@ var ProjectCodeSync = class _ProjectCodeSync {
21265
21959
  this.#root = path2.join(await realpath(path2.dirname(requestedRoot)), path2.basename(requestedRoot));
21266
21960
  }
21267
21961
  this.#stateRoot = this.#options.stateRoot ?? path2.join(homedir2(), ".gatherthread", "code-sync", digest(this.#root));
21962
+ const sharedRoot = this.#stateRoot;
21963
+ if (this.#options.storage)
21964
+ this.#stateRoot = path2.join(this.#stateRoot, digest(this.#options.storage.key));
21268
21965
  if (isWithin(this.#root, path2.resolve(this.#stateRoot)))
21269
21966
  throw new CodeSyncError("code_sync_binding", "Code sync state must stay outside source files.");
21270
21967
  await mkdir2(this.#stateRoot, { recursive: true, mode: 448 });
@@ -21274,6 +21971,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
21274
21971
  if (isWithin(this.#root, this.#stateRoot))
21275
21972
  throw new CodeSyncError("code_sync_binding", "Private code sync state resolves inside source files.");
21276
21973
  await chmod2(this.#stateRoot, 448);
21974
+ this.#lockRoot = await realpath(sharedRoot);
21277
21975
  await this.#locked(async () => {
21278
21976
  const inventoryPath = path2.join(this.#stateRoot, "inventory.git");
21279
21977
  try {
@@ -21284,6 +21982,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
21284
21982
  throw error62;
21285
21983
  }
21286
21984
  await git(["init", "--bare", "--quiet", inventoryPath], this.#stateRoot);
21985
+ await this.#options.storage?.initialize(this.#stateRoot);
21287
21986
  const filename = path2.join(this.#stateRoot, "binding.json");
21288
21987
  try {
21289
21988
  await this.#readBinding();
@@ -21297,7 +21996,9 @@ var ProjectCodeSync = class _ProjectCodeSync {
21297
21996
  actor_id: this.#options.actorId,
21298
21997
  workspace_path: this.#root,
21299
21998
  base_commit: null,
21300
- automatic_upload: false,
21999
+ // GitHub authorization is an explicit local action. New GitHub
22000
+ // bindings follow settled source edits automatically thereafter.
22001
+ automatic_upload: this.#options.storage?.metadata.provider === "github",
21301
22002
  baseline: {}
21302
22003
  }));
21303
22004
  }
@@ -21348,7 +22049,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
21348
22049
  idempotency_key: `code-upload-${digest(JSON.stringify([this.#options.projectId, this.#options.actorId, uploadBase, inventory.hashes]))}`
21349
22050
  };
21350
22051
  await this.#requireWorkspace();
21351
- const parsed = CodeMutationResultSchema.safeParse(await this.#request("/checkpoints", body));
22052
+ const parsed = CodeMutationResultSchema.safeParse(this.#options.storage ? await this.#options.storage.checkpoint(body) : await this.#request("/checkpoints", body));
21352
22053
  if (!parsed.success)
21353
22054
  throw invalidResponse();
21354
22055
  const verified = await this.#snapshot(parsed.data.status);
@@ -21383,13 +22084,13 @@ var ProjectCodeSync = class _ProjectCodeSync {
21383
22084
  if (!sameHashes(current.hashes, binding.baseline))
21384
22085
  throw new CodeSyncError("code_sync_dirty", "Local files have unuploaded changes. Upload first, or recover cloud code into a new folder.");
21385
22086
  await applySnapshot(this.#root, snapshot.files, binding.baseline, {
21386
- backupRoot: path2.join(this.#stateRoot, "download-backups", randomUUID()),
22087
+ backupRoot: path2.join(this.#stateRoot, "download-backups", randomUUID2()),
21387
22088
  beforeWrite: async () => {
21388
22089
  binding.interrupted_download = true;
21389
22090
  await this.#save(binding);
21390
22091
  },
21391
22092
  baselineExecutables: new Set(binding.executable_paths ?? [])
21392
- });
22093
+ }, this.#limits);
21393
22094
  const verified = await this.#inventory(new Set(snapshot.files.filter((file2) => file2.executable).map((file2) => file2.path)));
21394
22095
  if (!sameHashes(verified.hashes, nextHashes))
21395
22096
  throw new CodeSyncError("code_sync_conflict", "Local files changed during download. Check the workspace before retrying.");
@@ -21427,14 +22128,28 @@ var ProjectCodeSync = class _ProjectCodeSync {
21427
22128
  const recoveryStem = [...path2.basename(this.#root)];
21428
22129
  while (Buffer.byteLength(recoveryStem.join("")) > 180)
21429
22130
  recoveryStem.pop();
21430
- const recoveryName = `${recoveryStem.join("")}-recovered-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/gu, "").slice(0, 14)}-${randomUUID().slice(0, 8)}`;
22131
+ const recoveryName = `${recoveryStem.join("")}-recovered-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/gu, "").slice(0, 14)}-${randomUUID2().slice(0, 8)}`;
21431
22132
  const recoveryPath = path2.join(path2.dirname(this.#root), recoveryName);
21432
22133
  await mkdir2(recoveryPath, { mode: 448 });
21433
- await applySnapshot(recoveryPath, snapshot.files, {});
22134
+ await applySnapshot(recoveryPath, snapshot.files, {}, void 0, this.#limits);
21434
22135
  const restored = new _ProjectCodeSync({
21435
22136
  ...this.#options,
21436
22137
  workspacePath: recoveryPath,
21437
- stateRoot: path2.join(path2.dirname(this.#stateRoot), digest(recoveryPath))
22138
+ ...this.#options.stateRoot ? { stateRoot: path2.join(path2.dirname(this.#options.stateRoot), digest(recoveryPath)) } : {},
22139
+ // The transport has private mutable Git state; recovery only seeds the
22140
+ // baseline and does not need another transport initialization here.
22141
+ ...this.#options.storage ? { storage: {
22142
+ ...this.#options.storage,
22143
+ key: this.#options.storage.key,
22144
+ limits: this.#limits,
22145
+ metadata: this.#options.storage.metadata,
22146
+ initialize: async () => {
22147
+ },
22148
+ status: () => this.#cloudStatus(),
22149
+ snapshot: (status) => this.#snapshot(status),
22150
+ checkpoint: (input2) => this.#options.storage.checkpoint(input2),
22151
+ update: () => this.#options.storage.update()
22152
+ } } : {}
21438
22153
  });
21439
22154
  await restored.initialize();
21440
22155
  await restored.#locked(async () => {
@@ -21460,7 +22175,18 @@ var ProjectCodeSync = class _ProjectCodeSync {
21460
22175
  await this.initialize();
21461
22176
  return this.#locked(async () => {
21462
22177
  const binding = await this.#readBinding();
21463
- const cloud = await this.#cloudStatus();
22178
+ if (!enabled) {
22179
+ binding.automatic_upload = false;
22180
+ await this.#save(binding);
22181
+ }
22182
+ let cloud;
22183
+ try {
22184
+ cloud = await this.#cloudStatus();
22185
+ } catch (error62) {
22186
+ if (enabled)
22187
+ throw error62;
22188
+ return { ...this.#status(binding, { repository: { enabled: false, main_commit: null }, branches: [], own_branch_id: null }, { files: [], hashes: binding.baseline, excluded: 0 }), local_status_unknown: true };
22189
+ }
21464
22190
  if (enabled)
21465
22191
  assertEnabled(cloud);
21466
22192
  binding.automatic_upload = enabled;
@@ -21505,7 +22231,12 @@ var ProjectCodeSync = class _ProjectCodeSync {
21505
22231
  return this.#upload(inventory.hashes);
21506
22232
  }
21507
22233
  async execute(kind, { busy = false, operationId } = {}) {
21508
- if (busy && ["code_upload", "code_download", "code_recover", "code_auto_upload_enable"].includes(kind)) {
22234
+ if (kind.startsWith("github_") !== (this.#options.storage?.metadata.provider === "github")) {
22235
+ throw new CodeSyncError("code_sync_unsupported", "The requested action belongs to a different code provider.");
22236
+ }
22237
+ if (kind.startsWith("github_"))
22238
+ kind = kind.slice(7);
22239
+ if (busy && ["code_upload", "code_download", "code_recover", "code_auto_upload_enable", "code_update"].includes(kind)) {
21509
22240
  throw new CodeSyncError("code_sync_busy", "An Agent is working in this workspace. Wait until it finishes before syncing code.");
21510
22241
  }
21511
22242
  switch (kind) {
@@ -21521,11 +22252,26 @@ var ProjectCodeSync = class _ProjectCodeSync {
21521
22252
  return this.setAutomaticUpload(true);
21522
22253
  case "code_auto_upload_disable":
21523
22254
  return this.setAutomaticUpload(false);
22255
+ case "code_update": {
22256
+ await this.initialize();
22257
+ if (!this.#options.storage)
22258
+ throw new CodeSyncError("code_sync_unsupported", "Use the cloud review controls to update this branch.");
22259
+ return this.#locked(async () => {
22260
+ const binding = await this.#readBinding();
22261
+ const inventory = await this.#inventory();
22262
+ if (!sameHashes(binding.baseline, inventory.hashes)) {
22263
+ throw new CodeSyncError("code_sync_dirty", "Local source files have unuploaded changes. Upload them or recover the remote version before updating your GitHub branch.");
22264
+ }
22265
+ return this.#status(binding, await this.#options.storage.update(), inventory);
22266
+ });
22267
+ }
21524
22268
  default:
21525
22269
  throw new CodeSyncError("code_sync_unsupported", "Unsupported code sync action.");
21526
22270
  }
21527
22271
  }
21528
22272
  async #cloudStatus() {
22273
+ if (this.#options.storage)
22274
+ return this.#options.storage.status();
21529
22275
  const parsed = CodeStatusSchema.safeParse(await this.#request(""));
21530
22276
  if (!parsed.success)
21531
22277
  throw invalidResponse();
@@ -21534,6 +22280,8 @@ var ProjectCodeSync = class _ProjectCodeSync {
21534
22280
  async #snapshot(cloud) {
21535
22281
  if (!cloud.repository.main_commit && !cloud.own_branch_id)
21536
22282
  throw new CodeSyncError("code_sync_empty", "No code has been uploaded yet.");
22283
+ if (this.#options.storage)
22284
+ return this.#options.storage.snapshot(cloud);
21537
22285
  const parsed = CodeSnapshotResultSchema.safeParse(await this.#request(`/snapshot?branch_id=${encodeURIComponent(cloud.own_branch_id ?? "main")}`));
21538
22286
  if (!parsed.success)
21539
22287
  throw invalidResponse();
@@ -21542,6 +22290,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
21542
22290
  #status(binding, cloud, inventory) {
21543
22291
  const cloudCommit = cloud.branches.find((branch) => branch.id === cloud.own_branch_id)?.head_commit ?? cloud.repository.main_commit;
21544
22292
  return {
22293
+ ...this.#options.storage?.metadata,
21545
22294
  enabled: cloud.repository.enabled,
21546
22295
  automatic_upload: binding.automatic_upload,
21547
22296
  local_changes: changedCount(binding.baseline, inventory.hashes),
@@ -21609,7 +22358,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
21609
22358
  async #readBinding() {
21610
22359
  const file2 = path2.join(this.#stateRoot, "binding.json");
21611
22360
  const info = await lstat2(file2);
21612
- if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.size > MAX_BINDING_BYTES)
22361
+ if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.size > MAX_BINDING_BYTES * (this.#options.storage ? 10 : 1))
21613
22362
  throw new CodeSyncError("code_sync_binding", "Private code sync state is not a regular bounded file.");
21614
22363
  let binding;
21615
22364
  try {
@@ -21624,13 +22373,13 @@ var ProjectCodeSync = class _ProjectCodeSync {
21624
22373
  }
21625
22374
  if (binding.interrupted_download !== void 0 && typeof binding.interrupted_download !== "boolean")
21626
22375
  throw new CodeSyncError("code_sync_binding", "Private download recovery state is invalid.");
21627
- if (binding.executable_paths !== void 0 && (!Array.isArray(binding.executable_paths) || binding.executable_paths.length > CODE_SYNC_MAX_FILES || binding.executable_paths.some((name2) => typeof name2 !== "string" || !isCodeSyncPathAllowed(name2))))
22376
+ if (binding.executable_paths !== void 0 && (!Array.isArray(binding.executable_paths) || binding.executable_paths.length > this.#limits.maxFiles || binding.executable_paths.some((name2) => typeof name2 !== "string" || !isCodeSyncPathAllowed(name2))))
21628
22377
  throw new CodeSyncError("code_sync_binding", "Private code file-mode state is invalid.");
21629
22378
  const baseline = Object.entries(binding.baseline);
21630
- if (baseline.length > CODE_SYNC_MAX_FILES)
22379
+ if (baseline.length > this.#limits.maxFiles)
21631
22380
  throw new CodeSyncError("code_sync_binding", "Private code sync baseline exceeds the file limit.");
21632
- for (const [name2, hash2] of baseline) {
21633
- if (!isCodeSyncPathAllowed(name2) || typeof hash2 !== "string" || !/^[a-f0-9]{64}$/u.test(hash2))
22381
+ for (const [name2, hash3] of baseline) {
22382
+ if (!isCodeSyncPathAllowed(name2) || typeof hash3 !== "string" || !/^[a-f0-9]{64}$/u.test(hash3))
21634
22383
  throw new CodeSyncError("code_sync_binding", "Private code sync baseline is invalid.");
21635
22384
  }
21636
22385
  return binding;
@@ -21663,19 +22412,23 @@ var ProjectCodeSync = class _ProjectCodeSync {
21663
22412
  const nativeExecutables = /* @__PURE__ */ new Set();
21664
22413
  let candidates;
21665
22414
  let nativeRoot = "";
22415
+ const inventoryOutputLimit = this.#options.storage ? this.#limits.maxFiles * 2100 : 2 * 1024 * 1024;
21666
22416
  try {
21667
22417
  nativeRoot = (await git(["rev-parse", "--show-toplevel"], this.#root)).trim();
21668
22418
  } catch {
21669
22419
  }
21670
22420
  if (nativeRoot && await realpath(nativeRoot) === this.#root) {
21671
- const trackedEntries = (await git(["ls-files", "--stage", "-z"], this.#root)).split("\0").filter(Boolean);
22421
+ const trackedEntries = (await git(["ls-files", "--stage", "-z"], this.#root, inventoryOutputLimit)).split("\0").filter(Boolean);
21672
22422
  tracked = new Set(trackedEntries.map((entry) => entry.slice(entry.indexOf(" ") + 1)));
22423
+ if (this.#options.storage?.metadata.provider === "github" && trackedEntries.some((entry) => !/^(?:100644|100755) /u.test(entry))) {
22424
+ throw new CodeSyncError("code_sync_unsupported", "Tracked links or submodules cannot be synchronized to GitHub. Remove them from the native Git index or use native Git.");
22425
+ }
21673
22426
  for (const entry of trackedEntries)
21674
22427
  if (entry.startsWith("100755 "))
21675
22428
  nativeExecutables.add(entry.slice(entry.indexOf(" ") + 1));
21676
- candidates = [...tracked, ...(await git(["ls-files", "--others", "--exclude-standard", "-z"], this.#root)).split("\0").filter(Boolean)];
22429
+ candidates = [...tracked, ...(await git(["ls-files", "--others", "--exclude-standard", "-z"], this.#root, inventoryOutputLimit)).split("\0").filter(Boolean)];
21677
22430
  } else {
21678
- candidates = (await git([`--git-dir=${path2.join(this.#stateRoot, "inventory.git")}`, `--work-tree=${this.#root}`, "ls-files", "--others", "--exclude-standard", "-z"], this.#root)).split("\0").filter(Boolean);
22431
+ candidates = (await git([`--git-dir=${path2.join(this.#stateRoot, "inventory.git")}`, `--work-tree=${this.#root}`, "ls-files", "--others", "--exclude-standard", "-z"], this.#root, inventoryOutputLimit)).split("\0").filter(Boolean);
21679
22432
  }
21680
22433
  const binding = await this.#readBinding();
21681
22434
  const executablePaths = executableHints ?? new Set(binding.executable_paths ?? []);
@@ -21688,6 +22441,9 @@ var ProjectCodeSync = class _ProjectCodeSync {
21688
22441
  let totalBytes = 0;
21689
22442
  for (const name2 of [...new Set(candidates)].sort()) {
21690
22443
  if (!isCodeSyncPathAllowed(name2) || BUILD_PATH.test(name2)) {
22444
+ if (this.#options.storage?.metadata.provider === "github" && tracked.has(name2)) {
22445
+ throw new CodeSyncError("code_sync_unsupported", "A tracked private, unsafe or generated path cannot be synchronized to GitHub. Remove it from the native Git index or use native Git.");
22446
+ }
21691
22447
  if (tracked.has(name2) && !BUILD_PATH.test(name2))
21692
22448
  throw new CodeSyncError("code_sync_secret", "A tracked private or unsafe file is excluded from code sync. Remove it from version control before uploading.");
21693
22449
  excluded += 1;
@@ -21695,6 +22451,8 @@ var ProjectCodeSync = class _ProjectCodeSync {
21695
22451
  }
21696
22452
  const target = path2.join(this.#root, ...name2.split("/"));
21697
22453
  if (!await safeAncestors(this.#root, name2)) {
22454
+ if (this.#options.storage?.metadata.provider === "github" && tracked.has(name2))
22455
+ throw new CodeSyncError("code_sync_unsupported", "A tracked path crosses a link or unsafe directory. Use native Git after reviewing it.");
21698
22456
  excluded += 1;
21699
22457
  continue;
21700
22458
  }
@@ -21707,26 +22465,28 @@ var ProjectCodeSync = class _ProjectCodeSync {
21707
22465
  throw error62;
21708
22466
  }
21709
22467
  if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
22468
+ if (this.#options.storage?.metadata.provider === "github" && tracked.has(name2))
22469
+ throw new CodeSyncError("code_sync_unsupported", "A tracked link or non-regular file cannot be synchronized to GitHub. Use native Git after reviewing it.");
21710
22470
  excluded += 1;
21711
22471
  continue;
21712
22472
  }
21713
- if (info.size > CODE_SYNC_MAX_FILE_BYTES)
21714
- throw new CodeSyncError("code_sync_limit", "A source file exceeds the 2 MiB code sync limit.");
21715
- const source = await readBoundedSource(target);
21716
- const bytes = source.bytes;
21717
- if (containsCodeSyncSecret(bytes.toString("utf8")))
22473
+ if (info.size > this.#limits.maxFileBytes)
22474
+ throw new CodeSyncError("code_sync_limit", "A source file exceeds the selected provider's file limit.");
22475
+ const source = await readBoundedSource(target, this.#limits.maxFileBytes);
22476
+ const bytes2 = source.bytes;
22477
+ if (containsCodeSyncSecret(bytes2.toString("utf8")))
21718
22478
  throw new CodeSyncError("code_sync_secret", "A source file contains a recognizable credential or private key. Remove it before uploading.");
21719
- totalBytes += bytes.byteLength;
21720
- if (totalBytes > CODE_SYNC_MAX_SNAPSHOT_BYTES || files.length >= CODE_SYNC_MAX_FILES)
21721
- throw new CodeSyncError("code_sync_limit", "Code sync is limited to 1,000 files and 8 MiB per checkpoint.");
21722
- files.push({ path: name2, content_base64: bytes.toString("base64"), executable: process.platform === "win32" ? executablePaths.has(name2) || nativeExecutables.has(name2) : source.executable });
22479
+ totalBytes += bytes2.byteLength;
22480
+ if (totalBytes > this.#limits.maxSnapshotBytes || files.length >= this.#limits.maxFiles)
22481
+ throw new CodeSyncError("code_sync_limit", "Source files exceed the selected provider's checkpoint limit.");
22482
+ files.push({ path: name2, content_base64: bytes2.toString("base64"), executable: process.platform === "win32" ? executablePaths.has(name2) || nativeExecutables.has(name2) : source.executable });
21723
22483
  }
21724
- if (!CodeFilesSchema.safeParse(files).success)
22484
+ if (!createCodeFilesSchema(this.#limits).safeParse(files).success)
21725
22485
  throw new CodeSyncError("code_sync_unsafe_path", "Source files contain non-portable or conflicting paths.");
21726
22486
  return { files, hashes: fileHashes(files), excluded };
21727
22487
  }
21728
22488
  async #locked(operation) {
21729
- const lockPath = path2.join(this.#stateRoot, "operation.lock");
22489
+ const lockPath = path2.join(this.#lockRoot, "operation.lock");
21730
22490
  let handle;
21731
22491
  try {
21732
22492
  handle = await open3(lockPath, "wx", 384);
@@ -21798,14 +22558,14 @@ function isWithin(root, target) {
21798
22558
  const relative = path2.relative(root, target);
21799
22559
  return !relative || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
21800
22560
  }
21801
- async function git(args, cwd) {
22561
+ async function git(args, cwd, maxBuffer = 2 * 1024 * 1024) {
21802
22562
  const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_") && !key.startsWith("GATHERTHREAD_")));
21803
22563
  try {
21804
22564
  const result = await execFileAsync("git", ["-c", "core.fsmonitor=false", "-c", "core.hooksPath=/dev/null", ...args], {
21805
22565
  cwd,
21806
22566
  env: { ...env, GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null", GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" },
21807
22567
  timeout: 15e3,
21808
- maxBuffer: 2 * 1024 * 1024,
22568
+ maxBuffer,
21809
22569
  encoding: "utf8",
21810
22570
  windowsHide: true
21811
22571
  });
@@ -21815,7 +22575,7 @@ async function git(args, cwd) {
21815
22575
  }
21816
22576
  }
21817
22577
  async function writePrivate(filename, content) {
21818
- const temporary = `${filename}.${randomUUID()}.tmp`;
22578
+ const temporary = `${filename}.${randomUUID2()}.tmp`;
21819
22579
  const handle = await open3(temporary, "wx", 384);
21820
22580
  try {
21821
22581
  await handle.writeFile(content);
@@ -21844,7 +22604,7 @@ async function safeAncestors(root, relative, create = false) {
21844
22604
  }
21845
22605
  return true;
21846
22606
  }
21847
- async function fileHash(root, name2, executableHint) {
22607
+ async function fileHash(root, name2, executableHint, maxBytes = CODE_SYNC_MAX_FILE_BYTES) {
21848
22608
  if (!await safeAncestors(root, name2))
21849
22609
  throw new CodeSyncError("code_sync_unsafe_path", "A destination directory is a symbolic link or not a directory.");
21850
22610
  const target = path2.join(root, ...name2.split("/"));
@@ -21856,17 +22616,17 @@ async function fileHash(root, name2, executableHint) {
21856
22616
  return;
21857
22617
  throw error62;
21858
22618
  }
21859
- if (!info.isFile() || info.isSymbolicLink() || info.size > CODE_SYNC_MAX_FILE_BYTES)
22619
+ if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes)
21860
22620
  throw new CodeSyncError("code_sync_unsafe_path", "A destination is not a bounded regular source file.");
21861
- const source = await readBoundedSource(target);
22621
+ const source = await readBoundedSource(target, maxBytes);
21862
22622
  const executable = process.platform === "win32" && executableHint !== void 0 ? executableHint : source.executable;
21863
22623
  return digest(`${executable ? "1" : "0"}\0${source.bytes.toString("base64")}`);
21864
22624
  }
21865
- async function readBoundedSource(filename) {
22625
+ async function readBoundedSource(filename, maxBytes = CODE_SYNC_MAX_FILE_BYTES) {
21866
22626
  const handle = await open3(filename, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
21867
22627
  try {
21868
22628
  const before = await handle.stat();
21869
- if (!before.isFile() || before.nlink !== 1 || before.size > CODE_SYNC_MAX_FILE_BYTES)
22629
+ if (!before.isFile() || before.nlink !== 1 || before.size > maxBytes)
21870
22630
  throw new CodeSyncError("code_sync_unsafe_path", "A source path is not a bounded regular file.");
21871
22631
  const buffer = Buffer.alloc(before.size + 1);
21872
22632
  const result = await handle.read(buffer, 0, buffer.length, 0);
@@ -21878,18 +22638,18 @@ async function readBoundedSource(filename) {
21878
22638
  await handle.close();
21879
22639
  }
21880
22640
  }
21881
- async function applySnapshot(root, files, baseline, options) {
21882
- if (!CodeFilesSchema.safeParse(files).success)
22641
+ async function applySnapshot(root, files, baseline, options, limits = CLOUD_CODE_SYNC_LIMITS) {
22642
+ if (!createCodeFilesSchema(limits).safeParse(files).success)
21883
22643
  throw new CodeSyncError("code_sync_unsafe_path", "Cloud code contains unsafe paths.");
21884
22644
  const names = /* @__PURE__ */ new Set([...Object.keys(baseline), ...files.map((file2) => file2.path)]);
21885
22645
  for (const name2 of names) {
21886
- if (await fileHash(root, name2, options?.baselineExecutables?.has(name2)) !== baseline[name2])
22646
+ if (await fileHash(root, name2, options?.baselineExecutables?.has(name2), limits.maxFileBytes) !== baseline[name2])
21887
22647
  throw new CodeSyncError("code_sync_dirty", "Cloud code would overwrite local or ignored files. Recover into a new folder instead.");
21888
22648
  }
21889
22649
  if (options) {
21890
22650
  await mkdir2(options.backupRoot, { recursive: true, mode: 448 });
21891
22651
  for (const name2 of Object.keys(baseline)) {
21892
- const source = await readBoundedSource(path2.join(root, ...name2.split("/")));
22652
+ const source = await readBoundedSource(path2.join(root, ...name2.split("/")), limits.maxFileBytes);
21893
22653
  if (process.platform === "win32" && options.baselineExecutables)
21894
22654
  source.executable = options.baselineExecutables.has(name2);
21895
22655
  if (digest(`${source.executable ? "1" : "0"}\0${source.bytes.toString("base64")}`) !== baseline[name2])
@@ -21908,10 +22668,10 @@ async function applySnapshot(root, files, baseline, options) {
21908
22668
  }
21909
22669
  const targetNames = new Set(files.map((file2) => file2.path));
21910
22670
  for (const file2 of files) {
21911
- if (!await safeAncestors(root, file2.path, true) || await fileHash(root, file2.path, options?.baselineExecutables?.has(file2.path)) !== baseline[file2.path])
22671
+ if (!await safeAncestors(root, file2.path, true) || await fileHash(root, file2.path, options?.baselineExecutables?.has(file2.path), limits.maxFileBytes) !== baseline[file2.path])
21912
22672
  throw new CodeSyncError("code_sync_busy", "Local files changed during download. No further files were applied.");
21913
22673
  const filename = path2.join(root, ...file2.path.split("/"));
21914
- const temporary = path2.join(path2.dirname(filename), `.gatherthread-${randomUUID()}.tmp`);
22674
+ const temporary = path2.join(path2.dirname(filename), `.gatherthread-${randomUUID2()}.tmp`);
21915
22675
  const handle = await open3(temporary, "wx", file2.executable ? 448 : 384);
21916
22676
  try {
21917
22677
  await handle.writeFile(Buffer.from(file2.content_base64, "base64"));
@@ -21919,7 +22679,7 @@ async function applySnapshot(root, files, baseline, options) {
21919
22679
  } finally {
21920
22680
  await handle.close();
21921
22681
  }
21922
- if (!await safeAncestors(root, file2.path) || await fileHash(root, file2.path, options?.baselineExecutables?.has(file2.path)) !== baseline[file2.path]) {
22682
+ if (!await safeAncestors(root, file2.path) || await fileHash(root, file2.path, options?.baselineExecutables?.has(file2.path), limits.maxFileBytes) !== baseline[file2.path]) {
21923
22683
  await unlink(temporary);
21924
22684
  throw new CodeSyncError("code_sync_busy", "Source files changed while preparing a download. Original files were backed up; no further files were applied.");
21925
22685
  }
@@ -21927,55 +22687,442 @@ async function applySnapshot(root, files, baseline, options) {
21927
22687
  }
21928
22688
  for (const name2 of Object.keys(baseline)) {
21929
22689
  if (!targetNames.has(name2)) {
21930
- if (await fileHash(root, name2, options?.baselineExecutables?.has(name2)) !== baseline[name2])
22690
+ if (await fileHash(root, name2, options?.baselineExecutables?.has(name2), limits.maxFileBytes) !== baseline[name2])
21931
22691
  throw new CodeSyncError("code_sync_busy", "Local files changed during download. No further files were applied.");
21932
22692
  await unlink(path2.join(root, ...name2.split("/")));
21933
22693
  }
21934
22694
  }
21935
22695
  }
21936
22696
 
22697
+ // ../bridge/dist/src/github-code-sync.js
22698
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
22699
+ import { spawn } from "node:child_process";
22700
+ import { constants as constants2 } from "node:fs";
22701
+ import { lstat as lstat3, mkdir as mkdir3, open as open4, unlink as unlink2 } from "node:fs/promises";
22702
+ import path3 from "node:path";
22703
+ var hash2 = (text) => createHash3("sha256").update(text).digest("hex");
22704
+ var Commit2 = /^[a-f0-9]{40}$/u;
22705
+ var filesSchema = createCodeFilesSchema(GITHUB_CODE_SYNC_LIMITS);
22706
+ var generatedPath = /(?:^|\/)(?:node_modules|dist|build|coverage|\.next|\.cache|\.turbo|\.venv|venv|__pycache__)(?:\/|$)/u;
22707
+ function createGitHubCodeSync(options, runner = runGitHubGit) {
22708
+ return new ProjectCodeSync({ ...options, storage: new GitHubCodeStorage(options, runner) });
22709
+ }
22710
+ var GitHubCodeStorage = class {
22711
+ limits = GITHUB_CODE_SYNC_LIMITS;
22712
+ key;
22713
+ metadata;
22714
+ #options;
22715
+ #run;
22716
+ #remote;
22717
+ #branchId;
22718
+ #directory = "";
22719
+ #revision;
22720
+ constructor(options, runner) {
22721
+ if (!GitHubConnectionSchema.safeParse({ repository: options.repository, base_branch: options.baseBranch, revision: options.revision, enabled: true }).success) {
22722
+ throw new CodeSyncError("code_sync_binding", "GitHub requires an owner/repository and a valid base branch.");
22723
+ }
22724
+ const branch = `gatherthread/${hash2(options.projectId).slice(0, 24)}/${hash2(options.actorId).slice(0, 24)}`;
22725
+ if (options.baseBranch.startsWith("gatherthread/"))
22726
+ throw new CodeSyncError("code_sync_binding", "Choose a base branch outside GatherThread personal branches.");
22727
+ this.#options = options;
22728
+ this.#run = runner;
22729
+ this.#revision = options.revision;
22730
+ this.#remote = `https://github.com/${options.repository}.git`;
22731
+ this.#branchId = `branch-${hash2(branch).slice(0, 24)}`;
22732
+ this.key = JSON.stringify(["github", options.apiUrl.replace(/\/$/u, "").replace(/\/v1$/u, ""), options.projectId, options.actorId, options.repository.toLowerCase(), options.baseBranch, options.revision]);
22733
+ this.metadata = { provider: "github", repository: options.repository, branch, base_branch: options.baseBranch };
22734
+ }
22735
+ async initialize(privateDirectory) {
22736
+ this.#directory = path3.join(privateDirectory, "github.git");
22737
+ try {
22738
+ if ((await lstat3(this.#directory)).isSymbolicLink())
22739
+ throw new CodeSyncError("code_sync_binding", "Private GitHub state cannot be a symbolic link.");
22740
+ } catch (error62) {
22741
+ if (error62.code !== "ENOENT")
22742
+ throw error62;
22743
+ }
22744
+ await mkdir3(this.#directory, { recursive: true, mode: 448 });
22745
+ await this.#git(["init", "--bare", "--quiet", "."]);
22746
+ const info = path3.join(this.#directory, "info");
22747
+ await mkdir3(info, { recursive: true, mode: 448 });
22748
+ if ((await lstat3(info)).isSymbolicLink())
22749
+ throw new CodeSyncError("code_sync_binding", "Private GitHub attributes directory is unsafe.");
22750
+ const attributes = await open4(path3.join(info, "attributes"), constants2.O_WRONLY | constants2.O_CREAT | (constants2.O_NOFOLLOW ?? 0), 384);
22751
+ try {
22752
+ if (!(await attributes.stat()).isFile() || (await attributes.stat()).nlink !== 1)
22753
+ throw new CodeSyncError("code_sync_binding", "Private GitHub attributes must be a regular file.");
22754
+ await attributes.truncate(0);
22755
+ await attributes.writeFile("* conflict-marker-size=7\n");
22756
+ } finally {
22757
+ await attributes.close();
22758
+ }
22759
+ }
22760
+ async #authorize() {
22761
+ const url2 = new URL(this.#options.apiUrl);
22762
+ url2.pathname = url2.pathname.replace(/\/$/u, "").replace(/\/v1$/u, "") + `/v1/projects/${encodeURIComponent(this.#options.projectId)}/github`;
22763
+ let response;
22764
+ try {
22765
+ response = await (this.#options.fetch ?? fetch)(url2.toString(), {
22766
+ headers: { authorization: `Bearer ${this.#options.token}` },
22767
+ redirect: "error",
22768
+ signal: AbortSignal.timeout(15e3)
22769
+ });
22770
+ } catch {
22771
+ throw new CodeSyncError("code_sync_unavailable", "GatherThread could not verify current GitHub project access.");
22772
+ }
22773
+ if (!response.ok) {
22774
+ await response.body?.cancel();
22775
+ throw new CodeSyncError("code_sync_forbidden", "Current GatherThread project access is required for GitHub synchronization.");
22776
+ }
22777
+ if (!response.body)
22778
+ throw new CodeSyncError("code_sync_unavailable", "Invalid GitHub configuration response.");
22779
+ const reader = response.body.getReader();
22780
+ const chunks = [];
22781
+ let size = 0;
22782
+ for (; ; ) {
22783
+ const part = await reader.read();
22784
+ if (part.done)
22785
+ break;
22786
+ size += part.value.length;
22787
+ if (size > 16384) {
22788
+ await reader.cancel();
22789
+ throw new CodeSyncError("code_sync_limit", "GitHub configuration response is too large.");
22790
+ }
22791
+ chunks.push(part.value);
22792
+ }
22793
+ let data;
22794
+ try {
22795
+ data = GitHubProjectStatusSchema.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")).data);
22796
+ } catch {
22797
+ throw new CodeSyncError("code_sync_unavailable", "Invalid GitHub configuration response.");
22798
+ }
22799
+ const connection = data.connection;
22800
+ if (!data.can_write || data.branch !== this.metadata.branch || !connection || connection.repository.toLowerCase() !== this.#options.repository.toLowerCase() || connection.base_branch !== this.#options.baseBranch) {
22801
+ throw new CodeSyncError("code_sync_binding", "GitHub target or project permissions changed. Review the connection and authorize it locally again.");
22802
+ }
22803
+ if (!connection.enabled)
22804
+ return false;
22805
+ if (this.#revision !== connection.revision)
22806
+ throw new CodeSyncError("code_sync_binding", "GitHub configuration changed. Reconnect and authorize the current target locally again.");
22807
+ return true;
22808
+ }
22809
+ async #enabled() {
22810
+ if (!await this.#authorize())
22811
+ throw new CodeSyncError("code_sync_disabled", "GitHub synchronization is paused for this project.");
22812
+ }
22813
+ async status() {
22814
+ if (!await this.#authorize())
22815
+ return { repository: { enabled: false, main_commit: null }, branches: [], own_branch_id: null };
22816
+ const refs = (await this.#git(["ls-remote", "--refs", this.#remote, `refs/heads/${this.#options.baseBranch}`, `refs/heads/${this.metadata.branch}`])).toString("utf8");
22817
+ let main = null;
22818
+ let own2 = null;
22819
+ for (const line of refs.trim().split("\n")) {
22820
+ const [oid, ref] = line.split(" ");
22821
+ if (!oid || !Commit2.test(oid))
22822
+ continue;
22823
+ if (ref === `refs/heads/${this.#options.baseBranch}`)
22824
+ main = oid;
22825
+ if (ref === `refs/heads/${this.metadata.branch}`)
22826
+ own2 = oid;
22827
+ }
22828
+ return {
22829
+ repository: { enabled: true, main_commit: main },
22830
+ own_branch_id: own2 ? this.#branchId : null,
22831
+ branches: own2 ? [{ id: this.#branchId, name: "My GitHub branch", user_id: this.#options.actorId, head_commit: own2, review_status: "draft" }] : []
22832
+ };
22833
+ }
22834
+ #head(status) {
22835
+ return status.branches[0]?.head_commit ?? status.repository.main_commit;
22836
+ }
22837
+ async #fetch(status) {
22838
+ await this.#enabled();
22839
+ const refs = [];
22840
+ if (status.repository.main_commit)
22841
+ refs.push(`+refs/heads/${this.#options.baseBranch}:refs/gt/base`);
22842
+ if (status.own_branch_id)
22843
+ refs.push(`+refs/heads/${this.metadata.branch}:refs/gt/own`);
22844
+ if (refs.length)
22845
+ await this.#git(["fetch", "--quiet", "--no-tags", "--no-recurse-submodules", this.#remote, ...refs]);
22846
+ for (const [ref, expected] of [["refs/gt/base", status.repository.main_commit], ["refs/gt/own", status.branches[0]?.head_commit]]) {
22847
+ if (expected && (await this.#git(["rev-parse", "--verify", ref])).toString().trim() !== expected)
22848
+ throw conflict();
22849
+ }
22850
+ }
22851
+ async snapshot(status) {
22852
+ const commit = this.#head(status);
22853
+ if (!commit)
22854
+ throw new CodeSyncError("code_sync_empty", "No GitHub files have been uploaded yet.");
22855
+ await this.#fetch(status);
22856
+ return { commit, files: await this.#files(commit) };
22857
+ }
22858
+ async #files(commit) {
22859
+ if (!Commit2.test(commit))
22860
+ throw conflict();
22861
+ const listing = await this.#git(["ls-tree", "-r", "-z", "-l", commit]);
22862
+ if (!Buffer.from(listing.toString("utf8"), "utf8").equals(listing))
22863
+ throw new CodeSyncError("code_sync_unsafe_path", "The GitHub tree contains non-UTF-8 filenames. Use native Git for this repository.");
22864
+ const entries = listing.toString("utf8").split("\0").filter(Boolean).map((entry) => {
22865
+ const match = /^(100644|100755) blob ([a-f0-9]{40})\s+(\d+)\t(.+)$/u.exec(entry);
22866
+ if (!match)
22867
+ throw new CodeSyncError("code_sync_unsupported", "This GitHub tree contains unsupported links, submodules or paths. Use native Git for this repository.");
22868
+ return { mode: match[1], oid: match[2], size: Number(match[3]), path: match[4] };
22869
+ });
22870
+ if (entries.length > this.limits.maxFiles || entries.some((entry) => entry.size > this.limits.maxFileBytes) || entries.reduce((sum, entry) => sum + entry.size, 0) > this.limits.maxSnapshotBytes)
22871
+ throw new CodeSyncError("code_sync_limit", "GitHub source exceeds the supported checkpoint limit. Use native Git for larger trees.");
22872
+ const output2 = entries.length ? await this.#git(["cat-file", "--batch"], Buffer.from(entries.map((entry) => entry.oid).join("\n") + "\n")) : Buffer.alloc(0);
22873
+ let offset = 0;
22874
+ const files = [];
22875
+ for (const entry of entries) {
22876
+ const end = output2.indexOf(10, offset);
22877
+ if (end < 0 || output2.subarray(offset, end).toString() !== `${entry.oid} blob ${entry.size}`)
22878
+ throw conflict();
22879
+ const content = output2.subarray(end + 1, end + 1 + entry.size);
22880
+ offset = end + 2 + entry.size;
22881
+ if (content.length !== entry.size || output2[offset - 1] !== 10)
22882
+ throw conflict();
22883
+ if (generatedPath.test(entry.path) || containsCodeSyncSecret(content.toString("utf8")) || content.subarray(0, 80).toString().startsWith("version https://git-lfs.github.com/spec/v1")) {
22884
+ throw new CodeSyncError("code_sync_unsupported", "This GitHub tree contains excluded generated files, credentials or Git LFS pointers. Nothing was applied; use native Git or a source-only repository.");
22885
+ }
22886
+ files.push({ path: entry.path, content_base64: content.toString("base64"), executable: entry.mode === "100755" });
22887
+ }
22888
+ if (!filesSchema.safeParse(files).success)
22889
+ throw new CodeSyncError("code_sync_unsafe_path", "The GitHub tree contains non-portable or private paths. Nothing was applied.");
22890
+ return files;
22891
+ }
22892
+ async #tree(files) {
22893
+ if (!filesSchema.safeParse(files).success)
22894
+ throw new CodeSyncError("code_sync_limit", "Source tree exceeds GitHub checkpoint limits.");
22895
+ const index = path3.join(this.#directory, `index-${randomUUID3()}`);
22896
+ try {
22897
+ await this.#git(["read-tree", "--empty"], void 0, index);
22898
+ const records = [];
22899
+ for (const file2 of files) {
22900
+ const bytes2 = Buffer.from(file2.content_base64, "base64");
22901
+ if (containsCodeSyncSecret(bytes2.toString("utf8")))
22902
+ throw new CodeSyncError("code_sync_secret", "A recognizable credential was detected in source.");
22903
+ if (generatedPath.test(file2.path) || bytes2.subarray(0, 80).toString().startsWith("version https://git-lfs.github.com/spec/v1"))
22904
+ throw new CodeSyncError("code_sync_unsupported", "Use native Git for Git LFS or generated-file repositories.");
22905
+ const oid = (await this.#git(["hash-object", "-w", "--stdin", "--no-filters"], bytes2)).toString().trim();
22906
+ if (!Commit2.test(oid))
22907
+ throw conflict();
22908
+ records.push(`${file2.executable ? "100755" : "100644"} ${oid} ${file2.path}\0`);
22909
+ }
22910
+ await this.#git(["update-index", "-z", "--index-info"], Buffer.from(records.join("")), index);
22911
+ return (await this.#git(["write-tree"], void 0, index)).toString().trim();
22912
+ } finally {
22913
+ await unlink2(index).catch(() => {
22914
+ });
22915
+ }
22916
+ }
22917
+ async checkpoint(input2) {
22918
+ const status = await this.status();
22919
+ await this.#enabled();
22920
+ const current = this.#head(status);
22921
+ await this.#fetch(status);
22922
+ if (current)
22923
+ await this.#files(current);
22924
+ const tree = await this.#tree(input2.files);
22925
+ if (current && (await this.#git(["rev-parse", `${current}^{tree}`])).toString().trim() === tree)
22926
+ return { status, commit: current };
22927
+ if (input2.base_commit !== current)
22928
+ throw conflict();
22929
+ const commit = (await this.#git(["commit-tree", tree, ...current ? ["-p", current] : [], "-m", "GatherThread workspace checkpoint"])).toString().trim();
22930
+ await this.#push(commit, status);
22931
+ const next = await this.status();
22932
+ if (this.#head(next) !== commit)
22933
+ throw conflict();
22934
+ return { status: next, commit };
22935
+ }
22936
+ async #push(commit, expected) {
22937
+ await this.#enabled();
22938
+ const latest = await this.status();
22939
+ if (this.#head(latest) !== this.#head(expected) || latest.own_branch_id !== expected.own_branch_id)
22940
+ throw conflict();
22941
+ await this.#git(["push", "--porcelain", "--no-verify", this.#remote, `${commit}:refs/heads/${this.metadata.branch}`]);
22942
+ }
22943
+ async update() {
22944
+ const status = await this.status();
22945
+ await this.#enabled();
22946
+ if (!status.own_branch_id || !status.repository.main_commit)
22947
+ return status;
22948
+ await this.#fetch(status);
22949
+ const own2 = this.#head(status);
22950
+ const base = status.repository.main_commit;
22951
+ await this.#files(own2);
22952
+ await this.#files(base);
22953
+ if (own2 === base)
22954
+ return status;
22955
+ try {
22956
+ await this.#git(["merge-base", "--is-ancestor", base, own2]);
22957
+ return status;
22958
+ } catch {
22959
+ }
22960
+ let tree;
22961
+ try {
22962
+ tree = (await this.#git(["merge-tree", "--write-tree", own2, base])).toString().split("\n")[0].trim();
22963
+ } catch {
22964
+ throw new CodeSyncError("code_sync_conflict", "The base branch could not be merged automatically. Resolve conflicts using native Git or GitHub, then download again. Git 2.38 or newer is required.");
22965
+ }
22966
+ if (!Commit2.test(tree))
22967
+ throw conflict();
22968
+ await this.#files(tree);
22969
+ const commit = (await this.#git(["commit-tree", tree, "-p", own2, "-p", base, "-m", "Merge shared GitHub base into my GatherThread branch"])).toString().trim();
22970
+ await this.#push(commit, status);
22971
+ return this.status();
22972
+ }
22973
+ #git(args, input2, index) {
22974
+ return this.#run(args, { cwd: this.#directory, ...input2 ? { input: input2 } : {}, ...index ? { index } : {} });
22975
+ }
22976
+ };
22977
+ function conflict() {
22978
+ return new CodeSyncError("code_sync_conflict", "GitHub progress changed. Refresh, then download or recover before uploading again.");
22979
+ }
22980
+ async function runGitHubGit(args, options) {
22981
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_") && !key.startsWith("GATHERTHREAD_") && !["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GH_HOST", "GH_PROMPT_DISABLED"].includes(key)));
22982
+ return new Promise((resolve, reject) => {
22983
+ const child = spawn("git", [
22984
+ "-c",
22985
+ "core.hooksPath=/dev/null",
22986
+ "-c",
22987
+ "core.fsmonitor=false",
22988
+ "-c",
22989
+ "commit.gpgSign=false",
22990
+ "-c",
22991
+ "protocol.allow=never",
22992
+ "-c",
22993
+ "protocol.https.allow=always",
22994
+ "-c",
22995
+ "http.followRedirects=false",
22996
+ "-c",
22997
+ "credential.helper=",
22998
+ "-c",
22999
+ "credential.helper=!gh auth git-credential",
23000
+ "-c",
23001
+ "credential.interactive=false",
23002
+ "-c",
23003
+ "user.name=GatherThread",
23004
+ "-c",
23005
+ "user.email=checkpoint@users.noreply.github.com",
23006
+ ...args
23007
+ ], {
23008
+ cwd: options.cwd,
23009
+ windowsHide: true,
23010
+ stdio: ["pipe", "pipe", "pipe"],
23011
+ env: { ...env, GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null", GIT_TERMINAL_PROMPT: "0", GCM_INTERACTIVE: "never", ...options.index ? { GIT_INDEX_FILE: options.index } : {} }
23012
+ });
23013
+ const chunks = [];
23014
+ let size = 0;
23015
+ let failed = false;
23016
+ const fail = () => {
23017
+ if (failed)
23018
+ return;
23019
+ failed = true;
23020
+ child.kill();
23021
+ reject(new CodeSyncError("code_sync_unavailable", "GitHub Git operation failed. Check Git 2.38+, GitHub CLI login, repository access and network. No credentials are sent through GatherThread."));
23022
+ };
23023
+ const timer = setTimeout(fail, 12e4);
23024
+ timer.unref();
23025
+ child.stdout.on("data", (chunk) => {
23026
+ size += chunk.length;
23027
+ if (size > GITHUB_CODE_SYNC_LIMITS.maxSnapshotBytes + 8 * 1024 * 1024)
23028
+ fail();
23029
+ else
23030
+ chunks.push(chunk);
23031
+ });
23032
+ child.stderr.resume();
23033
+ child.stdin.on("error", () => {
23034
+ });
23035
+ child.on("error", fail);
23036
+ child.on("close", (code) => {
23037
+ clearTimeout(timer);
23038
+ if (code !== 0)
23039
+ fail();
23040
+ else if (!failed)
23041
+ resolve(Buffer.concat(chunks));
23042
+ });
23043
+ child.stdin.end(options.input);
23044
+ });
23045
+ }
23046
+
23047
+ // ../bridge/dist/src/github-auth.js
23048
+ import { spawn as spawn2 } from "node:child_process";
23049
+ async function connectGitHubAccount(command = runGitHubAuthCommand) {
23050
+ if (await command(["auth", "status", "--hostname", "github.com"], 1e4))
23051
+ return;
23052
+ const signedIn = await command(["auth", "login", "--hostname", "github.com", "--git-protocol", "https", "--web", "--clipboard"], 24e4);
23053
+ if (!signedIn || !await command(["auth", "status", "--hostname", "github.com"], 1e4)) {
23054
+ throw new CodeSyncError("github_auth_failed", "GitHub sign-in was not completed on this device. Reopen the connection and finish in your browser.");
23055
+ }
23056
+ }
23057
+ function runGitHubAuthCommand(args, timeoutMs) {
23058
+ return new Promise((resolve, reject) => {
23059
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GH_HOST", "GH_PROMPT_DISABLED"].includes(key)));
23060
+ const child = spawn2("gh", [...args], { env, windowsHide: true, stdio: ["ignore", "ignore", "ignore"] });
23061
+ let settled = false;
23062
+ const finish = (value) => {
23063
+ if (settled)
23064
+ return;
23065
+ settled = true;
23066
+ clearTimeout(timer);
23067
+ resolve(value);
23068
+ };
23069
+ const timer = setTimeout(() => {
23070
+ child.kill();
23071
+ finish(false);
23072
+ }, timeoutMs);
23073
+ child.on("error", (error62) => {
23074
+ if (settled)
23075
+ return;
23076
+ settled = true;
23077
+ clearTimeout(timer);
23078
+ reject(new CodeSyncError(error62.code === "ENOENT" ? "github_cli_unavailable" : "github_auth_failed", error62.code === "ENOENT" ? "Install GitHub CLI on this device before connecting." : "GitHub CLI could not start on this device."));
23079
+ });
23080
+ child.on("close", (code) => finish(code === 0));
23081
+ });
23082
+ }
23083
+
21937
23084
  // dist/src/native-plugin.js
21938
- import { createHash as createHash8 } from "node:crypto";
21939
- import path9 from "node:path";
23085
+ import { createHash as createHash10 } from "node:crypto";
23086
+ import path10 from "node:path";
21940
23087
 
21941
23088
  // dist/src/code-sync-controller.js
21942
- import { constants as constants2 } from "node:fs";
21943
- import { lstat as lstat4, mkdir as mkdir3, open as open4, rename as rename2, unlink as unlink2 } from "node:fs/promises";
21944
- import { randomUUID as randomUUID2 } from "node:crypto";
21945
- import path4 from "node:path";
23089
+ import { constants as constants3 } from "node:fs";
23090
+ import { lstat as lstat5, mkdir as mkdir4, open as open5, rename as rename2, unlink as unlink3 } from "node:fs/promises";
23091
+ import { randomUUID as randomUUID4 } from "node:crypto";
23092
+ import path5 from "node:path";
21946
23093
 
21947
23094
  // dist/src/path-security.js
21948
- import { lstat as lstat3, realpath as realpath2 } from "node:fs/promises";
21949
- import path3 from "node:path";
23095
+ import { lstat as lstat4, realpath as realpath2 } from "node:fs/promises";
23096
+ import path4 from "node:path";
21950
23097
  async function prospectiveRealPath(candidate) {
21951
- let current = path3.resolve(candidate);
23098
+ let current = path4.resolve(candidate);
21952
23099
  const missing = [];
21953
23100
  while (true) {
21954
23101
  try {
21955
- return path3.join(await realpath2(current), ...missing);
23102
+ return path4.join(await realpath2(current), ...missing);
21956
23103
  } catch (error62) {
21957
23104
  if (!isNodeError2(error62) || error62.code !== "ENOENT")
21958
23105
  throw error62;
21959
- const parent = path3.dirname(current);
23106
+ const parent = path4.dirname(current);
21960
23107
  if (parent === current)
21961
23108
  throw new Error(`No existing ancestor for path: ${candidate}`);
21962
- missing.unshift(path3.basename(current));
23109
+ missing.unshift(path4.basename(current));
21963
23110
  current = parent;
21964
23111
  }
21965
23112
  }
21966
23113
  }
21967
23114
  async function secureDirectoryChain(directory) {
21968
- const absolute = path3.resolve(directory);
21969
- const root = path3.parse(absolute).root;
21970
- const relative = path3.relative(root, absolute);
21971
- const components = relative ? relative.split(path3.sep) : [];
23115
+ const absolute = path4.resolve(directory);
23116
+ const root = path4.parse(absolute).root;
23117
+ const relative = path4.relative(root, absolute);
23118
+ const components = relative ? relative.split(path4.sep) : [];
21972
23119
  const identities = [];
21973
23120
  let current = root;
21974
23121
  for (const component of components) {
21975
- current = path3.join(current, component);
23122
+ current = path4.join(current, component);
21976
23123
  let metadata;
21977
23124
  try {
21978
- metadata = await lstat3(current);
23125
+ metadata = await lstat4(current);
21979
23126
  } catch (error62) {
21980
23127
  if (isNodeError2(error62) && error62.code === "ENOENT")
21981
23128
  break;
@@ -22012,6 +23159,7 @@ var ACTIONS = /* @__PURE__ */ new Set([
22012
23159
  "code_auto_upload_enable",
22013
23160
  "code_auto_upload_disable"
22014
23161
  ]);
23162
+ var GITHUB_CODE_ACTIONS = githubCodeSyncRequestKinds.filter((kind) => kind !== "github_auth_connect");
22015
23163
  var DshCodeSyncController = class {
22016
23164
  #authorized = false;
22017
23165
  #status;
@@ -22021,8 +23169,10 @@ var DshCodeSyncController = class {
22021
23169
  #poll;
22022
23170
  #stopped = false;
22023
23171
  #options;
23172
+ #actions;
22024
23173
  constructor(options) {
22025
23174
  this.#options = options;
23175
+ this.#actions = options.actionKinds ? new Set(options.actionKinds) : ACTIONS;
22026
23176
  }
22027
23177
  view() {
22028
23178
  return {
@@ -22091,7 +23241,7 @@ var DshCodeSyncController = class {
22091
23241
  if (this.#stopped)
22092
23242
  return;
22093
23243
  const runtime = runtimes.get(job.sessionId);
22094
- if (!runtime || job.targetRuntimeId !== runtime || !ACTIONS.has(job.kind))
23244
+ if (!runtime || job.targetRuntimeId !== runtime || !this.#actions.has(job.kind))
22095
23245
  continue;
22096
23246
  let claimed;
22097
23247
  try {
@@ -22134,11 +23284,11 @@ var DshCodeSyncController = class {
22134
23284
  await this.#tail;
22135
23285
  }
22136
23286
  async #execute(kind, operationId) {
22137
- if (!ACTIONS.has(kind))
23287
+ if (!this.#actions.has(kind))
22138
23288
  throw new CodeSyncLocalError("code_action_invalid");
22139
23289
  if (!this.#authorized)
22140
23290
  throw new CodeSyncLocalError("code_sync_disabled");
22141
- if (this.#options.isBusy() && kind !== "code_sync_status" && kind !== "code_auto_upload_disable") {
23291
+ if (this.#options.isBusy() && !["code_sync_status", "code_auto_upload_disable", "github_code_sync_status", "github_code_auto_upload_disable"].includes(kind)) {
22142
23292
  throw new CodeSyncLocalError("code_sync_busy");
22143
23293
  }
22144
23294
  return this.#requireEngine().execute(kind, {
@@ -22169,13 +23319,13 @@ var CodeSyncLocalError = class extends Error {
22169
23319
  };
22170
23320
  function codeSyncErrorCode(error62) {
22171
23321
  const code = typeof error62 === "object" && error62 !== null && "code" in error62 ? error62.code : void 0;
22172
- return typeof code === "string" && /^code_[a-z_]{1,60}$/u.test(code) ? code : "code_sync_failed";
23322
+ return typeof code === "string" && /^(?:github_)?code_[a-z_]{1,60}$/u.test(code) ? code : "code_sync_failed";
22173
23323
  }
22174
23324
  async function loadConsent(file2, binding) {
22175
- const ancestors = await secureDirectoryChain(path4.dirname(file2));
23325
+ const ancestors = await secureDirectoryChain(path5.dirname(file2));
22176
23326
  let before;
22177
23327
  try {
22178
- before = await lstat4(file2);
23328
+ before = await lstat5(file2);
22179
23329
  } catch (error62) {
22180
23330
  if (error62.code === "ENOENT")
22181
23331
  return false;
@@ -22184,25 +23334,25 @@ async function loadConsent(file2, binding) {
22184
23334
  if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.size > 4096 || process.platform !== "win32" && (before.mode & 63) !== 0) {
22185
23335
  throw new CodeSyncLocalError("code_consent_invalid");
22186
23336
  }
22187
- const handle = await open4(file2, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
23337
+ const handle = await open5(file2, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
22188
23338
  try {
22189
23339
  const opened = await handle.stat();
22190
23340
  if (before.dev !== opened.dev || before.ino !== opened.ino)
22191
23341
  throw new CodeSyncLocalError("code_consent_invalid");
22192
23342
  const value = JSON.parse(await handle.readFile("utf8"));
22193
- assertSameDirectoryChain(ancestors, await secureDirectoryChain(path4.dirname(file2)));
23343
+ assertSameDirectoryChain(ancestors, await secureDirectoryChain(path5.dirname(file2)));
22194
23344
  return value.version === 1 && value.binding === binding && value.enabled === true;
22195
23345
  } finally {
22196
23346
  await handle.close();
22197
23347
  }
22198
23348
  }
22199
23349
  async function saveConsent(file2, binding, enabled) {
22200
- await secureDirectoryChain(path4.dirname(file2));
22201
- await mkdir3(path4.dirname(file2), { recursive: true, mode: 448 });
22202
- const ancestors = await secureDirectoryChain(path4.dirname(file2));
23350
+ await secureDirectoryChain(path5.dirname(file2));
23351
+ await mkdir4(path5.dirname(file2), { recursive: true, mode: 448 });
23352
+ const ancestors = await secureDirectoryChain(path5.dirname(file2));
22203
23353
  await loadConsent(file2, binding);
22204
- const temporary = `${file2}.${randomUUID2()}.tmp`;
22205
- const handle = await open4(temporary, "wx", 384);
23354
+ const temporary = `${file2}.${randomUUID4()}.tmp`;
23355
+ const handle = await open5(temporary, "wx", 384);
22206
23356
  try {
22207
23357
  await handle.writeFile(JSON.stringify({ version: 1, binding, enabled }));
22208
23358
  await handle.sync();
@@ -22210,14 +23360,136 @@ async function saveConsent(file2, binding, enabled) {
22210
23360
  await handle.close();
22211
23361
  }
22212
23362
  try {
22213
- assertSameDirectoryChain(ancestors, await secureDirectoryChain(path4.dirname(file2)));
23363
+ assertSameDirectoryChain(ancestors, await secureDirectoryChain(path5.dirname(file2)));
22214
23364
  await rename2(temporary, file2);
22215
23365
  } catch (error62) {
22216
- await unlink2(temporary).catch(() => void 0);
23366
+ await unlink3(temporary).catch(() => void 0);
22217
23367
  throw error62;
22218
23368
  }
22219
23369
  }
22220
23370
 
23371
+ // dist/src/github-code-sync-controller.js
23372
+ import { createHash as createHash4 } from "node:crypto";
23373
+ var DshGitHubCodeSyncController = class {
23374
+ options;
23375
+ #controller;
23376
+ #metadata;
23377
+ #key;
23378
+ #error;
23379
+ #tail = Promise.resolve();
23380
+ #stopped = false;
23381
+ #poll;
23382
+ constructor(options) {
23383
+ this.options = options;
23384
+ }
23385
+ view() {
23386
+ return {
23387
+ ...this.#controller?.view() ?? { authorized: false },
23388
+ connection: this.#metadata?.connection ? { ...this.#metadata.connection } : null,
23389
+ branch: this.#metadata?.branch ?? null,
23390
+ ...this.#error ? { error: this.#error } : {}
23391
+ };
23392
+ }
23393
+ async start() {
23394
+ await this.#serialize(async () => {
23395
+ await this.#refresh();
23396
+ });
23397
+ }
23398
+ authorize(enabled, expected) {
23399
+ return this.#serialize(async () => {
23400
+ if (!enabled) {
23401
+ await this.#controller?.authorize(false);
23402
+ return this.view();
23403
+ }
23404
+ await this.#refresh();
23405
+ const target = this.#metadata?.connection;
23406
+ if (!target || !expected || targetKey(target) !== targetKey(expected))
23407
+ throw coded("code_github_binding_changed");
23408
+ if (!this.#metadata?.can_write || !target.enabled)
23409
+ throw coded("code_sync_forbidden");
23410
+ await this.#controller.authorize(true);
23411
+ return this.view();
23412
+ });
23413
+ }
23414
+ execute(kind) {
23415
+ return this.#serialize(async () => {
23416
+ if (kind === "github_code_auto_upload_disable" && this.#controller) {
23417
+ await this.#controller.execute(kind);
23418
+ return this.view();
23419
+ }
23420
+ await this.#refresh();
23421
+ if (kind === "github_code_sync_status" && !this.#controller?.view().authorized)
23422
+ return this.view();
23423
+ if (!this.#controller)
23424
+ throw coded("code_github_not_configured");
23425
+ if (!this.#metadata?.connection?.enabled && kind !== "github_code_sync_status")
23426
+ throw coded("code_not_enabled");
23427
+ await this.#controller.execute(kind);
23428
+ return this.view();
23429
+ });
23430
+ }
23431
+ poll() {
23432
+ if (this.#stopped)
23433
+ return Promise.resolve();
23434
+ this.#poll ??= this.#serialize(async () => {
23435
+ await this.#refresh();
23436
+ await this.#controller?.poll();
23437
+ }).finally(() => {
23438
+ this.#poll = void 0;
23439
+ });
23440
+ return this.#poll;
23441
+ }
23442
+ async stop() {
23443
+ this.#stopped = true;
23444
+ await this.#tail;
23445
+ await this.#controller?.stop();
23446
+ }
23447
+ async #refresh() {
23448
+ const metadata = await this.options.readMetadata();
23449
+ const target = metadata.connection;
23450
+ const key = target && metadata.can_write ? targetKey(target) : void 0;
23451
+ if (key !== this.#key) {
23452
+ await this.#controller?.authorize(false);
23453
+ await this.#controller?.stop();
23454
+ this.#controller = void 0;
23455
+ this.#key = void 0;
23456
+ if (target && key) {
23457
+ const capturedTarget = { ...target };
23458
+ this.#controller = new DshCodeSyncController({
23459
+ ...this.options,
23460
+ binding: createHash4("sha256").update(JSON.stringify([this.options.binding, key])).digest("hex"),
23461
+ actionKinds: GITHUB_CODE_ACTIONS,
23462
+ createEngine: () => this.options.createEngine(capturedTarget)
23463
+ });
23464
+ await this.#controller.start();
23465
+ this.#key = key;
23466
+ }
23467
+ }
23468
+ this.#metadata = metadata;
23469
+ }
23470
+ #serialize(operation) {
23471
+ const pending = this.#tail.then(async () => {
23472
+ if (this.#stopped)
23473
+ throw coded("code_sync_stopped");
23474
+ this.#error = void 0;
23475
+ try {
23476
+ return await operation();
23477
+ } catch (error62) {
23478
+ this.#error = codeSyncErrorCode(error62);
23479
+ throw coded(this.#error);
23480
+ }
23481
+ });
23482
+ this.#tail = pending.catch(() => void 0);
23483
+ return pending;
23484
+ }
23485
+ };
23486
+ function targetKey(target) {
23487
+ return JSON.stringify([target.repository, target.base_branch, target.revision]);
23488
+ }
23489
+ function coded(code) {
23490
+ return Object.assign(new Error(code), { code });
23491
+ }
23492
+
22221
23493
  // dist/src/collaboration-api.js
22222
23494
  function createHttpDshCollaborationApi(options) {
22223
23495
  const client = new HttpCollaborationClient({
@@ -22293,9 +23565,9 @@ function clipToUtf8Bytes(value, maxBytes) {
22293
23565
  }
22294
23566
 
22295
23567
  // dist/src/config.js
22296
- import { createHash as createHash3 } from "node:crypto";
22297
- import { lstat as lstat5 } from "node:fs/promises";
22298
- import path5 from "node:path";
23568
+ import { createHash as createHash5 } from "node:crypto";
23569
+ import { lstat as lstat6 } from "node:fs/promises";
23570
+ import path6 from "node:path";
22299
23571
  var CONFIG_KEYS = /* @__PURE__ */ new Set([
22300
23572
  "enabled",
22301
23573
  "bindingMode",
@@ -22408,7 +23680,7 @@ async function assertSafeDshStateRoot(config2) {
22408
23680
  await assertRealStateSeparation(config2.workspacePath, config2.stateRoot, "config.stateRoot");
22409
23681
  await secureDirectoryChain(config2.stateRoot);
22410
23682
  try {
22411
- const target = await lstat5(config2.stateRoot);
23683
+ const target = await lstat6(config2.stateRoot);
22412
23684
  if (target.isSymbolicLink() || !target.isDirectory()) {
22413
23685
  throw new Error("config.stateRoot must be a real directory, never a symbolic link");
22414
23686
  }
@@ -22437,11 +23709,11 @@ function rejectModeKeys(value, forbidden, bindingMode) {
22437
23709
  }
22438
23710
  }
22439
23711
  function deriveDshSessionId(projectId, sessionId, workspacePath) {
22440
- const digest3 = createHash3("sha256").update(JSON.stringify([projectId, sessionId, path5.resolve(workspacePath)])).digest("hex").slice(0, 32);
23712
+ const digest3 = createHash5("sha256").update(JSON.stringify([projectId, sessionId, path6.resolve(workspacePath)])).digest("hex").slice(0, 32);
22441
23713
  return `gatherthread-${digest3}`;
22442
23714
  }
22443
23715
  function deriveNativeDshSessionId(projectId, sessionId, workspacePath) {
22444
- const digest3 = createHash3("sha256").update(JSON.stringify(["native-web-v2", projectId, sessionId, path5.resolve(workspacePath)])).digest("hex").slice(0, 32);
23716
+ const digest3 = createHash5("sha256").update(JSON.stringify(["native-web-v2", projectId, sessionId, path6.resolve(workspacePath)])).digest("hex").slice(0, 32);
22445
23717
  return `gatherthread-${digest3}`;
22446
23718
  }
22447
23719
  function normalizeApiUrl(value) {
@@ -22479,13 +23751,13 @@ function safeText(value, label, maximum) {
22479
23751
  }
22480
23752
  function absolutePath(value, label) {
22481
23753
  const text = requiredString2(value, label, 4096);
22482
- if (!path5.isAbsolute(text))
23754
+ if (!path6.isAbsolute(text))
22483
23755
  throw new Error(`${label} must be absolute`);
22484
- return path5.resolve(text);
23756
+ return path6.resolve(text);
22485
23757
  }
22486
23758
  function isWithin2(parent, candidate) {
22487
- const relative = path5.relative(parent, candidate);
22488
- return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== "..";
23759
+ const relative = path6.relative(parent, candidate);
23760
+ return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== "..";
22489
23761
  }
22490
23762
  function isNodeError3(error62) {
22491
23763
  return error62 instanceof Error && "code" in error62;
@@ -22524,7 +23796,7 @@ function rejectUnknownKeys(value, allowed, label) {
22524
23796
  }
22525
23797
 
22526
23798
  // dist/src/connector.js
22527
- import { createHash as createHash4 } from "node:crypto";
23799
+ import { createHash as createHash6 } from "node:crypto";
22528
23800
 
22529
23801
  // dist/src/canonical-prompt.js
22530
23802
  var MAX_EVENT_TEXT_BYTES = 8 * 1024;
@@ -22597,12 +23869,12 @@ ${footer}`, "utf8");
22597
23869
  const retained = [];
22598
23870
  let used = 0;
22599
23871
  for (const line of contextLines.toReversed()) {
22600
- const bytes = Buffer.byteLength(`${line}
23872
+ const bytes2 = Buffer.byteLength(`${line}
22601
23873
  `, "utf8");
22602
- if (used + bytes > contextBudget)
23874
+ if (used + bytes2 > contextBudget)
22603
23875
  continue;
22604
23876
  retained.push(line);
22605
- used += bytes;
23877
+ used += bytes2;
22606
23878
  }
22607
23879
  retained.reverse();
22608
23880
  const omitted = retained.length < contextLines.length ? [`[${contextLines.length - retained.length} older public context update(s) omitted by size limit]`] : [];
@@ -22816,41 +24088,290 @@ function safeJson(value) {
22816
24088
  return "[Unserializable tool value]";
22817
24089
  }
22818
24090
  }
22819
- function boundText2(value, maximumBytes) {
22820
- if (Buffer.byteLength(value, "utf8") <= maximumBytes)
22821
- return value;
22822
- const suffix = "\n[TRUNCATED]";
22823
- const target = Math.max(0, maximumBytes - Buffer.byteLength(suffix, "utf8"));
22824
- let low = 0;
22825
- let high = value.length;
22826
- while (low < high) {
22827
- const middle = Math.ceil((low + high) / 2);
22828
- if (Buffer.byteLength(value.slice(0, middle), "utf8") <= target)
22829
- low = middle;
22830
- else
22831
- high = middle - 1;
24091
+ function boundText2(value, maximumBytes) {
24092
+ if (Buffer.byteLength(value, "utf8") <= maximumBytes)
24093
+ return value;
24094
+ const suffix = "\n[TRUNCATED]";
24095
+ const target = Math.max(0, maximumBytes - Buffer.byteLength(suffix, "utf8"));
24096
+ let low = 0;
24097
+ let high = value.length;
24098
+ while (low < high) {
24099
+ const middle = Math.ceil((low + high) / 2);
24100
+ if (Buffer.byteLength(value.slice(0, middle), "utf8") <= target)
24101
+ low = middle;
24102
+ else
24103
+ high = middle - 1;
24104
+ }
24105
+ return `${value.slice(0, low)}${suffix}`;
24106
+ }
24107
+ function checkedLimit(value, fallback) {
24108
+ if (value === void 0)
24109
+ return fallback;
24110
+ if (!Number.isSafeInteger(value) || value < 1024 || value > 2 * 1024 * 1024) {
24111
+ throw new Error("DSH event mapping byte limits must be integers from 1024 to 2097152");
24112
+ }
24113
+ return value;
24114
+ }
24115
+ function safeTimestamp(value) {
24116
+ try {
24117
+ const timestamp3 = new Date(value);
24118
+ if (!Number.isFinite(timestamp3.getTime()))
24119
+ return void 0;
24120
+ return timestamp3.toISOString();
24121
+ } catch {
24122
+ return void 0;
24123
+ }
24124
+ }
24125
+ function asObject2(value) {
24126
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
24127
+ }
24128
+
24129
+ // dist/src/live-event-mapper.js
24130
+ var MAX_ATTEMPTS = 64;
24131
+ var MAX_BLOCKS = 128;
24132
+ var MAX_TEXT = 96 * 1024;
24133
+ function createDshLiveEventMapper(publisher) {
24134
+ const attempts = /* @__PURE__ */ new Map();
24135
+ const steps = /* @__PURE__ */ new Map();
24136
+ const tools = /* @__PURE__ */ new Map();
24137
+ let revision = 0;
24138
+ const deliver = (operation) => {
24139
+ void operation.catch(() => void 0);
24140
+ };
24141
+ const snapshot = (block, state) => deliver(publisher.onSnapshot(block.key, block.text, block.kind, block.title, state));
24142
+ const toolFor = (id, fallbackKey) => {
24143
+ let tool = tools.get(id);
24144
+ if (tool === void 0 && tools.size < MAX_BLOCKS) {
24145
+ tool = { key: fallbackKey, arguments: "" };
24146
+ tools.set(id, tool);
24147
+ }
24148
+ return tool;
24149
+ };
24150
+ const settle2 = (attempt, state) => {
24151
+ if (state === "failed")
24152
+ attempt.failed = true;
24153
+ for (const block of attempt.blocks.values()) {
24154
+ if (block.kind !== "tool")
24155
+ snapshot(block, state);
24156
+ else if (state === "failed") {
24157
+ const argumentsText = block.callId === void 0 ? void 0 : tools.get(block.callId)?.arguments;
24158
+ deliver(publisher.onSnapshot(block.key, `Arguments:
24159
+ ${argumentsText || "[Unparseable tool arguments]"}`, "tool", block.title, state));
24160
+ }
24161
+ }
24162
+ };
24163
+ const onFrame = (frame) => {
24164
+ if (frame.revision <= revision)
24165
+ return;
24166
+ const identity = frame.attemptId;
24167
+ if (frame.type === "start") {
24168
+ if (attempts.has(identity) || attempts.size >= MAX_ATTEMPTS)
24169
+ return;
24170
+ revision = frame.revision;
24171
+ const attempt2 = { turn: frame.turn, step: frame.step, nextIndex: 0, closed: false, failed: false, blocks: /* @__PURE__ */ new Map() };
24172
+ attempts.set(identity, attempt2);
24173
+ steps.set(`${frame.turn}:${frame.step}`, attempt2);
24174
+ return;
24175
+ }
24176
+ const attempt = attempts.get(identity);
24177
+ if (attempt === void 0 || attempt.closed || frame.index !== attempt.nextIndex)
24178
+ return;
24179
+ revision = frame.revision;
24180
+ if (frame.type === "end") {
24181
+ attempt.closed = true;
24182
+ settle2(attempt, attempt.failed || frame.outcome.kind === "abandoned" || frame.outcome.eventType === "assistant/attempt" ? "failed" : "completed");
24183
+ return;
24184
+ }
24185
+ attempt.nextIndex++;
24186
+ const chunk = object2(frame.chunk);
24187
+ if (chunk?.type === "finish") {
24188
+ const reason = object2(chunk.reason)?.kind ?? chunk.reason;
24189
+ if (reason === "error" || reason === "aborted")
24190
+ settle2(attempt, "failed");
24191
+ return;
24192
+ }
24193
+ if (!integer2(chunk?.index) || Number(chunk.index) >= MAX_BLOCKS)
24194
+ return;
24195
+ const index = Number(chunk.index);
24196
+ if (chunk.type === "block-start") {
24197
+ const kind = chunk.blockType === "text" ? "answer" : chunk.blockType === "reasoning" ? "reasoning" : chunk.blockType === "tool-call" ? "tool" : void 0;
24198
+ if (kind !== void 0 && !attempt.blocks.has(index)) {
24199
+ attempt.blocks.set(index, { key: `${identity}:${index}`, kind, text: "" });
24200
+ }
24201
+ return;
24202
+ }
24203
+ const block = attempt.blocks.get(index);
24204
+ if (block === void 0)
24205
+ return;
24206
+ if (chunk.type === "text-delta" && block.kind === "answer" || chunk.type === "reasoning-delta" && block.kind === "reasoning") {
24207
+ if (typeof chunk.text !== "string")
24208
+ return;
24209
+ block.text = bound2(block.text + chunk.text);
24210
+ deliver(publisher.onDelta(block.key, chunk.text, block.kind));
24211
+ } else if (chunk.type === "tool-call-delta" && block.kind === "tool") {
24212
+ if (!identifier(chunk.id) || typeof chunk.argumentsDelta !== "string" || block.callId !== void 0 && block.callId !== chunk.id)
24213
+ return;
24214
+ block.callId = chunk.id;
24215
+ if (typeof chunk.name === "string")
24216
+ block.title = bound2(chunk.name).slice(0, 256);
24217
+ const tool = toolFor(block.callId, block.key);
24218
+ if (tool === void 0)
24219
+ return;
24220
+ block.key = tool.key;
24221
+ block.text = bound2(block.text + chunk.argumentsDelta);
24222
+ if (block.title !== void 0)
24223
+ tool.title = block.title;
24224
+ const argumentsText = parsedArguments(block.text);
24225
+ if (argumentsText !== void 0)
24226
+ tool.arguments = argumentsText;
24227
+ deliver(publisher.onSnapshot(block.key, argumentsText === void 0 ? "Preparing tool arguments.\n" : `Arguments:
24228
+ ${argumentsText}`, "tool", block.title));
24229
+ } else if (chunk.type === "block-end") {
24230
+ const content = object2(chunk.block);
24231
+ if ((content?.type === "text" && block.kind === "answer" || content?.type === "reasoning" && block.kind === "reasoning") && typeof content.text === "string") {
24232
+ block.text = bound2(content.text);
24233
+ snapshot(block, "completed");
24234
+ } else if (content?.type === "tool-call" && block.kind === "tool" && identifier(content.id) && typeof content.arguments === "string" && (block.callId === void 0 || content.id === block.callId)) {
24235
+ const tool = toolFor(content.id, block.key);
24236
+ if (tool === void 0)
24237
+ return;
24238
+ block.callId = content.id;
24239
+ block.key = tool.key;
24240
+ block.text = publicArguments(content.arguments);
24241
+ if (typeof content.name === "string")
24242
+ block.title = content.name.slice(0, 256);
24243
+ tool.arguments = block.text;
24244
+ if (block.title !== void 0)
24245
+ tool.title = block.title;
24246
+ snapshot(block, "running");
24247
+ }
24248
+ }
24249
+ };
24250
+ const onDurable = (event) => {
24251
+ const data = object2(event.data);
24252
+ if (data === void 0)
24253
+ return;
24254
+ if (event.type === "assistant/message" && integer2(data.turn) && integer2(data.step)) {
24255
+ const attempt = steps.get(`${data.turn}:${data.step}`);
24256
+ if (attempt !== void 0 && data.interrupted === true)
24257
+ attempt.failed = true;
24258
+ const message = object2(data.message);
24259
+ if (!Array.isArray(message?.content))
24260
+ return;
24261
+ const nativeBlocks = [...attempt?.blocks.values() ?? []];
24262
+ for (const [index, value] of message.content.slice(0, MAX_BLOCKS).entries()) {
24263
+ const content = object2(value);
24264
+ const kind = content?.type === "text" ? "answer" : content?.type === "reasoning" ? "reasoning" : void 0;
24265
+ if (kind !== void 0 && typeof content.text === "string") {
24266
+ const candidate = nativeBlocks[index];
24267
+ const key = candidate?.kind === kind ? candidate.key : `assistant:${event.seq}:${index}`;
24268
+ if (candidate?.kind === kind)
24269
+ candidate.text = bound2(content.text);
24270
+ deliver(publisher.onSnapshot(key, content.text, kind, void 0, data.interrupted === true ? "failed" : "completed"));
24271
+ }
24272
+ }
24273
+ } else if (event.type === "assistant/attempt" && integer2(data.turn) && integer2(data.step)) {
24274
+ const attempt = steps.get(`${data.turn}:${data.step}`);
24275
+ if (attempt !== void 0)
24276
+ settle2(attempt, "failed");
24277
+ } else if (event.type === "tool/call" && identifier(data.callId) && typeof data.name === "string" && typeof data.arguments === "string") {
24278
+ const tool = toolFor(data.callId, `tool:${event.seq}`);
24279
+ if (tool === void 0)
24280
+ return;
24281
+ tool.arguments = publicArguments(data.arguments);
24282
+ tool.title = data.name.slice(0, 256);
24283
+ deliver(publisher.onSnapshot(tool.key, `Arguments:
24284
+ ${tool.arguments}`, "tool", tool.title));
24285
+ } else if (event.type === "tool/result") {
24286
+ const message = object2(data.message);
24287
+ const content = Array.isArray(message?.content) ? message.content : [];
24288
+ const wrapped = content.map(object2).find((block) => block?.type === "tool-result");
24289
+ const id = wrapped?.toolCallId ?? message?.toolCallId;
24290
+ if (!identifier(id))
24291
+ return;
24292
+ const tool = tools.get(id);
24293
+ if (tool === void 0)
24294
+ return;
24295
+ const blocks = wrapped === void 0 ? content : wrapped.content;
24296
+ if (!Array.isArray(blocks))
24297
+ return;
24298
+ const text = blocks.slice(0, MAX_BLOCKS).flatMap((value) => {
24299
+ const block = object2(value);
24300
+ return block?.type === "text" && typeof block.text === "string" ? [bound2(block.text)] : [];
24301
+ }).join("");
24302
+ const failed = wrapped?.isError === true || message?.isError === true || data.error !== void 0;
24303
+ deliver(publisher.onSnapshot(tool.key, `Arguments:
24304
+ ${tool.arguments}
24305
+
24306
+ Result:
24307
+ ${bound2(text)}`, "tool", tool.title, failed ? "failed" : "completed"));
24308
+ } else if (event.type === "todo/write" && Array.isArray(data.todos) && data.todos.length <= 64) {
24309
+ const plan = [];
24310
+ for (const value of data.todos) {
24311
+ const item = object2(value);
24312
+ if (typeof item?.content !== "string" || !["pending", "in_progress", "completed"].includes(String(item.status)))
24313
+ return;
24314
+ plan.push({ step: item.content, status: item.status });
24315
+ }
24316
+ deliver(publisher.onPlan("plan", plan));
24317
+ }
24318
+ };
24319
+ return {
24320
+ onEvent(event) {
24321
+ if (event.kind === "assistant-stream")
24322
+ onFrame(event.frame);
24323
+ else
24324
+ onDurable(event.event);
24325
+ },
24326
+ async finish(success2 = true) {
24327
+ await publisher.finish(success2);
24328
+ }
24329
+ };
24330
+ }
24331
+ function parseDshAssistantStreamFrame(value) {
24332
+ const frame = object2(value);
24333
+ if (!identifier(frame?.attemptId) || !integer2(frame?.revision) || Number(frame.revision) < 1)
24334
+ return void 0;
24335
+ const shared = { attemptId: frame.attemptId, revision: Number(frame.revision) };
24336
+ if (frame.type === "start" && integer2(frame.turn) && integer2(frame.step)) {
24337
+ return { type: "start", ...shared, turn: Number(frame.turn), step: Number(frame.step) };
22832
24338
  }
22833
- return `${value.slice(0, low)}${suffix}`;
22834
- }
22835
- function checkedLimit(value, fallback) {
22836
- if (value === void 0)
22837
- return fallback;
22838
- if (!Number.isSafeInteger(value) || value < 1024 || value > 2 * 1024 * 1024) {
22839
- throw new Error("DSH event mapping byte limits must be integers from 1024 to 2097152");
24339
+ if (frame.type === "chunk" && integer2(frame.index) && integer2(frame.time) && object2(frame.chunk) !== void 0) {
24340
+ return { type: "chunk", ...shared, index: Number(frame.index), time: Number(frame.time), chunk: frame.chunk };
22840
24341
  }
22841
- return value;
24342
+ const outcome = object2(frame.outcome);
24343
+ if (frame.type === "end" && integer2(frame.index)) {
24344
+ if (outcome?.kind === "abandoned")
24345
+ return { type: "end", ...shared, index: Number(frame.index), outcome: { kind: "abandoned" } };
24346
+ if (outcome?.kind === "committed" && integer2(outcome.seq) && (outcome.eventType === "assistant/message" || outcome.eventType === "assistant/attempt")) {
24347
+ return { type: "end", ...shared, index: Number(frame.index), outcome: { kind: "committed", eventType: outcome.eventType, seq: Number(outcome.seq) } };
24348
+ }
24349
+ }
24350
+ return void 0;
22842
24351
  }
22843
- function safeTimestamp(value) {
24352
+ function publicArguments(text) {
24353
+ return parsedArguments(text) ?? "[Unparseable tool arguments]";
24354
+ }
24355
+ function parsedArguments(text) {
24356
+ if (text.length > MAX_TEXT)
24357
+ return void 0;
22844
24358
  try {
22845
- const timestamp3 = new Date(value);
22846
- if (!Number.isFinite(timestamp3.getTime()))
22847
- return void 0;
22848
- return timestamp3.toISOString();
24359
+ return JSON.stringify(redactValue(JSON.parse(text)));
22849
24360
  } catch {
22850
24361
  return void 0;
22851
24362
  }
22852
24363
  }
22853
- function asObject2(value) {
24364
+ function bound2(text) {
24365
+ return text.length > MAX_TEXT ? `${text.slice(0, MAX_TEXT)}
24366
+ [TRUNCATED]` : text;
24367
+ }
24368
+ function integer2(value) {
24369
+ return Number.isSafeInteger(value) && Number(value) >= 0;
24370
+ }
24371
+ function identifier(value) {
24372
+ return typeof value === "string" && value.length > 0 && value.length <= 512;
24373
+ }
24374
+ function object2(value) {
22854
24375
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
22855
24376
  }
22856
24377
 
@@ -22881,6 +24402,7 @@ var DshHostConnector = class {
22881
24402
  #publicationPromise;
22882
24403
  #heartbeatOffline = false;
22883
24404
  #liveProgressPromise = Promise.resolve();
24405
+ #activeLiveOutput;
22884
24406
  #lastLiveProgressKey;
22885
24407
  #lastLiveProgressAt = 0;
22886
24408
  #stopPromise;
@@ -22891,6 +24413,8 @@ var DshHostConnector = class {
22891
24413
  #activeExecutionSelection;
22892
24414
  #summaryScanSequence = 0;
22893
24415
  #hasSummaryHistory = false;
24416
+ #revisionPollPromise;
24417
+ #revisionSupported = false;
22894
24418
  #liveEventSequences = /* @__PURE__ */ new Set();
22895
24419
  #session;
22896
24420
  #visibleState = "connecting";
@@ -22916,6 +24440,15 @@ var DshHostConnector = class {
22916
24440
  get stopped() {
22917
24441
  return this.#stopped;
22918
24442
  }
24443
+ /** Project refresh is authoritative for the mode of future DSH bubbles. */
24444
+ updateSessionSummary(session) {
24445
+ if (!this.#started || this.#stopped)
24446
+ throw new Error("DSH connector is not running");
24447
+ if (session.id !== this.#config.sessionId || session.projectId !== this.#config.projectId || session.state !== "active" || !isSessionWritableBy(session, this.#actorUserId)) {
24448
+ throw new Error("Refreshed GatherThread Session is not this writable binding");
24449
+ }
24450
+ this.#session = session;
24451
+ }
22919
24452
  /**
22920
24453
  * Adopt a refreshed execution-profile catalog on a running connection.
22921
24454
  *
@@ -22986,7 +24519,8 @@ var DshHostConnector = class {
22986
24519
  ...executionProfiles === void 0 ? {} : {
22987
24520
  executionProfiles: cloneExecutionProfiles(executionProfiles)
22988
24521
  },
22989
- purpose: "execution"
24522
+ purpose: "execution",
24523
+ ...this.#host.contextDeletionVersion === 1 ? { contextDeletionVersion: 1 } : {}
22990
24524
  };
22991
24525
  }
22992
24526
  get executionRuntimeId() {
@@ -23019,6 +24553,7 @@ var DshHostConnector = class {
23019
24553
  }
23020
24554
  uploadLocalTurns() {
23021
24555
  return this.#withLocalSyncControl(async () => {
24556
+ await this.#refreshContentRevision();
23022
24557
  const before = this.localSyncStatus();
23023
24558
  await this.#flushOutbox(true);
23024
24559
  await this.#captureLocalTurns(0);
@@ -23041,6 +24576,15 @@ var DshHostConnector = class {
23041
24576
  this.#started = true;
23042
24577
  this.#automaticPolling = options.schedule !== false;
23043
24578
  this.#notifyLifecycle("connecting");
24579
+ if (this.#host.onLiveOutput !== void 0) {
24580
+ this.#listenerDisposers.push(this.#host.onLiveOutput((event) => {
24581
+ const active = this.#state?.activeRequest;
24582
+ const output2 = this.#activeLiveOutput;
24583
+ if (this.#stopped || active === void 0 || output2 === void 0 || event.sourceSessionId !== output2.sourceSessionId || event.sourceSessionId !== (active.contextExecution?.sessionId ?? this.#host.sessionId))
24584
+ return;
24585
+ output2.mapper.onEvent(event);
24586
+ }));
24587
+ }
23044
24588
  this.#listenerDisposers.push(this.#host.onSessionEvent((event) => {
23045
24589
  const active = this.#state?.activeRequest;
23046
24590
  if (active?.contextExecution !== void 0 && event.sourceSessionId !== active.contextExecution.sessionId)
@@ -23086,6 +24630,7 @@ var DshHostConnector = class {
23086
24630
  }));
23087
24631
  try {
23088
24632
  const session = await this.#verifySessionBinding();
24633
+ this.#revisionSupported = session.contentRevision !== void 0;
23089
24634
  this.#session = session;
23090
24635
  this.#notifyLifecycle("connecting");
23091
24636
  const storedState = await this.#stateStore.load();
@@ -23093,8 +24638,16 @@ var DshHostConnector = class {
23093
24638
  throw new Error("GatherThread Session head precedes the persisted DSH cursor; refusing identity reuse");
23094
24639
  }
23095
24640
  const mode = await this.#host.open();
24641
+ if (storedState?.projectionSessionId && (storedState.contentRevision ?? 0) > 0) {
24642
+ if (!this.#host.resetCanonicalContext)
24643
+ throw new Error("DSH deletion-safe projection requires an updated native facade");
24644
+ const restored = await this.#host.resetCanonicalContext(storedState.contentRevision);
24645
+ if (restored.sessionId !== storedState.projectionSessionId)
24646
+ throw new Error("DSH restored another native projection");
24647
+ }
23096
24648
  this.#state = this.#reconcileState(storedState, mode);
23097
24649
  await this.#stateStore.save(this.#state);
24650
+ await this.#refreshContentRevision();
23098
24651
  this.#runtime = await this.#api.registerRuntime(this.#runtimeRegistration());
23099
24652
  this.#assertRuntime(this.#runtime);
23100
24653
  this.#notifyLifecycle("idle");
@@ -23166,6 +24719,8 @@ var DshHostConnector = class {
23166
24719
  stop() {
23167
24720
  this.#stopPromise ??= (async () => {
23168
24721
  this.#stopped = true;
24722
+ const liveOutput = this.#activeLiveOutput;
24723
+ this.#activeLiveOutput = void 0;
23169
24724
  this.#notifyLifecycle("stopped");
23170
24725
  this.#lifecycleAbort.abort(new Error("DSH connector stopped"));
23171
24726
  if (this.#timer !== void 0) {
@@ -23179,6 +24734,7 @@ var DshHostConnector = class {
23179
24734
  for (const dispose of this.#listenerDisposers.splice(0))
23180
24735
  dispose();
23181
24736
  await this.#host.dispose();
24737
+ void liveOutput?.mapper.finish(false).catch(() => void 0);
23182
24738
  try {
23183
24739
  await this.#pollPromise;
23184
24740
  } catch {
@@ -23191,6 +24747,7 @@ var DshHostConnector = class {
23191
24747
  return this.#stopPromise;
23192
24748
  }
23193
24749
  async #poll() {
24750
+ await this.#refreshContentRevision();
23194
24751
  const state = this.#requireState();
23195
24752
  if (state.activeRequest !== void 0) {
23196
24753
  await this.#withExecutionPermit(() => this.#recoverActiveRequest());
@@ -23207,6 +24764,12 @@ var DshHostConnector = class {
23207
24764
  let scanned = 0;
23208
24765
  while (!this.#stopped) {
23209
24766
  const page = await this.#api.readEvents(this.#config.sessionId, scanCursor, this.#config.pollLimit);
24767
+ if (page.contentRevision !== void 0)
24768
+ this.#revisionSupported = true;
24769
+ if ((page.contentRevision ?? 0) !== (state.contentRevision ?? 0)) {
24770
+ await this.#refreshContentRevision();
24771
+ return this.#poll();
24772
+ }
23210
24773
  this.#validatePage(page.events, scanCursor);
23211
24774
  const passive = [];
23212
24775
  for (const event of page.events) {
@@ -23240,6 +24803,39 @@ var DshHostConnector = class {
23240
24803
  }
23241
24804
  throw new Error("DSH connector stopped during event replay");
23242
24805
  }
24806
+ async #refreshContentRevision() {
24807
+ const state = this.#requireState();
24808
+ if (!this.#revisionSupported && (state.contentRevision ?? 0) === 0)
24809
+ return;
24810
+ const page = await this.#api.readEvents(this.#config.sessionId, 0, 1);
24811
+ const revision = page.contentRevision ?? 0;
24812
+ if (!Number.isSafeInteger(revision) || revision < (state.contentRevision ?? 0))
24813
+ throw new Error("Stale DSH shared context revision");
24814
+ if (revision === (state.contentRevision ?? 0)) {
24815
+ if (revision > 0 && state.projectionSessionId && this.#host.resetCanonicalContext) {
24816
+ const restored = await this.#host.resetCanonicalContext(revision);
24817
+ if (restored.sessionId !== state.projectionSessionId)
24818
+ throw new Error("DSH clean projection binding changed on reconnect");
24819
+ }
24820
+ return;
24821
+ }
24822
+ state.localOnlySessionIds = [.../* @__PURE__ */ new Set([...state.localOnlySessionIds ?? [], this.#config.dshSessionId, this.#host.sessionId])];
24823
+ state.outbox = [];
24824
+ delete state.activeRequest;
24825
+ this.#activeStatuses = [];
24826
+ this.#activeExecutionSelection = void 0;
24827
+ this.#summaryScanSequence = 0;
24828
+ this.#hasSummaryHistory = false;
24829
+ await this.#stateStore.save(state);
24830
+ if (this.#host.resetCanonicalContext) {
24831
+ const replacement = await this.#host.resetCanonicalContext(revision);
24832
+ state.projectionSessionId = replacement.sessionId;
24833
+ state.projectionCursor = 0;
24834
+ state.publishedDshSequence = this.#host.currentSequence();
24835
+ }
24836
+ state.contentRevision = revision;
24837
+ await this.#stateStore.save(state);
24838
+ }
23243
24839
  async #executeRequest(request, selection) {
23244
24840
  if (this.#stopped)
23245
24841
  throw new Error("DSH connector stopped before claiming an Agent request");
@@ -23258,6 +24854,8 @@ var DshHostConnector = class {
23258
24854
  await this.#projectCanonicalBatch([request], request.sequence);
23259
24855
  return { claimed: false, completed: false };
23260
24856
  }
24857
+ if ((claim2.contentRevision ?? state.contentRevision ?? 0) !== (state.contentRevision ?? 0))
24858
+ throw new Error("context_changed");
23261
24859
  const quotedEvent = request.replyTo ? (await this.#readThroughRequest(request.id, request.sequence)).find((event) => event.id === request.replyTo) : void 0;
23262
24860
  const prompt = buildDshRequestPrompt(request, quotedEvent);
23263
24861
  const context = await this.#contextForRequest(request);
@@ -23270,6 +24868,7 @@ var DshHostConnector = class {
23270
24868
  if (prepared !== void 0 && prepared.fromSequence !== baseline)
23271
24869
  throw new Error("DSH context preparation returned an inconsistent sequence");
23272
24870
  state.activeRequest = {
24871
+ contentRevision: state.contentRevision ?? 0,
23273
24872
  requestId: request.id,
23274
24873
  requestSequence: request.sequence,
23275
24874
  dshFromSequence: baseline,
@@ -23283,7 +24882,7 @@ var DshHostConnector = class {
23283
24882
  this.#activeExecutionSelection = selection;
23284
24883
  this.#liveEventSequences.clear();
23285
24884
  await this.#stateStore.save(state);
23286
- const result = await this.#host.prompt(prompt, selection, prepared?.sessionId);
24885
+ const result = await this.#promptWithLiveOutput(prompt, selection, prepared?.sessionId);
23287
24886
  if (result.fromSequence !== baseline || result.toSequence < result.fromSequence) {
23288
24887
  throw new Error("DSH Host prompt returned an inconsistent durable event range");
23289
24888
  }
@@ -23305,7 +24904,7 @@ var DshHostConnector = class {
23305
24904
  throw new Error("GatherThread history summary request has invalid source metadata");
23306
24905
  if (selectedOnly)
23307
24906
  this.#hasSummaryHistory = true;
23308
- if (!selectedOnly && !await this.#sessionHasSummaryBefore(request.sequence))
24907
+ if (!selectedOnly && (this.#requireState().contentRevision ?? 0) === 0 && !await this.#sessionHasSummaryBefore(request.sequence))
23309
24908
  return void 0;
23310
24909
  if (!selectedOnly && this.#api.readContext === void 0) {
23311
24910
  throw new Error("GatherThread summarized context is unavailable; update the connector before continuing");
@@ -23313,7 +24912,9 @@ var DshHostConnector = class {
23313
24912
  if (this.#host.prepareContextExecution === void 0) {
23314
24913
  throw new Error("DSH isolated context execution is unavailable; refusing to run a summary-aware request in native history");
23315
24914
  }
23316
- const received = selectedOnly ? { view: "original", through_sequence: request.sequence - 1, items: [] } : parseHistoryContext(await this.#api.readContext(this.#config.sessionId, void 0, request.sequence - 1), void 0, request.sequence - 1);
24915
+ const received = selectedOnly ? { view: "original", content_revision: this.#requireState().contentRevision ?? 0, through_sequence: request.sequence - 1, items: [] } : parseHistoryContext(await this.#api.readContext(this.#config.sessionId, void 0, request.sequence - 1), void 0, request.sequence - 1);
24916
+ if ((received.content_revision ?? 0) !== (this.#requireState().contentRevision ?? 0))
24917
+ throw new Error("context_changed");
23317
24918
  const historyContext = { ...received, items: received.items.map((item) => ({ ...item, content: redactText(item.content) })) };
23318
24919
  return { historyContext, selectedOnly };
23319
24920
  }
@@ -23411,6 +25012,10 @@ var DshHostConnector = class {
23411
25012
  throw new Error("Active GatherThread request no longer has a recoverable claim");
23412
25013
  }
23413
25014
  const claimAttempt = claim2.attemptCount ?? active.claimAttempt ?? 1;
25015
+ if ((active.contentRevision ?? 0) !== (state.contentRevision ?? 0) || (claim2.contentRevision ?? active.contentRevision ?? 0) !== (active.contentRevision ?? 0)) {
25016
+ await this.#retireFencedActiveRequest(state, active);
25017
+ return;
25018
+ }
23414
25019
  active.claimAttempt = claimAttempt;
23415
25020
  if (active.dshToSequence !== void 0) {
23416
25021
  state.outbox = state.outbox.map((operation) => {
@@ -23483,7 +25088,7 @@ var DshHostConnector = class {
23483
25088
  this.#activeStatuses = [];
23484
25089
  this.#liveEventSequences.clear();
23485
25090
  await this.#stateStore.save(state);
23486
- const result = await this.#host.prompt(continuation, selection, execution?.sessionId);
25091
+ const result = await this.#promptWithLiveOutput(continuation, selection, execution?.sessionId);
23487
25092
  if (result.fromSequence !== baseline || result.toSequence < result.fromSequence) {
23488
25093
  throw new Error("DSH Host recovery prompt returned an inconsistent durable event range");
23489
25094
  }
@@ -23512,6 +25117,94 @@ var DshHostConnector = class {
23512
25117
  await this.#flushOutbox();
23513
25118
  await this.#finalizeDeliveredRequest();
23514
25119
  }
25120
+ async #promptWithLiveOutput(text, selection, executionSessionId) {
25121
+ const active = this.#requireState().activeRequest;
25122
+ if (active === void 0)
25123
+ throw new Error("No claimed DSH request owns live output");
25124
+ const requestId = active.requestId;
25125
+ const claimAttempt = active.claimAttempt ?? 1;
25126
+ const fromSequence = active.dshFromSequence;
25127
+ const runtime = this.#requireRuntime();
25128
+ const sourceSessionId = executionSessionId ?? this.#host.sessionId;
25129
+ const publisher = createLiveOutputPublisher({
25130
+ publish: async (update) => {
25131
+ const publication = this.#liveProgressPromise.then(async () => {
25132
+ const current = this.#state?.activeRequest;
25133
+ if (this.#stopped || this.#activeLiveOutput?.publisher !== publisher || current?.requestId !== requestId || (current.claimAttempt ?? 1) !== claimAttempt || current.dshFromSequence !== fromSequence || (current.contextExecution?.sessionId ?? this.#host.sessionId) !== sourceSessionId || this.#runtime?.id !== runtime.id || this.#activeExecutionSelection !== selection)
25134
+ return false;
25135
+ await this.#api.appendAgentProgress(this.#config.sessionId, requestId, {
25136
+ runtimeId: runtime.id,
25137
+ claimAttempt,
25138
+ contentRevision: active.contentRevision ?? 0,
25139
+ idempotencyKey: `${this.#config.deviceId}:${digest2(requestId).slice(0, 24)}:${claimAttempt}:live-${update.id}`,
25140
+ payload: { ...liveOutputPayload(update), capture_fidelity: "harness_transcript", source_harness: "deepseek-harness" },
25141
+ observedModel: selection.model,
25142
+ ...selection.reasoningEffort === void 0 ? {} : { observedReasoningEffort: selection.reasoningEffort }
25143
+ });
25144
+ return true;
25145
+ });
25146
+ this.#liveProgressPromise = publication.then(() => void 0).catch((error62) => {
25147
+ this.#onBackgroundError?.(publicError(error62));
25148
+ });
25149
+ return publication.catch(() => false);
25150
+ }
25151
+ });
25152
+ const output2 = { publisher, mapper: createDshLiveEventMapper(publisher), sourceSessionId };
25153
+ this.#activeLiveOutput = output2;
25154
+ const abort = new AbortController();
25155
+ const abortLifecycle = () => abort.abort(this.#lifecycleAbort.signal.reason);
25156
+ this.#lifecycleAbort.signal.addEventListener("abort", abortLifecycle, { once: true });
25157
+ const verifyRevision = async () => {
25158
+ if (!this.#revisionSupported && (active.contentRevision ?? 0) === 0)
25159
+ return;
25160
+ try {
25161
+ const page = await this.#api.readEvents(this.#config.sessionId, 0, 1);
25162
+ if ((page.contentRevision ?? 0) !== (active.contentRevision ?? 0))
25163
+ abort.abort(new Error("context_changed"));
25164
+ } catch (error62) {
25165
+ abort.abort(error62);
25166
+ }
25167
+ };
25168
+ const timer = setInterval(() => {
25169
+ if (this.#revisionPollPromise)
25170
+ return;
25171
+ const poll = verifyRevision();
25172
+ this.#revisionPollPromise = poll;
25173
+ void poll.finally(() => {
25174
+ if (this.#revisionPollPromise === poll)
25175
+ this.#revisionPollPromise = void 0;
25176
+ });
25177
+ }, 1e3);
25178
+ timer.unref();
25179
+ try {
25180
+ await verifyRevision();
25181
+ abort.signal.throwIfAborted();
25182
+ const result = await this.#host.prompt(text, selection, executionSessionId, abort.signal);
25183
+ await verifyRevision();
25184
+ abort.signal.throwIfAborted();
25185
+ await output2.mapper.finish();
25186
+ return result;
25187
+ } catch (error62) {
25188
+ if (abort.signal.aborted) {
25189
+ const state = this.#requireState();
25190
+ state.outbox = state.outbox.filter((operation) => !outboxBelongsToRequest(operation, requestId));
25191
+ delete state.activeRequest;
25192
+ this.#activeExecutionSelection = void 0;
25193
+ await this.#stateStore.save(state);
25194
+ }
25195
+ throw error62;
25196
+ } finally {
25197
+ clearInterval(timer);
25198
+ this.#lifecycleAbort.signal.removeEventListener("abort", abortLifecycle);
25199
+ await this.#revisionPollPromise;
25200
+ if (this.#activeLiveOutput === output2)
25201
+ this.#activeLiveOutput = void 0;
25202
+ if (this.#stopped)
25203
+ void output2.mapper.finish(false).catch(() => void 0);
25204
+ else
25205
+ await output2.mapper.finish(false);
25206
+ }
25207
+ }
23515
25208
  #queueLiveProgress(idSuffix, payload, throttle = true) {
23516
25209
  const active = this.#state?.activeRequest;
23517
25210
  const runtime = this.#runtime;
@@ -23521,6 +25214,9 @@ var DshHostConnector = class {
23521
25214
  const claimAttempt = active.claimAttempt ?? 1;
23522
25215
  const progressKey = `${requestId}:${String(claimAttempt)}`;
23523
25216
  const now = Date.now();
25217
+ const streamedAt = this.#activeLiveOutput?.publisher.lastPublishedAt();
25218
+ if (throttle && streamedAt !== void 0 && now - streamedAt < LIVE_PROGRESS_MIN_INTERVAL_MS)
25219
+ return;
23524
25220
  if (throttle && this.#lastLiveProgressKey === progressKey && now - this.#lastLiveProgressAt < LIVE_PROGRESS_MIN_INTERVAL_MS) {
23525
25221
  return;
23526
25222
  }
@@ -23531,7 +25227,7 @@ var DshHostConnector = class {
23531
25227
  const idempotencyKey = `${this.#config.deviceId}:${digest2(requestId).slice(0, 24)}:${String(claimAttempt)}:${idSuffix}`;
23532
25228
  this.#liveProgressPromise = this.#liveProgressPromise.then(async () => {
23533
25229
  const current = this.#state?.activeRequest;
23534
- if (current?.requestId !== requestId || (current.claimAttempt ?? 1) !== claimAttempt)
25230
+ if (this.#stopped || current?.requestId !== requestId || (current.claimAttempt ?? 1) !== claimAttempt || this.#runtime?.id !== runtime.id)
23535
25231
  return;
23536
25232
  const selection = this.#activeExecutionSelection;
23537
25233
  if (selection === void 0)
@@ -23539,6 +25235,7 @@ var DshHostConnector = class {
23539
25235
  await this.#api.appendAgentProgress(this.#config.sessionId, requestId, {
23540
25236
  runtimeId: runtime.id,
23541
25237
  claimAttempt,
25238
+ contentRevision: active.contentRevision ?? 0,
23542
25239
  idempotencyKey,
23543
25240
  payload,
23544
25241
  observedModel: selection.model,
@@ -23556,11 +25253,15 @@ var DshHostConnector = class {
23556
25253
  }
23557
25254
  async #projectCanonicalBatch(events, throughSequence) {
23558
25255
  const state = this.#requireState();
25256
+ if (state.localOnlySessionIds?.includes(this.#host.sessionId))
25257
+ return;
23559
25258
  if (throughSequence <= state.projectionCursor)
23560
25259
  return;
23561
25260
  const runtime = this.#requireRuntime();
23562
25261
  const projections = [];
23563
25262
  for (const event of events) {
25263
+ if (event.deleted)
25264
+ continue;
23564
25265
  if (event.type === "agent_request" && asObject3(event.payload)?.history_summary !== void 0) {
23565
25266
  this.#hasSummaryHistory = true;
23566
25267
  continue;
@@ -23577,7 +25278,7 @@ var DshHostConnector = class {
23577
25278
  role: "user",
23578
25279
  content,
23579
25280
  occurredAt: event.timestamp,
23580
- ...event.actorDisplayName === void 0 ? {} : {
25281
+ ...this.#session?.mode !== "multi" || event.actorDisplayName === void 0 ? {} : {
23581
25282
  actorDisplayName: event.actorDisplayName
23582
25283
  }
23583
25284
  });
@@ -23605,6 +25306,8 @@ var DshHostConnector = class {
23605
25306
  }
23606
25307
  async #captureLocalTurns(basedOnSequence) {
23607
25308
  const state = this.#requireState();
25309
+ if (state.localOnlySessionIds?.includes(this.#host.sessionId))
25310
+ return;
23608
25311
  if (state.activeRequest !== void 0 || state.outbox.length > 0)
23609
25312
  return;
23610
25313
  const current = this.#host.currentSequence();
@@ -23621,6 +25324,7 @@ var DshHostConnector = class {
23621
25324
  input: {
23622
25325
  localTurnId: turn.localTurnId,
23623
25326
  runtimeId: runtime.id,
25327
+ contentRevision: state.contentRevision ?? 0,
23624
25328
  basedOnSequence: basedOnSequence ?? state.serverCursor,
23625
25329
  occurredAt: turn.occurredAt,
23626
25330
  observedModel: this.#config.model,
@@ -23734,7 +25438,10 @@ var DshHostConnector = class {
23734
25438
  }
23735
25439
  }
23736
25440
  });
23737
- return operations;
25441
+ return operations.map((operation) => ({
25442
+ ...operation,
25443
+ input: { ...operation.input, contentRevision: this.#requireState().activeRequest?.contentRevision ?? 0 }
25444
+ }));
23738
25445
  }
23739
25446
  async #flushOutbox(includeLocalTurns = true) {
23740
25447
  const state = this.#requireState();
@@ -23743,6 +25450,11 @@ var DshHostConnector = class {
23743
25450
  const operation = state.outbox[0];
23744
25451
  if (operation === void 0)
23745
25452
  break;
25453
+ if ((operation.input.contentRevision ?? 0) !== (state.contentRevision ?? 0) || operation.kind === "local_turn" && state.localOnlySessionIds?.includes(this.#host.sessionId)) {
25454
+ state.outbox.shift();
25455
+ await this.#stateStore.save(state);
25456
+ continue;
25457
+ }
23746
25458
  if (operation.kind === "local_turn" && !includeLocalTurns)
23747
25459
  break;
23748
25460
  if (operation.input.runtimeId !== runtime.id) {
@@ -24011,7 +25723,7 @@ function turnSettlement(events) {
24011
25723
  return typeof reason?.kind === "string" ? reason.kind : void 0;
24012
25724
  }
24013
25725
  function digest2(value) {
24014
- return createHash4("sha256").update(value).digest("hex");
25726
+ return createHash6("sha256").update(value).digest("hex");
24015
25727
  }
24016
25728
  function cloneExecutionProfiles(profiles) {
24017
25729
  return profiles.map((profile) => ({
@@ -24077,6 +25789,8 @@ function captureCompletedLocalTurns(events, fromSequence, dshSessionId) {
24077
25789
  return { turns, safeThroughSequence };
24078
25790
  }
24079
25791
  function hasCompletedDshLocalTurn(events, dshSessionId) {
25792
+ if (events.some((event) => event.type === "user/message" && /^gatherthread-retired:[0-9]+$/.test(String(asObject3(event.data)?.id)) && asObject3(asObject3(event.data)?.source)?.plugin === "gatherthread"))
25793
+ return false;
24080
25794
  return captureCompletedLocalTurns(events, 0, dshSessionId).turns.length > 0;
24081
25795
  }
24082
25796
  function isDirectLocalUserMessage(event) {
@@ -24147,16 +25861,16 @@ function asObject3(value) {
24147
25861
  }
24148
25862
 
24149
25863
  // dist/src/dsh-compat.js
24150
- import { createHash as createHash5 } from "node:crypto";
25864
+ import { createHash as createHash7, randomUUID as randomUUID5 } from "node:crypto";
24151
25865
  var DSH_COMPATIBILITY = Object.freeze({
24152
- tag: "dsh-v0.1.3-alpha.1",
24153
- version: "0.1.3-alpha.1",
24154
- commit: "d347e703908d0406b7a7ef80e3a0e594d86b2215",
25866
+ tag: "dsh-v0.1.7-rc.2",
25867
+ version: "0.1.7-rc.2",
25868
+ commit: "477b4f420553e8a52c2fbccc464d7561b239c443",
24155
25869
  profile: "headless"
24156
25870
  });
24157
25871
  var DSH_NPM_COMPATIBILITY = Object.freeze({
24158
25872
  package: "@deepseek-ai/dsh",
24159
- version: "0.1.2-rc.1",
25873
+ version: "0.1.7-rc.2",
24160
25874
  profile: "web",
24161
25875
  persistenceProbe: "list"
24162
25876
  });
@@ -24186,16 +25900,19 @@ function createDshHostFacade(options) {
24186
25900
  const sessionTitle = options.sessionTitle === void 0 ? void 0 : requireService(context, "sessionTitle", ["rename"]);
24187
25901
  const workspaceRegistry = options.workspaceTitle === void 0 ? void 0 : requireService(context, "workspaceRegistry", ["create"]);
24188
25902
  const eventSubscribers = /* @__PURE__ */ new Set();
25903
+ const liveSubscribers = /* @__PURE__ */ new Set();
24189
25904
  const statusSubscribers = /* @__PURE__ */ new Set();
24190
25905
  const listenerDisposers = [];
24191
25906
  const lifecycleAbort = new AbortController();
24192
25907
  let handle;
24193
25908
  let executionHandle;
24194
- let contextPreparationPromise;
25909
+ let nativeSessionId = options.sessionId;
25910
+ let contentRevision = 0;
25911
+ let contextOperationPromise;
24195
25912
  const executionBinding = JSON.stringify({ nativeSessionId: options.sessionId, ...options.contextBinding });
24196
- const executionSessionId = `gatherthread-execution-${createHash5("sha256").update(executionBinding).digest("hex").slice(0, 32)}`;
24197
- const executionOwnerId = `gatherthread-execution-owner:${createHash5("sha256").update(executionBinding).digest("hex")}`;
24198
- const executionOwnerText = JSON.stringify({
25913
+ let executionSessionId = `gatherthread-execution-${createHash7("sha256").update(executionBinding).digest("hex").slice(0, 32)}`;
25914
+ let executionOwnerId = `gatherthread-execution-owner:${createHash7("sha256").update(executionBinding).digest("hex")}`;
25915
+ let executionOwnerText = JSON.stringify({
24199
25916
  version: 1,
24200
25917
  owner: "gatherthread",
24201
25918
  native_session_id: options.sessionId,
@@ -24208,19 +25925,76 @@ function createDshHostFacade(options) {
24208
25925
  let knownProjectionMessageIds;
24209
25926
  let disposePromise;
24210
25927
  let disposed = false;
25928
+ let livePrompt;
25929
+ const emitLive = (owner, event) => {
25930
+ if (disposed || livePrompt !== owner || owner.closed)
25931
+ return;
25932
+ for (const subscriber of liveSubscribers)
25933
+ subscriber(event);
25934
+ };
24211
25935
  listenerDisposers.push(context.on("session/event", (...args) => {
24212
25936
  const session = asSession(args[0]);
24213
25937
  const event = asSessionEvent(args[1]);
24214
- if (session === void 0 || session.id !== options.sessionId && session.id !== executionHandle?.agent.session.id || event === void 0)
25938
+ if (session === void 0 || session.id !== nativeSessionId && session.id !== executionHandle?.agent.session.id || event === void 0)
24215
25939
  return;
24216
25940
  for (const subscriber of eventSubscribers)
24217
25941
  subscriber({ ...event, sourceSessionId: session.id });
25942
+ const owner = livePrompt;
25943
+ if (owner === void 0 || owner.closed || session !== owner.agent.session)
25944
+ return;
25945
+ const data = asObject4(event.data);
25946
+ if (event.type === "turn/start" && owner.turn === void 0 && Number.isSafeInteger(data?.turn)) {
25947
+ owner.turn = Number(data.turn);
25948
+ }
25949
+ if (owner.turn === void 0 || data?.turn !== void 0 && data.turn !== owner.turn)
25950
+ return;
25951
+ if (event.type === "step/start" && Number.isSafeInteger(data?.step))
25952
+ owner.step = Number(data.step);
25953
+ if (["assistant/message", "assistant/attempt", "tool/call", "tool/result", "todo/write"].includes(event.type)) {
25954
+ emitLive(owner, { kind: "session-event", sourceSessionId: session.id, event });
25955
+ }
25956
+ if (event.type === "turn/end")
25957
+ owner.closed = true;
25958
+ }, { global: true }));
25959
+ listenerDisposers.push(context.on("agent/assistant-stream", (...args) => {
25960
+ const owner = livePrompt;
25961
+ const payload = asObject4(args[0]);
25962
+ if (owner === void 0 || !owner.nativeFrames || owner.turn === void 0 || payload?.agent !== owner.agent)
25963
+ return;
25964
+ const frame = parseDshAssistantStreamFrame(payload.frame);
25965
+ if (frame === void 0 || frame.type === "start" && frame.turn !== owner.turn)
25966
+ return;
25967
+ emitLive(owner, { kind: "assistant-stream", sourceSessionId: owner.agent.session.id, frame });
25968
+ }, { global: true }));
25969
+ listenerDisposers.push(context.on("llm/stream", (...args) => {
25970
+ const request = asObject4(args[0]);
25971
+ const next = args[1];
25972
+ if (typeof next !== "function")
25973
+ return;
25974
+ const stream = next();
25975
+ const owner = livePrompt;
25976
+ if (owner === void 0 || owner.nativeFrames || owner.closed || owner.turn === void 0 || owner.step === void 0 || owner.isLoopRequest === void 0 || request === void 0 || !Object.isFrozen(args[0]) || request.sessionId !== owner.agent.session.id || request.provider !== owner.provider || request.model !== owner.model || typeof agents.get !== "function" || agents.get(owner.agent.session.id) !== owner.agent || !owner.isLoopRequest(args[0]))
25977
+ return stream;
25978
+ const attemptId = randomUUID5();
25979
+ const publish = (frame) => emitLive(owner, {
25980
+ kind: "assistant-stream",
25981
+ sourceSessionId: owner.agent.session.id,
25982
+ frame
25983
+ });
25984
+ return (async function* () {
25985
+ let index = 0;
25986
+ publish({ type: "start", attemptId, revision: ++owner.revision, turn: owner.turn, step: owner.step });
25987
+ for await (const chunk of stream) {
25988
+ publish({ type: "chunk", attemptId, revision: ++owner.revision, index: index++, time: Date.now(), chunk });
25989
+ yield chunk;
25990
+ }
25991
+ })();
24218
25992
  }, { global: true }));
24219
25993
  listenerDisposers.push(context.on("agent/status", (...args) => {
24220
25994
  const payload = asObject4(args[0]);
24221
25995
  const agent = payload ? asAgent(payload.agent) : void 0;
24222
25996
  const status = payload?.status;
24223
- if (agent === void 0 || agent.session.id !== options.sessionId && agent.session.id !== executionHandle?.agent.session.id || status !== "running" && status !== "idle")
25997
+ if (agent === void 0 || agent.session.id !== nativeSessionId && agent.session.id !== executionHandle?.agent.session.id || status !== "running" && status !== "idle")
24224
25998
  return;
24225
25999
  for (const subscriber of statusSubscribers)
24226
26000
  subscriber(status, agent.session.id);
@@ -24232,7 +26006,7 @@ function createDshHostFacade(options) {
24232
26006
  await releaseHandle();
24233
26007
  throw new Error("DSH host facade was disposed during open");
24234
26008
  }
24235
- if (acquired.agent.session.id !== options.sessionId) {
26009
+ if (acquired.agent.session.id !== nativeSessionId) {
24236
26010
  await releaseHandle();
24237
26011
  throw new Error("DSH Host returned an agent for an unexpected Session identity");
24238
26012
  }
@@ -24259,13 +26033,13 @@ function createDshHostFacade(options) {
24259
26033
  const parent = requireAgent(handle);
24260
26034
  if (parent.status !== "idle")
24261
26035
  throw new Error("Native DSH Agent must be idle before isolated context execution");
24262
- if (parent.session.header?.cwd !== options.workspacePath || agents.get?.(options.sessionId) !== parent) {
26036
+ if (parent.session.header?.cwd !== options.workspacePath || agents.get?.(nativeSessionId) !== parent) {
24263
26037
  throw new Error("Native DSH workspace or live owner does not match the isolated context binding");
24264
26038
  }
24265
26039
  };
24266
26040
  const assertExecutionOwnership = (agent) => {
24267
26041
  const session = agent.session;
24268
- if (session.id !== executionSessionId || session.header?.cwd !== options.workspacePath || session.header.parentSession !== options.sessionId || session.header.origin !== "subagent") {
26042
+ if (session.id !== executionSessionId || session.header?.cwd !== options.workspacePath || session.header.parentSession !== nativeSessionId || session.header.origin !== "subagent") {
24269
26043
  throw new Error("DSH isolated execution Session ownership does not match this native binding");
24270
26044
  }
24271
26045
  if (agents.get !== void 0 && agents.get(executionSessionId) !== agent) {
@@ -24286,6 +26060,118 @@ function createDshHostFacade(options) {
24286
26060
  throw new Error("DSH isolated execution Session contains local manual input; refusing to replace or execute it");
24287
26061
  }
24288
26062
  };
26063
+ const recordedRevision = (session) => session.snapshotEvents(0).reduce((highest, value) => {
26064
+ const event = asObject4(value), data = asObject4(event?.data);
26065
+ const id = data?.id;
26066
+ if (event?.type !== "user/message" || typeof id !== "string" || !/^gatherthread-retired:[0-9]+$/.test(id) || asObject4(data?.source)?.kind !== "plugin" || asObject4(data?.source)?.plugin !== "gatherthread")
26067
+ return highest;
26068
+ const revision = Number(id.slice("gatherthread-retired:".length));
26069
+ return Number.isSafeInteger(revision) ? Math.max(highest, revision) : highest;
26070
+ }, 0);
26071
+ const resetCanonicalContext = async (revision) => {
26072
+ if (!Number.isSafeInteger(revision) || revision < 1 || revision < contentRevision)
26073
+ throw new Error("Stale DSH content revision");
26074
+ if (options.contextBinding === void 0 || Object.values(options.contextBinding).some((value) => !value) || typeof agents.get !== "function")
26075
+ throw new Error("DSH context reset requires an explicit owned GatherThread binding");
26076
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26077
+ await open7();
26078
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26079
+ if (promptActive || projectionActive)
26080
+ throw new Error("Wait for the active DSH turn before replacing its projection");
26081
+ const parent = requireAgent(handle);
26082
+ if (revision < recordedRevision(parent.session))
26083
+ throw new Error("Retired DSH revision cannot resume");
26084
+ assertExecutionParentIdle();
26085
+ const targetId = `gatherthread-projection-${createHash7("sha256").update(`${executionBinding}:${revision}`).digest("hex").slice(0, 32)}`;
26086
+ if (nativeSessionId === targetId && contentRevision === revision)
26087
+ return { sessionId: targetId };
26088
+ const ownerId = `gatherthread-projection-owner:${createHash7("sha256").update(`${executionBinding}:${revision}`).digest("hex")}`;
26089
+ const ownerText = JSON.stringify({ version: 1, binding: options.contextBinding, native_session_id: options.sessionId, content_revision: revision });
26090
+ projectionActive = true;
26091
+ let candidate;
26092
+ try {
26093
+ const retired = await createIsolatedMessage(`gatherthread-retired:${revision}`, "GatherThread retired this context. Continue here locally only.", options);
26094
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26095
+ if (recordedRevision(parent.session) < revision) {
26096
+ parent.session.append("user/message", retired, { surfaceOp: "append" });
26097
+ await sessions.flush(parent.session);
26098
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26099
+ }
26100
+ if (agents.get?.(targetId) !== void 0)
26101
+ throw new Error("DSH clean projection already has another live owner");
26102
+ const stored = await hasPersistedSession(persistence, targetId, lifecycleAbort.signal);
26103
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26104
+ const presets = asObject4(context.get("agentPresets"));
26105
+ const setup = (ctx) => {
26106
+ if (presets !== void 0) {
26107
+ if (typeof presets.composeFrom !== "function" || typeof presets.composedPreset !== "function")
26108
+ throw new Error("Native DSH tool inheritance unavailable");
26109
+ const expected = presets.composedPreset(parent.ctx);
26110
+ if (presets.composeFrom(ctx, parent.ctx) !== expected)
26111
+ throw new Error("DSH clean projection did not inherit native tools");
26112
+ }
26113
+ };
26114
+ candidate = await (stored ? agents.resume({
26115
+ resumeSessionId: targetId,
26116
+ agentOptions: { provider: options.provider, model: options.model },
26117
+ signal: lifecycleAbort.signal,
26118
+ setup
26119
+ }) : agents.create({
26120
+ sessionId: targetId,
26121
+ meta: { cwd: options.workspacePath, parentSession: options.sessionId, origin: "subagent" },
26122
+ agentOptions: { provider: options.provider, model: options.model },
26123
+ signal: lifecycleAbort.signal,
26124
+ setup
26125
+ }));
26126
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26127
+ const agent = candidate.agent;
26128
+ if (agent.session.id !== targetId || agent.session.header?.cwd !== options.workspacePath || agent.status !== "idle" || agent.session.header.parentSession !== options.sessionId || agent.session.header.origin !== "subagent" || agents.get?.(targetId) !== agent)
26129
+ throw new Error("DSH clean projection ownership is invalid");
26130
+ if (!stored) {
26131
+ if (agent.session.seq !== 0)
26132
+ throw new Error("DSH clean projection is not fresh");
26133
+ const owner = await createIsolatedMessage(ownerId, ownerText, options);
26134
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26135
+ agent.session.append("user/message", owner, { surfaceOp: "append" });
26136
+ }
26137
+ const events = agent.session.snapshotEvents(0).map(asObject4);
26138
+ if (!events.some((event) => {
26139
+ const data = asObject4(event?.data), source = asObject4(data?.source);
26140
+ return event?.type === "user/message" && data?.id === ownerId && source?.kind === "plugin" && source.plugin === "gatherthread" && Array.isArray(data?.content) && data.content.length === 1 && asObject4(data.content[0])?.text === ownerText;
26141
+ })) {
26142
+ throw new Error("DSH clean projection is missing its durable owner marker");
26143
+ }
26144
+ await sessions.flush(agent.session);
26145
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26146
+ if (sessionTitle && options.sessionTitle) {
26147
+ await sessionTitle.rename(agent.session, options.sessionTitle);
26148
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26149
+ }
26150
+ if (workspaceRegistry && options.workspaceTitle) {
26151
+ const workspace = await workspaceRegistry.create(options.workspacePath, options.workspaceTitle);
26152
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26153
+ await workspace.attachSession(targetId);
26154
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26155
+ }
26156
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26157
+ await releaseExecutionHandle();
26158
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26159
+ await releaseHandle();
26160
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26161
+ handle = candidate;
26162
+ candidate = void 0;
26163
+ nativeSessionId = targetId;
26164
+ contentRevision = revision;
26165
+ knownProjectionMessageIds = void 0;
26166
+ return { sessionId: targetId };
26167
+ } finally {
26168
+ try {
26169
+ await candidate?.dispose();
26170
+ } finally {
26171
+ projectionActive = false;
26172
+ }
26173
+ }
26174
+ };
24289
26175
  const prepareContextExecution = async (input2) => {
24290
26176
  if (disposed)
24291
26177
  throw new Error("DSH host facade is disposed");
@@ -24302,10 +26188,34 @@ function createDshHostFacade(options) {
24302
26188
  if (input2.selectedOnly && historyContext.items.length !== 0) {
24303
26189
  throw new Error("Selected-only summary execution cannot include other shared history");
24304
26190
  }
24305
- await open6();
26191
+ await open7();
24306
26192
  if (disposed || promptActive || projectionActive)
24307
26193
  throw new Error("DSH host facade permits only one active write");
24308
26194
  const parent = requireAgent(handle);
26195
+ const revision = historyContext.content_revision ?? 0;
26196
+ if (revision < Math.max(contentRevision, recordedRevision(parent.session)))
26197
+ throw new Error("Stale retired DSH context revision");
26198
+ if (revision !== contentRevision || revision > 0 && !executionSessionId.endsWith(createHash7("sha256").update(`${executionBinding}:${revision}`).digest("hex").slice(0, 32))) {
26199
+ await releaseExecutionHandle();
26200
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26201
+ contentRevision = revision;
26202
+ const binding = `${executionBinding}:${revision}`;
26203
+ executionSessionId = `gatherthread-execution-${createHash7("sha256").update(binding).digest("hex").slice(0, 32)}`;
26204
+ executionOwnerId = `gatherthread-execution-owner:${createHash7("sha256").update(binding).digest("hex")}`;
26205
+ executionOwnerText = JSON.stringify({
26206
+ version: 1,
26207
+ owner: "gatherthread",
26208
+ native_session_id: options.sessionId,
26209
+ execution_session_id: executionSessionId,
26210
+ binding: options.contextBinding,
26211
+ content_revision: revision
26212
+ });
26213
+ const marker = await createIsolatedMessage(`gatherthread-retired:${revision}`, "GatherThread previous execution context is local only.", options);
26214
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26215
+ parent.session.append("user/message", marker, { surfaceOp: "append" });
26216
+ await sessions.flush(parent.session);
26217
+ throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
26218
+ }
24309
26219
  assertExecutionParentIdle();
24310
26220
  const presets = asObject4(context.get("agentPresets"));
24311
26221
  let parentPreset;
@@ -24314,7 +26224,8 @@ function createDshHostFacade(options) {
24314
26224
  throw new Error("Pinned DSH public preset inheritance API is unavailable; refusing an isolated Agent without native tools");
24315
26225
  }
24316
26226
  parentPreset = presets.composedPreset(parent.ctx);
24317
- if (parentPreset === void 0 && (!Array.isArray(presets.roots) || presets.roots.length > 0)) {
26227
+ const roots = Array.isArray(presets.roots) ? presets.roots : void 0;
26228
+ if (parentPreset === void 0 && roots !== void 0 && roots.length > 0) {
24318
26229
  throw new Error("Native DSH Agent has no composed preset; restore its tool composition before using summarized context");
24319
26230
  }
24320
26231
  }
@@ -24323,7 +26234,7 @@ function createDshHostFacade(options) {
24323
26234
  throw new Error("DSH isolated Agent did not inherit its native Agent's exact tool composition");
24324
26235
  }
24325
26236
  };
24326
- const markerId = `gatherthread-context:${createHash5("sha256").update(JSON.stringify({
26237
+ const markerId = `gatherthread-context:${createHash7("sha256").update(JSON.stringify({
24327
26238
  requestId: input2.requestId,
24328
26239
  requestSequence: input2.requestSequence,
24329
26240
  selectedOnly: input2.selectedOnly,
@@ -24348,7 +26259,7 @@ function createDshHostFacade(options) {
24348
26259
  sessionId: executionSessionId,
24349
26260
  meta: {
24350
26261
  cwd: options.workspacePath,
24351
- parentSession: options.sessionId,
26262
+ parentSession: nativeSessionId,
24352
26263
  origin: "subagent",
24353
26264
  ...parentPreset === void 0 ? {} : { agentPreset: parentPreset }
24354
26265
  },
@@ -24362,7 +26273,7 @@ function createDshHostFacade(options) {
24362
26273
  }
24363
26274
  const agent2 = operationAgent(executionSessionId);
24364
26275
  if (!stored) {
24365
- if (agent2.session.id !== executionSessionId || agent2.session.seq !== 0 || agent2.session.header?.parentSession !== options.sessionId || agent2.session.header.origin !== "subagent" || agent2.session.header.cwd !== options.workspacePath || agent2.status !== "idle") {
26276
+ if (agent2.session.id !== executionSessionId || agent2.session.seq !== 0 || agent2.session.header?.parentSession !== nativeSessionId || agent2.session.header.origin !== "subagent" || agent2.session.header.cwd !== options.workspacePath || agent2.status !== "idle") {
24366
26277
  throw new Error("DSH did not create a fresh, owned isolated execution Session");
24367
26278
  }
24368
26279
  const ownerMessage = await createIsolatedMessage(executionOwnerId, executionOwnerText, options);
@@ -24398,7 +26309,7 @@ ${JSON.stringify(historyContext.items)}`;
24398
26309
  const nodes = agent.session.surface?.nodes;
24399
26310
  if (!Array.isArray(nodes) || nodes.some((seq) => !Number.isSafeInteger(seq) || seq < 0 || seq >= agent.session.seq) || new Set(nodes).size !== nodes.length)
24400
26311
  throw new Error("Pinned DSH public Session surface API is unavailable or invalid");
24401
- agent.session.append("user/message", message, nodes.length === 0 ? { surfaceOp: "append" } : { surfaceOp: { op: "replace", start: nodes[0], end: nodes.at(-1) }, sourceEventSeqs: [...nodes] });
26312
+ agent.session.append("user/message", message, nodes.length === 0 ? { surfaceOp: "append" } : { surfaceOp: { op: "replace", startSeq: nodes[0], endSeq: nodes.at(-1) }, sourceEventSeqs: [...nodes] });
24402
26313
  }
24403
26314
  await sessions.flush(agent.session);
24404
26315
  throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
@@ -24407,11 +26318,11 @@ ${JSON.stringify(historyContext.items)}`;
24407
26318
  projectionActive = false;
24408
26319
  }
24409
26320
  };
24410
- const setupNativeAgent = async (agentContext) => {
26321
+ const setupNativeAgent = async (agentContext, agentValue) => {
24411
26322
  const presets = asObject4(context.get("agentPresets"));
24412
26323
  if (presets === void 0 || Array.isArray(presets.roots) && presets.roots.length === 0)
24413
26324
  return;
24414
- const agent = asAgent(asObject4(agentContext)?.agent);
26325
+ const agent = asAgent(agentValue);
24415
26326
  const projections = asObject4(context.get("sessionProjections"));
24416
26327
  if (agent === void 0 || typeof presets.mount !== "function" || typeof projections?.stateOf !== "function") {
24417
26328
  throw new Error("Pinned DSH public native preset composition API is unavailable");
@@ -24422,7 +26333,7 @@ ${JSON.stringify(historyContext.items)}`;
24422
26333
  }
24423
26334
  await presets.mount(agentContext, recorded ?? options.agentPreset);
24424
26335
  };
24425
- const open6 = () => {
26336
+ const open7 = () => {
24426
26337
  if (disposed)
24427
26338
  return Promise.reject(new Error("DSH host facade is disposed"));
24428
26339
  if (openPromise !== void 0)
@@ -24514,7 +26425,8 @@ ${JSON.stringify(historyContext.items)}`;
24514
26425
  return parsed;
24515
26426
  });
24516
26427
  };
24517
- const prompt = async (text, requestedSelection, requestedExecutionId) => {
26428
+ const prompt = async (text, requestedSelection, requestedExecutionId, signal) => {
26429
+ signal?.throwIfAborted();
24518
26430
  if (disposed)
24519
26431
  throw new Error("DSH host facade is disposed");
24520
26432
  if (!text.trim())
@@ -24529,11 +26441,15 @@ ${JSON.stringify(historyContext.items)}`;
24529
26441
  promptActive = true;
24530
26442
  let disposeSelection;
24531
26443
  let selectionRef;
26444
+ let abortPrompt;
24532
26445
  try {
24533
26446
  if (requestedExecutionId === void 0)
24534
- await open6();
26447
+ await open7();
24535
26448
  throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
24536
26449
  const agent = operationAgent(requestedExecutionId);
26450
+ signal?.throwIfAborted();
26451
+ if (signal && typeof agent.cancel !== "function")
26452
+ throw new Error("DSH public cancel API is required for deletion-safe execution");
24537
26453
  if (requestedExecutionId !== void 0) {
24538
26454
  assertExecutionParentIdle();
24539
26455
  assertExecutionOwnership(agent);
@@ -24551,7 +26467,7 @@ ${JSON.stringify(historyContext.items)}`;
24551
26467
  }
24552
26468
  }
24553
26469
  const fromSequence = agent.session.seq;
24554
- const message = requestedExecutionId === void 0 ? await createUserMessage(text, options) : await createIsolatedMessage(`gatherthread-request:${createHash5("sha256").update(`${requestedExecutionId}:${fromSequence}:${text}`).digest("hex")}`, text, options);
26470
+ const message = requestedExecutionId === void 0 ? await createUserMessage(text, options) : await createIsolatedMessage(`gatherthread-request:${createHash7("sha256").update(`${requestedExecutionId}:${fromSequence}:${text}`).digest("hex")}`, text, options);
24555
26471
  throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
24556
26472
  if (agent.status !== "idle" || agent.session.seq !== fromSequence)
24557
26473
  throw new Error("DSH Agent changed while preparing a GatherThread prompt");
@@ -24559,8 +26475,41 @@ ${JSON.stringify(historyContext.items)}`;
24559
26475
  assertExecutionParentIdle();
24560
26476
  assertExecutionOwnership(agent);
24561
26477
  }
26478
+ if (liveSubscribers.size > 0) {
26479
+ let imported;
26480
+ try {
26481
+ imported = asObject4(await (options.moduleImporter ?? importDshModule)(USER_MESSAGE_MODULE));
26482
+ } catch {
26483
+ }
26484
+ throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
26485
+ if (agent.status !== "idle" || agent.session.seq !== fromSequence)
26486
+ throw new Error("DSH Agent changed while preparing live output");
26487
+ livePrompt = {
26488
+ agent,
26489
+ closed: false,
26490
+ revision: 0,
26491
+ nativeFrames: typeof imported?.expandAssistantStream === "function",
26492
+ ...typeof imported?.isAgentLoopRequest === "function" ? { isLoopRequest: imported.isAgentLoopRequest } : {},
26493
+ provider: requestedSelection?.provider ?? options.provider,
26494
+ model: requestedSelection?.model ?? options.model
26495
+ };
26496
+ }
26497
+ signal?.throwIfAborted();
24562
26498
  agent.followup(message);
26499
+ abortPrompt = () => {
26500
+ livePrompt = void 0;
26501
+ if (typeof agent.cancel === "function")
26502
+ agent.cancel({ kind: "user" });
26503
+ else if (requestedExecutionId !== void 0)
26504
+ void releaseExecutionHandle();
26505
+ else
26506
+ void releaseHandle();
26507
+ };
26508
+ signal?.addEventListener("abort", abortPrompt, { once: true });
26509
+ if (signal?.aborted)
26510
+ abortPrompt();
24563
26511
  await agent.whenIdle();
26512
+ signal?.throwIfAborted();
24564
26513
  throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
24565
26514
  await sessions.flush(agent.session);
24566
26515
  throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
@@ -24571,18 +26520,21 @@ ${JSON.stringify(historyContext.items)}`;
24571
26520
  events: snapshotFrom(fromSequence, requestedExecutionId)
24572
26521
  };
24573
26522
  } finally {
26523
+ if (abortPrompt)
26524
+ signal?.removeEventListener("abort", abortPrompt);
24574
26525
  if (selectionRef !== void 0) {
24575
26526
  selectionRef.current = void 0;
24576
26527
  selectionRef.assembled = void 0;
24577
26528
  }
24578
26529
  disposeSelection?.();
26530
+ livePrompt = void 0;
24579
26531
  promptActive = false;
24580
26532
  }
24581
26533
  };
24582
26534
  const flush = async () => {
24583
26535
  if (disposed)
24584
26536
  throw new Error("DSH host facade is disposed");
24585
- await open6();
26537
+ await open7();
24586
26538
  throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
24587
26539
  await sessions.flush(requireAgent(handle).session);
24588
26540
  throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
@@ -24596,7 +26548,7 @@ ${JSON.stringify(historyContext.items)}`;
24596
26548
  return;
24597
26549
  projectionActive = true;
24598
26550
  try {
24599
- await open6();
26551
+ await open7();
24600
26552
  throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
24601
26553
  const agent = requireAgent(handle);
24602
26554
  if (agent.status !== "idle") {
@@ -24639,7 +26591,7 @@ ${JSON.stringify(historyContext.items)}`;
24639
26591
  } catch {
24640
26592
  }
24641
26593
  try {
24642
- await contextPreparationPromise;
26594
+ await contextOperationPromise;
24643
26595
  } catch {
24644
26596
  }
24645
26597
  try {
@@ -24654,20 +26606,33 @@ ${JSON.stringify(historyContext.items)}`;
24654
26606
  return disposePromise;
24655
26607
  };
24656
26608
  return {
24657
- sessionId: options.sessionId,
24658
- open: open6,
26609
+ get sessionId() {
26610
+ return nativeSessionId;
26611
+ },
26612
+ ...options.contextBinding === void 0 ? {} : { contextDeletionVersion: 1 },
26613
+ resetCanonicalContext(revision) {
26614
+ if (contextOperationPromise !== void 0)
26615
+ return Promise.reject(new Error("DSH host facade permits only one active context operation"));
26616
+ const operation = resetCanonicalContext(revision);
26617
+ contextOperationPromise = operation;
26618
+ return operation.finally(() => {
26619
+ if (contextOperationPromise === operation)
26620
+ contextOperationPromise = void 0;
26621
+ });
26622
+ },
26623
+ open: open7,
24659
26624
  currentSequence(requestedExecutionId) {
24660
26625
  return operationAgent(requestedExecutionId).session.seq;
24661
26626
  },
24662
26627
  snapshotFrom,
24663
26628
  prepareContextExecution(input2) {
24664
- if (contextPreparationPromise !== void 0)
26629
+ if (contextOperationPromise !== void 0)
24665
26630
  return Promise.reject(new Error("DSH host facade permits only one active context preparation"));
24666
26631
  const operation = prepareContextExecution(input2);
24667
- contextPreparationPromise = operation;
26632
+ contextOperationPromise = operation;
24668
26633
  return operation.finally(() => {
24669
- if (contextPreparationPromise === operation)
24670
- contextPreparationPromise = void 0;
26634
+ if (contextOperationPromise === operation)
26635
+ contextOperationPromise = void 0;
24671
26636
  });
24672
26637
  },
24673
26638
  projectCanonicalEvents,
@@ -24679,6 +26644,12 @@ ${JSON.stringify(historyContext.items)}`;
24679
26644
  eventSubscribers.add(listener);
24680
26645
  return () => eventSubscribers.delete(listener);
24681
26646
  },
26647
+ onLiveOutput(listener) {
26648
+ if (disposed)
26649
+ throw new Error("DSH host facade is disposed");
26650
+ liveSubscribers.add(listener);
26651
+ return () => liveSubscribers.delete(listener);
26652
+ },
24682
26653
  onStatus(listener) {
24683
26654
  if (disposed)
24684
26655
  throw new Error("DSH host facade is disposed");
@@ -24711,6 +26682,7 @@ async function registerDshNativeWorkspace(contextValue, workspacePath, workspace
24711
26682
  const sessionQuery = requireService(context, "sessionQuery", ["readSession"]);
24712
26683
  const sessionTitle = requireService(context, "sessionTitle", ["rename", "get"]);
24713
26684
  const completed = /* @__PURE__ */ new Map();
26685
+ const retired = /* @__PURE__ */ new Set();
24714
26686
  const observedSessionIds = new Set(workspace.sessionIds);
24715
26687
  const runningSessionIds = /* @__PURE__ */ new Set();
24716
26688
  const offActivity = context.on("agent/status", (...args) => {
@@ -24731,6 +26703,10 @@ async function registerDshNativeWorkspace(contextValue, workspacePath, workspace
24731
26703
  return;
24732
26704
  observedSessionIds.add(session.id);
24733
26705
  const event = asSessionEvent(args[1]);
26706
+ if (event?.type === "user/message" && /^gatherthread-retired:[0-9]+$/.test(String(asObject4(event.data)?.id)) && asObject4(asObject4(event.data)?.source)?.plugin === "gatherthread") {
26707
+ retired.add(session.id);
26708
+ completed.delete(session.id);
26709
+ }
24734
26710
  if (event?.type === "turn/start")
24735
26711
  runningSessionIds.add(session.id);
24736
26712
  if (event?.type === "turn/end")
@@ -24781,6 +26757,10 @@ async function registerDshNativeWorkspace(contextValue, workspacePath, workspace
24781
26757
  for (const localSessionId of [...currentIds]) {
24782
26758
  if (!isUserCreatedDshSessionId(localSessionId))
24783
26759
  continue;
26760
+ if (retired.has(localSessionId)) {
26761
+ candidates.delete(localSessionId);
26762
+ continue;
26763
+ }
24784
26764
  const cached2 = completed.get(localSessionId);
24785
26765
  if (cached2 !== void 0)
24786
26766
  continue;
@@ -24795,8 +26775,11 @@ async function registerDshNativeWorkspace(contextValue, workspacePath, workspace
24795
26775
  throw new Error("Pinned DSH Session reader returned an invalid event");
24796
26776
  return parsed;
24797
26777
  });
24798
- if (!hasCompletedDshLocalTurn(events, localSessionId))
26778
+ if (!hasCompletedDshLocalTurn(events, localSessionId)) {
26779
+ completed.delete(localSessionId);
26780
+ candidates.delete(localSessionId);
24799
26781
  continue;
26782
+ }
24800
26783
  const title = live === void 0 ? titleFromEvents(events) : normalizedLocalSessionTitle(sessionTitle.get(live)?.title) ?? titleFromEvents(events);
24801
26784
  const candidate = { localSessionId, title };
24802
26785
  completed.set(localSessionId, candidate);
@@ -24821,8 +26804,10 @@ async function registerDshNativeWorkspace(contextValue, workspacePath, workspace
24821
26804
  return;
24822
26805
  events.push(parsed);
24823
26806
  }
24824
- if (!hasCompletedDshLocalTurn(events, session.id))
26807
+ if (!hasCompletedDshLocalTurn(events, session.id)) {
26808
+ completed.delete(session.id);
24825
26809
  return;
26810
+ }
24826
26811
  completed.set(session.id, {
24827
26812
  localSessionId: session.id,
24828
26813
  title: normalizedLocalSessionTitle(sessionTitle.get(session)?.title) ?? titleFromEvents(events)
@@ -24903,8 +26888,11 @@ async function createProjectionMessage(projection, options) {
24903
26888
  });
24904
26889
  }
24905
26890
  function formatProjectionText(projection) {
24906
- if (projection.role === "user")
24907
- return projection.content;
26891
+ if (projection.role === "user") {
26892
+ const sender = projection.actorDisplayName?.replace(/[\p{Cc}\p{Cf}【】]+/gu, " ").replace(/\s+/gu, " ").trim();
26893
+ return sender ? `\u3010${sender} \xB7 GatherThread\u3011
26894
+ ${projection.content}` : projection.content;
26895
+ }
24908
26896
  const route = [projection.provider, projection.model].filter(Boolean).join(" / ");
24909
26897
  const attribution = [projection.actorDisplayName, route].filter(Boolean).join(" \xB7 ");
24910
26898
  return `[GatherThread remote Agent reply${attribution ? ` \xB7 ${attribution}` : ""}]
@@ -25069,9 +27057,9 @@ function asObject4(value) {
25069
27057
  }
25070
27058
 
25071
27059
  // dist/src/native-connection.js
25072
- import { createHash as createHash6 } from "node:crypto";
27060
+ import { createHash as createHash8 } from "node:crypto";
25073
27061
  import { homedir as homedir3 } from "node:os";
25074
- import path6 from "node:path";
27062
+ import path7 from "node:path";
25075
27063
  var DSH_NATIVE_CREDENTIAL_KEY = "gatherthread-dsh-host/default";
25076
27064
  var DSH_NATIVE_GRANT_SCHEMA_VERSION = 2;
25077
27065
  var DSH_NATIVE_PAIRING_CHANNEL = "/gatherthread";
@@ -25204,7 +27192,7 @@ function createNativeProjectConfig(options) {
25204
27192
  projectId: options.binding.projectId,
25205
27193
  projectName: options.binding.projectName,
25206
27194
  deviceId: options.grant.deviceId,
25207
- workspacePath: path6.resolve(options.workspacePath),
27195
+ workspacePath: path7.resolve(options.workspacePath),
25208
27196
  stateRoot,
25209
27197
  provider: options.binding.provider,
25210
27198
  model: options.binding.model,
@@ -25293,12 +27281,12 @@ function normalizeDshServerUrl(value) {
25293
27281
  return { serverUrl, apiUrl: `${serverUrl}/v1` };
25294
27282
  }
25295
27283
  function nativeStateRoot(dshHome, serverUrl, projectId, workspacePath) {
25296
- const digest3 = createHash6("sha256").update(JSON.stringify([serverUrl, projectId, path6.resolve(workspacePath)])).digest("hex").slice(0, 32);
25297
- return path6.join(path6.resolve(dshHome), "gatherthread", "state", digest3);
27284
+ const digest3 = createHash8("sha256").update(JSON.stringify([serverUrl, projectId, path7.resolve(workspacePath)])).digest("hex").slice(0, 32);
27285
+ return path7.join(path7.resolve(dshHome), "gatherthread", "state", digest3);
25298
27286
  }
25299
27287
  function resolveDshHome(env) {
25300
27288
  const configured = env.DSH_HOME?.trim();
25301
- return path6.resolve(configured ? configured : path6.join(homedir3(), ".dsh"));
27289
+ return path7.resolve(configured ? configured : path7.join(homedir3(), ".dsh"));
25302
27290
  }
25303
27291
  function parseBinding(value) {
25304
27292
  const input2 = exactObject(value, /* @__PURE__ */ new Set([
@@ -25442,8 +27430,8 @@ function isLoopback2(hostname3) {
25442
27430
  }
25443
27431
 
25444
27432
  // dist/src/project-manager.js
25445
- import path7 from "node:path";
25446
- import { createHash as createHash7 } from "node:crypto";
27433
+ import path8 from "node:path";
27434
+ import { createHash as createHash9 } from "node:crypto";
25447
27435
 
25448
27436
  // dist/src/execution-gate.js
25449
27437
  var BoundedDshExecutionGate = class {
@@ -25722,6 +27710,27 @@ var DshProjectManager = class {
25722
27710
  for (const error62 of permissions.errors)
25723
27711
  this.#reportError(error62);
25724
27712
  const eligibleSessions = [...permissions.eligibleSessions];
27713
+ const sessionUpdateErrors = [];
27714
+ for (const session of eligibleSessions) {
27715
+ const binding = this.#managed.get(session.id);
27716
+ if (binding?.phase === "active" && !binding.connector.stopped) {
27717
+ try {
27718
+ binding.connector.updateSessionSummary(session);
27719
+ } catch (error62) {
27720
+ this.#managed.delete(session.id);
27721
+ try {
27722
+ await binding.connector.stop();
27723
+ } catch (stopError) {
27724
+ this.#reportError(publicError2(stopError, "DSH Session connector cleanup failed"));
27725
+ }
27726
+ if (!this.#stopped)
27727
+ this.#recordFailure(session.id);
27728
+ const failure2 = publicError2(error62, `DSH Session ${session.id} mode refresh failed`);
27729
+ sessionUpdateErrors.push(failure2);
27730
+ this.#reportError(failure2);
27731
+ }
27732
+ }
27733
+ }
25725
27734
  if (this.#discoverLocalSessions !== void 0) {
25726
27735
  try {
25727
27736
  const candidates2 = await this.#discoverLocalSessions();
@@ -25765,7 +27774,7 @@ var DshProjectManager = class {
25765
27774
  status: "updated",
25766
27775
  eligibleSessionIds: [...eligibleIds].sort(),
25767
27776
  activeSessionIds: this.activeSessionIds(),
25768
- errors: [...permissions.errors, ...attachErrors]
27777
+ errors: [...permissions.errors, ...sessionUpdateErrors, ...attachErrors]
25769
27778
  };
25770
27779
  this.#notifyStatus({
25771
27780
  state: "connected",
@@ -25880,7 +27889,7 @@ function projectSessionConfig(config2, sessionId, nativeSessionId) {
25880
27889
  sessionId,
25881
27890
  deviceId: config2.deviceId,
25882
27891
  workspacePath: config2.workspacePath,
25883
- statePath: path7.join(config2.stateRoot, `${dshSessionId}.json`),
27892
+ statePath: path8.join(config2.stateRoot, `${dshSessionId}.json`),
25884
27893
  provider: config2.provider,
25885
27894
  model: config2.model,
25886
27895
  ...config2.executionProfiles === void 0 ? {} : {
@@ -25898,7 +27907,7 @@ function projectSessionConfig(config2, sessionId, nativeSessionId) {
25898
27907
  };
25899
27908
  }
25900
27909
  function nativeSessionCreationKey(projectId, localSessionId) {
25901
- const identity = createHash7("sha256").update(`${projectId}\0${localSessionId}`).digest("hex");
27910
+ const identity = createHash9("sha256").update(`${projectId}\0${localSessionId}`).digest("hex");
25902
27911
  return `dsh-native:${identity}`;
25903
27912
  }
25904
27913
  function retryDelay(config2, attempts) {
@@ -25928,31 +27937,31 @@ function publicError2(error62, fallback) {
25928
27937
  }
25929
27938
 
25930
27939
  // dist/src/state-store.js
25931
- import { randomUUID as randomUUID3 } from "node:crypto";
25932
- import { constants as constants3 } from "node:fs";
25933
- import { lstat as lstat6, mkdir as mkdir4, open as open5, rename as rename3, unlink as unlink3 } from "node:fs/promises";
25934
- import path8 from "node:path";
27940
+ import { randomUUID as randomUUID6 } from "node:crypto";
27941
+ import { constants as constants4 } from "node:fs";
27942
+ import { lstat as lstat7, mkdir as mkdir5, open as open6, rename as rename3, unlink as unlink4 } from "node:fs/promises";
27943
+ import path9 from "node:path";
25935
27944
  var FileConnectorStateStore = class {
25936
27945
  #path;
25937
27946
  constructor(statePath) {
25938
- if (!path8.isAbsolute(statePath))
27947
+ if (!path9.isAbsolute(statePath))
25939
27948
  throw new Error("DSH connector state path must be absolute");
25940
- this.#path = path8.resolve(statePath);
27949
+ this.#path = path9.resolve(statePath);
25941
27950
  }
25942
27951
  async load() {
25943
- const directory = path8.dirname(this.#path);
27952
+ const directory = path9.dirname(this.#path);
25944
27953
  const ancestorsBefore = await secureDirectoryChain(directory);
25945
27954
  let metadata;
25946
27955
  try {
25947
- metadata = await lstat6(this.#path);
27956
+ metadata = await lstat7(this.#path);
25948
27957
  } catch (error62) {
25949
27958
  if (isNodeError4(error62) && error62.code === "ENOENT")
25950
27959
  return void 0;
25951
27960
  throw error62;
25952
27961
  }
25953
27962
  assertPrivateRegularFile(metadata, "DSH connector state");
25954
- const noFollow = "O_NOFOLLOW" in constants3 ? constants3.O_NOFOLLOW : 0;
25955
- const handle = await open5(this.#path, constants3.O_RDONLY | noFollow);
27963
+ const noFollow = "O_NOFOLLOW" in constants4 ? constants4.O_NOFOLLOW : 0;
27964
+ const handle = await open6(this.#path, constants4.O_RDONLY | noFollow);
25956
27965
  let raw;
25957
27966
  try {
25958
27967
  const opened = await handle.stat();
@@ -25974,17 +27983,17 @@ var FileConnectorStateStore = class {
25974
27983
  }
25975
27984
  async save(state) {
25976
27985
  const validated = validateConnectorState(structuredClone(state));
25977
- const directory = path8.dirname(this.#path);
27986
+ const directory = path9.dirname(this.#path);
25978
27987
  await secureDirectoryChain(directory);
25979
- await mkdir4(directory, { recursive: true, mode: 448 });
27988
+ await mkdir5(directory, { recursive: true, mode: 448 });
25980
27989
  const ancestorsBefore = await secureDirectoryChain(directory);
25981
- const directoryBefore = await lstat6(directory);
27990
+ const directoryBefore = await lstat7(directory);
25982
27991
  assertSecureDirectory(directoryBefore);
25983
27992
  const targetBefore = await optionalLstat(this.#path);
25984
27993
  if (targetBefore !== void 0)
25985
27994
  assertPrivateRegularFile(targetBefore, "DSH connector state");
25986
- const temporaryPath = `${this.#path}.tmp-${process.pid}-${randomUUID3()}`;
25987
- const handle = await open5(temporaryPath, "wx", 384);
27995
+ const temporaryPath = `${this.#path}.tmp-${process.pid}-${randomUUID6()}`;
27996
+ const handle = await open6(temporaryPath, "wx", 384);
25988
27997
  try {
25989
27998
  await handle.writeFile(`${JSON.stringify(validated, null, 2)}
25990
27999
  `, { encoding: "utf8" });
@@ -25993,7 +28002,7 @@ var FileConnectorStateStore = class {
25993
28002
  await handle.close();
25994
28003
  }
25995
28004
  try {
25996
- const directoryAfter = await lstat6(directory);
28005
+ const directoryAfter = await lstat7(directory);
25997
28006
  if (directoryAfter.isSymbolicLink() || directoryAfter.dev !== directoryBefore.dev || directoryAfter.ino !== directoryBefore.ino) {
25998
28007
  throw new Error("DSH connector state directory changed during atomic save");
25999
28008
  }
@@ -26004,9 +28013,9 @@ var FileConnectorStateStore = class {
26004
28013
  }
26005
28014
  await rename3(temporaryPath, this.#path);
26006
28015
  assertSameDirectoryChain(ancestorsBefore, await secureDirectoryChain(directory));
26007
- assertPrivateRegularFile(await lstat6(this.#path), "DSH connector state");
28016
+ assertPrivateRegularFile(await lstat7(this.#path), "DSH connector state");
26008
28017
  } catch (error62) {
26009
- await unlink3(temporaryPath).catch(() => void 0);
28018
+ await unlink4(temporaryPath).catch(() => void 0);
26010
28019
  throw error62;
26011
28020
  }
26012
28021
  }
@@ -26023,7 +28032,10 @@ function validateConnectorState(value) {
26023
28032
  "publishedDshSequence",
26024
28033
  ...legacyUploadPolicy ? [] : ["automaticUpload"],
26025
28034
  "activeRequest",
26026
- "outbox"
28035
+ "outbox",
28036
+ "contentRevision",
28037
+ "projectionSessionId",
28038
+ "localOnlySessionIds"
26027
28039
  ]), "state");
26028
28040
  if (state.version !== 1 && state.version !== 2 && state.version !== 3) {
26029
28041
  throw new Error("Unsupported DSH connector state version");
@@ -26038,6 +28050,9 @@ function validateConnectorState(value) {
26038
28050
  throw new Error("state.outbox must be an array");
26039
28051
  const outbox = state.outbox.map(parseOutboxOperation);
26040
28052
  const validated = {
28053
+ ...state.contentRevision === void 0 ? {} : { contentRevision: nonNegativeInteger(state.contentRevision, "state.contentRevision") },
28054
+ ...state.projectionSessionId === void 0 ? {} : { projectionSessionId: safeString(state.projectionSessionId, "state.projectionSessionId", 128) },
28055
+ ...state.localOnlySessionIds === void 0 ? {} : { localOnlySessionIds: parseRetiredSessionIds(state.localOnlySessionIds) },
26041
28056
  version: 3,
26042
28057
  binding: {
26043
28058
  projectId: safeString(binding.projectId, "state.binding.projectId", 128),
@@ -26056,8 +28071,9 @@ function validateConnectorState(value) {
26056
28071
  }
26057
28072
  function parseActiveRequest(value) {
26058
28073
  const active = requiredObject4(value, "state.activeRequest");
26059
- exactKeys(active, /* @__PURE__ */ new Set(["requestId", "requestSequence", "dshFromSequence", "dshToSequence", "promptDigest", "claimAttempt", "contextExecution"]), "state.activeRequest");
28074
+ exactKeys(active, /* @__PURE__ */ new Set(["requestId", "requestSequence", "dshFromSequence", "dshToSequence", "promptDigest", "claimAttempt", "contextExecution", "contentRevision"]), "state.activeRequest");
26060
28075
  const parsed = {
28076
+ ...active.contentRevision === void 0 ? {} : { contentRevision: nonNegativeInteger(active.contentRevision, "state.activeRequest.contentRevision") },
26061
28077
  requestId: safeString(active.requestId, "state.activeRequest.requestId", 128),
26062
28078
  requestSequence: positiveInteger(active.requestSequence, "state.activeRequest.requestSequence"),
26063
28079
  dshFromSequence: nonNegativeInteger(active.dshFromSequence, "state.activeRequest.dshFromSequence"),
@@ -26133,7 +28149,8 @@ function parseLocalTurnInput(value, label) {
26133
28149
  "observedReasoningEffort",
26134
28150
  "requestPayload",
26135
28151
  "responsePayload",
26136
- "toolEvents"
28152
+ "toolEvents",
28153
+ "contentRevision"
26137
28154
  ]), label);
26138
28155
  const occurredAt = safeString(input2.occurredAt, `${label}.occurredAt`, 64);
26139
28156
  if (!Number.isFinite(Date.parse(occurredAt)))
@@ -26143,6 +28160,7 @@ function parseLocalTurnInput(value, label) {
26143
28160
  }
26144
28161
  return {
26145
28162
  localTurnId: safeString(input2.localTurnId, `${label}.localTurnId`, 200),
28163
+ ...input2.contentRevision === void 0 ? {} : { contentRevision: nonNegativeInteger(input2.contentRevision, `${label}.contentRevision`) },
26146
28164
  runtimeId: safeString(input2.runtimeId, `${label}.runtimeId`, 128),
26147
28165
  basedOnSequence: nonNegativeInteger(input2.basedOnSequence, `${label}.basedOnSequence`),
26148
28166
  occurredAt,
@@ -26170,13 +28188,15 @@ function parseAppendInput(value, label) {
26170
28188
  "runtimeId",
26171
28189
  "claimAttempt",
26172
28190
  "observedModel",
26173
- "observedReasoningEffort"
28191
+ "observedReasoningEffort",
28192
+ "contentRevision"
26174
28193
  ]), label);
26175
28194
  if (input2.type !== "tool_call" && input2.type !== "tool_result") {
26176
28195
  throw new Error(`${label}.type must be tool_call or tool_result`);
26177
28196
  }
26178
28197
  return {
26179
28198
  type: input2.type,
28199
+ ...input2.contentRevision === void 0 ? {} : { contentRevision: nonNegativeInteger(input2.contentRevision, `${label}.contentRevision`) },
26180
28200
  idempotencyKey: safeString(input2.idempotencyKey, `${label}.idempotencyKey`, 200),
26181
28201
  payload: jsonValue(input2.payload, `${label}.payload`),
26182
28202
  ...input2.replyTo === void 0 ? {} : { replyTo: safeString(input2.replyTo, `${label}.replyTo`, 128) },
@@ -26193,9 +28213,10 @@ function parseAppendInput(value, label) {
26193
28213
  }
26194
28214
  function parseCompletionInput(value, label) {
26195
28215
  const input2 = requiredObject4(value, label);
26196
- exactKeys(input2, /* @__PURE__ */ new Set(["runtimeId", "claimAttempt", "idempotencyKey", "payload", "observedModel", "observedReasoningEffort"]), label);
28216
+ exactKeys(input2, /* @__PURE__ */ new Set(["runtimeId", "claimAttempt", "idempotencyKey", "payload", "observedModel", "observedReasoningEffort", "contentRevision"]), label);
26197
28217
  return {
26198
28218
  runtimeId: safeString(input2.runtimeId, `${label}.runtimeId`, 128),
28219
+ ...input2.contentRevision === void 0 ? {} : { contentRevision: nonNegativeInteger(input2.contentRevision, `${label}.contentRevision`) },
26199
28220
  ...input2.claimAttempt === void 0 ? {} : {
26200
28221
  claimAttempt: positiveInteger(input2.claimAttempt, `${label}.claimAttempt`)
26201
28222
  },
@@ -26207,6 +28228,11 @@ function parseCompletionInput(value, label) {
26207
28228
  }
26208
28229
  };
26209
28230
  }
28231
+ function parseRetiredSessionIds(value) {
28232
+ if (!Array.isArray(value) || value.length > 4096 || new Set(value).size !== value.length)
28233
+ throw new Error("Invalid DSH retired Session identities");
28234
+ return value.map((id, index) => safeString(id, `state.localOnlySessionIds[${index}]`, 128));
28235
+ }
26210
28236
  function assertAlreadyRedacted(state) {
26211
28237
  const encoded = JSON.stringify(state);
26212
28238
  const redacted = JSON.stringify(redactValue(state));
@@ -26269,7 +28295,7 @@ function isNodeError4(error62) {
26269
28295
  }
26270
28296
  async function optionalLstat(target) {
26271
28297
  try {
26272
- return await lstat6(target);
28298
+ return await lstat7(target);
26273
28299
  } catch (error62) {
26274
28300
  if (isNodeError4(error62) && error62.code === "ENOENT")
26275
28301
  return void 0;
@@ -26526,7 +28552,10 @@ var RPC_ENDPOINTS = /* @__PURE__ */ new Set([
26526
28552
  "sync/set-auto-upload",
26527
28553
  "sync/upload",
26528
28554
  "code/authorize",
26529
- "code/action"
28555
+ "code/action",
28556
+ "github/authorize",
28557
+ "github/action",
28558
+ "github/connect"
26530
28559
  ]);
26531
28560
  var MAX_CATALOG_PROVIDERS = 64;
26532
28561
  var MAX_CATALOG_MODELS = 256;
@@ -26628,6 +28657,12 @@ var DshNativeHostController = class {
26628
28657
  projectName: this.#projects.get(projectId)?.name ?? projectId,
26629
28658
  ...view
26630
28659
  }];
28660
+ }).slice(0, 100),
28661
+ githubSync: [...this.#owners].flatMap(([projectId, owner]) => {
28662
+ if (this.#projects.get(projectId)?.role === "viewer")
28663
+ return [];
28664
+ const view = owner.githubSyncView?.();
28665
+ return view === void 0 ? [] : [{ projectId, projectName: this.#projects.get(projectId)?.name ?? projectId, ...view }];
26631
28666
  }).slice(0, 100)
26632
28667
  }
26633
28668
  },
@@ -26889,6 +28924,40 @@ var DshNativeHostController = class {
26889
28924
  }
26890
28925
  return this.publicState();
26891
28926
  }
28927
+ async githubSyncAction(inputValue, authorize = false) {
28928
+ this.#assertAvailable();
28929
+ const input2 = exactInput(inputValue, new Set(authorize ? ["projectId", "enabled", "repository", "base_branch", "revision"] : ["projectId", "action"]));
28930
+ const projectId = safeInputIdentifier(input2.projectId, "projectId");
28931
+ const owner = this.#owners.get(projectId);
28932
+ if (!owner || this.#projects.get(projectId)?.role === "viewer")
28933
+ throw new Error("This Project is not writable on this DSH device");
28934
+ if (authorize) {
28935
+ if (typeof input2.enabled !== "boolean" || !owner.authorizeGitHubSync)
28936
+ throw new Error("Invalid GitHub authorization");
28937
+ let expected;
28938
+ if (input2.enabled) {
28939
+ if (typeof input2.repository !== "string" || typeof input2.base_branch !== "string" || typeof input2.revision !== "string" || input2.repository.length > 160 || input2.base_branch.length > 255 || input2.revision.length > 128)
28940
+ throw new Error("Invalid GitHub target");
28941
+ expected = { repository: input2.repository, base_branch: input2.base_branch, revision: input2.revision };
28942
+ }
28943
+ await owner.authorizeGitHubSync(input2.enabled, expected);
28944
+ } else {
28945
+ if (typeof input2.action !== "string" || !GITHUB_CODE_ACTIONS.includes(input2.action) || !owner.executeGitHubSync)
28946
+ throw new Error("Invalid GitHub action");
28947
+ await owner.executeGitHubSync(input2.action);
28948
+ }
28949
+ return this.publicState();
28950
+ }
28951
+ async connectGitHub(inputValue) {
28952
+ this.#assertAvailable();
28953
+ const input2 = exactInput(inputValue, /* @__PURE__ */ new Set(["projectId"]));
28954
+ const projectId = safeInputIdentifier(input2.projectId, "projectId");
28955
+ if (!this.#owners.has(projectId) || this.#projects.get(projectId)?.role === "viewer") {
28956
+ throw new Error("This Project is not writable on this DSH device");
28957
+ }
28958
+ await connectGitHubAccount();
28959
+ return this.publicState();
28960
+ }
26892
28961
  dispose() {
26893
28962
  this.#disposePromise ??= (async () => {
26894
28963
  this.#disposed = true;
@@ -26990,9 +29059,13 @@ var DshNativeHostController = class {
26990
29059
  if (route === void 0)
26991
29060
  return;
26992
29061
  throwIfAborted(signal);
26993
- const profilesChanged = await this.#refreshExecutionProfiles(route, signal);
29062
+ const catalogUpdate = await this.#refreshExecutionProfiles(route, signal);
26994
29063
  throwIfAborted(signal);
26995
- if (profilesChanged) {
29064
+ if (catalogUpdate === "route_unavailable") {
29065
+ await this.#abandonUnavailableRoute(grant);
29066
+ return;
29067
+ }
29068
+ if (catalogUpdate === "changed") {
26996
29069
  for (const owner of this.#owners.values())
26997
29070
  this.#pendingProfileOwners.add(owner);
26998
29071
  }
@@ -27034,16 +29107,17 @@ var DshNativeHostController = class {
27034
29107
  /**
27035
29108
  * Re-read the harness catalog when the route changed or the entry went stale.
27036
29109
  *
27037
- * Returns whether the catalog differs from the one already advertised. Discovery
27038
- * is cached per route and also time-bounded: DSH publishes a model metadata
27039
- * catalog that can gain models while a connection stays open, so a connect-time
27040
- * snapshot would otherwise keep offering the old list for the life of the process.
29110
+ * Returns whether the catalog differs from the one already advertised, or that
29111
+ * DSH no longer offers the model this connection runs. Discovery is cached per
29112
+ * route and also time-bounded: DSH publishes a model metadata catalog that can
29113
+ * gain models while a connection stays open, so a connect-time snapshot would
29114
+ * otherwise keep offering the old list for the life of the process.
27041
29115
  */
27042
29116
  async #refreshExecutionProfiles(route, signal) {
27043
29117
  const routeKey = `${route.provider}\0${route.model}`;
27044
29118
  const discoveredAt = this.#executionProfilesDiscoveredAt;
27045
29119
  if (this.#executionProfileRouteKey === routeKey && discoveredAt !== void 0 && this.#now() - discoveredAt < EXECUTION_PROFILE_REFRESH_INTERVAL_MS) {
27046
- return false;
29120
+ return "unchanged";
27047
29121
  }
27048
29122
  const previous = this.#executionProfiles;
27049
29123
  const remember = (profiles2) => {
@@ -27051,18 +29125,44 @@ var DshNativeHostController = class {
27051
29125
  this.#executionProfiles = profiles2;
27052
29126
  this.#executionProfileRouteKey = routeKey;
27053
29127
  this.#executionProfilesDiscoveredAt = this.#now();
27054
- return changed;
29128
+ return changed ? "changed" : "unchanged";
27055
29129
  };
27056
29130
  if (route.provider !== "deepseek-official")
27057
29131
  return remember(void 0);
27058
29132
  const discovered = this.#options.listExecutionProfiles === void 0 ? await listNativeExecutionProfiles(this.#options.context, route.provider, signal) : await this.#options.listExecutionProfiles(route.provider, signal);
27059
29133
  throwIfAborted(signal);
27060
29134
  const profiles = normalizeExecutionProfiles(discovered, route.provider);
27061
- if (!profiles.some((profile) => profile.model === route.model)) {
27062
- throw new Error("DeepSeek Harness did not advertise the configured model as an execution profile");
27063
- }
29135
+ if (!profiles.some((profile) => profile.model === route.model))
29136
+ return "route_unavailable";
27064
29137
  return remember(profiles);
27065
29138
  }
29139
+ /**
29140
+ * Drop the route after DSH stops offering the model this connection runs.
29141
+ *
29142
+ * A harness catalog is the authority on what a runtime can run, so the route is
29143
+ * abandoned instead of being repointed at another model: registering a
29144
+ * replacement silently would run a model this device never selected. Clearing the
29145
+ * stored route returns the connection to the state it has before its first
29146
+ * selection, where the panel asks for an explicit provider and model against the
29147
+ * harness's current catalog. Owners and the cached declaration go with it, so
29148
+ * nothing keeps advertising the profiles and bindings built from the dropped route.
29149
+ */
29150
+ async #abandonUnavailableRoute(grant) {
29151
+ await this.#stopOwners();
29152
+ this.#executionProfiles = void 0;
29153
+ this.#executionProfileRouteKey = void 0;
29154
+ this.#executionProfilesDiscoveredAt = void 0;
29155
+ this.#grant = await this.#credentials.save({
29156
+ schemaVersion: grant.schemaVersion,
29157
+ serverUrl: grant.serverUrl,
29158
+ apiUrl: grant.apiUrl,
29159
+ deviceId: grant.deviceId,
29160
+ deviceName: grant.deviceName,
29161
+ token: grant.token
29162
+ });
29163
+ this.#recoverableError = "route_unavailable";
29164
+ this.#options.status.setConnection("stopped");
29165
+ }
27066
29166
  /**
27067
29167
  * Republish the current catalog to every owner that has not confirmed it.
27068
29168
  *
@@ -27192,7 +29292,7 @@ async function apply(context, rawConfig) {
27192
29292
  workspacePath: config2.workspacePath,
27193
29293
  ...dshHome === void 0 ? {} : {
27194
29294
  dshHome,
27195
- projectWorkspaceHomeDirectory: path9.dirname(dshHome)
29295
+ projectWorkspaceHomeDirectory: path10.dirname(dshHome)
27196
29296
  },
27197
29297
  ...config2.officialServerUrl === void 0 ? {} : { officialServerUrl: config2.officialServerUrl }
27198
29298
  });
@@ -27250,6 +29350,12 @@ function registerNativeDshRpc(contextValue, controller) {
27250
29350
  return rpcSuccess(await controller.codeSyncAction(payload, true));
27251
29351
  case "code/action":
27252
29352
  return rpcSuccess(await controller.codeSyncAction(payload));
29353
+ case "github/authorize":
29354
+ return rpcSuccess(await controller.githubSyncAction(payload, true));
29355
+ case "github/action":
29356
+ return rpcSuccess(await controller.githubSyncAction(payload));
29357
+ case "github/connect":
29358
+ return rpcSuccess(await controller.connectGitHub(payload));
27253
29359
  default:
27254
29360
  return rpcFailure("gatherthread/not-found", "GatherThread DSH action is unavailable");
27255
29361
  }
@@ -27344,8 +29450,8 @@ async function createProductionOwner(options) {
27344
29450
  throw error62;
27345
29451
  }
27346
29452
  const codeSync = new DshCodeSyncController({
27347
- permissionPath: path9.join(config2.stateRoot, `code-consent-${createHash8("sha256").update(actor.id).digest("hex")}.json`),
27348
- binding: createHash8("sha256").update(JSON.stringify([
29453
+ permissionPath: path10.join(config2.stateRoot, `code-consent-${createHash10("sha256").update(actor.id).digest("hex")}.json`),
29454
+ binding: createHash10("sha256").update(JSON.stringify([
27349
29455
  config2.apiUrl,
27350
29456
  config2.projectId,
27351
29457
  actor.id,
@@ -27360,7 +29466,7 @@ async function createProductionOwner(options) {
27360
29466
  // A custom DSH home is an isolated profile, including its code-sync state.
27361
29467
  // Default profiles keep the harness-neutral state shared with Codex.
27362
29468
  ...options.dshHome === void 0 ? {} : {
27363
- stateRoot: path9.join(options.dshHome, "gatherthread-code-sync", createHash8("sha256").update(config2.workspacePath).digest("hex"))
29469
+ stateRoot: path10.join(options.dshHome, "gatherthread-code-sync", createHash10("sha256").update(config2.workspacePath).digest("hex"))
27364
29470
  }
27365
29471
  }),
27366
29472
  api: new HttpCollaborationClient({
@@ -27372,8 +29478,77 @@ async function createProductionOwner(options) {
27372
29478
  isBusy: () => nativeWorkspace.isBusy()
27373
29479
  });
27374
29480
  await codeSync.start().catch(() => void 0);
29481
+ const githubSync = new DshGitHubCodeSyncController({
29482
+ permissionPath: path10.join(config2.stateRoot, `github-consent-${createHash10("sha256").update(actor.id).digest("hex")}.json`),
29483
+ binding: createHash10("sha256").update(JSON.stringify([
29484
+ "github",
29485
+ config2.apiUrl,
29486
+ config2.projectId,
29487
+ actor.id,
29488
+ config2.workspacePath
29489
+ ])).digest("hex"),
29490
+ readMetadata: async () => {
29491
+ const response = await fetch(`${config2.apiUrl}/projects/${encodeURIComponent(config2.projectId)}/github`, {
29492
+ headers: { authorization: `Bearer ${options.grant.token}` },
29493
+ redirect: "error",
29494
+ signal: AbortSignal.any([ownerAbort.signal, AbortSignal.timeout(15e3)])
29495
+ });
29496
+ if (!response.ok || !response.body)
29497
+ throw Object.assign(new Error("GitHub metadata unavailable"), { code: "code_github_unavailable" });
29498
+ const reader = response.body.getReader();
29499
+ const chunks = [];
29500
+ let size = 0;
29501
+ try {
29502
+ for (; ; ) {
29503
+ const next = await reader.read();
29504
+ if (next.done)
29505
+ break;
29506
+ size += next.value.byteLength;
29507
+ if (size > 16 * 1024)
29508
+ throw new Error("GitHub metadata too large");
29509
+ chunks.push(next.value);
29510
+ }
29511
+ } finally {
29512
+ await reader.cancel().catch(() => void 0);
29513
+ }
29514
+ const envelope = JSON.parse(Buffer.concat(chunks).toString("utf8"));
29515
+ return GitHubProjectStatusSchema.parse(envelope.data);
29516
+ },
29517
+ createEngine: (target) => createGitHubCodeSync({
29518
+ apiUrl: config2.apiUrl,
29519
+ token: options.grant.token,
29520
+ projectId: config2.projectId,
29521
+ actorId: actor.id,
29522
+ workspacePath: config2.workspacePath,
29523
+ repository: target.repository,
29524
+ baseBranch: target.base_branch,
29525
+ revision: target.revision,
29526
+ ...options.dshHome === void 0 ? {} : {
29527
+ stateRoot: path10.join(options.dshHome, "gatherthread-code-sync", createHash10("sha256").update(config2.workspacePath).digest("hex"))
29528
+ }
29529
+ }),
29530
+ api: new HttpCollaborationClient({ baseUrl: config2.apiUrl, bearerToken: options.grant.token, signal: ownerAbort.signal }),
29531
+ runtimes: () => new Map([...codeRuntimes].flatMap(([id, connector]) => connector.stopped || !connector.executionRuntimeId ? [] : [[id, connector.executionRuntimeId]])),
29532
+ isBusy: () => nativeWorkspace.isBusy()
29533
+ });
29534
+ await githubSync.start().catch(() => void 0);
29535
+ let localCodeTail = Promise.resolve();
29536
+ function scheduleCode(operation) {
29537
+ const task = localCodeTail.then(operation);
29538
+ localCodeTail = task.catch(() => void 0);
29539
+ return task;
29540
+ }
29541
+ let codePolling = false;
27375
29542
  const codeTimer = setInterval(() => {
27376
- void codeSync.poll();
29543
+ if (codePolling)
29544
+ return;
29545
+ codePolling = true;
29546
+ void scheduleCode(async () => {
29547
+ await codeSync.poll();
29548
+ await githubSync.poll();
29549
+ }).catch(() => void 0).finally(() => {
29550
+ codePolling = false;
29551
+ });
27377
29552
  }, 5e3);
27378
29553
  codeTimer.unref();
27379
29554
  let stopPromise;
@@ -27383,14 +29558,19 @@ async function createProductionOwner(options) {
27383
29558
  setLocalAutoUpload: (sessionId, enabled) => manager.setLocalAutoUpload(sessionId, enabled),
27384
29559
  uploadLocalTurns: (sessionId) => manager.uploadLocalTurns(sessionId),
27385
29560
  codeSyncView: () => codeSync.view(),
27386
- authorizeCodeSync: (enabled) => codeSync.authorize(enabled),
27387
- executeCodeSync: (action) => codeSync.execute(action),
29561
+ authorizeCodeSync: (enabled) => scheduleCode(() => codeSync.authorize(enabled)),
29562
+ executeCodeSync: (action) => scheduleCode(() => codeSync.execute(action)),
29563
+ githubSyncView: () => githubSync.view(),
29564
+ authorizeGitHubSync: (enabled, expected) => scheduleCode(() => githubSync.authorize(enabled, expected)),
29565
+ executeGitHubSync: (action) => scheduleCode(() => githubSync.execute(action)),
27388
29566
  stop() {
27389
29567
  stopPromise ??= (async () => {
27390
29568
  options.signal.removeEventListener("abort", abortFromRoot);
27391
29569
  clearInterval(codeTimer);
27392
29570
  ownerAbort.abort(new Error("GatherThread native DSH manager stopped"));
29571
+ await localCodeTail;
27393
29572
  await codeSync.stop();
29573
+ await githubSync.stop();
27394
29574
  await manager.stop();
27395
29575
  nativeWorkspace.dispose();
27396
29576
  })();
@@ -27460,6 +29640,7 @@ function createNativeManagedConnector(options) {
27460
29640
  await connector.start();
27461
29641
  },
27462
29642
  updateExecutionProfiles: (profiles) => connector.updateExecutionProfiles(profiles),
29643
+ updateSessionSummary: (session) => connector.updateSessionSummary(session),
27463
29644
  localSyncStatus: () => connector.localSyncStatus(),
27464
29645
  setLocalAutoUpload: (enabled) => connector.setLocalAutoUpload(enabled),
27465
29646
  uploadLocalTurns: () => connector.uploadLocalTurns(),
@@ -27486,7 +29667,7 @@ async function resolveNativeProjectWorkspace(grant, binding, signal, homeDirecto
27486
29667
  }
27487
29668
  function nativeDshHomeFromEnvironment() {
27488
29669
  const value = process.env.DSH_HOME?.trim();
27489
- return value ? path9.resolve(value) : void 0;
29670
+ return value ? path10.resolve(value) : void 0;
27490
29671
  }
27491
29672
  async function listNativeLlmCatalog(contextValue, signal) {
27492
29673
  const llm = requireLlm(contextValue);