@gatherthread/dsh-host 0.1.0-alpha.8 → 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/bundle/manifest.json +1 -1
- package/client/client.js +115 -36
- package/dist/bundle/native-plugin.js +1189 -209
- package/dist/bundle/plugin.js +324 -46
- package/package.json +2 -2
|
@@ -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,
|
|
827
|
-
if (!
|
|
826
|
+
function getElementAtPath(obj, path11) {
|
|
827
|
+
if (!path11)
|
|
828
828
|
return obj;
|
|
829
|
-
return
|
|
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(
|
|
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(
|
|
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
|
|
1280
|
+
const bytes2 = new Uint8Array(binaryString.length);
|
|
1281
1281
|
for (let i = 0; i < binaryString.length; i++) {
|
|
1282
|
-
|
|
1282
|
+
bytes2[i] = binaryString.charCodeAt(i);
|
|
1283
1283
|
}
|
|
1284
|
-
return
|
|
1284
|
+
return bytes2;
|
|
1285
1285
|
}
|
|
1286
|
-
function uint8ArrayToBase64(
|
|
1286
|
+
function uint8ArrayToBase64(bytes2) {
|
|
1287
1287
|
let binaryString = "";
|
|
1288
|
-
for (let i = 0; i <
|
|
1289
|
-
binaryString += String.fromCharCode(
|
|
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(
|
|
1299
|
-
return uint8ArrayToBase64(
|
|
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
|
|
1306
|
+
const bytes2 = new Uint8Array(cleanHex.length / 2);
|
|
1307
1307
|
for (let i = 0; i < cleanHex.length; i += 2) {
|
|
1308
|
-
|
|
1308
|
+
bytes2[i / 2] = Number.parseInt(cleanHex.slice(i, i + 2), 16);
|
|
1309
1309
|
}
|
|
1310
|
-
return
|
|
1310
|
+
return bytes2;
|
|
1311
1311
|
}
|
|
1312
|
-
function uint8ArrayToHex(
|
|
1313
|
-
return Array.from(
|
|
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,
|
|
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 }, [...
|
|
1626
|
+
issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
|
|
1627
1627
|
} else if (issue2.code === "invalid_key") {
|
|
1628
|
-
processError({ issues: issue2.issues }, [...
|
|
1628
|
+
processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
|
|
1629
1629
|
} else if (issue2.code === "invalid_element") {
|
|
1630
|
-
processError({ issues: issue2.issues }, [...
|
|
1630
|
+
processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
|
|
1631
1631
|
} else {
|
|
1632
|
-
const fullpath = [...
|
|
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,
|
|
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 }, [...
|
|
1675
|
+
issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
|
|
1676
1676
|
} else if (issue2.code === "invalid_key") {
|
|
1677
|
-
processError({ issues: issue2.issues }, [...
|
|
1677
|
+
processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
|
|
1678
1678
|
} else if (issue2.code === "invalid_element") {
|
|
1679
|
-
processError({ issues: issue2.issues }, [...
|
|
1679
|
+
processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
|
|
1680
1680
|
} else {
|
|
1681
|
-
const fullpath = [...
|
|
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
|
|
1721
|
-
for (const seg of
|
|
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")
|
|
@@ -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
|
|
18824
|
-
if (
|
|
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 (
|
|
18829
|
-
const key =
|
|
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
|
}
|
|
@@ -19685,16 +19685,16 @@ var Commit = external_exports.string().regex(/^[a-f0-9]{40}$/);
|
|
|
19685
19685
|
var BranchId = external_exports.string().regex(/^branch-[a-f0-9]{24}$/);
|
|
19686
19686
|
var pathEncoder = new TextEncoder();
|
|
19687
19687
|
var hfsIgnoredCharacters = /[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/gu;
|
|
19688
|
-
function portablePathKey(
|
|
19689
|
-
return
|
|
19688
|
+
function portablePathKey(path11) {
|
|
19689
|
+
return path11.normalize("NFC").replace(hfsIgnoredCharacters, "").toLowerCase();
|
|
19690
19690
|
}
|
|
19691
19691
|
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);
|
|
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]|gteg?)|acp(?:i|d)?)_[A-Za-z0-9_-]{20,}|\bAKIA[A-Z0-9]{16}\b/u.test(text);
|
|
19693
19693
|
}
|
|
19694
|
-
function isCodeSyncPathAllowed(
|
|
19695
|
-
if (!
|
|
19694
|
+
function isCodeSyncPathAllowed(path11) {
|
|
19695
|
+
if (!path11 || path11.length > 500 || path11.startsWith("/") || /[\\\u0000-\u001f\u007f\ud800-\udfff:<>"|?*]/u.test(path11))
|
|
19696
19696
|
return false;
|
|
19697
|
-
const components =
|
|
19697
|
+
const components = path11.split("/");
|
|
19698
19698
|
return components.every((part) => {
|
|
19699
19699
|
const lower = portablePathKey(part);
|
|
19700
19700
|
if (!part || pathEncoder.encode(part).byteLength > 255 || part === "." || part === ".." || part.endsWith(".") || part.endsWith(" "))
|
|
@@ -19714,34 +19714,42 @@ function isCodeSyncPathAllowed(path10) {
|
|
|
19714
19714
|
return !/\.(?:pem|p12|pfx|key)$/iu.test(lower) && !/^(?:id_rsa|id_ed25519)(?:\.|$)/iu.test(lower);
|
|
19715
19715
|
});
|
|
19716
19716
|
}
|
|
19717
|
-
var
|
|
19718
|
-
|
|
19719
|
-
|
|
19720
|
-
|
|
19721
|
-
|
|
19722
|
-
|
|
19723
|
-
|
|
19724
|
-
|
|
19725
|
-
|
|
19726
|
-
|
|
19727
|
-
|
|
19728
|
-
|
|
19729
|
-
|
|
19730
|
-
|
|
19731
|
-
|
|
19732
|
-
|
|
19733
|
-
|
|
19734
|
-
|
|
19735
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
19738
|
-
|
|
19739
|
-
|
|
19740
|
-
|
|
19717
|
+
var CLOUD_CODE_SYNC_LIMITS = { maxFiles: CODE_SYNC_MAX_FILES, maxFileBytes: CODE_SYNC_MAX_FILE_BYTES, maxSnapshotBytes: CODE_SYNC_MAX_SNAPSHOT_BYTES };
|
|
19718
|
+
var GITHUB_CODE_SYNC_LIMITS = { maxFiles: 1e4, maxFileBytes: 20 * 1024 * 1024, maxSnapshotBytes: 128 * 1024 * 1024 };
|
|
19719
|
+
function createCodeFileSchema(limits) {
|
|
19720
|
+
return external_exports.object({
|
|
19721
|
+
path: external_exports.string().refine(isCodeSyncPathAllowed, "Unsafe or private code path"),
|
|
19722
|
+
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}=)?$/),
|
|
19723
|
+
executable: external_exports.boolean()
|
|
19724
|
+
}).strict();
|
|
19725
|
+
}
|
|
19726
|
+
var CodeFileSchema = createCodeFileSchema(CLOUD_CODE_SYNC_LIMITS);
|
|
19727
|
+
function createCodeFilesSchema(limits) {
|
|
19728
|
+
return external_exports.array(createCodeFileSchema(limits)).max(limits.maxFiles).superRefine((files, context) => {
|
|
19729
|
+
let size = 0;
|
|
19730
|
+
const names = /* @__PURE__ */ new Set();
|
|
19731
|
+
for (const [index, file2] of files.entries()) {
|
|
19732
|
+
const bytes2 = file2.content_base64.length * 3 / 4 - (file2.content_base64.endsWith("==") ? 2 : file2.content_base64.endsWith("=") ? 1 : 0);
|
|
19733
|
+
size += bytes2;
|
|
19734
|
+
const normalized = portablePathKey(file2.path);
|
|
19735
|
+
if (bytes2 > limits.maxFileBytes || names.has(normalized))
|
|
19736
|
+
context.addIssue({ code: "custom", path: [index], message: "File limit or duplicate portable path" });
|
|
19737
|
+
names.add(normalized);
|
|
19738
|
+
}
|
|
19739
|
+
if (size > limits.maxSnapshotBytes)
|
|
19740
|
+
context.addIssue({ code: "custom", message: "Code snapshot exceeds provider limit" });
|
|
19741
|
+
for (const name2 of names) {
|
|
19742
|
+
const parts = name2.split("/");
|
|
19741
19743
|
parts.pop();
|
|
19744
|
+
while (parts.length) {
|
|
19745
|
+
if (names.has(parts.join("/")))
|
|
19746
|
+
context.addIssue({ code: "custom", message: "File and directory paths overlap" });
|
|
19747
|
+
parts.pop();
|
|
19748
|
+
}
|
|
19742
19749
|
}
|
|
19743
|
-
}
|
|
19744
|
-
}
|
|
19750
|
+
});
|
|
19751
|
+
}
|
|
19752
|
+
var CodeFilesSchema = createCodeFilesSchema(CLOUD_CODE_SYNC_LIMITS);
|
|
19745
19753
|
var CodeEnableInputSchema = external_exports.object({ idempotency_key: Key }).strict();
|
|
19746
19754
|
var CodeDisableInputSchema = external_exports.object({ idempotency_key: Key }).strict();
|
|
19747
19755
|
var CodeCheckpointInputSchema = external_exports.object({
|
|
@@ -19803,6 +19811,47 @@ var CodeSnapshotResultSchema = external_exports.object({ snapshot: external_expo
|
|
|
19803
19811
|
}).strict() }).strict();
|
|
19804
19812
|
var codeSyncRequestKinds = ["code_sync_status", "code_upload", "code_download", "code_recover", "code_auto_upload_enable", "code_auto_upload_disable"];
|
|
19805
19813
|
|
|
19814
|
+
// ../protocol/dist/src/github-sync.js
|
|
19815
|
+
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) => {
|
|
19816
|
+
const repository = value.split("/")[1];
|
|
19817
|
+
return repository !== "." && repository !== ".." && !repository.toLowerCase().endsWith(".git");
|
|
19818
|
+
}, "Use owner/repository without a .git suffix");
|
|
19819
|
+
function isGitHubBranchAllowed(value) {
|
|
19820
|
+
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";
|
|
19821
|
+
}
|
|
19822
|
+
var GitHubBranchSchema = external_exports.string().refine(isGitHubBranchAllowed, "Unsafe GitHub branch name");
|
|
19823
|
+
var GitHubBaseBranchSchema = GitHubBranchSchema.refine((value) => !value.startsWith("gatherthread/"), "The shared base cannot use the GatherThread personal branch namespace");
|
|
19824
|
+
var GitHubPersonalBranchSchema = external_exports.string().regex(/^gatherthread\/[a-f0-9]{24}\/[a-f0-9]{24}$/);
|
|
19825
|
+
var GitHubConnectionSchema = external_exports.object({
|
|
19826
|
+
repository: GitHubRepositorySchema,
|
|
19827
|
+
base_branch: GitHubBaseBranchSchema,
|
|
19828
|
+
enabled: external_exports.boolean(),
|
|
19829
|
+
revision: external_exports.string().uuid()
|
|
19830
|
+
}).strict();
|
|
19831
|
+
var GitHubProjectStatusSchema = external_exports.object({
|
|
19832
|
+
connection: GitHubConnectionSchema.nullable(),
|
|
19833
|
+
branch: GitHubPersonalBranchSchema,
|
|
19834
|
+
can_configure: external_exports.boolean(),
|
|
19835
|
+
// GatherThread membership only. GitHub permissions must be checked locally.
|
|
19836
|
+
can_write: external_exports.boolean()
|
|
19837
|
+
}).strict();
|
|
19838
|
+
var GitHubConnectionInputSchema = external_exports.object({
|
|
19839
|
+
repository: GitHubRepositorySchema,
|
|
19840
|
+
base_branch: GitHubBaseBranchSchema,
|
|
19841
|
+
enabled: external_exports.boolean(),
|
|
19842
|
+
expected_revision: external_exports.string().uuid().nullable()
|
|
19843
|
+
}).strict();
|
|
19844
|
+
var githubCodeSyncRequestKinds = [
|
|
19845
|
+
"github_auth_connect",
|
|
19846
|
+
"github_code_sync_status",
|
|
19847
|
+
"github_code_upload",
|
|
19848
|
+
"github_code_download",
|
|
19849
|
+
"github_code_recover",
|
|
19850
|
+
"github_code_auto_upload_enable",
|
|
19851
|
+
"github_code_auto_upload_disable",
|
|
19852
|
+
"github_code_update"
|
|
19853
|
+
];
|
|
19854
|
+
|
|
19806
19855
|
// ../protocol/dist/src/history-summary.js
|
|
19807
19856
|
var HISTORY_SUMMARY_MAX_SOURCE_BYTES = 20 * 1024;
|
|
19808
19857
|
var HISTORY_SUMMARY_MAX_PROMPT_BYTES = 30 * 1024;
|
|
@@ -19827,6 +19876,155 @@ function historySummaryMarker(event) {
|
|
|
19827
19876
|
return { version: 1, source_event_ids: ids, source_digest: marker.source_digest };
|
|
19828
19877
|
}
|
|
19829
19878
|
|
|
19879
|
+
// ../protocol/dist/src/test-gate.js
|
|
19880
|
+
var TestGateExchangeInputSchema = external_exports.object({
|
|
19881
|
+
admission_code: external_exports.string().regex(/^gte_[A-Za-z0-9_-]{43}$/)
|
|
19882
|
+
}).strict();
|
|
19883
|
+
var TestGateStatusSchema = external_exports.object({
|
|
19884
|
+
enabled: external_exports.boolean(),
|
|
19885
|
+
admitted: external_exports.boolean(),
|
|
19886
|
+
environment: external_exports.enum(["production", "test"])
|
|
19887
|
+
}).strict();
|
|
19888
|
+
|
|
19889
|
+
// ../protocol/dist/src/hosted-github.js
|
|
19890
|
+
var Key2 = external_exports.string().min(8).max(200);
|
|
19891
|
+
var HostedGithubRepositoryInputSchema = external_exports.object({
|
|
19892
|
+
repository: external_exports.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u).max(140),
|
|
19893
|
+
base_branch: external_exports.string().min(1).max(200).refine((v) => !/[\s~^:?*\[\\]/u.test(v) && !v.includes("..") && !v.startsWith("-") && !v.endsWith("/") && !v.endsWith(".lock"))
|
|
19894
|
+
}).strict();
|
|
19895
|
+
var HostedGithubTaskInputSchema = external_exports.object({
|
|
19896
|
+
content: external_exports.string().trim().min(1).max(6e3),
|
|
19897
|
+
profile_id: external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/u),
|
|
19898
|
+
idempotency_key: Key2,
|
|
19899
|
+
continue_task_id: external_exports.string().regex(/^gh-task-[a-f0-9]{32}$/u).optional(),
|
|
19900
|
+
reply_to_event_id: external_exports.string().min(1).max(128).nullable().optional()
|
|
19901
|
+
}).strict();
|
|
19902
|
+
var HostedGithubCompleteInputSchema = external_exports.object({
|
|
19903
|
+
state: external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/u),
|
|
19904
|
+
code: external_exports.string().min(1).max(500)
|
|
19905
|
+
}).strict();
|
|
19906
|
+
var HostedGithubPrInputSchema = external_exports.object({
|
|
19907
|
+
title: external_exports.string().trim().min(1).max(200),
|
|
19908
|
+
body: external_exports.string().max(6e3),
|
|
19909
|
+
expected_revision: external_exports.string().regex(/^[a-f0-9]{64}$/u)
|
|
19910
|
+
}).strict();
|
|
19911
|
+
var Login = external_exports.string().regex(/^[A-Za-z0-9-]{1,39}$/u);
|
|
19912
|
+
var TaskId = external_exports.string().regex(/^gh-task-[a-f0-9]{32}$/u);
|
|
19913
|
+
var Sha = external_exports.string().regex(/^[a-f0-9]{40}$/u).nullable();
|
|
19914
|
+
var Revision = external_exports.string().regex(/^[a-f0-9]{64}$/u).nullable();
|
|
19915
|
+
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();
|
|
19916
|
+
var HostedGithubBindingSchema = HostedGithubRepositoryInputSchema;
|
|
19917
|
+
var HostedGithubStatusSchema = external_exports.union([
|
|
19918
|
+
external_exports.object({ enabled: external_exports.literal(false) }).strict(),
|
|
19919
|
+
external_exports.object({
|
|
19920
|
+
enabled: external_exports.literal(true),
|
|
19921
|
+
connected: external_exports.boolean(),
|
|
19922
|
+
login: Login.nullable(),
|
|
19923
|
+
binding: HostedGithubBindingSchema.nullable(),
|
|
19924
|
+
installation_url: external_exports.string().max(2048).regex(/^https:\/\/github\.com\/apps\/[A-Za-z0-9_.-]+\/installations\/new$/u)
|
|
19925
|
+
}).strict().refine((v) => v.connected ? v.login !== null : v.login === null && v.binding === null)
|
|
19926
|
+
]);
|
|
19927
|
+
var HostedGithubAuthorizationSchema = external_exports.object({ authorization_url: external_exports.string().max(4096).refine((value) => {
|
|
19928
|
+
try {
|
|
19929
|
+
const url2 = new URL(value);
|
|
19930
|
+
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));
|
|
19931
|
+
} catch {
|
|
19932
|
+
return false;
|
|
19933
|
+
}
|
|
19934
|
+
}) }).strict();
|
|
19935
|
+
var HostedGithubConnectionSchema = external_exports.object({ connected: external_exports.literal(true), login: Login }).strict();
|
|
19936
|
+
var HostedGithubDisconnectionSchema = external_exports.object({ connected: external_exports.literal(false) }).strict();
|
|
19937
|
+
var HostedGithubTaskSummarySchema = external_exports.object({
|
|
19938
|
+
id: TaskId,
|
|
19939
|
+
session_id: external_exports.string().min(1).max(128),
|
|
19940
|
+
profile_id: HostedGithubTaskInputSchema.shape.profile_id,
|
|
19941
|
+
resumable: external_exports.boolean(),
|
|
19942
|
+
state: external_exports.enum(["running", "completed", "failed", "interrupted"]),
|
|
19943
|
+
repository: HostedGithubRepositoryInputSchema.shape.repository,
|
|
19944
|
+
base_branch: HostedGithubRepositoryInputSchema.shape.base_branch,
|
|
19945
|
+
base_sha: Sha,
|
|
19946
|
+
revision: Revision,
|
|
19947
|
+
error_code: external_exports.string().regex(/^[a-z][a-z0-9_]{0,63}$/u).nullable(),
|
|
19948
|
+
pull_request_url: PullUrl,
|
|
19949
|
+
expires_at: external_exports.number().int().positive().max(Number.MAX_SAFE_INTEGER)
|
|
19950
|
+
}).strict();
|
|
19951
|
+
var bytes = (value) => value.length / 4 * 3 - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0);
|
|
19952
|
+
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();
|
|
19953
|
+
var HostedGithubChangeSchema = external_exports.object({
|
|
19954
|
+
path: external_exports.string().max(4096).refine(isCodeSyncPathAllowed),
|
|
19955
|
+
before_base64: Source,
|
|
19956
|
+
after_base64: Source,
|
|
19957
|
+
before_executable: external_exports.boolean().nullable(),
|
|
19958
|
+
after_executable: external_exports.boolean().nullable()
|
|
19959
|
+
}).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));
|
|
19960
|
+
var HostedGithubTaskSchema = HostedGithubTaskSummarySchema.extend({
|
|
19961
|
+
answer: external_exports.string().max(14e3).nullable(),
|
|
19962
|
+
changes: external_exports.array(HostedGithubChangeSchema).max(2e3)
|
|
19963
|
+
}).strict().refine((v) => {
|
|
19964
|
+
if (new Set(v.changes.map((c) => c.path)).size !== v.changes.length)
|
|
19965
|
+
return false;
|
|
19966
|
+
for (const side of ["before_base64", "after_base64"]) {
|
|
19967
|
+
const values = v.changes.map((c) => c[side]).filter((s) => s !== null);
|
|
19968
|
+
if (values.length > 1e3 || values.reduce((sum, s) => sum + bytes(s), 0) > 8 * 1024 * 1024)
|
|
19969
|
+
return false;
|
|
19970
|
+
}
|
|
19971
|
+
return !v.resumable || v.state !== "running" && v.pull_request_url === null;
|
|
19972
|
+
});
|
|
19973
|
+
var HostedGithubTaskListSchema = external_exports.object({ tasks: external_exports.array(HostedGithubTaskSummarySchema).max(10) }).strict();
|
|
19974
|
+
|
|
19975
|
+
// ../protocol/dist/src/registration.js
|
|
19976
|
+
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));
|
|
19977
|
+
var Label = external_exports.string().regex(/^[^\u0000-\u001f\u007f-\u009f]*$/u).trim().min(1).max(120);
|
|
19978
|
+
var SendRegistrationInputSchema = external_exports.object({
|
|
19979
|
+
email: RegistrationEmailSchema,
|
|
19980
|
+
challenge_token: external_exports.string().min(1).max(2048),
|
|
19981
|
+
idempotency_key: external_exports.string().uuid(),
|
|
19982
|
+
locale: external_exports.enum(["en", "zh-CN"])
|
|
19983
|
+
}).strict();
|
|
19984
|
+
var AccountPasswordSchema = external_exports.string().min(12).max(128).refine((value) => new TextEncoder().encode(value).byteLength <= 512);
|
|
19985
|
+
var EmailLoginInputSchema = external_exports.object({
|
|
19986
|
+
email: RegistrationEmailSchema,
|
|
19987
|
+
password: AccountPasswordSchema,
|
|
19988
|
+
device_name: Label,
|
|
19989
|
+
remember_device: external_exports.boolean().default(false)
|
|
19990
|
+
}).strict();
|
|
19991
|
+
var VerifyRegistrationInputSchema = external_exports.object({
|
|
19992
|
+
registration_id: external_exports.string().uuid(),
|
|
19993
|
+
code: external_exports.string().regex(/^\d{8}$/),
|
|
19994
|
+
display_name: Label,
|
|
19995
|
+
device_name: Label,
|
|
19996
|
+
remember_device: external_exports.boolean().default(false),
|
|
19997
|
+
privacy_acknowledged: external_exports.literal(true),
|
|
19998
|
+
password: AccountPasswordSchema
|
|
19999
|
+
}).strict();
|
|
20000
|
+
var RegistrationStatusSchema = external_exports.object({
|
|
20001
|
+
enabled: external_exports.boolean(),
|
|
20002
|
+
site_key: external_exports.string().nullable(),
|
|
20003
|
+
challenge_binding: external_exports.string().nullable()
|
|
20004
|
+
}).strict();
|
|
20005
|
+
var RegistrationSentSchema = external_exports.object({
|
|
20006
|
+
registration_id: external_exports.string().uuid(),
|
|
20007
|
+
expires_in_seconds: external_exports.literal(600),
|
|
20008
|
+
resend_after_seconds: external_exports.literal(60)
|
|
20009
|
+
}).strict();
|
|
20010
|
+
var PasswordResetSentSchema = external_exports.object({
|
|
20011
|
+
reset_id: external_exports.string().uuid(),
|
|
20012
|
+
expires_in_seconds: external_exports.literal(600),
|
|
20013
|
+
resend_after_seconds: external_exports.literal(60)
|
|
20014
|
+
}).strict();
|
|
20015
|
+
var VerifyPasswordResetInputSchema = external_exports.object({
|
|
20016
|
+
reset_id: external_exports.string().uuid(),
|
|
20017
|
+
email: RegistrationEmailSchema,
|
|
20018
|
+
code: external_exports.string().regex(/^\d{8}$/),
|
|
20019
|
+
password: AccountPasswordSchema,
|
|
20020
|
+
password_confirmation: AccountPasswordSchema,
|
|
20021
|
+
locale: external_exports.enum(["en", "zh-CN"])
|
|
20022
|
+
}).strict().refine((value) => value.password === value.password_confirmation);
|
|
20023
|
+
var EmailAccountSessionSchema = external_exports.object({
|
|
20024
|
+
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(),
|
|
20025
|
+
expires_at: external_exports.string().datetime()
|
|
20026
|
+
}).strict();
|
|
20027
|
+
|
|
19830
20028
|
// ../protocol/dist/src/index.js
|
|
19831
20029
|
var sessionModes = ["solo", "multi"];
|
|
19832
20030
|
var membershipRoles = ["owner", "participant", "viewer"];
|
|
@@ -19971,6 +20169,43 @@ var AppendEventInputSchema = external_exports.object({
|
|
|
19971
20169
|
observed_model: external_exports.string().trim().min(1).max(160).regex(/^[^\u0000-\u001f\u007f-\u009f]+$/u).optional(),
|
|
19972
20170
|
observed_reasoning_effort: external_exports.string().trim().min(1).max(80).regex(/^[^\u0000-\u001f\u007f-\u009f]+$/u).optional()
|
|
19973
20171
|
});
|
|
20172
|
+
var HostedAgentRequestInputSchema = external_exports.object({
|
|
20173
|
+
profile_id: external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/u).default("default"),
|
|
20174
|
+
content: external_exports.string().trim().min(1).max(6e3),
|
|
20175
|
+
include_code: external_exports.boolean().default(false),
|
|
20176
|
+
idempotency_key: IdempotencyKeySchema,
|
|
20177
|
+
reply_to_event_id: IdSchema.nullable().optional(),
|
|
20178
|
+
github_task_id: external_exports.string().regex(/^gh-task-[a-f0-9]{32}$/u).optional()
|
|
20179
|
+
}).strict();
|
|
20180
|
+
var HostedAgentStatusSchema = external_exports.discriminatedUnion("enabled", [
|
|
20181
|
+
external_exports.object({ enabled: external_exports.literal(false) }).strict(),
|
|
20182
|
+
external_exports.object({
|
|
20183
|
+
enabled: external_exports.literal(true),
|
|
20184
|
+
harness: external_exports.literal("opencode"),
|
|
20185
|
+
utc_day: external_exports.string(),
|
|
20186
|
+
user_limit_runs: external_exports.number().int().nonnegative().nullable(),
|
|
20187
|
+
user_used_runs: external_exports.number().int().nonnegative(),
|
|
20188
|
+
global_limit_runs: external_exports.number().int().nonnegative().nullable(),
|
|
20189
|
+
global_used_runs: external_exports.number().int().nonnegative(),
|
|
20190
|
+
rate_limits: external_exports.object({
|
|
20191
|
+
user_min_interval_seconds: external_exports.number().int().min(1).max(3600),
|
|
20192
|
+
user_max_concurrent: external_exports.number().int().min(1).max(8),
|
|
20193
|
+
user_active_runs: external_exports.number().int().nonnegative(),
|
|
20194
|
+
retry_after_seconds: external_exports.number().int().nonnegative()
|
|
20195
|
+
}).strict().optional(),
|
|
20196
|
+
profiles: external_exports.array(external_exports.object({
|
|
20197
|
+
id: external_exports.string(),
|
|
20198
|
+
label: external_exports.string(),
|
|
20199
|
+
provider: external_exports.string(),
|
|
20200
|
+
model: external_exports.string(),
|
|
20201
|
+
available: external_exports.boolean(),
|
|
20202
|
+
capacity: external_exports.number().int().nonnegative(),
|
|
20203
|
+
status: external_exports.enum(["available", "busy", "daily_limit", "cooldown", "user_busy", "rate_limit"])
|
|
20204
|
+
}).strict()),
|
|
20205
|
+
capabilities: external_exports.array(external_exports.string()),
|
|
20206
|
+
privacy: external_exports.string()
|
|
20207
|
+
}).strict()
|
|
20208
|
+
]);
|
|
19974
20209
|
var SingleLineTitleSchema = external_exports.string().regex(/^[^\u0000-\u001f\u007f-\u009f]*$/u, "Title must not contain control characters").trim().min(1).max(200);
|
|
19975
20210
|
var SessionTitleSchema = SingleLineTitleSchema;
|
|
19976
20211
|
var ProjectTitleSchema = SingleLineTitleSchema;
|
|
@@ -20017,7 +20252,12 @@ var ProjectListItemSchema = ProjectRecordSchema.omit({ owner_user_id: true }).ex
|
|
|
20017
20252
|
role: MembershipRoleSchema,
|
|
20018
20253
|
session_count: external_exports.number().int().nonnegative()
|
|
20019
20254
|
});
|
|
20255
|
+
var AvatarIdSchema = external_exports.enum(["cat", "fox", "bear", "rabbit", "penguin", "owl"]);
|
|
20256
|
+
var SetAccountAvatarInputSchema = external_exports.object({ avatar_id: AvatarIdSchema.nullable() }).strict();
|
|
20257
|
+
var AccountAvatarProfileSchema = external_exports.object({ user_id: IdSchema, avatar_id: AvatarIdSchema.nullable() });
|
|
20258
|
+
var SessionAvatarProfilesSchema = external_exports.object({ profiles: external_exports.array(AccountAvatarProfileSchema) });
|
|
20020
20259
|
var ProjectMemberRecordSchema = external_exports.object({
|
|
20260
|
+
avatar_id: AvatarIdSchema.nullable().optional(),
|
|
20021
20261
|
user_id: IdSchema,
|
|
20022
20262
|
display_name: external_exports.string().trim().min(1).max(120),
|
|
20023
20263
|
role: MembershipRoleSchema
|
|
@@ -20232,6 +20472,7 @@ var snapshotRequestStatuses = ["pending", "claimed", "completed", "failed"];
|
|
|
20232
20472
|
var SnapshotRequestStatusSchema = external_exports.enum(snapshotRequestStatuses);
|
|
20233
20473
|
var snapshotRequestKinds = [
|
|
20234
20474
|
...codeSyncRequestKinds,
|
|
20475
|
+
...githubCodeSyncRequestKinds,
|
|
20235
20476
|
"immutable",
|
|
20236
20477
|
"visible_history_replace",
|
|
20237
20478
|
"local_sync_status",
|
|
@@ -20553,9 +20794,9 @@ var HttpCollaborationClient = class {
|
|
|
20553
20794
|
}));
|
|
20554
20795
|
return fromWireSnapshotRequest(body.snapshot_request ?? body);
|
|
20555
20796
|
}
|
|
20556
|
-
async #request(
|
|
20797
|
+
async #request(path11, init = {}) {
|
|
20557
20798
|
const signal = combineSignals(init.signal ?? void 0, this.#signal, AbortSignal.timeout(this.#requestTimeoutMs));
|
|
20558
|
-
const response = await this.#fetch(`${this.#baseUrl}${
|
|
20799
|
+
const response = await this.#fetch(`${this.#baseUrl}${path11}`, {
|
|
20559
20800
|
...init,
|
|
20560
20801
|
redirect: "error",
|
|
20561
20802
|
signal,
|
|
@@ -21228,10 +21469,14 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21228
21469
|
#apiUrl;
|
|
21229
21470
|
#root = "";
|
|
21230
21471
|
#stateRoot = "";
|
|
21472
|
+
#lockRoot = "";
|
|
21231
21473
|
#initialized;
|
|
21232
21474
|
#stableDigest = "";
|
|
21233
21475
|
#stableSince = 0;
|
|
21234
21476
|
#lastAutomaticAttempt = 0;
|
|
21477
|
+
get #limits() {
|
|
21478
|
+
return this.#options.storage?.limits ?? CLOUD_CODE_SYNC_LIMITS;
|
|
21479
|
+
}
|
|
21235
21480
|
constructor(options) {
|
|
21236
21481
|
const url2 = new URL(options.apiUrl);
|
|
21237
21482
|
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 +21510,9 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21265
21510
|
this.#root = path2.join(await realpath(path2.dirname(requestedRoot)), path2.basename(requestedRoot));
|
|
21266
21511
|
}
|
|
21267
21512
|
this.#stateRoot = this.#options.stateRoot ?? path2.join(homedir2(), ".gatherthread", "code-sync", digest(this.#root));
|
|
21513
|
+
const sharedRoot = this.#stateRoot;
|
|
21514
|
+
if (this.#options.storage)
|
|
21515
|
+
this.#stateRoot = path2.join(this.#stateRoot, digest(this.#options.storage.key));
|
|
21268
21516
|
if (isWithin(this.#root, path2.resolve(this.#stateRoot)))
|
|
21269
21517
|
throw new CodeSyncError("code_sync_binding", "Code sync state must stay outside source files.");
|
|
21270
21518
|
await mkdir2(this.#stateRoot, { recursive: true, mode: 448 });
|
|
@@ -21274,6 +21522,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21274
21522
|
if (isWithin(this.#root, this.#stateRoot))
|
|
21275
21523
|
throw new CodeSyncError("code_sync_binding", "Private code sync state resolves inside source files.");
|
|
21276
21524
|
await chmod2(this.#stateRoot, 448);
|
|
21525
|
+
this.#lockRoot = await realpath(sharedRoot);
|
|
21277
21526
|
await this.#locked(async () => {
|
|
21278
21527
|
const inventoryPath = path2.join(this.#stateRoot, "inventory.git");
|
|
21279
21528
|
try {
|
|
@@ -21284,6 +21533,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21284
21533
|
throw error62;
|
|
21285
21534
|
}
|
|
21286
21535
|
await git(["init", "--bare", "--quiet", inventoryPath], this.#stateRoot);
|
|
21536
|
+
await this.#options.storage?.initialize(this.#stateRoot);
|
|
21287
21537
|
const filename = path2.join(this.#stateRoot, "binding.json");
|
|
21288
21538
|
try {
|
|
21289
21539
|
await this.#readBinding();
|
|
@@ -21297,7 +21547,9 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21297
21547
|
actor_id: this.#options.actorId,
|
|
21298
21548
|
workspace_path: this.#root,
|
|
21299
21549
|
base_commit: null,
|
|
21300
|
-
|
|
21550
|
+
// GitHub authorization is an explicit local action. New GitHub
|
|
21551
|
+
// bindings follow settled source edits automatically thereafter.
|
|
21552
|
+
automatic_upload: this.#options.storage?.metadata.provider === "github",
|
|
21301
21553
|
baseline: {}
|
|
21302
21554
|
}));
|
|
21303
21555
|
}
|
|
@@ -21348,7 +21600,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21348
21600
|
idempotency_key: `code-upload-${digest(JSON.stringify([this.#options.projectId, this.#options.actorId, uploadBase, inventory.hashes]))}`
|
|
21349
21601
|
};
|
|
21350
21602
|
await this.#requireWorkspace();
|
|
21351
|
-
const parsed = CodeMutationResultSchema.safeParse(await this.#request("/checkpoints", body));
|
|
21603
|
+
const parsed = CodeMutationResultSchema.safeParse(this.#options.storage ? await this.#options.storage.checkpoint(body) : await this.#request("/checkpoints", body));
|
|
21352
21604
|
if (!parsed.success)
|
|
21353
21605
|
throw invalidResponse();
|
|
21354
21606
|
const verified = await this.#snapshot(parsed.data.status);
|
|
@@ -21389,7 +21641,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21389
21641
|
await this.#save(binding);
|
|
21390
21642
|
},
|
|
21391
21643
|
baselineExecutables: new Set(binding.executable_paths ?? [])
|
|
21392
|
-
});
|
|
21644
|
+
}, this.#limits);
|
|
21393
21645
|
const verified = await this.#inventory(new Set(snapshot.files.filter((file2) => file2.executable).map((file2) => file2.path)));
|
|
21394
21646
|
if (!sameHashes(verified.hashes, nextHashes))
|
|
21395
21647
|
throw new CodeSyncError("code_sync_conflict", "Local files changed during download. Check the workspace before retrying.");
|
|
@@ -21430,11 +21682,25 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21430
21682
|
const recoveryName = `${recoveryStem.join("")}-recovered-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/gu, "").slice(0, 14)}-${randomUUID().slice(0, 8)}`;
|
|
21431
21683
|
const recoveryPath = path2.join(path2.dirname(this.#root), recoveryName);
|
|
21432
21684
|
await mkdir2(recoveryPath, { mode: 448 });
|
|
21433
|
-
await applySnapshot(recoveryPath, snapshot.files, {});
|
|
21685
|
+
await applySnapshot(recoveryPath, snapshot.files, {}, void 0, this.#limits);
|
|
21434
21686
|
const restored = new _ProjectCodeSync({
|
|
21435
21687
|
...this.#options,
|
|
21436
21688
|
workspacePath: recoveryPath,
|
|
21437
|
-
stateRoot: path2.join(path2.dirname(this.#stateRoot), digest(recoveryPath))
|
|
21689
|
+
...this.#options.stateRoot ? { stateRoot: path2.join(path2.dirname(this.#options.stateRoot), digest(recoveryPath)) } : {},
|
|
21690
|
+
// The transport has private mutable Git state; recovery only seeds the
|
|
21691
|
+
// baseline and does not need another transport initialization here.
|
|
21692
|
+
...this.#options.storage ? { storage: {
|
|
21693
|
+
...this.#options.storage,
|
|
21694
|
+
key: this.#options.storage.key,
|
|
21695
|
+
limits: this.#limits,
|
|
21696
|
+
metadata: this.#options.storage.metadata,
|
|
21697
|
+
initialize: async () => {
|
|
21698
|
+
},
|
|
21699
|
+
status: () => this.#cloudStatus(),
|
|
21700
|
+
snapshot: (status) => this.#snapshot(status),
|
|
21701
|
+
checkpoint: (input2) => this.#options.storage.checkpoint(input2),
|
|
21702
|
+
update: () => this.#options.storage.update()
|
|
21703
|
+
} } : {}
|
|
21438
21704
|
});
|
|
21439
21705
|
await restored.initialize();
|
|
21440
21706
|
await restored.#locked(async () => {
|
|
@@ -21460,7 +21726,18 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21460
21726
|
await this.initialize();
|
|
21461
21727
|
return this.#locked(async () => {
|
|
21462
21728
|
const binding = await this.#readBinding();
|
|
21463
|
-
|
|
21729
|
+
if (!enabled) {
|
|
21730
|
+
binding.automatic_upload = false;
|
|
21731
|
+
await this.#save(binding);
|
|
21732
|
+
}
|
|
21733
|
+
let cloud;
|
|
21734
|
+
try {
|
|
21735
|
+
cloud = await this.#cloudStatus();
|
|
21736
|
+
} catch (error62) {
|
|
21737
|
+
if (enabled)
|
|
21738
|
+
throw error62;
|
|
21739
|
+
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 };
|
|
21740
|
+
}
|
|
21464
21741
|
if (enabled)
|
|
21465
21742
|
assertEnabled(cloud);
|
|
21466
21743
|
binding.automatic_upload = enabled;
|
|
@@ -21505,7 +21782,12 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21505
21782
|
return this.#upload(inventory.hashes);
|
|
21506
21783
|
}
|
|
21507
21784
|
async execute(kind, { busy = false, operationId } = {}) {
|
|
21508
|
-
if (
|
|
21785
|
+
if (kind.startsWith("github_") !== (this.#options.storage?.metadata.provider === "github")) {
|
|
21786
|
+
throw new CodeSyncError("code_sync_unsupported", "The requested action belongs to a different code provider.");
|
|
21787
|
+
}
|
|
21788
|
+
if (kind.startsWith("github_"))
|
|
21789
|
+
kind = kind.slice(7);
|
|
21790
|
+
if (busy && ["code_upload", "code_download", "code_recover", "code_auto_upload_enable", "code_update"].includes(kind)) {
|
|
21509
21791
|
throw new CodeSyncError("code_sync_busy", "An Agent is working in this workspace. Wait until it finishes before syncing code.");
|
|
21510
21792
|
}
|
|
21511
21793
|
switch (kind) {
|
|
@@ -21521,11 +21803,26 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21521
21803
|
return this.setAutomaticUpload(true);
|
|
21522
21804
|
case "code_auto_upload_disable":
|
|
21523
21805
|
return this.setAutomaticUpload(false);
|
|
21806
|
+
case "code_update": {
|
|
21807
|
+
await this.initialize();
|
|
21808
|
+
if (!this.#options.storage)
|
|
21809
|
+
throw new CodeSyncError("code_sync_unsupported", "Use the cloud review controls to update this branch.");
|
|
21810
|
+
return this.#locked(async () => {
|
|
21811
|
+
const binding = await this.#readBinding();
|
|
21812
|
+
const inventory = await this.#inventory();
|
|
21813
|
+
if (!sameHashes(binding.baseline, inventory.hashes)) {
|
|
21814
|
+
throw new CodeSyncError("code_sync_dirty", "Local source files have unuploaded changes. Upload them or recover the remote version before updating your GitHub branch.");
|
|
21815
|
+
}
|
|
21816
|
+
return this.#status(binding, await this.#options.storage.update(), inventory);
|
|
21817
|
+
});
|
|
21818
|
+
}
|
|
21524
21819
|
default:
|
|
21525
21820
|
throw new CodeSyncError("code_sync_unsupported", "Unsupported code sync action.");
|
|
21526
21821
|
}
|
|
21527
21822
|
}
|
|
21528
21823
|
async #cloudStatus() {
|
|
21824
|
+
if (this.#options.storage)
|
|
21825
|
+
return this.#options.storage.status();
|
|
21529
21826
|
const parsed = CodeStatusSchema.safeParse(await this.#request(""));
|
|
21530
21827
|
if (!parsed.success)
|
|
21531
21828
|
throw invalidResponse();
|
|
@@ -21534,6 +21831,8 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21534
21831
|
async #snapshot(cloud) {
|
|
21535
21832
|
if (!cloud.repository.main_commit && !cloud.own_branch_id)
|
|
21536
21833
|
throw new CodeSyncError("code_sync_empty", "No code has been uploaded yet.");
|
|
21834
|
+
if (this.#options.storage)
|
|
21835
|
+
return this.#options.storage.snapshot(cloud);
|
|
21537
21836
|
const parsed = CodeSnapshotResultSchema.safeParse(await this.#request(`/snapshot?branch_id=${encodeURIComponent(cloud.own_branch_id ?? "main")}`));
|
|
21538
21837
|
if (!parsed.success)
|
|
21539
21838
|
throw invalidResponse();
|
|
@@ -21542,6 +21841,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21542
21841
|
#status(binding, cloud, inventory) {
|
|
21543
21842
|
const cloudCommit = cloud.branches.find((branch) => branch.id === cloud.own_branch_id)?.head_commit ?? cloud.repository.main_commit;
|
|
21544
21843
|
return {
|
|
21844
|
+
...this.#options.storage?.metadata,
|
|
21545
21845
|
enabled: cloud.repository.enabled,
|
|
21546
21846
|
automatic_upload: binding.automatic_upload,
|
|
21547
21847
|
local_changes: changedCount(binding.baseline, inventory.hashes),
|
|
@@ -21609,7 +21909,7 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21609
21909
|
async #readBinding() {
|
|
21610
21910
|
const file2 = path2.join(this.#stateRoot, "binding.json");
|
|
21611
21911
|
const info = await lstat2(file2);
|
|
21612
|
-
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.size > MAX_BINDING_BYTES)
|
|
21912
|
+
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1 || info.size > MAX_BINDING_BYTES * (this.#options.storage ? 10 : 1))
|
|
21613
21913
|
throw new CodeSyncError("code_sync_binding", "Private code sync state is not a regular bounded file.");
|
|
21614
21914
|
let binding;
|
|
21615
21915
|
try {
|
|
@@ -21624,13 +21924,13 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21624
21924
|
}
|
|
21625
21925
|
if (binding.interrupted_download !== void 0 && typeof binding.interrupted_download !== "boolean")
|
|
21626
21926
|
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 >
|
|
21927
|
+
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
21928
|
throw new CodeSyncError("code_sync_binding", "Private code file-mode state is invalid.");
|
|
21629
21929
|
const baseline = Object.entries(binding.baseline);
|
|
21630
|
-
if (baseline.length >
|
|
21930
|
+
if (baseline.length > this.#limits.maxFiles)
|
|
21631
21931
|
throw new CodeSyncError("code_sync_binding", "Private code sync baseline exceeds the file limit.");
|
|
21632
|
-
for (const [name2,
|
|
21633
|
-
if (!isCodeSyncPathAllowed(name2) || typeof
|
|
21932
|
+
for (const [name2, hash3] of baseline) {
|
|
21933
|
+
if (!isCodeSyncPathAllowed(name2) || typeof hash3 !== "string" || !/^[a-f0-9]{64}$/u.test(hash3))
|
|
21634
21934
|
throw new CodeSyncError("code_sync_binding", "Private code sync baseline is invalid.");
|
|
21635
21935
|
}
|
|
21636
21936
|
return binding;
|
|
@@ -21663,19 +21963,23 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21663
21963
|
const nativeExecutables = /* @__PURE__ */ new Set();
|
|
21664
21964
|
let candidates;
|
|
21665
21965
|
let nativeRoot = "";
|
|
21966
|
+
const inventoryOutputLimit = this.#options.storage ? this.#limits.maxFiles * 2100 : 2 * 1024 * 1024;
|
|
21666
21967
|
try {
|
|
21667
21968
|
nativeRoot = (await git(["rev-parse", "--show-toplevel"], this.#root)).trim();
|
|
21668
21969
|
} catch {
|
|
21669
21970
|
}
|
|
21670
21971
|
if (nativeRoot && await realpath(nativeRoot) === this.#root) {
|
|
21671
|
-
const trackedEntries = (await git(["ls-files", "--stage", "-z"], this.#root)).split("\0").filter(Boolean);
|
|
21972
|
+
const trackedEntries = (await git(["ls-files", "--stage", "-z"], this.#root, inventoryOutputLimit)).split("\0").filter(Boolean);
|
|
21672
21973
|
tracked = new Set(trackedEntries.map((entry) => entry.slice(entry.indexOf(" ") + 1)));
|
|
21974
|
+
if (this.#options.storage?.metadata.provider === "github" && trackedEntries.some((entry) => !/^(?:100644|100755) /u.test(entry))) {
|
|
21975
|
+
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.");
|
|
21976
|
+
}
|
|
21673
21977
|
for (const entry of trackedEntries)
|
|
21674
21978
|
if (entry.startsWith("100755 "))
|
|
21675
21979
|
nativeExecutables.add(entry.slice(entry.indexOf(" ") + 1));
|
|
21676
|
-
candidates = [...tracked, ...(await git(["ls-files", "--others", "--exclude-standard", "-z"], this.#root)).split("\0").filter(Boolean)];
|
|
21980
|
+
candidates = [...tracked, ...(await git(["ls-files", "--others", "--exclude-standard", "-z"], this.#root, inventoryOutputLimit)).split("\0").filter(Boolean)];
|
|
21677
21981
|
} 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);
|
|
21982
|
+
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
21983
|
}
|
|
21680
21984
|
const binding = await this.#readBinding();
|
|
21681
21985
|
const executablePaths = executableHints ?? new Set(binding.executable_paths ?? []);
|
|
@@ -21688,6 +21992,9 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21688
21992
|
let totalBytes = 0;
|
|
21689
21993
|
for (const name2 of [...new Set(candidates)].sort()) {
|
|
21690
21994
|
if (!isCodeSyncPathAllowed(name2) || BUILD_PATH.test(name2)) {
|
|
21995
|
+
if (this.#options.storage?.metadata.provider === "github" && tracked.has(name2)) {
|
|
21996
|
+
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.");
|
|
21997
|
+
}
|
|
21691
21998
|
if (tracked.has(name2) && !BUILD_PATH.test(name2))
|
|
21692
21999
|
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
22000
|
excluded += 1;
|
|
@@ -21695,6 +22002,8 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21695
22002
|
}
|
|
21696
22003
|
const target = path2.join(this.#root, ...name2.split("/"));
|
|
21697
22004
|
if (!await safeAncestors(this.#root, name2)) {
|
|
22005
|
+
if (this.#options.storage?.metadata.provider === "github" && tracked.has(name2))
|
|
22006
|
+
throw new CodeSyncError("code_sync_unsupported", "A tracked path crosses a link or unsafe directory. Use native Git after reviewing it.");
|
|
21698
22007
|
excluded += 1;
|
|
21699
22008
|
continue;
|
|
21700
22009
|
}
|
|
@@ -21707,26 +22016,28 @@ var ProjectCodeSync = class _ProjectCodeSync {
|
|
|
21707
22016
|
throw error62;
|
|
21708
22017
|
}
|
|
21709
22018
|
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
|
|
22019
|
+
if (this.#options.storage?.metadata.provider === "github" && tracked.has(name2))
|
|
22020
|
+
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
22021
|
excluded += 1;
|
|
21711
22022
|
continue;
|
|
21712
22023
|
}
|
|
21713
|
-
if (info.size >
|
|
21714
|
-
throw new CodeSyncError("code_sync_limit", "A source file exceeds the
|
|
21715
|
-
const source = await readBoundedSource(target);
|
|
21716
|
-
const
|
|
21717
|
-
if (containsCodeSyncSecret(
|
|
22024
|
+
if (info.size > this.#limits.maxFileBytes)
|
|
22025
|
+
throw new CodeSyncError("code_sync_limit", "A source file exceeds the selected provider's file limit.");
|
|
22026
|
+
const source = await readBoundedSource(target, this.#limits.maxFileBytes);
|
|
22027
|
+
const bytes2 = source.bytes;
|
|
22028
|
+
if (containsCodeSyncSecret(bytes2.toString("utf8")))
|
|
21718
22029
|
throw new CodeSyncError("code_sync_secret", "A source file contains a recognizable credential or private key. Remove it before uploading.");
|
|
21719
|
-
totalBytes +=
|
|
21720
|
-
if (totalBytes >
|
|
21721
|
-
throw new CodeSyncError("code_sync_limit", "
|
|
21722
|
-
files.push({ path: name2, content_base64:
|
|
22030
|
+
totalBytes += bytes2.byteLength;
|
|
22031
|
+
if (totalBytes > this.#limits.maxSnapshotBytes || files.length >= this.#limits.maxFiles)
|
|
22032
|
+
throw new CodeSyncError("code_sync_limit", "Source files exceed the selected provider's checkpoint limit.");
|
|
22033
|
+
files.push({ path: name2, content_base64: bytes2.toString("base64"), executable: process.platform === "win32" ? executablePaths.has(name2) || nativeExecutables.has(name2) : source.executable });
|
|
21723
22034
|
}
|
|
21724
|
-
if (!
|
|
22035
|
+
if (!createCodeFilesSchema(this.#limits).safeParse(files).success)
|
|
21725
22036
|
throw new CodeSyncError("code_sync_unsafe_path", "Source files contain non-portable or conflicting paths.");
|
|
21726
22037
|
return { files, hashes: fileHashes(files), excluded };
|
|
21727
22038
|
}
|
|
21728
22039
|
async #locked(operation) {
|
|
21729
|
-
const lockPath = path2.join(this.#
|
|
22040
|
+
const lockPath = path2.join(this.#lockRoot, "operation.lock");
|
|
21730
22041
|
let handle;
|
|
21731
22042
|
try {
|
|
21732
22043
|
handle = await open3(lockPath, "wx", 384);
|
|
@@ -21798,14 +22109,14 @@ function isWithin(root, target) {
|
|
|
21798
22109
|
const relative = path2.relative(root, target);
|
|
21799
22110
|
return !relative || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
|
|
21800
22111
|
}
|
|
21801
|
-
async function git(args, cwd) {
|
|
22112
|
+
async function git(args, cwd, maxBuffer = 2 * 1024 * 1024) {
|
|
21802
22113
|
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_") && !key.startsWith("GATHERTHREAD_")));
|
|
21803
22114
|
try {
|
|
21804
22115
|
const result = await execFileAsync("git", ["-c", "core.fsmonitor=false", "-c", "core.hooksPath=/dev/null", ...args], {
|
|
21805
22116
|
cwd,
|
|
21806
22117
|
env: { ...env, GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null", GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" },
|
|
21807
22118
|
timeout: 15e3,
|
|
21808
|
-
maxBuffer
|
|
22119
|
+
maxBuffer,
|
|
21809
22120
|
encoding: "utf8",
|
|
21810
22121
|
windowsHide: true
|
|
21811
22122
|
});
|
|
@@ -21844,7 +22155,7 @@ async function safeAncestors(root, relative, create = false) {
|
|
|
21844
22155
|
}
|
|
21845
22156
|
return true;
|
|
21846
22157
|
}
|
|
21847
|
-
async function fileHash(root, name2, executableHint) {
|
|
22158
|
+
async function fileHash(root, name2, executableHint, maxBytes = CODE_SYNC_MAX_FILE_BYTES) {
|
|
21848
22159
|
if (!await safeAncestors(root, name2))
|
|
21849
22160
|
throw new CodeSyncError("code_sync_unsafe_path", "A destination directory is a symbolic link or not a directory.");
|
|
21850
22161
|
const target = path2.join(root, ...name2.split("/"));
|
|
@@ -21856,17 +22167,17 @@ async function fileHash(root, name2, executableHint) {
|
|
|
21856
22167
|
return;
|
|
21857
22168
|
throw error62;
|
|
21858
22169
|
}
|
|
21859
|
-
if (!info.isFile() || info.isSymbolicLink() || info.size >
|
|
22170
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes)
|
|
21860
22171
|
throw new CodeSyncError("code_sync_unsafe_path", "A destination is not a bounded regular source file.");
|
|
21861
|
-
const source = await readBoundedSource(target);
|
|
22172
|
+
const source = await readBoundedSource(target, maxBytes);
|
|
21862
22173
|
const executable = process.platform === "win32" && executableHint !== void 0 ? executableHint : source.executable;
|
|
21863
22174
|
return digest(`${executable ? "1" : "0"}\0${source.bytes.toString("base64")}`);
|
|
21864
22175
|
}
|
|
21865
|
-
async function readBoundedSource(filename) {
|
|
22176
|
+
async function readBoundedSource(filename, maxBytes = CODE_SYNC_MAX_FILE_BYTES) {
|
|
21866
22177
|
const handle = await open3(filename, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
21867
22178
|
try {
|
|
21868
22179
|
const before = await handle.stat();
|
|
21869
|
-
if (!before.isFile() || before.nlink !== 1 || before.size >
|
|
22180
|
+
if (!before.isFile() || before.nlink !== 1 || before.size > maxBytes)
|
|
21870
22181
|
throw new CodeSyncError("code_sync_unsafe_path", "A source path is not a bounded regular file.");
|
|
21871
22182
|
const buffer = Buffer.alloc(before.size + 1);
|
|
21872
22183
|
const result = await handle.read(buffer, 0, buffer.length, 0);
|
|
@@ -21878,18 +22189,18 @@ async function readBoundedSource(filename) {
|
|
|
21878
22189
|
await handle.close();
|
|
21879
22190
|
}
|
|
21880
22191
|
}
|
|
21881
|
-
async function applySnapshot(root, files, baseline, options) {
|
|
21882
|
-
if (!
|
|
22192
|
+
async function applySnapshot(root, files, baseline, options, limits = CLOUD_CODE_SYNC_LIMITS) {
|
|
22193
|
+
if (!createCodeFilesSchema(limits).safeParse(files).success)
|
|
21883
22194
|
throw new CodeSyncError("code_sync_unsafe_path", "Cloud code contains unsafe paths.");
|
|
21884
22195
|
const names = /* @__PURE__ */ new Set([...Object.keys(baseline), ...files.map((file2) => file2.path)]);
|
|
21885
22196
|
for (const name2 of names) {
|
|
21886
|
-
if (await fileHash(root, name2, options?.baselineExecutables?.has(name2)) !== baseline[name2])
|
|
22197
|
+
if (await fileHash(root, name2, options?.baselineExecutables?.has(name2), limits.maxFileBytes) !== baseline[name2])
|
|
21887
22198
|
throw new CodeSyncError("code_sync_dirty", "Cloud code would overwrite local or ignored files. Recover into a new folder instead.");
|
|
21888
22199
|
}
|
|
21889
22200
|
if (options) {
|
|
21890
22201
|
await mkdir2(options.backupRoot, { recursive: true, mode: 448 });
|
|
21891
22202
|
for (const name2 of Object.keys(baseline)) {
|
|
21892
|
-
const source = await readBoundedSource(path2.join(root, ...name2.split("/")));
|
|
22203
|
+
const source = await readBoundedSource(path2.join(root, ...name2.split("/")), limits.maxFileBytes);
|
|
21893
22204
|
if (process.platform === "win32" && options.baselineExecutables)
|
|
21894
22205
|
source.executable = options.baselineExecutables.has(name2);
|
|
21895
22206
|
if (digest(`${source.executable ? "1" : "0"}\0${source.bytes.toString("base64")}`) !== baseline[name2])
|
|
@@ -21908,7 +22219,7 @@ async function applySnapshot(root, files, baseline, options) {
|
|
|
21908
22219
|
}
|
|
21909
22220
|
const targetNames = new Set(files.map((file2) => file2.path));
|
|
21910
22221
|
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])
|
|
22222
|
+
if (!await safeAncestors(root, file2.path, true) || await fileHash(root, file2.path, options?.baselineExecutables?.has(file2.path), limits.maxFileBytes) !== baseline[file2.path])
|
|
21912
22223
|
throw new CodeSyncError("code_sync_busy", "Local files changed during download. No further files were applied.");
|
|
21913
22224
|
const filename = path2.join(root, ...file2.path.split("/"));
|
|
21914
22225
|
const temporary = path2.join(path2.dirname(filename), `.gatherthread-${randomUUID()}.tmp`);
|
|
@@ -21919,7 +22230,7 @@ async function applySnapshot(root, files, baseline, options) {
|
|
|
21919
22230
|
} finally {
|
|
21920
22231
|
await handle.close();
|
|
21921
22232
|
}
|
|
21922
|
-
if (!await safeAncestors(root, file2.path) || await fileHash(root, file2.path, options?.baselineExecutables?.has(file2.path)) !== baseline[file2.path]) {
|
|
22233
|
+
if (!await safeAncestors(root, file2.path) || await fileHash(root, file2.path, options?.baselineExecutables?.has(file2.path), limits.maxFileBytes) !== baseline[file2.path]) {
|
|
21923
22234
|
await unlink(temporary);
|
|
21924
22235
|
throw new CodeSyncError("code_sync_busy", "Source files changed while preparing a download. Original files were backed up; no further files were applied.");
|
|
21925
22236
|
}
|
|
@@ -21927,55 +22238,442 @@ async function applySnapshot(root, files, baseline, options) {
|
|
|
21927
22238
|
}
|
|
21928
22239
|
for (const name2 of Object.keys(baseline)) {
|
|
21929
22240
|
if (!targetNames.has(name2)) {
|
|
21930
|
-
if (await fileHash(root, name2, options?.baselineExecutables?.has(name2)) !== baseline[name2])
|
|
22241
|
+
if (await fileHash(root, name2, options?.baselineExecutables?.has(name2), limits.maxFileBytes) !== baseline[name2])
|
|
21931
22242
|
throw new CodeSyncError("code_sync_busy", "Local files changed during download. No further files were applied.");
|
|
21932
22243
|
await unlink(path2.join(root, ...name2.split("/")));
|
|
21933
22244
|
}
|
|
21934
22245
|
}
|
|
21935
22246
|
}
|
|
21936
22247
|
|
|
22248
|
+
// ../bridge/dist/src/github-code-sync.js
|
|
22249
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
22250
|
+
import { spawn } from "node:child_process";
|
|
22251
|
+
import { constants as constants2 } from "node:fs";
|
|
22252
|
+
import { lstat as lstat3, mkdir as mkdir3, open as open4, unlink as unlink2 } from "node:fs/promises";
|
|
22253
|
+
import path3 from "node:path";
|
|
22254
|
+
var hash2 = (text) => createHash3("sha256").update(text).digest("hex");
|
|
22255
|
+
var Commit2 = /^[a-f0-9]{40}$/u;
|
|
22256
|
+
var filesSchema = createCodeFilesSchema(GITHUB_CODE_SYNC_LIMITS);
|
|
22257
|
+
var generatedPath = /(?:^|\/)(?:node_modules|dist|build|coverage|\.next|\.cache|\.turbo|\.venv|venv|__pycache__)(?:\/|$)/u;
|
|
22258
|
+
function createGitHubCodeSync(options, runner = runGitHubGit) {
|
|
22259
|
+
return new ProjectCodeSync({ ...options, storage: new GitHubCodeStorage(options, runner) });
|
|
22260
|
+
}
|
|
22261
|
+
var GitHubCodeStorage = class {
|
|
22262
|
+
limits = GITHUB_CODE_SYNC_LIMITS;
|
|
22263
|
+
key;
|
|
22264
|
+
metadata;
|
|
22265
|
+
#options;
|
|
22266
|
+
#run;
|
|
22267
|
+
#remote;
|
|
22268
|
+
#branchId;
|
|
22269
|
+
#directory = "";
|
|
22270
|
+
#revision;
|
|
22271
|
+
constructor(options, runner) {
|
|
22272
|
+
if (!GitHubConnectionSchema.safeParse({ repository: options.repository, base_branch: options.baseBranch, revision: options.revision, enabled: true }).success) {
|
|
22273
|
+
throw new CodeSyncError("code_sync_binding", "GitHub requires an owner/repository and a valid base branch.");
|
|
22274
|
+
}
|
|
22275
|
+
const branch = `gatherthread/${hash2(options.projectId).slice(0, 24)}/${hash2(options.actorId).slice(0, 24)}`;
|
|
22276
|
+
if (options.baseBranch.startsWith("gatherthread/"))
|
|
22277
|
+
throw new CodeSyncError("code_sync_binding", "Choose a base branch outside GatherThread personal branches.");
|
|
22278
|
+
this.#options = options;
|
|
22279
|
+
this.#run = runner;
|
|
22280
|
+
this.#revision = options.revision;
|
|
22281
|
+
this.#remote = `https://github.com/${options.repository}.git`;
|
|
22282
|
+
this.#branchId = `branch-${hash2(branch).slice(0, 24)}`;
|
|
22283
|
+
this.key = JSON.stringify(["github", options.apiUrl.replace(/\/$/u, "").replace(/\/v1$/u, ""), options.projectId, options.actorId, options.repository.toLowerCase(), options.baseBranch, options.revision]);
|
|
22284
|
+
this.metadata = { provider: "github", repository: options.repository, branch, base_branch: options.baseBranch };
|
|
22285
|
+
}
|
|
22286
|
+
async initialize(privateDirectory) {
|
|
22287
|
+
this.#directory = path3.join(privateDirectory, "github.git");
|
|
22288
|
+
try {
|
|
22289
|
+
if ((await lstat3(this.#directory)).isSymbolicLink())
|
|
22290
|
+
throw new CodeSyncError("code_sync_binding", "Private GitHub state cannot be a symbolic link.");
|
|
22291
|
+
} catch (error62) {
|
|
22292
|
+
if (error62.code !== "ENOENT")
|
|
22293
|
+
throw error62;
|
|
22294
|
+
}
|
|
22295
|
+
await mkdir3(this.#directory, { recursive: true, mode: 448 });
|
|
22296
|
+
await this.#git(["init", "--bare", "--quiet", "."]);
|
|
22297
|
+
const info = path3.join(this.#directory, "info");
|
|
22298
|
+
await mkdir3(info, { recursive: true, mode: 448 });
|
|
22299
|
+
if ((await lstat3(info)).isSymbolicLink())
|
|
22300
|
+
throw new CodeSyncError("code_sync_binding", "Private GitHub attributes directory is unsafe.");
|
|
22301
|
+
const attributes = await open4(path3.join(info, "attributes"), constants2.O_WRONLY | constants2.O_CREAT | (constants2.O_NOFOLLOW ?? 0), 384);
|
|
22302
|
+
try {
|
|
22303
|
+
if (!(await attributes.stat()).isFile() || (await attributes.stat()).nlink !== 1)
|
|
22304
|
+
throw new CodeSyncError("code_sync_binding", "Private GitHub attributes must be a regular file.");
|
|
22305
|
+
await attributes.truncate(0);
|
|
22306
|
+
await attributes.writeFile("* conflict-marker-size=7\n");
|
|
22307
|
+
} finally {
|
|
22308
|
+
await attributes.close();
|
|
22309
|
+
}
|
|
22310
|
+
}
|
|
22311
|
+
async #authorize() {
|
|
22312
|
+
const url2 = new URL(this.#options.apiUrl);
|
|
22313
|
+
url2.pathname = url2.pathname.replace(/\/$/u, "").replace(/\/v1$/u, "") + `/v1/projects/${encodeURIComponent(this.#options.projectId)}/github`;
|
|
22314
|
+
let response;
|
|
22315
|
+
try {
|
|
22316
|
+
response = await (this.#options.fetch ?? fetch)(url2.toString(), {
|
|
22317
|
+
headers: { authorization: `Bearer ${this.#options.token}` },
|
|
22318
|
+
redirect: "error",
|
|
22319
|
+
signal: AbortSignal.timeout(15e3)
|
|
22320
|
+
});
|
|
22321
|
+
} catch {
|
|
22322
|
+
throw new CodeSyncError("code_sync_unavailable", "GatherThread could not verify current GitHub project access.");
|
|
22323
|
+
}
|
|
22324
|
+
if (!response.ok) {
|
|
22325
|
+
await response.body?.cancel();
|
|
22326
|
+
throw new CodeSyncError("code_sync_forbidden", "Current GatherThread project access is required for GitHub synchronization.");
|
|
22327
|
+
}
|
|
22328
|
+
if (!response.body)
|
|
22329
|
+
throw new CodeSyncError("code_sync_unavailable", "Invalid GitHub configuration response.");
|
|
22330
|
+
const reader = response.body.getReader();
|
|
22331
|
+
const chunks = [];
|
|
22332
|
+
let size = 0;
|
|
22333
|
+
for (; ; ) {
|
|
22334
|
+
const part = await reader.read();
|
|
22335
|
+
if (part.done)
|
|
22336
|
+
break;
|
|
22337
|
+
size += part.value.length;
|
|
22338
|
+
if (size > 16384) {
|
|
22339
|
+
await reader.cancel();
|
|
22340
|
+
throw new CodeSyncError("code_sync_limit", "GitHub configuration response is too large.");
|
|
22341
|
+
}
|
|
22342
|
+
chunks.push(part.value);
|
|
22343
|
+
}
|
|
22344
|
+
let data;
|
|
22345
|
+
try {
|
|
22346
|
+
data = GitHubProjectStatusSchema.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")).data);
|
|
22347
|
+
} catch {
|
|
22348
|
+
throw new CodeSyncError("code_sync_unavailable", "Invalid GitHub configuration response.");
|
|
22349
|
+
}
|
|
22350
|
+
const connection = data.connection;
|
|
22351
|
+
if (!data.can_write || data.branch !== this.metadata.branch || !connection || connection.repository.toLowerCase() !== this.#options.repository.toLowerCase() || connection.base_branch !== this.#options.baseBranch) {
|
|
22352
|
+
throw new CodeSyncError("code_sync_binding", "GitHub target or project permissions changed. Review the connection and authorize it locally again.");
|
|
22353
|
+
}
|
|
22354
|
+
if (!connection.enabled)
|
|
22355
|
+
return false;
|
|
22356
|
+
if (this.#revision !== connection.revision)
|
|
22357
|
+
throw new CodeSyncError("code_sync_binding", "GitHub configuration changed. Reconnect and authorize the current target locally again.");
|
|
22358
|
+
return true;
|
|
22359
|
+
}
|
|
22360
|
+
async #enabled() {
|
|
22361
|
+
if (!await this.#authorize())
|
|
22362
|
+
throw new CodeSyncError("code_sync_disabled", "GitHub synchronization is paused for this project.");
|
|
22363
|
+
}
|
|
22364
|
+
async status() {
|
|
22365
|
+
if (!await this.#authorize())
|
|
22366
|
+
return { repository: { enabled: false, main_commit: null }, branches: [], own_branch_id: null };
|
|
22367
|
+
const refs = (await this.#git(["ls-remote", "--refs", this.#remote, `refs/heads/${this.#options.baseBranch}`, `refs/heads/${this.metadata.branch}`])).toString("utf8");
|
|
22368
|
+
let main = null;
|
|
22369
|
+
let own2 = null;
|
|
22370
|
+
for (const line of refs.trim().split("\n")) {
|
|
22371
|
+
const [oid, ref] = line.split(" ");
|
|
22372
|
+
if (!oid || !Commit2.test(oid))
|
|
22373
|
+
continue;
|
|
22374
|
+
if (ref === `refs/heads/${this.#options.baseBranch}`)
|
|
22375
|
+
main = oid;
|
|
22376
|
+
if (ref === `refs/heads/${this.metadata.branch}`)
|
|
22377
|
+
own2 = oid;
|
|
22378
|
+
}
|
|
22379
|
+
return {
|
|
22380
|
+
repository: { enabled: true, main_commit: main },
|
|
22381
|
+
own_branch_id: own2 ? this.#branchId : null,
|
|
22382
|
+
branches: own2 ? [{ id: this.#branchId, name: "My GitHub branch", user_id: this.#options.actorId, head_commit: own2, review_status: "draft" }] : []
|
|
22383
|
+
};
|
|
22384
|
+
}
|
|
22385
|
+
#head(status) {
|
|
22386
|
+
return status.branches[0]?.head_commit ?? status.repository.main_commit;
|
|
22387
|
+
}
|
|
22388
|
+
async #fetch(status) {
|
|
22389
|
+
await this.#enabled();
|
|
22390
|
+
const refs = [];
|
|
22391
|
+
if (status.repository.main_commit)
|
|
22392
|
+
refs.push(`+refs/heads/${this.#options.baseBranch}:refs/gt/base`);
|
|
22393
|
+
if (status.own_branch_id)
|
|
22394
|
+
refs.push(`+refs/heads/${this.metadata.branch}:refs/gt/own`);
|
|
22395
|
+
if (refs.length)
|
|
22396
|
+
await this.#git(["fetch", "--quiet", "--no-tags", "--no-recurse-submodules", this.#remote, ...refs]);
|
|
22397
|
+
for (const [ref, expected] of [["refs/gt/base", status.repository.main_commit], ["refs/gt/own", status.branches[0]?.head_commit]]) {
|
|
22398
|
+
if (expected && (await this.#git(["rev-parse", "--verify", ref])).toString().trim() !== expected)
|
|
22399
|
+
throw conflict();
|
|
22400
|
+
}
|
|
22401
|
+
}
|
|
22402
|
+
async snapshot(status) {
|
|
22403
|
+
const commit = this.#head(status);
|
|
22404
|
+
if (!commit)
|
|
22405
|
+
throw new CodeSyncError("code_sync_empty", "No GitHub files have been uploaded yet.");
|
|
22406
|
+
await this.#fetch(status);
|
|
22407
|
+
return { commit, files: await this.#files(commit) };
|
|
22408
|
+
}
|
|
22409
|
+
async #files(commit) {
|
|
22410
|
+
if (!Commit2.test(commit))
|
|
22411
|
+
throw conflict();
|
|
22412
|
+
const listing = await this.#git(["ls-tree", "-r", "-z", "-l", commit]);
|
|
22413
|
+
if (!Buffer.from(listing.toString("utf8"), "utf8").equals(listing))
|
|
22414
|
+
throw new CodeSyncError("code_sync_unsafe_path", "The GitHub tree contains non-UTF-8 filenames. Use native Git for this repository.");
|
|
22415
|
+
const entries = listing.toString("utf8").split("\0").filter(Boolean).map((entry) => {
|
|
22416
|
+
const match = /^(100644|100755) blob ([a-f0-9]{40})\s+(\d+)\t(.+)$/u.exec(entry);
|
|
22417
|
+
if (!match)
|
|
22418
|
+
throw new CodeSyncError("code_sync_unsupported", "This GitHub tree contains unsupported links, submodules or paths. Use native Git for this repository.");
|
|
22419
|
+
return { mode: match[1], oid: match[2], size: Number(match[3]), path: match[4] };
|
|
22420
|
+
});
|
|
22421
|
+
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)
|
|
22422
|
+
throw new CodeSyncError("code_sync_limit", "GitHub source exceeds the supported checkpoint limit. Use native Git for larger trees.");
|
|
22423
|
+
const output2 = entries.length ? await this.#git(["cat-file", "--batch"], Buffer.from(entries.map((entry) => entry.oid).join("\n") + "\n")) : Buffer.alloc(0);
|
|
22424
|
+
let offset = 0;
|
|
22425
|
+
const files = [];
|
|
22426
|
+
for (const entry of entries) {
|
|
22427
|
+
const end = output2.indexOf(10, offset);
|
|
22428
|
+
if (end < 0 || output2.subarray(offset, end).toString() !== `${entry.oid} blob ${entry.size}`)
|
|
22429
|
+
throw conflict();
|
|
22430
|
+
const content = output2.subarray(end + 1, end + 1 + entry.size);
|
|
22431
|
+
offset = end + 2 + entry.size;
|
|
22432
|
+
if (content.length !== entry.size || output2[offset - 1] !== 10)
|
|
22433
|
+
throw conflict();
|
|
22434
|
+
if (generatedPath.test(entry.path) || containsCodeSyncSecret(content.toString("utf8")) || content.subarray(0, 80).toString().startsWith("version https://git-lfs.github.com/spec/v1")) {
|
|
22435
|
+
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.");
|
|
22436
|
+
}
|
|
22437
|
+
files.push({ path: entry.path, content_base64: content.toString("base64"), executable: entry.mode === "100755" });
|
|
22438
|
+
}
|
|
22439
|
+
if (!filesSchema.safeParse(files).success)
|
|
22440
|
+
throw new CodeSyncError("code_sync_unsafe_path", "The GitHub tree contains non-portable or private paths. Nothing was applied.");
|
|
22441
|
+
return files;
|
|
22442
|
+
}
|
|
22443
|
+
async #tree(files) {
|
|
22444
|
+
if (!filesSchema.safeParse(files).success)
|
|
22445
|
+
throw new CodeSyncError("code_sync_limit", "Source tree exceeds GitHub checkpoint limits.");
|
|
22446
|
+
const index = path3.join(this.#directory, `index-${randomUUID2()}`);
|
|
22447
|
+
try {
|
|
22448
|
+
await this.#git(["read-tree", "--empty"], void 0, index);
|
|
22449
|
+
const records = [];
|
|
22450
|
+
for (const file2 of files) {
|
|
22451
|
+
const bytes2 = Buffer.from(file2.content_base64, "base64");
|
|
22452
|
+
if (containsCodeSyncSecret(bytes2.toString("utf8")))
|
|
22453
|
+
throw new CodeSyncError("code_sync_secret", "A recognizable credential was detected in source.");
|
|
22454
|
+
if (generatedPath.test(file2.path) || bytes2.subarray(0, 80).toString().startsWith("version https://git-lfs.github.com/spec/v1"))
|
|
22455
|
+
throw new CodeSyncError("code_sync_unsupported", "Use native Git for Git LFS or generated-file repositories.");
|
|
22456
|
+
const oid = (await this.#git(["hash-object", "-w", "--stdin", "--no-filters"], bytes2)).toString().trim();
|
|
22457
|
+
if (!Commit2.test(oid))
|
|
22458
|
+
throw conflict();
|
|
22459
|
+
records.push(`${file2.executable ? "100755" : "100644"} ${oid} ${file2.path}\0`);
|
|
22460
|
+
}
|
|
22461
|
+
await this.#git(["update-index", "-z", "--index-info"], Buffer.from(records.join("")), index);
|
|
22462
|
+
return (await this.#git(["write-tree"], void 0, index)).toString().trim();
|
|
22463
|
+
} finally {
|
|
22464
|
+
await unlink2(index).catch(() => {
|
|
22465
|
+
});
|
|
22466
|
+
}
|
|
22467
|
+
}
|
|
22468
|
+
async checkpoint(input2) {
|
|
22469
|
+
const status = await this.status();
|
|
22470
|
+
await this.#enabled();
|
|
22471
|
+
const current = this.#head(status);
|
|
22472
|
+
await this.#fetch(status);
|
|
22473
|
+
if (current)
|
|
22474
|
+
await this.#files(current);
|
|
22475
|
+
const tree = await this.#tree(input2.files);
|
|
22476
|
+
if (current && (await this.#git(["rev-parse", `${current}^{tree}`])).toString().trim() === tree)
|
|
22477
|
+
return { status, commit: current };
|
|
22478
|
+
if (input2.base_commit !== current)
|
|
22479
|
+
throw conflict();
|
|
22480
|
+
const commit = (await this.#git(["commit-tree", tree, ...current ? ["-p", current] : [], "-m", "GatherThread workspace checkpoint"])).toString().trim();
|
|
22481
|
+
await this.#push(commit, status);
|
|
22482
|
+
const next = await this.status();
|
|
22483
|
+
if (this.#head(next) !== commit)
|
|
22484
|
+
throw conflict();
|
|
22485
|
+
return { status: next, commit };
|
|
22486
|
+
}
|
|
22487
|
+
async #push(commit, expected) {
|
|
22488
|
+
await this.#enabled();
|
|
22489
|
+
const latest = await this.status();
|
|
22490
|
+
if (this.#head(latest) !== this.#head(expected) || latest.own_branch_id !== expected.own_branch_id)
|
|
22491
|
+
throw conflict();
|
|
22492
|
+
await this.#git(["push", "--porcelain", "--no-verify", this.#remote, `${commit}:refs/heads/${this.metadata.branch}`]);
|
|
22493
|
+
}
|
|
22494
|
+
async update() {
|
|
22495
|
+
const status = await this.status();
|
|
22496
|
+
await this.#enabled();
|
|
22497
|
+
if (!status.own_branch_id || !status.repository.main_commit)
|
|
22498
|
+
return status;
|
|
22499
|
+
await this.#fetch(status);
|
|
22500
|
+
const own2 = this.#head(status);
|
|
22501
|
+
const base = status.repository.main_commit;
|
|
22502
|
+
await this.#files(own2);
|
|
22503
|
+
await this.#files(base);
|
|
22504
|
+
if (own2 === base)
|
|
22505
|
+
return status;
|
|
22506
|
+
try {
|
|
22507
|
+
await this.#git(["merge-base", "--is-ancestor", base, own2]);
|
|
22508
|
+
return status;
|
|
22509
|
+
} catch {
|
|
22510
|
+
}
|
|
22511
|
+
let tree;
|
|
22512
|
+
try {
|
|
22513
|
+
tree = (await this.#git(["merge-tree", "--write-tree", own2, base])).toString().split("\n")[0].trim();
|
|
22514
|
+
} catch {
|
|
22515
|
+
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.");
|
|
22516
|
+
}
|
|
22517
|
+
if (!Commit2.test(tree))
|
|
22518
|
+
throw conflict();
|
|
22519
|
+
await this.#files(tree);
|
|
22520
|
+
const commit = (await this.#git(["commit-tree", tree, "-p", own2, "-p", base, "-m", "Merge shared GitHub base into my GatherThread branch"])).toString().trim();
|
|
22521
|
+
await this.#push(commit, status);
|
|
22522
|
+
return this.status();
|
|
22523
|
+
}
|
|
22524
|
+
#git(args, input2, index) {
|
|
22525
|
+
return this.#run(args, { cwd: this.#directory, ...input2 ? { input: input2 } : {}, ...index ? { index } : {} });
|
|
22526
|
+
}
|
|
22527
|
+
};
|
|
22528
|
+
function conflict() {
|
|
22529
|
+
return new CodeSyncError("code_sync_conflict", "GitHub progress changed. Refresh, then download or recover before uploading again.");
|
|
22530
|
+
}
|
|
22531
|
+
async function runGitHubGit(args, options) {
|
|
22532
|
+
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)));
|
|
22533
|
+
return new Promise((resolve, reject) => {
|
|
22534
|
+
const child = spawn("git", [
|
|
22535
|
+
"-c",
|
|
22536
|
+
"core.hooksPath=/dev/null",
|
|
22537
|
+
"-c",
|
|
22538
|
+
"core.fsmonitor=false",
|
|
22539
|
+
"-c",
|
|
22540
|
+
"commit.gpgSign=false",
|
|
22541
|
+
"-c",
|
|
22542
|
+
"protocol.allow=never",
|
|
22543
|
+
"-c",
|
|
22544
|
+
"protocol.https.allow=always",
|
|
22545
|
+
"-c",
|
|
22546
|
+
"http.followRedirects=false",
|
|
22547
|
+
"-c",
|
|
22548
|
+
"credential.helper=",
|
|
22549
|
+
"-c",
|
|
22550
|
+
"credential.helper=!gh auth git-credential",
|
|
22551
|
+
"-c",
|
|
22552
|
+
"credential.interactive=false",
|
|
22553
|
+
"-c",
|
|
22554
|
+
"user.name=GatherThread",
|
|
22555
|
+
"-c",
|
|
22556
|
+
"user.email=checkpoint@users.noreply.github.com",
|
|
22557
|
+
...args
|
|
22558
|
+
], {
|
|
22559
|
+
cwd: options.cwd,
|
|
22560
|
+
windowsHide: true,
|
|
22561
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
22562
|
+
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 } : {} }
|
|
22563
|
+
});
|
|
22564
|
+
const chunks = [];
|
|
22565
|
+
let size = 0;
|
|
22566
|
+
let failed = false;
|
|
22567
|
+
const fail = () => {
|
|
22568
|
+
if (failed)
|
|
22569
|
+
return;
|
|
22570
|
+
failed = true;
|
|
22571
|
+
child.kill();
|
|
22572
|
+
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."));
|
|
22573
|
+
};
|
|
22574
|
+
const timer = setTimeout(fail, 12e4);
|
|
22575
|
+
timer.unref();
|
|
22576
|
+
child.stdout.on("data", (chunk) => {
|
|
22577
|
+
size += chunk.length;
|
|
22578
|
+
if (size > GITHUB_CODE_SYNC_LIMITS.maxSnapshotBytes + 8 * 1024 * 1024)
|
|
22579
|
+
fail();
|
|
22580
|
+
else
|
|
22581
|
+
chunks.push(chunk);
|
|
22582
|
+
});
|
|
22583
|
+
child.stderr.resume();
|
|
22584
|
+
child.stdin.on("error", () => {
|
|
22585
|
+
});
|
|
22586
|
+
child.on("error", fail);
|
|
22587
|
+
child.on("close", (code) => {
|
|
22588
|
+
clearTimeout(timer);
|
|
22589
|
+
if (code !== 0)
|
|
22590
|
+
fail();
|
|
22591
|
+
else if (!failed)
|
|
22592
|
+
resolve(Buffer.concat(chunks));
|
|
22593
|
+
});
|
|
22594
|
+
child.stdin.end(options.input);
|
|
22595
|
+
});
|
|
22596
|
+
}
|
|
22597
|
+
|
|
22598
|
+
// ../bridge/dist/src/github-auth.js
|
|
22599
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
22600
|
+
async function connectGitHubAccount(command = runGitHubAuthCommand) {
|
|
22601
|
+
if (await command(["auth", "status", "--hostname", "github.com"], 1e4))
|
|
22602
|
+
return;
|
|
22603
|
+
const signedIn = await command(["auth", "login", "--hostname", "github.com", "--git-protocol", "https", "--web", "--clipboard"], 24e4);
|
|
22604
|
+
if (!signedIn || !await command(["auth", "status", "--hostname", "github.com"], 1e4)) {
|
|
22605
|
+
throw new CodeSyncError("github_auth_failed", "GitHub sign-in was not completed on this device. Reopen the connection and finish in your browser.");
|
|
22606
|
+
}
|
|
22607
|
+
}
|
|
22608
|
+
function runGitHubAuthCommand(args, timeoutMs) {
|
|
22609
|
+
return new Promise((resolve, reject) => {
|
|
22610
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GH_HOST", "GH_PROMPT_DISABLED"].includes(key)));
|
|
22611
|
+
const child = spawn2("gh", [...args], { env, windowsHide: true, stdio: ["ignore", "ignore", "ignore"] });
|
|
22612
|
+
let settled = false;
|
|
22613
|
+
const finish = (value) => {
|
|
22614
|
+
if (settled)
|
|
22615
|
+
return;
|
|
22616
|
+
settled = true;
|
|
22617
|
+
clearTimeout(timer);
|
|
22618
|
+
resolve(value);
|
|
22619
|
+
};
|
|
22620
|
+
const timer = setTimeout(() => {
|
|
22621
|
+
child.kill();
|
|
22622
|
+
finish(false);
|
|
22623
|
+
}, timeoutMs);
|
|
22624
|
+
child.on("error", (error62) => {
|
|
22625
|
+
if (settled)
|
|
22626
|
+
return;
|
|
22627
|
+
settled = true;
|
|
22628
|
+
clearTimeout(timer);
|
|
22629
|
+
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."));
|
|
22630
|
+
});
|
|
22631
|
+
child.on("close", (code) => finish(code === 0));
|
|
22632
|
+
});
|
|
22633
|
+
}
|
|
22634
|
+
|
|
21937
22635
|
// dist/src/native-plugin.js
|
|
21938
|
-
import { createHash as
|
|
21939
|
-
import
|
|
22636
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
22637
|
+
import path10 from "node:path";
|
|
21940
22638
|
|
|
21941
22639
|
// dist/src/code-sync-controller.js
|
|
21942
|
-
import { constants as
|
|
21943
|
-
import { lstat as
|
|
21944
|
-
import { randomUUID as
|
|
21945
|
-
import
|
|
22640
|
+
import { constants as constants3 } from "node:fs";
|
|
22641
|
+
import { lstat as lstat5, mkdir as mkdir4, open as open5, rename as rename2, unlink as unlink3 } from "node:fs/promises";
|
|
22642
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
22643
|
+
import path5 from "node:path";
|
|
21946
22644
|
|
|
21947
22645
|
// dist/src/path-security.js
|
|
21948
|
-
import { lstat as
|
|
21949
|
-
import
|
|
22646
|
+
import { lstat as lstat4, realpath as realpath2 } from "node:fs/promises";
|
|
22647
|
+
import path4 from "node:path";
|
|
21950
22648
|
async function prospectiveRealPath(candidate) {
|
|
21951
|
-
let current =
|
|
22649
|
+
let current = path4.resolve(candidate);
|
|
21952
22650
|
const missing = [];
|
|
21953
22651
|
while (true) {
|
|
21954
22652
|
try {
|
|
21955
|
-
return
|
|
22653
|
+
return path4.join(await realpath2(current), ...missing);
|
|
21956
22654
|
} catch (error62) {
|
|
21957
22655
|
if (!isNodeError2(error62) || error62.code !== "ENOENT")
|
|
21958
22656
|
throw error62;
|
|
21959
|
-
const parent =
|
|
22657
|
+
const parent = path4.dirname(current);
|
|
21960
22658
|
if (parent === current)
|
|
21961
22659
|
throw new Error(`No existing ancestor for path: ${candidate}`);
|
|
21962
|
-
missing.unshift(
|
|
22660
|
+
missing.unshift(path4.basename(current));
|
|
21963
22661
|
current = parent;
|
|
21964
22662
|
}
|
|
21965
22663
|
}
|
|
21966
22664
|
}
|
|
21967
22665
|
async function secureDirectoryChain(directory) {
|
|
21968
|
-
const absolute =
|
|
21969
|
-
const root =
|
|
21970
|
-
const relative =
|
|
21971
|
-
const components = relative ? relative.split(
|
|
22666
|
+
const absolute = path4.resolve(directory);
|
|
22667
|
+
const root = path4.parse(absolute).root;
|
|
22668
|
+
const relative = path4.relative(root, absolute);
|
|
22669
|
+
const components = relative ? relative.split(path4.sep) : [];
|
|
21972
22670
|
const identities = [];
|
|
21973
22671
|
let current = root;
|
|
21974
22672
|
for (const component of components) {
|
|
21975
|
-
current =
|
|
22673
|
+
current = path4.join(current, component);
|
|
21976
22674
|
let metadata;
|
|
21977
22675
|
try {
|
|
21978
|
-
metadata = await
|
|
22676
|
+
metadata = await lstat4(current);
|
|
21979
22677
|
} catch (error62) {
|
|
21980
22678
|
if (isNodeError2(error62) && error62.code === "ENOENT")
|
|
21981
22679
|
break;
|
|
@@ -22012,6 +22710,7 @@ var ACTIONS = /* @__PURE__ */ new Set([
|
|
|
22012
22710
|
"code_auto_upload_enable",
|
|
22013
22711
|
"code_auto_upload_disable"
|
|
22014
22712
|
]);
|
|
22713
|
+
var GITHUB_CODE_ACTIONS = githubCodeSyncRequestKinds.filter((kind) => kind !== "github_auth_connect");
|
|
22015
22714
|
var DshCodeSyncController = class {
|
|
22016
22715
|
#authorized = false;
|
|
22017
22716
|
#status;
|
|
@@ -22021,8 +22720,10 @@ var DshCodeSyncController = class {
|
|
|
22021
22720
|
#poll;
|
|
22022
22721
|
#stopped = false;
|
|
22023
22722
|
#options;
|
|
22723
|
+
#actions;
|
|
22024
22724
|
constructor(options) {
|
|
22025
22725
|
this.#options = options;
|
|
22726
|
+
this.#actions = options.actionKinds ? new Set(options.actionKinds) : ACTIONS;
|
|
22026
22727
|
}
|
|
22027
22728
|
view() {
|
|
22028
22729
|
return {
|
|
@@ -22091,7 +22792,7 @@ var DshCodeSyncController = class {
|
|
|
22091
22792
|
if (this.#stopped)
|
|
22092
22793
|
return;
|
|
22093
22794
|
const runtime = runtimes.get(job.sessionId);
|
|
22094
|
-
if (!runtime || job.targetRuntimeId !== runtime || !
|
|
22795
|
+
if (!runtime || job.targetRuntimeId !== runtime || !this.#actions.has(job.kind))
|
|
22095
22796
|
continue;
|
|
22096
22797
|
let claimed;
|
|
22097
22798
|
try {
|
|
@@ -22134,11 +22835,11 @@ var DshCodeSyncController = class {
|
|
|
22134
22835
|
await this.#tail;
|
|
22135
22836
|
}
|
|
22136
22837
|
async #execute(kind, operationId) {
|
|
22137
|
-
if (!
|
|
22838
|
+
if (!this.#actions.has(kind))
|
|
22138
22839
|
throw new CodeSyncLocalError("code_action_invalid");
|
|
22139
22840
|
if (!this.#authorized)
|
|
22140
22841
|
throw new CodeSyncLocalError("code_sync_disabled");
|
|
22141
|
-
if (this.#options.isBusy() &&
|
|
22842
|
+
if (this.#options.isBusy() && !["code_sync_status", "code_auto_upload_disable", "github_code_sync_status", "github_code_auto_upload_disable"].includes(kind)) {
|
|
22142
22843
|
throw new CodeSyncLocalError("code_sync_busy");
|
|
22143
22844
|
}
|
|
22144
22845
|
return this.#requireEngine().execute(kind, {
|
|
@@ -22169,13 +22870,13 @@ var CodeSyncLocalError = class extends Error {
|
|
|
22169
22870
|
};
|
|
22170
22871
|
function codeSyncErrorCode(error62) {
|
|
22171
22872
|
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";
|
|
22873
|
+
return typeof code === "string" && /^(?:github_)?code_[a-z_]{1,60}$/u.test(code) ? code : "code_sync_failed";
|
|
22173
22874
|
}
|
|
22174
22875
|
async function loadConsent(file2, binding) {
|
|
22175
|
-
const ancestors = await secureDirectoryChain(
|
|
22876
|
+
const ancestors = await secureDirectoryChain(path5.dirname(file2));
|
|
22176
22877
|
let before;
|
|
22177
22878
|
try {
|
|
22178
|
-
before = await
|
|
22879
|
+
before = await lstat5(file2);
|
|
22179
22880
|
} catch (error62) {
|
|
22180
22881
|
if (error62.code === "ENOENT")
|
|
22181
22882
|
return false;
|
|
@@ -22184,25 +22885,25 @@ async function loadConsent(file2, binding) {
|
|
|
22184
22885
|
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.size > 4096 || process.platform !== "win32" && (before.mode & 63) !== 0) {
|
|
22185
22886
|
throw new CodeSyncLocalError("code_consent_invalid");
|
|
22186
22887
|
}
|
|
22187
|
-
const handle = await
|
|
22888
|
+
const handle = await open5(file2, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
22188
22889
|
try {
|
|
22189
22890
|
const opened = await handle.stat();
|
|
22190
22891
|
if (before.dev !== opened.dev || before.ino !== opened.ino)
|
|
22191
22892
|
throw new CodeSyncLocalError("code_consent_invalid");
|
|
22192
22893
|
const value = JSON.parse(await handle.readFile("utf8"));
|
|
22193
|
-
assertSameDirectoryChain(ancestors, await secureDirectoryChain(
|
|
22894
|
+
assertSameDirectoryChain(ancestors, await secureDirectoryChain(path5.dirname(file2)));
|
|
22194
22895
|
return value.version === 1 && value.binding === binding && value.enabled === true;
|
|
22195
22896
|
} finally {
|
|
22196
22897
|
await handle.close();
|
|
22197
22898
|
}
|
|
22198
22899
|
}
|
|
22199
22900
|
async function saveConsent(file2, binding, enabled) {
|
|
22200
|
-
await secureDirectoryChain(
|
|
22201
|
-
await
|
|
22202
|
-
const ancestors = await secureDirectoryChain(
|
|
22901
|
+
await secureDirectoryChain(path5.dirname(file2));
|
|
22902
|
+
await mkdir4(path5.dirname(file2), { recursive: true, mode: 448 });
|
|
22903
|
+
const ancestors = await secureDirectoryChain(path5.dirname(file2));
|
|
22203
22904
|
await loadConsent(file2, binding);
|
|
22204
|
-
const temporary = `${file2}.${
|
|
22205
|
-
const handle = await
|
|
22905
|
+
const temporary = `${file2}.${randomUUID3()}.tmp`;
|
|
22906
|
+
const handle = await open5(temporary, "wx", 384);
|
|
22206
22907
|
try {
|
|
22207
22908
|
await handle.writeFile(JSON.stringify({ version: 1, binding, enabled }));
|
|
22208
22909
|
await handle.sync();
|
|
@@ -22210,14 +22911,136 @@ async function saveConsent(file2, binding, enabled) {
|
|
|
22210
22911
|
await handle.close();
|
|
22211
22912
|
}
|
|
22212
22913
|
try {
|
|
22213
|
-
assertSameDirectoryChain(ancestors, await secureDirectoryChain(
|
|
22914
|
+
assertSameDirectoryChain(ancestors, await secureDirectoryChain(path5.dirname(file2)));
|
|
22214
22915
|
await rename2(temporary, file2);
|
|
22215
22916
|
} catch (error62) {
|
|
22216
|
-
await
|
|
22917
|
+
await unlink3(temporary).catch(() => void 0);
|
|
22217
22918
|
throw error62;
|
|
22218
22919
|
}
|
|
22219
22920
|
}
|
|
22220
22921
|
|
|
22922
|
+
// dist/src/github-code-sync-controller.js
|
|
22923
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
22924
|
+
var DshGitHubCodeSyncController = class {
|
|
22925
|
+
options;
|
|
22926
|
+
#controller;
|
|
22927
|
+
#metadata;
|
|
22928
|
+
#key;
|
|
22929
|
+
#error;
|
|
22930
|
+
#tail = Promise.resolve();
|
|
22931
|
+
#stopped = false;
|
|
22932
|
+
#poll;
|
|
22933
|
+
constructor(options) {
|
|
22934
|
+
this.options = options;
|
|
22935
|
+
}
|
|
22936
|
+
view() {
|
|
22937
|
+
return {
|
|
22938
|
+
...this.#controller?.view() ?? { authorized: false },
|
|
22939
|
+
connection: this.#metadata?.connection ? { ...this.#metadata.connection } : null,
|
|
22940
|
+
branch: this.#metadata?.branch ?? null,
|
|
22941
|
+
...this.#error ? { error: this.#error } : {}
|
|
22942
|
+
};
|
|
22943
|
+
}
|
|
22944
|
+
async start() {
|
|
22945
|
+
await this.#serialize(async () => {
|
|
22946
|
+
await this.#refresh();
|
|
22947
|
+
});
|
|
22948
|
+
}
|
|
22949
|
+
authorize(enabled, expected) {
|
|
22950
|
+
return this.#serialize(async () => {
|
|
22951
|
+
if (!enabled) {
|
|
22952
|
+
await this.#controller?.authorize(false);
|
|
22953
|
+
return this.view();
|
|
22954
|
+
}
|
|
22955
|
+
await this.#refresh();
|
|
22956
|
+
const target = this.#metadata?.connection;
|
|
22957
|
+
if (!target || !expected || targetKey(target) !== targetKey(expected))
|
|
22958
|
+
throw coded("code_github_binding_changed");
|
|
22959
|
+
if (!this.#metadata?.can_write || !target.enabled)
|
|
22960
|
+
throw coded("code_sync_forbidden");
|
|
22961
|
+
await this.#controller.authorize(true);
|
|
22962
|
+
return this.view();
|
|
22963
|
+
});
|
|
22964
|
+
}
|
|
22965
|
+
execute(kind) {
|
|
22966
|
+
return this.#serialize(async () => {
|
|
22967
|
+
if (kind === "github_code_auto_upload_disable" && this.#controller) {
|
|
22968
|
+
await this.#controller.execute(kind);
|
|
22969
|
+
return this.view();
|
|
22970
|
+
}
|
|
22971
|
+
await this.#refresh();
|
|
22972
|
+
if (kind === "github_code_sync_status" && !this.#controller?.view().authorized)
|
|
22973
|
+
return this.view();
|
|
22974
|
+
if (!this.#controller)
|
|
22975
|
+
throw coded("code_github_not_configured");
|
|
22976
|
+
if (!this.#metadata?.connection?.enabled && kind !== "github_code_sync_status")
|
|
22977
|
+
throw coded("code_not_enabled");
|
|
22978
|
+
await this.#controller.execute(kind);
|
|
22979
|
+
return this.view();
|
|
22980
|
+
});
|
|
22981
|
+
}
|
|
22982
|
+
poll() {
|
|
22983
|
+
if (this.#stopped)
|
|
22984
|
+
return Promise.resolve();
|
|
22985
|
+
this.#poll ??= this.#serialize(async () => {
|
|
22986
|
+
await this.#refresh();
|
|
22987
|
+
await this.#controller?.poll();
|
|
22988
|
+
}).finally(() => {
|
|
22989
|
+
this.#poll = void 0;
|
|
22990
|
+
});
|
|
22991
|
+
return this.#poll;
|
|
22992
|
+
}
|
|
22993
|
+
async stop() {
|
|
22994
|
+
this.#stopped = true;
|
|
22995
|
+
await this.#tail;
|
|
22996
|
+
await this.#controller?.stop();
|
|
22997
|
+
}
|
|
22998
|
+
async #refresh() {
|
|
22999
|
+
const metadata = await this.options.readMetadata();
|
|
23000
|
+
const target = metadata.connection;
|
|
23001
|
+
const key = target && metadata.can_write ? targetKey(target) : void 0;
|
|
23002
|
+
if (key !== this.#key) {
|
|
23003
|
+
await this.#controller?.authorize(false);
|
|
23004
|
+
await this.#controller?.stop();
|
|
23005
|
+
this.#controller = void 0;
|
|
23006
|
+
this.#key = void 0;
|
|
23007
|
+
if (target && key) {
|
|
23008
|
+
const capturedTarget = { ...target };
|
|
23009
|
+
this.#controller = new DshCodeSyncController({
|
|
23010
|
+
...this.options,
|
|
23011
|
+
binding: createHash4("sha256").update(JSON.stringify([this.options.binding, key])).digest("hex"),
|
|
23012
|
+
actionKinds: GITHUB_CODE_ACTIONS,
|
|
23013
|
+
createEngine: () => this.options.createEngine(capturedTarget)
|
|
23014
|
+
});
|
|
23015
|
+
await this.#controller.start();
|
|
23016
|
+
this.#key = key;
|
|
23017
|
+
}
|
|
23018
|
+
}
|
|
23019
|
+
this.#metadata = metadata;
|
|
23020
|
+
}
|
|
23021
|
+
#serialize(operation) {
|
|
23022
|
+
const pending = this.#tail.then(async () => {
|
|
23023
|
+
if (this.#stopped)
|
|
23024
|
+
throw coded("code_sync_stopped");
|
|
23025
|
+
this.#error = void 0;
|
|
23026
|
+
try {
|
|
23027
|
+
return await operation();
|
|
23028
|
+
} catch (error62) {
|
|
23029
|
+
this.#error = codeSyncErrorCode(error62);
|
|
23030
|
+
throw coded(this.#error);
|
|
23031
|
+
}
|
|
23032
|
+
});
|
|
23033
|
+
this.#tail = pending.catch(() => void 0);
|
|
23034
|
+
return pending;
|
|
23035
|
+
}
|
|
23036
|
+
};
|
|
23037
|
+
function targetKey(target) {
|
|
23038
|
+
return JSON.stringify([target.repository, target.base_branch, target.revision]);
|
|
23039
|
+
}
|
|
23040
|
+
function coded(code) {
|
|
23041
|
+
return Object.assign(new Error(code), { code });
|
|
23042
|
+
}
|
|
23043
|
+
|
|
22221
23044
|
// dist/src/collaboration-api.js
|
|
22222
23045
|
function createHttpDshCollaborationApi(options) {
|
|
22223
23046
|
const client = new HttpCollaborationClient({
|
|
@@ -22293,9 +23116,9 @@ function clipToUtf8Bytes(value, maxBytes) {
|
|
|
22293
23116
|
}
|
|
22294
23117
|
|
|
22295
23118
|
// dist/src/config.js
|
|
22296
|
-
import { createHash as
|
|
22297
|
-
import { lstat as
|
|
22298
|
-
import
|
|
23119
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
23120
|
+
import { lstat as lstat6 } from "node:fs/promises";
|
|
23121
|
+
import path6 from "node:path";
|
|
22299
23122
|
var CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
22300
23123
|
"enabled",
|
|
22301
23124
|
"bindingMode",
|
|
@@ -22408,7 +23231,7 @@ async function assertSafeDshStateRoot(config2) {
|
|
|
22408
23231
|
await assertRealStateSeparation(config2.workspacePath, config2.stateRoot, "config.stateRoot");
|
|
22409
23232
|
await secureDirectoryChain(config2.stateRoot);
|
|
22410
23233
|
try {
|
|
22411
|
-
const target = await
|
|
23234
|
+
const target = await lstat6(config2.stateRoot);
|
|
22412
23235
|
if (target.isSymbolicLink() || !target.isDirectory()) {
|
|
22413
23236
|
throw new Error("config.stateRoot must be a real directory, never a symbolic link");
|
|
22414
23237
|
}
|
|
@@ -22437,11 +23260,11 @@ function rejectModeKeys(value, forbidden, bindingMode) {
|
|
|
22437
23260
|
}
|
|
22438
23261
|
}
|
|
22439
23262
|
function deriveDshSessionId(projectId, sessionId, workspacePath) {
|
|
22440
|
-
const digest3 =
|
|
23263
|
+
const digest3 = createHash5("sha256").update(JSON.stringify([projectId, sessionId, path6.resolve(workspacePath)])).digest("hex").slice(0, 32);
|
|
22441
23264
|
return `gatherthread-${digest3}`;
|
|
22442
23265
|
}
|
|
22443
23266
|
function deriveNativeDshSessionId(projectId, sessionId, workspacePath) {
|
|
22444
|
-
const digest3 =
|
|
23267
|
+
const digest3 = createHash5("sha256").update(JSON.stringify(["native-web-v2", projectId, sessionId, path6.resolve(workspacePath)])).digest("hex").slice(0, 32);
|
|
22445
23268
|
return `gatherthread-${digest3}`;
|
|
22446
23269
|
}
|
|
22447
23270
|
function normalizeApiUrl(value) {
|
|
@@ -22479,13 +23302,13 @@ function safeText(value, label, maximum) {
|
|
|
22479
23302
|
}
|
|
22480
23303
|
function absolutePath(value, label) {
|
|
22481
23304
|
const text = requiredString2(value, label, 4096);
|
|
22482
|
-
if (!
|
|
23305
|
+
if (!path6.isAbsolute(text))
|
|
22483
23306
|
throw new Error(`${label} must be absolute`);
|
|
22484
|
-
return
|
|
23307
|
+
return path6.resolve(text);
|
|
22485
23308
|
}
|
|
22486
23309
|
function isWithin2(parent, candidate) {
|
|
22487
|
-
const relative =
|
|
22488
|
-
return relative === "" || !relative.startsWith(`..${
|
|
23310
|
+
const relative = path6.relative(parent, candidate);
|
|
23311
|
+
return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== "..";
|
|
22489
23312
|
}
|
|
22490
23313
|
function isNodeError3(error62) {
|
|
22491
23314
|
return error62 instanceof Error && "code" in error62;
|
|
@@ -22524,7 +23347,7 @@ function rejectUnknownKeys(value, allowed, label) {
|
|
|
22524
23347
|
}
|
|
22525
23348
|
|
|
22526
23349
|
// dist/src/connector.js
|
|
22527
|
-
import { createHash as
|
|
23350
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
22528
23351
|
|
|
22529
23352
|
// dist/src/canonical-prompt.js
|
|
22530
23353
|
var MAX_EVENT_TEXT_BYTES = 8 * 1024;
|
|
@@ -22597,12 +23420,12 @@ ${footer}`, "utf8");
|
|
|
22597
23420
|
const retained = [];
|
|
22598
23421
|
let used = 0;
|
|
22599
23422
|
for (const line of contextLines.toReversed()) {
|
|
22600
|
-
const
|
|
23423
|
+
const bytes2 = Buffer.byteLength(`${line}
|
|
22601
23424
|
`, "utf8");
|
|
22602
|
-
if (used +
|
|
23425
|
+
if (used + bytes2 > contextBudget)
|
|
22603
23426
|
continue;
|
|
22604
23427
|
retained.push(line);
|
|
22605
|
-
used +=
|
|
23428
|
+
used += bytes2;
|
|
22606
23429
|
}
|
|
22607
23430
|
retained.reverse();
|
|
22608
23431
|
const omitted = retained.length < contextLines.length ? [`[${contextLines.length - retained.length} older public context update(s) omitted by size limit]`] : [];
|
|
@@ -22916,6 +23739,15 @@ var DshHostConnector = class {
|
|
|
22916
23739
|
get stopped() {
|
|
22917
23740
|
return this.#stopped;
|
|
22918
23741
|
}
|
|
23742
|
+
/** Project refresh is authoritative for the mode of future DSH bubbles. */
|
|
23743
|
+
updateSessionSummary(session) {
|
|
23744
|
+
if (!this.#started || this.#stopped)
|
|
23745
|
+
throw new Error("DSH connector is not running");
|
|
23746
|
+
if (session.id !== this.#config.sessionId || session.projectId !== this.#config.projectId || session.state !== "active" || !isSessionWritableBy(session, this.#actorUserId)) {
|
|
23747
|
+
throw new Error("Refreshed GatherThread Session is not this writable binding");
|
|
23748
|
+
}
|
|
23749
|
+
this.#session = session;
|
|
23750
|
+
}
|
|
22919
23751
|
/**
|
|
22920
23752
|
* Adopt a refreshed execution-profile catalog on a running connection.
|
|
22921
23753
|
*
|
|
@@ -23577,7 +24409,7 @@ var DshHostConnector = class {
|
|
|
23577
24409
|
role: "user",
|
|
23578
24410
|
content,
|
|
23579
24411
|
occurredAt: event.timestamp,
|
|
23580
|
-
...event.actorDisplayName === void 0 ? {} : {
|
|
24412
|
+
...this.#session?.mode !== "multi" || event.actorDisplayName === void 0 ? {} : {
|
|
23581
24413
|
actorDisplayName: event.actorDisplayName
|
|
23582
24414
|
}
|
|
23583
24415
|
});
|
|
@@ -24011,7 +24843,7 @@ function turnSettlement(events) {
|
|
|
24011
24843
|
return typeof reason?.kind === "string" ? reason.kind : void 0;
|
|
24012
24844
|
}
|
|
24013
24845
|
function digest2(value) {
|
|
24014
|
-
return
|
|
24846
|
+
return createHash6("sha256").update(value).digest("hex");
|
|
24015
24847
|
}
|
|
24016
24848
|
function cloneExecutionProfiles(profiles) {
|
|
24017
24849
|
return profiles.map((profile) => ({
|
|
@@ -24147,7 +24979,7 @@ function asObject3(value) {
|
|
|
24147
24979
|
}
|
|
24148
24980
|
|
|
24149
24981
|
// dist/src/dsh-compat.js
|
|
24150
|
-
import { createHash as
|
|
24982
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
24151
24983
|
var DSH_COMPATIBILITY = Object.freeze({
|
|
24152
24984
|
tag: "dsh-v0.1.3-alpha.1",
|
|
24153
24985
|
version: "0.1.3-alpha.1",
|
|
@@ -24193,8 +25025,8 @@ function createDshHostFacade(options) {
|
|
|
24193
25025
|
let executionHandle;
|
|
24194
25026
|
let contextPreparationPromise;
|
|
24195
25027
|
const executionBinding = JSON.stringify({ nativeSessionId: options.sessionId, ...options.contextBinding });
|
|
24196
|
-
const executionSessionId = `gatherthread-execution-${
|
|
24197
|
-
const executionOwnerId = `gatherthread-execution-owner:${
|
|
25028
|
+
const executionSessionId = `gatherthread-execution-${createHash7("sha256").update(executionBinding).digest("hex").slice(0, 32)}`;
|
|
25029
|
+
const executionOwnerId = `gatherthread-execution-owner:${createHash7("sha256").update(executionBinding).digest("hex")}`;
|
|
24198
25030
|
const executionOwnerText = JSON.stringify({
|
|
24199
25031
|
version: 1,
|
|
24200
25032
|
owner: "gatherthread",
|
|
@@ -24302,7 +25134,7 @@ function createDshHostFacade(options) {
|
|
|
24302
25134
|
if (input2.selectedOnly && historyContext.items.length !== 0) {
|
|
24303
25135
|
throw new Error("Selected-only summary execution cannot include other shared history");
|
|
24304
25136
|
}
|
|
24305
|
-
await
|
|
25137
|
+
await open7();
|
|
24306
25138
|
if (disposed || promptActive || projectionActive)
|
|
24307
25139
|
throw new Error("DSH host facade permits only one active write");
|
|
24308
25140
|
const parent = requireAgent(handle);
|
|
@@ -24323,7 +25155,7 @@ function createDshHostFacade(options) {
|
|
|
24323
25155
|
throw new Error("DSH isolated Agent did not inherit its native Agent's exact tool composition");
|
|
24324
25156
|
}
|
|
24325
25157
|
};
|
|
24326
|
-
const markerId = `gatherthread-context:${
|
|
25158
|
+
const markerId = `gatherthread-context:${createHash7("sha256").update(JSON.stringify({
|
|
24327
25159
|
requestId: input2.requestId,
|
|
24328
25160
|
requestSequence: input2.requestSequence,
|
|
24329
25161
|
selectedOnly: input2.selectedOnly,
|
|
@@ -24422,7 +25254,7 @@ ${JSON.stringify(historyContext.items)}`;
|
|
|
24422
25254
|
}
|
|
24423
25255
|
await presets.mount(agentContext, recorded ?? options.agentPreset);
|
|
24424
25256
|
};
|
|
24425
|
-
const
|
|
25257
|
+
const open7 = () => {
|
|
24426
25258
|
if (disposed)
|
|
24427
25259
|
return Promise.reject(new Error("DSH host facade is disposed"));
|
|
24428
25260
|
if (openPromise !== void 0)
|
|
@@ -24531,7 +25363,7 @@ ${JSON.stringify(historyContext.items)}`;
|
|
|
24531
25363
|
let selectionRef;
|
|
24532
25364
|
try {
|
|
24533
25365
|
if (requestedExecutionId === void 0)
|
|
24534
|
-
await
|
|
25366
|
+
await open7();
|
|
24535
25367
|
throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
|
|
24536
25368
|
const agent = operationAgent(requestedExecutionId);
|
|
24537
25369
|
if (requestedExecutionId !== void 0) {
|
|
@@ -24551,7 +25383,7 @@ ${JSON.stringify(historyContext.items)}`;
|
|
|
24551
25383
|
}
|
|
24552
25384
|
}
|
|
24553
25385
|
const fromSequence = agent.session.seq;
|
|
24554
|
-
const message = requestedExecutionId === void 0 ? await createUserMessage(text, options) : await createIsolatedMessage(`gatherthread-request:${
|
|
25386
|
+
const message = requestedExecutionId === void 0 ? await createUserMessage(text, options) : await createIsolatedMessage(`gatherthread-request:${createHash7("sha256").update(`${requestedExecutionId}:${fromSequence}:${text}`).digest("hex")}`, text, options);
|
|
24555
25387
|
throwIfDisposed(disposed, lifecycleAbort.signal, "prompt");
|
|
24556
25388
|
if (agent.status !== "idle" || agent.session.seq !== fromSequence)
|
|
24557
25389
|
throw new Error("DSH Agent changed while preparing a GatherThread prompt");
|
|
@@ -24582,7 +25414,7 @@ ${JSON.stringify(historyContext.items)}`;
|
|
|
24582
25414
|
const flush = async () => {
|
|
24583
25415
|
if (disposed)
|
|
24584
25416
|
throw new Error("DSH host facade is disposed");
|
|
24585
|
-
await
|
|
25417
|
+
await open7();
|
|
24586
25418
|
throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
|
|
24587
25419
|
await sessions.flush(requireAgent(handle).session);
|
|
24588
25420
|
throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
|
|
@@ -24596,7 +25428,7 @@ ${JSON.stringify(historyContext.items)}`;
|
|
|
24596
25428
|
return;
|
|
24597
25429
|
projectionActive = true;
|
|
24598
25430
|
try {
|
|
24599
|
-
await
|
|
25431
|
+
await open7();
|
|
24600
25432
|
throwIfDisposed(disposed, lifecycleAbort.signal, "projection");
|
|
24601
25433
|
const agent = requireAgent(handle);
|
|
24602
25434
|
if (agent.status !== "idle") {
|
|
@@ -24655,7 +25487,7 @@ ${JSON.stringify(historyContext.items)}`;
|
|
|
24655
25487
|
};
|
|
24656
25488
|
return {
|
|
24657
25489
|
sessionId: options.sessionId,
|
|
24658
|
-
open:
|
|
25490
|
+
open: open7,
|
|
24659
25491
|
currentSequence(requestedExecutionId) {
|
|
24660
25492
|
return operationAgent(requestedExecutionId).session.seq;
|
|
24661
25493
|
},
|
|
@@ -24903,8 +25735,11 @@ async function createProjectionMessage(projection, options) {
|
|
|
24903
25735
|
});
|
|
24904
25736
|
}
|
|
24905
25737
|
function formatProjectionText(projection) {
|
|
24906
|
-
if (projection.role === "user")
|
|
24907
|
-
|
|
25738
|
+
if (projection.role === "user") {
|
|
25739
|
+
const sender = projection.actorDisplayName?.replace(/[\p{Cc}\p{Cf}【】]+/gu, " ").replace(/\s+/gu, " ").trim();
|
|
25740
|
+
return sender ? `\u3010${sender} \xB7 GatherThread\u3011
|
|
25741
|
+
${projection.content}` : projection.content;
|
|
25742
|
+
}
|
|
24908
25743
|
const route = [projection.provider, projection.model].filter(Boolean).join(" / ");
|
|
24909
25744
|
const attribution = [projection.actorDisplayName, route].filter(Boolean).join(" \xB7 ");
|
|
24910
25745
|
return `[GatherThread remote Agent reply${attribution ? ` \xB7 ${attribution}` : ""}]
|
|
@@ -25069,9 +25904,9 @@ function asObject4(value) {
|
|
|
25069
25904
|
}
|
|
25070
25905
|
|
|
25071
25906
|
// dist/src/native-connection.js
|
|
25072
|
-
import { createHash as
|
|
25907
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
25073
25908
|
import { homedir as homedir3 } from "node:os";
|
|
25074
|
-
import
|
|
25909
|
+
import path7 from "node:path";
|
|
25075
25910
|
var DSH_NATIVE_CREDENTIAL_KEY = "gatherthread-dsh-host/default";
|
|
25076
25911
|
var DSH_NATIVE_GRANT_SCHEMA_VERSION = 2;
|
|
25077
25912
|
var DSH_NATIVE_PAIRING_CHANNEL = "/gatherthread";
|
|
@@ -25204,7 +26039,7 @@ function createNativeProjectConfig(options) {
|
|
|
25204
26039
|
projectId: options.binding.projectId,
|
|
25205
26040
|
projectName: options.binding.projectName,
|
|
25206
26041
|
deviceId: options.grant.deviceId,
|
|
25207
|
-
workspacePath:
|
|
26042
|
+
workspacePath: path7.resolve(options.workspacePath),
|
|
25208
26043
|
stateRoot,
|
|
25209
26044
|
provider: options.binding.provider,
|
|
25210
26045
|
model: options.binding.model,
|
|
@@ -25293,12 +26128,12 @@ function normalizeDshServerUrl(value) {
|
|
|
25293
26128
|
return { serverUrl, apiUrl: `${serverUrl}/v1` };
|
|
25294
26129
|
}
|
|
25295
26130
|
function nativeStateRoot(dshHome, serverUrl, projectId, workspacePath) {
|
|
25296
|
-
const digest3 =
|
|
25297
|
-
return
|
|
26131
|
+
const digest3 = createHash8("sha256").update(JSON.stringify([serverUrl, projectId, path7.resolve(workspacePath)])).digest("hex").slice(0, 32);
|
|
26132
|
+
return path7.join(path7.resolve(dshHome), "gatherthread", "state", digest3);
|
|
25298
26133
|
}
|
|
25299
26134
|
function resolveDshHome(env) {
|
|
25300
26135
|
const configured = env.DSH_HOME?.trim();
|
|
25301
|
-
return
|
|
26136
|
+
return path7.resolve(configured ? configured : path7.join(homedir3(), ".dsh"));
|
|
25302
26137
|
}
|
|
25303
26138
|
function parseBinding(value) {
|
|
25304
26139
|
const input2 = exactObject(value, /* @__PURE__ */ new Set([
|
|
@@ -25442,8 +26277,8 @@ function isLoopback2(hostname3) {
|
|
|
25442
26277
|
}
|
|
25443
26278
|
|
|
25444
26279
|
// dist/src/project-manager.js
|
|
25445
|
-
import
|
|
25446
|
-
import { createHash as
|
|
26280
|
+
import path8 from "node:path";
|
|
26281
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
25447
26282
|
|
|
25448
26283
|
// dist/src/execution-gate.js
|
|
25449
26284
|
var BoundedDshExecutionGate = class {
|
|
@@ -25722,6 +26557,27 @@ var DshProjectManager = class {
|
|
|
25722
26557
|
for (const error62 of permissions.errors)
|
|
25723
26558
|
this.#reportError(error62);
|
|
25724
26559
|
const eligibleSessions = [...permissions.eligibleSessions];
|
|
26560
|
+
const sessionUpdateErrors = [];
|
|
26561
|
+
for (const session of eligibleSessions) {
|
|
26562
|
+
const binding = this.#managed.get(session.id);
|
|
26563
|
+
if (binding?.phase === "active" && !binding.connector.stopped) {
|
|
26564
|
+
try {
|
|
26565
|
+
binding.connector.updateSessionSummary(session);
|
|
26566
|
+
} catch (error62) {
|
|
26567
|
+
this.#managed.delete(session.id);
|
|
26568
|
+
try {
|
|
26569
|
+
await binding.connector.stop();
|
|
26570
|
+
} catch (stopError) {
|
|
26571
|
+
this.#reportError(publicError2(stopError, "DSH Session connector cleanup failed"));
|
|
26572
|
+
}
|
|
26573
|
+
if (!this.#stopped)
|
|
26574
|
+
this.#recordFailure(session.id);
|
|
26575
|
+
const failure2 = publicError2(error62, `DSH Session ${session.id} mode refresh failed`);
|
|
26576
|
+
sessionUpdateErrors.push(failure2);
|
|
26577
|
+
this.#reportError(failure2);
|
|
26578
|
+
}
|
|
26579
|
+
}
|
|
26580
|
+
}
|
|
25725
26581
|
if (this.#discoverLocalSessions !== void 0) {
|
|
25726
26582
|
try {
|
|
25727
26583
|
const candidates2 = await this.#discoverLocalSessions();
|
|
@@ -25765,7 +26621,7 @@ var DshProjectManager = class {
|
|
|
25765
26621
|
status: "updated",
|
|
25766
26622
|
eligibleSessionIds: [...eligibleIds].sort(),
|
|
25767
26623
|
activeSessionIds: this.activeSessionIds(),
|
|
25768
|
-
errors: [...permissions.errors, ...attachErrors]
|
|
26624
|
+
errors: [...permissions.errors, ...sessionUpdateErrors, ...attachErrors]
|
|
25769
26625
|
};
|
|
25770
26626
|
this.#notifyStatus({
|
|
25771
26627
|
state: "connected",
|
|
@@ -25880,7 +26736,7 @@ function projectSessionConfig(config2, sessionId, nativeSessionId) {
|
|
|
25880
26736
|
sessionId,
|
|
25881
26737
|
deviceId: config2.deviceId,
|
|
25882
26738
|
workspacePath: config2.workspacePath,
|
|
25883
|
-
statePath:
|
|
26739
|
+
statePath: path8.join(config2.stateRoot, `${dshSessionId}.json`),
|
|
25884
26740
|
provider: config2.provider,
|
|
25885
26741
|
model: config2.model,
|
|
25886
26742
|
...config2.executionProfiles === void 0 ? {} : {
|
|
@@ -25898,7 +26754,7 @@ function projectSessionConfig(config2, sessionId, nativeSessionId) {
|
|
|
25898
26754
|
};
|
|
25899
26755
|
}
|
|
25900
26756
|
function nativeSessionCreationKey(projectId, localSessionId) {
|
|
25901
|
-
const identity =
|
|
26757
|
+
const identity = createHash9("sha256").update(`${projectId}\0${localSessionId}`).digest("hex");
|
|
25902
26758
|
return `dsh-native:${identity}`;
|
|
25903
26759
|
}
|
|
25904
26760
|
function retryDelay(config2, attempts) {
|
|
@@ -25928,31 +26784,31 @@ function publicError2(error62, fallback) {
|
|
|
25928
26784
|
}
|
|
25929
26785
|
|
|
25930
26786
|
// dist/src/state-store.js
|
|
25931
|
-
import { randomUUID as
|
|
25932
|
-
import { constants as
|
|
25933
|
-
import { lstat as
|
|
25934
|
-
import
|
|
26787
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
26788
|
+
import { constants as constants4 } from "node:fs";
|
|
26789
|
+
import { lstat as lstat7, mkdir as mkdir5, open as open6, rename as rename3, unlink as unlink4 } from "node:fs/promises";
|
|
26790
|
+
import path9 from "node:path";
|
|
25935
26791
|
var FileConnectorStateStore = class {
|
|
25936
26792
|
#path;
|
|
25937
26793
|
constructor(statePath) {
|
|
25938
|
-
if (!
|
|
26794
|
+
if (!path9.isAbsolute(statePath))
|
|
25939
26795
|
throw new Error("DSH connector state path must be absolute");
|
|
25940
|
-
this.#path =
|
|
26796
|
+
this.#path = path9.resolve(statePath);
|
|
25941
26797
|
}
|
|
25942
26798
|
async load() {
|
|
25943
|
-
const directory =
|
|
26799
|
+
const directory = path9.dirname(this.#path);
|
|
25944
26800
|
const ancestorsBefore = await secureDirectoryChain(directory);
|
|
25945
26801
|
let metadata;
|
|
25946
26802
|
try {
|
|
25947
|
-
metadata = await
|
|
26803
|
+
metadata = await lstat7(this.#path);
|
|
25948
26804
|
} catch (error62) {
|
|
25949
26805
|
if (isNodeError4(error62) && error62.code === "ENOENT")
|
|
25950
26806
|
return void 0;
|
|
25951
26807
|
throw error62;
|
|
25952
26808
|
}
|
|
25953
26809
|
assertPrivateRegularFile(metadata, "DSH connector state");
|
|
25954
|
-
const noFollow = "O_NOFOLLOW" in
|
|
25955
|
-
const handle = await
|
|
26810
|
+
const noFollow = "O_NOFOLLOW" in constants4 ? constants4.O_NOFOLLOW : 0;
|
|
26811
|
+
const handle = await open6(this.#path, constants4.O_RDONLY | noFollow);
|
|
25956
26812
|
let raw;
|
|
25957
26813
|
try {
|
|
25958
26814
|
const opened = await handle.stat();
|
|
@@ -25974,17 +26830,17 @@ var FileConnectorStateStore = class {
|
|
|
25974
26830
|
}
|
|
25975
26831
|
async save(state) {
|
|
25976
26832
|
const validated = validateConnectorState(structuredClone(state));
|
|
25977
|
-
const directory =
|
|
26833
|
+
const directory = path9.dirname(this.#path);
|
|
25978
26834
|
await secureDirectoryChain(directory);
|
|
25979
|
-
await
|
|
26835
|
+
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
25980
26836
|
const ancestorsBefore = await secureDirectoryChain(directory);
|
|
25981
|
-
const directoryBefore = await
|
|
26837
|
+
const directoryBefore = await lstat7(directory);
|
|
25982
26838
|
assertSecureDirectory(directoryBefore);
|
|
25983
26839
|
const targetBefore = await optionalLstat(this.#path);
|
|
25984
26840
|
if (targetBefore !== void 0)
|
|
25985
26841
|
assertPrivateRegularFile(targetBefore, "DSH connector state");
|
|
25986
|
-
const temporaryPath = `${this.#path}.tmp-${process.pid}-${
|
|
25987
|
-
const handle = await
|
|
26842
|
+
const temporaryPath = `${this.#path}.tmp-${process.pid}-${randomUUID4()}`;
|
|
26843
|
+
const handle = await open6(temporaryPath, "wx", 384);
|
|
25988
26844
|
try {
|
|
25989
26845
|
await handle.writeFile(`${JSON.stringify(validated, null, 2)}
|
|
25990
26846
|
`, { encoding: "utf8" });
|
|
@@ -25993,7 +26849,7 @@ var FileConnectorStateStore = class {
|
|
|
25993
26849
|
await handle.close();
|
|
25994
26850
|
}
|
|
25995
26851
|
try {
|
|
25996
|
-
const directoryAfter = await
|
|
26852
|
+
const directoryAfter = await lstat7(directory);
|
|
25997
26853
|
if (directoryAfter.isSymbolicLink() || directoryAfter.dev !== directoryBefore.dev || directoryAfter.ino !== directoryBefore.ino) {
|
|
25998
26854
|
throw new Error("DSH connector state directory changed during atomic save");
|
|
25999
26855
|
}
|
|
@@ -26004,9 +26860,9 @@ var FileConnectorStateStore = class {
|
|
|
26004
26860
|
}
|
|
26005
26861
|
await rename3(temporaryPath, this.#path);
|
|
26006
26862
|
assertSameDirectoryChain(ancestorsBefore, await secureDirectoryChain(directory));
|
|
26007
|
-
assertPrivateRegularFile(await
|
|
26863
|
+
assertPrivateRegularFile(await lstat7(this.#path), "DSH connector state");
|
|
26008
26864
|
} catch (error62) {
|
|
26009
|
-
await
|
|
26865
|
+
await unlink4(temporaryPath).catch(() => void 0);
|
|
26010
26866
|
throw error62;
|
|
26011
26867
|
}
|
|
26012
26868
|
}
|
|
@@ -26269,7 +27125,7 @@ function isNodeError4(error62) {
|
|
|
26269
27125
|
}
|
|
26270
27126
|
async function optionalLstat(target) {
|
|
26271
27127
|
try {
|
|
26272
|
-
return await
|
|
27128
|
+
return await lstat7(target);
|
|
26273
27129
|
} catch (error62) {
|
|
26274
27130
|
if (isNodeError4(error62) && error62.code === "ENOENT")
|
|
26275
27131
|
return void 0;
|
|
@@ -26526,7 +27382,10 @@ var RPC_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
|
26526
27382
|
"sync/set-auto-upload",
|
|
26527
27383
|
"sync/upload",
|
|
26528
27384
|
"code/authorize",
|
|
26529
|
-
"code/action"
|
|
27385
|
+
"code/action",
|
|
27386
|
+
"github/authorize",
|
|
27387
|
+
"github/action",
|
|
27388
|
+
"github/connect"
|
|
26530
27389
|
]);
|
|
26531
27390
|
var MAX_CATALOG_PROVIDERS = 64;
|
|
26532
27391
|
var MAX_CATALOG_MODELS = 256;
|
|
@@ -26628,6 +27487,12 @@ var DshNativeHostController = class {
|
|
|
26628
27487
|
projectName: this.#projects.get(projectId)?.name ?? projectId,
|
|
26629
27488
|
...view
|
|
26630
27489
|
}];
|
|
27490
|
+
}).slice(0, 100),
|
|
27491
|
+
githubSync: [...this.#owners].flatMap(([projectId, owner]) => {
|
|
27492
|
+
if (this.#projects.get(projectId)?.role === "viewer")
|
|
27493
|
+
return [];
|
|
27494
|
+
const view = owner.githubSyncView?.();
|
|
27495
|
+
return view === void 0 ? [] : [{ projectId, projectName: this.#projects.get(projectId)?.name ?? projectId, ...view }];
|
|
26631
27496
|
}).slice(0, 100)
|
|
26632
27497
|
}
|
|
26633
27498
|
},
|
|
@@ -26889,6 +27754,40 @@ var DshNativeHostController = class {
|
|
|
26889
27754
|
}
|
|
26890
27755
|
return this.publicState();
|
|
26891
27756
|
}
|
|
27757
|
+
async githubSyncAction(inputValue, authorize = false) {
|
|
27758
|
+
this.#assertAvailable();
|
|
27759
|
+
const input2 = exactInput(inputValue, new Set(authorize ? ["projectId", "enabled", "repository", "base_branch", "revision"] : ["projectId", "action"]));
|
|
27760
|
+
const projectId = safeInputIdentifier(input2.projectId, "projectId");
|
|
27761
|
+
const owner = this.#owners.get(projectId);
|
|
27762
|
+
if (!owner || this.#projects.get(projectId)?.role === "viewer")
|
|
27763
|
+
throw new Error("This Project is not writable on this DSH device");
|
|
27764
|
+
if (authorize) {
|
|
27765
|
+
if (typeof input2.enabled !== "boolean" || !owner.authorizeGitHubSync)
|
|
27766
|
+
throw new Error("Invalid GitHub authorization");
|
|
27767
|
+
let expected;
|
|
27768
|
+
if (input2.enabled) {
|
|
27769
|
+
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)
|
|
27770
|
+
throw new Error("Invalid GitHub target");
|
|
27771
|
+
expected = { repository: input2.repository, base_branch: input2.base_branch, revision: input2.revision };
|
|
27772
|
+
}
|
|
27773
|
+
await owner.authorizeGitHubSync(input2.enabled, expected);
|
|
27774
|
+
} else {
|
|
27775
|
+
if (typeof input2.action !== "string" || !GITHUB_CODE_ACTIONS.includes(input2.action) || !owner.executeGitHubSync)
|
|
27776
|
+
throw new Error("Invalid GitHub action");
|
|
27777
|
+
await owner.executeGitHubSync(input2.action);
|
|
27778
|
+
}
|
|
27779
|
+
return this.publicState();
|
|
27780
|
+
}
|
|
27781
|
+
async connectGitHub(inputValue) {
|
|
27782
|
+
this.#assertAvailable();
|
|
27783
|
+
const input2 = exactInput(inputValue, /* @__PURE__ */ new Set(["projectId"]));
|
|
27784
|
+
const projectId = safeInputIdentifier(input2.projectId, "projectId");
|
|
27785
|
+
if (!this.#owners.has(projectId) || this.#projects.get(projectId)?.role === "viewer") {
|
|
27786
|
+
throw new Error("This Project is not writable on this DSH device");
|
|
27787
|
+
}
|
|
27788
|
+
await connectGitHubAccount();
|
|
27789
|
+
return this.publicState();
|
|
27790
|
+
}
|
|
26892
27791
|
dispose() {
|
|
26893
27792
|
this.#disposePromise ??= (async () => {
|
|
26894
27793
|
this.#disposed = true;
|
|
@@ -27192,7 +28091,7 @@ async function apply(context, rawConfig) {
|
|
|
27192
28091
|
workspacePath: config2.workspacePath,
|
|
27193
28092
|
...dshHome === void 0 ? {} : {
|
|
27194
28093
|
dshHome,
|
|
27195
|
-
projectWorkspaceHomeDirectory:
|
|
28094
|
+
projectWorkspaceHomeDirectory: path10.dirname(dshHome)
|
|
27196
28095
|
},
|
|
27197
28096
|
...config2.officialServerUrl === void 0 ? {} : { officialServerUrl: config2.officialServerUrl }
|
|
27198
28097
|
});
|
|
@@ -27250,6 +28149,12 @@ function registerNativeDshRpc(contextValue, controller) {
|
|
|
27250
28149
|
return rpcSuccess(await controller.codeSyncAction(payload, true));
|
|
27251
28150
|
case "code/action":
|
|
27252
28151
|
return rpcSuccess(await controller.codeSyncAction(payload));
|
|
28152
|
+
case "github/authorize":
|
|
28153
|
+
return rpcSuccess(await controller.githubSyncAction(payload, true));
|
|
28154
|
+
case "github/action":
|
|
28155
|
+
return rpcSuccess(await controller.githubSyncAction(payload));
|
|
28156
|
+
case "github/connect":
|
|
28157
|
+
return rpcSuccess(await controller.connectGitHub(payload));
|
|
27253
28158
|
default:
|
|
27254
28159
|
return rpcFailure("gatherthread/not-found", "GatherThread DSH action is unavailable");
|
|
27255
28160
|
}
|
|
@@ -27344,8 +28249,8 @@ async function createProductionOwner(options) {
|
|
|
27344
28249
|
throw error62;
|
|
27345
28250
|
}
|
|
27346
28251
|
const codeSync = new DshCodeSyncController({
|
|
27347
|
-
permissionPath:
|
|
27348
|
-
binding:
|
|
28252
|
+
permissionPath: path10.join(config2.stateRoot, `code-consent-${createHash10("sha256").update(actor.id).digest("hex")}.json`),
|
|
28253
|
+
binding: createHash10("sha256").update(JSON.stringify([
|
|
27349
28254
|
config2.apiUrl,
|
|
27350
28255
|
config2.projectId,
|
|
27351
28256
|
actor.id,
|
|
@@ -27360,7 +28265,7 @@ async function createProductionOwner(options) {
|
|
|
27360
28265
|
// A custom DSH home is an isolated profile, including its code-sync state.
|
|
27361
28266
|
// Default profiles keep the harness-neutral state shared with Codex.
|
|
27362
28267
|
...options.dshHome === void 0 ? {} : {
|
|
27363
|
-
stateRoot:
|
|
28268
|
+
stateRoot: path10.join(options.dshHome, "gatherthread-code-sync", createHash10("sha256").update(config2.workspacePath).digest("hex"))
|
|
27364
28269
|
}
|
|
27365
28270
|
}),
|
|
27366
28271
|
api: new HttpCollaborationClient({
|
|
@@ -27372,8 +28277,77 @@ async function createProductionOwner(options) {
|
|
|
27372
28277
|
isBusy: () => nativeWorkspace.isBusy()
|
|
27373
28278
|
});
|
|
27374
28279
|
await codeSync.start().catch(() => void 0);
|
|
28280
|
+
const githubSync = new DshGitHubCodeSyncController({
|
|
28281
|
+
permissionPath: path10.join(config2.stateRoot, `github-consent-${createHash10("sha256").update(actor.id).digest("hex")}.json`),
|
|
28282
|
+
binding: createHash10("sha256").update(JSON.stringify([
|
|
28283
|
+
"github",
|
|
28284
|
+
config2.apiUrl,
|
|
28285
|
+
config2.projectId,
|
|
28286
|
+
actor.id,
|
|
28287
|
+
config2.workspacePath
|
|
28288
|
+
])).digest("hex"),
|
|
28289
|
+
readMetadata: async () => {
|
|
28290
|
+
const response = await fetch(`${config2.apiUrl}/projects/${encodeURIComponent(config2.projectId)}/github`, {
|
|
28291
|
+
headers: { authorization: `Bearer ${options.grant.token}` },
|
|
28292
|
+
redirect: "error",
|
|
28293
|
+
signal: AbortSignal.any([ownerAbort.signal, AbortSignal.timeout(15e3)])
|
|
28294
|
+
});
|
|
28295
|
+
if (!response.ok || !response.body)
|
|
28296
|
+
throw Object.assign(new Error("GitHub metadata unavailable"), { code: "code_github_unavailable" });
|
|
28297
|
+
const reader = response.body.getReader();
|
|
28298
|
+
const chunks = [];
|
|
28299
|
+
let size = 0;
|
|
28300
|
+
try {
|
|
28301
|
+
for (; ; ) {
|
|
28302
|
+
const next = await reader.read();
|
|
28303
|
+
if (next.done)
|
|
28304
|
+
break;
|
|
28305
|
+
size += next.value.byteLength;
|
|
28306
|
+
if (size > 16 * 1024)
|
|
28307
|
+
throw new Error("GitHub metadata too large");
|
|
28308
|
+
chunks.push(next.value);
|
|
28309
|
+
}
|
|
28310
|
+
} finally {
|
|
28311
|
+
await reader.cancel().catch(() => void 0);
|
|
28312
|
+
}
|
|
28313
|
+
const envelope = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
28314
|
+
return GitHubProjectStatusSchema.parse(envelope.data);
|
|
28315
|
+
},
|
|
28316
|
+
createEngine: (target) => createGitHubCodeSync({
|
|
28317
|
+
apiUrl: config2.apiUrl,
|
|
28318
|
+
token: options.grant.token,
|
|
28319
|
+
projectId: config2.projectId,
|
|
28320
|
+
actorId: actor.id,
|
|
28321
|
+
workspacePath: config2.workspacePath,
|
|
28322
|
+
repository: target.repository,
|
|
28323
|
+
baseBranch: target.base_branch,
|
|
28324
|
+
revision: target.revision,
|
|
28325
|
+
...options.dshHome === void 0 ? {} : {
|
|
28326
|
+
stateRoot: path10.join(options.dshHome, "gatherthread-code-sync", createHash10("sha256").update(config2.workspacePath).digest("hex"))
|
|
28327
|
+
}
|
|
28328
|
+
}),
|
|
28329
|
+
api: new HttpCollaborationClient({ baseUrl: config2.apiUrl, bearerToken: options.grant.token, signal: ownerAbort.signal }),
|
|
28330
|
+
runtimes: () => new Map([...codeRuntimes].flatMap(([id, connector]) => connector.stopped || !connector.executionRuntimeId ? [] : [[id, connector.executionRuntimeId]])),
|
|
28331
|
+
isBusy: () => nativeWorkspace.isBusy()
|
|
28332
|
+
});
|
|
28333
|
+
await githubSync.start().catch(() => void 0);
|
|
28334
|
+
let localCodeTail = Promise.resolve();
|
|
28335
|
+
function scheduleCode(operation) {
|
|
28336
|
+
const task = localCodeTail.then(operation);
|
|
28337
|
+
localCodeTail = task.catch(() => void 0);
|
|
28338
|
+
return task;
|
|
28339
|
+
}
|
|
28340
|
+
let codePolling = false;
|
|
27375
28341
|
const codeTimer = setInterval(() => {
|
|
27376
|
-
|
|
28342
|
+
if (codePolling)
|
|
28343
|
+
return;
|
|
28344
|
+
codePolling = true;
|
|
28345
|
+
void scheduleCode(async () => {
|
|
28346
|
+
await codeSync.poll();
|
|
28347
|
+
await githubSync.poll();
|
|
28348
|
+
}).catch(() => void 0).finally(() => {
|
|
28349
|
+
codePolling = false;
|
|
28350
|
+
});
|
|
27377
28351
|
}, 5e3);
|
|
27378
28352
|
codeTimer.unref();
|
|
27379
28353
|
let stopPromise;
|
|
@@ -27383,14 +28357,19 @@ async function createProductionOwner(options) {
|
|
|
27383
28357
|
setLocalAutoUpload: (sessionId, enabled) => manager.setLocalAutoUpload(sessionId, enabled),
|
|
27384
28358
|
uploadLocalTurns: (sessionId) => manager.uploadLocalTurns(sessionId),
|
|
27385
28359
|
codeSyncView: () => codeSync.view(),
|
|
27386
|
-
authorizeCodeSync: (enabled) => codeSync.authorize(enabled),
|
|
27387
|
-
executeCodeSync: (action) => codeSync.execute(action),
|
|
28360
|
+
authorizeCodeSync: (enabled) => scheduleCode(() => codeSync.authorize(enabled)),
|
|
28361
|
+
executeCodeSync: (action) => scheduleCode(() => codeSync.execute(action)),
|
|
28362
|
+
githubSyncView: () => githubSync.view(),
|
|
28363
|
+
authorizeGitHubSync: (enabled, expected) => scheduleCode(() => githubSync.authorize(enabled, expected)),
|
|
28364
|
+
executeGitHubSync: (action) => scheduleCode(() => githubSync.execute(action)),
|
|
27388
28365
|
stop() {
|
|
27389
28366
|
stopPromise ??= (async () => {
|
|
27390
28367
|
options.signal.removeEventListener("abort", abortFromRoot);
|
|
27391
28368
|
clearInterval(codeTimer);
|
|
27392
28369
|
ownerAbort.abort(new Error("GatherThread native DSH manager stopped"));
|
|
28370
|
+
await localCodeTail;
|
|
27393
28371
|
await codeSync.stop();
|
|
28372
|
+
await githubSync.stop();
|
|
27394
28373
|
await manager.stop();
|
|
27395
28374
|
nativeWorkspace.dispose();
|
|
27396
28375
|
})();
|
|
@@ -27460,6 +28439,7 @@ function createNativeManagedConnector(options) {
|
|
|
27460
28439
|
await connector.start();
|
|
27461
28440
|
},
|
|
27462
28441
|
updateExecutionProfiles: (profiles) => connector.updateExecutionProfiles(profiles),
|
|
28442
|
+
updateSessionSummary: (session) => connector.updateSessionSummary(session),
|
|
27463
28443
|
localSyncStatus: () => connector.localSyncStatus(),
|
|
27464
28444
|
setLocalAutoUpload: (enabled) => connector.setLocalAutoUpload(enabled),
|
|
27465
28445
|
uploadLocalTurns: () => connector.uploadLocalTurns(),
|
|
@@ -27486,7 +28466,7 @@ async function resolveNativeProjectWorkspace(grant, binding, signal, homeDirecto
|
|
|
27486
28466
|
}
|
|
27487
28467
|
function nativeDshHomeFromEnvironment() {
|
|
27488
28468
|
const value = process.env.DSH_HOME?.trim();
|
|
27489
|
-
return value ?
|
|
28469
|
+
return value ? path10.resolve(value) : void 0;
|
|
27490
28470
|
}
|
|
27491
28471
|
async function listNativeLlmCatalog(contextValue, signal) {
|
|
27492
28472
|
const llm = requireLlm(contextValue);
|