openpond 0.0.30 → 0.0.32

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.
Files changed (115) hide show
  1. package/dist/chunks/{app-layer-FIGCNZJX.js → app-layer-CUQQUVJS.js} +20 -3
  2. package/dist/chunks/{apps-4RMNCKXS.js → apps-TAYMUCWX.js} +3 -3
  3. package/dist/chunks/{chunk-57P2GCW5.js → chunk-3UOLQG6N.js} +1 -1
  4. package/dist/chunks/{chunk-MFS3SYUH.js → chunk-3ZJIEA5F.js} +1 -1
  5. package/dist/chunks/{chunk-4S3XGFGL.js → chunk-4HQURM3N.js} +1 -1
  6. package/dist/chunks/{chunk-BNVHGSI7.js → chunk-4M4WAORL.js} +874 -798
  7. package/dist/chunks/{chunk-DANRS43D.js → chunk-5RHZKQRK.js} +14 -76
  8. package/dist/chunks/{chunk-BNPFMGZD.js → chunk-D264OHTR.js} +4 -5
  9. package/dist/chunks/{chunk-BBEVMP6O.js → chunk-DCNAXREU.js} +179 -160
  10. package/dist/chunks/chunk-E5RVRUJB.js +1222 -0
  11. package/dist/chunks/{chunk-RJXX6TJA.js → chunk-EONKWOLU.js} +2 -2
  12. package/dist/chunks/{chunk-VQX33YAG.js → chunk-FQW3AQVJ.js} +689 -121
  13. package/dist/chunks/{chunk-GGKA4HLQ.js → chunk-GYZYZGKS.js} +238 -69
  14. package/dist/chunks/{chunk-KMSMK7PI.js → chunk-OXQG773T.js} +33 -33
  15. package/dist/chunks/{chunk-BX4XSB2J.js → chunk-PJXJ2HRT.js} +1816 -1113
  16. package/dist/chunks/chunk-Q5MZCRJA.js +70 -0
  17. package/dist/chunks/{chunk-OV3KAFWB.js → chunk-W2DJR3FA.js} +1 -1
  18. package/dist/chunks/{chunk-KEDXE4BA.js → chunk-XADW35CL.js} +3 -3
  19. package/dist/chunks/{chunk-IUTUUNAX.js → chunk-Y4F4XD5V.js} +1 -1
  20. package/dist/chunks/{cli-2L4NB7HD.js → cli-WW5BVN5Z.js} +4 -3
  21. package/dist/chunks/{cli-5RL7B2Z3.js → cli-ZOCP7X5M.js} +6 -6
  22. package/dist/chunks/{core-commands-AZJZGTZS.js → core-commands-SYGK7K57.js} +4 -4
  23. package/dist/chunks/{extend-6IWYZI37.js → extend-Q5HZ7TMA.js} +11 -11
  24. package/dist/chunks/fileFromPath-DHCRY3BD.js +2151 -0
  25. package/dist/chunks/{harness-SSNXDSKM.js → harness-ZPBPQVAS.js} +3 -3
  26. package/dist/chunks/{help-MUJJMI3Q.js → help-3V3TRQNX.js} +1 -1
  27. package/dist/chunks/{opchat-RW4CFAQB.js → opchat-OUPMEFE5.js} +5 -5
  28. package/dist/chunks/{organizations-V7DTNLSI.js → organizations-5CSLBDAW.js} +4 -4
  29. package/dist/chunks/{profile-WDBKJ7YW.js → profile-OQYSXHOG.js} +206 -166
  30. package/dist/chunks/{project-agent-3TZ3BPYN.js → project-agent-TBTWOUJF.js} +6 -7
  31. package/dist/chunks/{sandbox-command-HOYHF4NC.js → sandbox-command-HCHPY7KW.js} +4 -4
  32. package/dist/chunks/{sandbox-template-IYGB4CZU.js → sandbox-template-PNTOZ6W3.js} +5 -6
  33. package/dist/chunks/{src-CBPRIWG5.js → src-C6MXLDYQ.js} +22 -6
  34. package/dist/chunks/{src-ZRQKSQTD.js → src-GZT4UX6I.js} +80887 -52945
  35. package/dist/chunks/{teams-bot-UWGCFL6C.js → teams-bot-CQ76XI6T.js} +3 -3
  36. package/dist/chunks/workspaces-DHVVOTVR.js +93 -0
  37. package/dist/cli.js +10 -10
  38. package/dist/config.d.ts +0 -2
  39. package/dist/index.js +273 -69
  40. package/dist/profile/local-profile-types.d.ts +8 -2
  41. package/dist/profile/local-profile.d.ts +3 -1
  42. package/dist/sandbox/types/org-project-agent.d.ts +1 -2
  43. package/dist/web/assets/{AppsView-Bu-Nc3Ew.js → AppsView-CF2g-7FH.js} +1 -1
  44. package/dist/web/assets/{BrowserSidebar-CCguQ-DR.js → BrowserSidebar-lUsLUl20.js} +1 -1
  45. package/dist/web/assets/CloudWorkView-BnfGW4OV.js +1 -0
  46. package/dist/web/assets/{CommandMenu-CnodJFHD.js → CommandMenu-BsS4Bgb6.js} +1 -1
  47. package/dist/web/assets/{CommunityView-CIbjNUZr.js → CommunityView-C8qVOwlp.js} +1 -1
  48. package/dist/web/assets/ComposerCreateImproveStrip-C_LIpfSq.js +1 -0
  49. package/dist/web/assets/GetStartedView-Du533wOh.js +1 -0
  50. package/dist/web/assets/LabsRoute-CgKtVYTP.css +1 -0
  51. package/dist/web/assets/LabsRoute-yk8lNMQc.js +3 -0
  52. package/dist/web/assets/{NewProjectDialog-CaKiCoVS.js → NewProjectDialog-DH7wA1z9.js} +1 -1
  53. package/dist/web/assets/ProfileSettingsSection-CqnfjiCl.css +1 -0
  54. package/dist/web/assets/ProfileSettingsSection-D7JvMZdW.js +1 -0
  55. package/dist/web/assets/SettingsView-C9TgCaWG.js +63 -0
  56. package/dist/web/assets/TeamChatView-D_CE7br9.js +3 -0
  57. package/dist/web/assets/{TerminalOverlay-C1iP4OS1.js → TerminalOverlay-CC-VDSlp.js} +1 -1
  58. package/dist/web/assets/TrainingCreationPanel-NFCoaBUI.js +1 -0
  59. package/dist/web/assets/TrainingDraftPanel-BWEmVFjM.js +1 -0
  60. package/dist/web/assets/WorkspaceDiffPanel-SlLlGZM1.js +14 -0
  61. package/dist/web/assets/{WorkspaceEnvironmentMenu-B8JJbwIz.js → WorkspaceEnvironmentMenu-BAoMBTVD.js} +1 -1
  62. package/dist/web/assets/{WorkspaceGitDialogs-B4sp9BGn.js → WorkspaceGitDialogs-CVIFrzyl.js} +1 -1
  63. package/dist/web/assets/{WorkspaceMonacoEditor-BBqimG5X.js → WorkspaceMonacoEditor-0trp0-TI.js} +3 -3
  64. package/dist/web/assets/{boxes-DAaDUabq.js → boxes-CFihgX2z.js} +1 -1
  65. package/dist/web/assets/create-pipeline-request-AJWLMTyw.js +4 -0
  66. package/dist/web/assets/{cssMode-CBF1mieJ.js → cssMode-HvqfCFyP.js} +1 -1
  67. package/dist/web/assets/{git-commit-horizontal-CAickXpN.js → git-commit-horizontal-C5mUmACv.js} +1 -1
  68. package/dist/web/assets/{htmlMode-2u8OHtWO.js → htmlMode-C8btgUt-.js} +1 -1
  69. package/dist/web/assets/index-8H-GDlM5.css +1 -0
  70. package/dist/web/assets/index-D7Vr3zcV.js +192 -0
  71. package/dist/web/assets/index-DUqAiF57.js +1 -0
  72. package/dist/web/assets/{info-CQNTh78o.js → info-D0xuy5IB.js} +1 -1
  73. package/dist/web/assets/{jsonMode-C4CWXViX.js → jsonMode-5zVz9G0X.js} +1 -1
  74. package/dist/web/assets/{lspLanguageFeatures-CBs_BOlB.js → lspLanguageFeatures-DxlSSL7c.js} +1 -1
  75. package/dist/web/assets/{monaco.contribution-lOXpG2xQ.js → monaco.contribution-B1pWWwqT.js} +2 -2
  76. package/dist/web/assets/{monaco.contribution-sd3cFkcN.js → monaco.contribution-BKpVvKK9.js} +2 -2
  77. package/dist/web/assets/{monaco.contribution-C1LUxAKf.js → monaco.contribution-BXdXsSWI.js} +2 -2
  78. package/dist/web/assets/{monaco.contribution-Cpf6dWsh.js → monaco.contribution-CIjjzpUR.js} +2 -2
  79. package/dist/web/assets/{monitor-DsIswrMV.js → monitor-B4-9Shtp.js} +1 -1
  80. package/dist/web/assets/{play-CeRw21NV.js → play-BJdhBqQE.js} +1 -1
  81. package/dist/web/assets/{python-CXYq3BUl.js → python-CEK8J4w_.js} +1 -1
  82. package/dist/web/assets/{refresh-cw-N5b_6AP5.js → refresh-cw-DE0u_nmZ.js} +1 -1
  83. package/dist/web/assets/{reply-BSKjZrJg.js → reply-2MfLJ93n.js} +1 -1
  84. package/dist/web/assets/{save-D0oHH1a3.js → save-CBIVKaf8.js} +1 -1
  85. package/dist/web/assets/{toggleHighContrast-DRjlcwes.js → toggleHighContrast-CC73mtvA.js} +1 -1
  86. package/dist/web/assets/training-BY8siNUK.css +1 -0
  87. package/dist/web/assets/training-model-data-BXUCzS0p.js +1 -0
  88. package/dist/web/assets/{tsMode-x-1A7ZcB.js → tsMode-Bhn9wr_1.js} +1 -1
  89. package/dist/web/assets/{wifi-off-Dt52ofzh.js → wifi-off-BKTqnqj0.js} +1 -1
  90. package/dist/web/assets/{workers-Ciff6Dzi.js → workers-C_adgrEg.js} +1 -1
  91. package/dist/web/assets/{yaml-CB67Kxnz.js → yaml-DDRAcADU.js} +1 -1
  92. package/dist/web/index.html +2 -2
  93. package/package.json +1 -1
  94. package/dist/chunks/chunk-OG75GMVO.js +0 -1
  95. package/dist/web/assets/CloudWorkView-Dr3xqLE5.js +0 -1
  96. package/dist/web/assets/GetStartedView-C8nyH_PT.js +0 -1
  97. package/dist/web/assets/LabsExtensions-GtJzxDnK.js +0 -1
  98. package/dist/web/assets/LabsProfile-DG7rWYur.js +0 -1
  99. package/dist/web/assets/LabsRoute-D6MDHBzq.js +0 -2
  100. package/dist/web/assets/LabsRoute-TEt9j1vB.css +0 -1
  101. package/dist/web/assets/LabsSignals-BfsDN86w.js +0 -1
  102. package/dist/web/assets/LabsSignals-D6iA6Ryl.css +0 -1
  103. package/dist/web/assets/ProfileSettingsSection-DTQS2DVL.css +0 -1
  104. package/dist/web/assets/ProfileSettingsSection-DxEzoF3K.js +0 -1
  105. package/dist/web/assets/ProfileView-CEscW7Ru.js +0 -1
  106. package/dist/web/assets/SettingsView-JIRR_FO4.js +0 -63
  107. package/dist/web/assets/TeamChatView-B5M1ThhJ.js +0 -3
  108. package/dist/web/assets/TrainingSettingsSection-BDYvI2te.js +0 -1
  109. package/dist/web/assets/TrainingView-CoCFK6W3.js +0 -5
  110. package/dist/web/assets/WorkspaceDiffPanel-CMxKQeo7.js +0 -14
  111. package/dist/web/assets/index-BdY6A-YN.css +0 -1
  112. package/dist/web/assets/index-CU2qhUYV.js +0 -195
  113. package/dist/web/assets/index-Dz2B3-4C.js +0 -1
  114. package/dist/web/assets/settings-lists-BEg6LMcy.css +0 -1
  115. package/dist/web/assets/training-model-data-BPrc68VG.js +0 -1
@@ -0,0 +1,1222 @@
1
+ import { createRequire as __openpondCreateRequire } from "node:module"; var require = __openpondCreateRequire(import.meta.url);
2
+ import {
3
+ now
4
+ } from "./chunk-Q5MZCRJA.js";
5
+
6
+ // ../server/src/workspace/workspace-command.ts
7
+ import { spawn, spawnSync } from "node:child_process";
8
+ import { accessSync, constants, existsSync, promises as fs } from "node:fs";
9
+ import path from "node:path";
10
+ var MACOS_GIT_MISSING_DEVELOPER_TOOLS_ERROR = "Git is required for OpenPond app workspaces, but macOS reports Apple Command Line Tools are not installed. OpenPond skipped /usr/bin/git to avoid opening the Xcode installer prompt. Install Command Line Tools with `xcode-select --install`, install Git from Homebrew, or put a non-Apple git binary earlier on PATH.";
11
+ var resolvedGitCommand;
12
+ function isExecutable(filePath) {
13
+ try {
14
+ if (!existsSync(filePath)) return false;
15
+ accessSync(filePath, constants.X_OK);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+ function firstNonAppleGitOnPath() {
22
+ const pathEntries = (process.env.PATH ?? "").split(path.delimiter).map((entry) => entry.trim()).filter(Boolean);
23
+ for (const entry of pathEntries) {
24
+ const candidate = path.join(entry, "git");
25
+ if (candidate === "/usr/bin/git" || candidate === "/bin/git") continue;
26
+ if (isExecutable(candidate)) return candidate;
27
+ }
28
+ return null;
29
+ }
30
+ function appleDeveloperToolsAvailable() {
31
+ const result = spawnSync("/usr/bin/xcode-select", ["-p"], {
32
+ encoding: "utf8",
33
+ stdio: ["ignore", "pipe", "pipe"]
34
+ });
35
+ return result.status === 0 && Boolean(result.stdout.trim());
36
+ }
37
+ function resolveWorkspaceCommand(command) {
38
+ if (command !== "git" || process.platform !== "darwin") return { command };
39
+ if (resolvedGitCommand !== void 0) {
40
+ return { command: resolvedGitCommand };
41
+ }
42
+ const nonAppleGit = firstNonAppleGitOnPath();
43
+ if (nonAppleGit) {
44
+ resolvedGitCommand = nonAppleGit;
45
+ return { command: nonAppleGit };
46
+ }
47
+ if (appleDeveloperToolsAvailable()) {
48
+ resolvedGitCommand = "git";
49
+ return { command: "git" };
50
+ }
51
+ return { error: MACOS_GIT_MISSING_DEVELOPER_TOOLS_ERROR };
52
+ }
53
+ function runCommand(command, args, cwd, extraEnv = {}, stdin) {
54
+ return new Promise((resolve) => {
55
+ const resolved = resolveWorkspaceCommand(command);
56
+ if ("error" in resolved) {
57
+ resolve({ code: 1, stdout: "", stderr: resolved.error });
58
+ return;
59
+ }
60
+ const child = spawn(resolved.command, args, {
61
+ cwd,
62
+ env: {
63
+ ...process.env,
64
+ ...extraEnv,
65
+ GIT_TERMINAL_PROMPT: "0"
66
+ },
67
+ stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
68
+ });
69
+ const stdoutChunks = [];
70
+ const stderrChunks = [];
71
+ if (stdin !== void 0) {
72
+ child.stdin?.end(stdin);
73
+ }
74
+ child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
75
+ child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
76
+ child.on("error", (error) => {
77
+ resolve({ code: 1, stdout: "", stderr: error.message });
78
+ });
79
+ child.on("close", (code) => {
80
+ resolve({
81
+ code: code ?? 1,
82
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
83
+ stderr: Buffer.concat(stderrChunks).toString("utf8")
84
+ });
85
+ });
86
+ });
87
+ }
88
+ function isMacOSGitDeveloperToolsMissing(value) {
89
+ return Boolean(value && value.includes(MACOS_GIT_MISSING_DEVELOPER_TOOLS_ERROR.slice(0, 80)));
90
+ }
91
+ async function checkWorkspaceGitAvailability(cwd) {
92
+ await fs.mkdir(cwd, { recursive: true });
93
+ const result = await runCommand("git", ["--version"], cwd);
94
+ if (result.code === 0) {
95
+ return {
96
+ ok: true,
97
+ command: resolvedGitCommand ?? "git",
98
+ version: (result.stdout || result.stderr).trim()
99
+ };
100
+ }
101
+ const error = result.stderr.trim() || result.stdout.trim() || "Git is required for OpenPond app workspaces.";
102
+ return {
103
+ ok: false,
104
+ error,
105
+ installAction: process.platform === "darwin" && isMacOSGitDeveloperToolsMissing(error) ? "macos_command_line_tools" : "manual_git_install"
106
+ };
107
+ }
108
+ function startMacOSCommandLineToolsInstall() {
109
+ if (process.platform !== "darwin") {
110
+ return { ok: false, error: "Apple Command Line Tools installation is only available on macOS." };
111
+ }
112
+ if (appleDeveloperToolsAvailable()) {
113
+ return { ok: true, message: "Apple Command Line Tools already appear to be installed." };
114
+ }
115
+ try {
116
+ const child = spawn("/usr/bin/xcode-select", ["--install"], {
117
+ detached: true,
118
+ stdio: "ignore"
119
+ });
120
+ child.unref();
121
+ return { ok: true, message: "Opened the Apple Command Line Tools installer." };
122
+ } catch (error) {
123
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
124
+ }
125
+ }
126
+
127
+ // ../server/src/workspace/workspace-common.ts
128
+ import { promises as fs2 } from "node:fs";
129
+ import path2 from "node:path";
130
+ var MAX_PATCH_CHARS = 24e3;
131
+ var MAX_FILE_CHARS = 8e4;
132
+ var MAX_READABLE_FILE_BYTES = 512 * 1024;
133
+ var MAX_WORKSPACE_IMAGE_BYTES = 15 * 1024 * 1024;
134
+ var GENERATED_WORKSPACE_DIRS = /* @__PURE__ */ new Set([
135
+ ".cache",
136
+ ".eggs",
137
+ ".git",
138
+ ".ipynb_checkpoints",
139
+ ".mypy_cache",
140
+ ".next",
141
+ ".nox",
142
+ ".nuxt",
143
+ ".openpond",
144
+ ".output",
145
+ ".parcel-cache",
146
+ ".pytest_cache",
147
+ ".pyre",
148
+ ".ruff_cache",
149
+ ".svelte-kit",
150
+ ".tox",
151
+ ".turbo",
152
+ ".venv",
153
+ "__pycache__",
154
+ "artifacts",
155
+ "build",
156
+ "coverage",
157
+ "dist",
158
+ "env",
159
+ "node_modules",
160
+ "out",
161
+ "site-packages",
162
+ "target",
163
+ "venv",
164
+ "volumes"
165
+ ]);
166
+ var BINARY_WORKSPACE_EXTENSIONS = /* @__PURE__ */ new Set([
167
+ ".7z",
168
+ ".avif",
169
+ ".db",
170
+ ".gif",
171
+ ".gz",
172
+ ".ico",
173
+ ".jpeg",
174
+ ".jpg",
175
+ ".mov",
176
+ ".mp4",
177
+ ".pdf",
178
+ ".png",
179
+ ".sqlite",
180
+ ".tar",
181
+ ".webp",
182
+ ".xls",
183
+ ".xlsm",
184
+ ".xlsx",
185
+ ".zip"
186
+ ]);
187
+ var WORKSPACE_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Map([
188
+ [".avif", "image/avif"],
189
+ [".gif", "image/gif"],
190
+ [".jpeg", "image/jpeg"],
191
+ [".jpg", "image/jpeg"],
192
+ [".png", "image/png"],
193
+ [".webp", "image/webp"]
194
+ ]);
195
+ function safeAppSegment(appId) {
196
+ return appId.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "app";
197
+ }
198
+ function sourceFromRemote(remoteUrl) {
199
+ if (!remoteUrl) return "unknown";
200
+ try {
201
+ const host = new URL(remoteUrl).hostname.toLowerCase();
202
+ if (host === "github.com" || host.endsWith(".github.com")) return "github";
203
+ if (host === "openpond.ai" || host.endsWith(".openpond.ai")) return "openpond";
204
+ } catch {
205
+ return "unknown";
206
+ }
207
+ return "local_git";
208
+ }
209
+ function redactRemoteUrl(value) {
210
+ return value.replace(/x-access-token:[^@]+@/g, "x-access-token:***@").replace(/Authorization:\s*Basic\s+[A-Za-z0-9+/=]+/gi, "Authorization: Basic ***");
211
+ }
212
+ async function pathExists(targetPath) {
213
+ try {
214
+ await fs2.access(targetPath);
215
+ return true;
216
+ } catch {
217
+ return false;
218
+ }
219
+ }
220
+ async function isGitRepo(repoPath) {
221
+ if (!await pathExists(repoPath)) return false;
222
+ const result = await runCommand("git", ["rev-parse", "--git-dir"], repoPath);
223
+ return result.code === 0;
224
+ }
225
+ function truncatePatch(value) {
226
+ if (value.length <= MAX_PATCH_CHARS) return value;
227
+ return `${value.slice(0, MAX_PATCH_CHARS)}
228
+
229
+ [diff truncated]`;
230
+ }
231
+ function truncateFileContent(value) {
232
+ if (value.length <= MAX_FILE_CHARS) return value;
233
+ return `${value.slice(0, MAX_FILE_CHARS)}
234
+
235
+ [file truncated]`;
236
+ }
237
+ async function readWorkspaceFile(repoPath, filePath) {
238
+ const target = path2.resolve(repoPath, filePath);
239
+ const root = path2.resolve(repoPath);
240
+ if (target !== root && !target.startsWith(`${root}${path2.sep}`)) return null;
241
+ try {
242
+ const stat = await fs2.lstat(target);
243
+ if (!stat.isFile()) return null;
244
+ if (!isReadableTextWorkspaceFile(filePath, stat.size)) return null;
245
+ return truncateFileContent(await fs2.readFile(target, "utf8"));
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
250
+ function workspaceImageContentType(filePath) {
251
+ return WORKSPACE_IMAGE_CONTENT_TYPES.get(path2.extname(filePath).toLowerCase()) ?? null;
252
+ }
253
+ async function readWorkspaceImageFile(repoPath, filePath) {
254
+ const normalizedPath = normalizeWorkspaceFilePath(filePath);
255
+ if (!normalizedPath) return null;
256
+ const contentType = workspaceImageContentType(normalizedPath);
257
+ if (!contentType) return null;
258
+ const target = path2.resolve(repoPath, normalizedPath);
259
+ const root = path2.resolve(repoPath);
260
+ if (target !== root && !target.startsWith(`${root}${path2.sep}`)) return null;
261
+ try {
262
+ const stat = await fs2.lstat(target);
263
+ if (!stat.isFile() || stat.size > MAX_WORKSPACE_IMAGE_BYTES) return null;
264
+ return {
265
+ path: normalizedPath,
266
+ contentType,
267
+ bytes: await fs2.readFile(target),
268
+ sizeBytes: stat.size
269
+ };
270
+ } catch {
271
+ return null;
272
+ }
273
+ }
274
+ async function readLocalImageFile(filePath) {
275
+ const cleanedPath = cleanLocalImagePath(filePath);
276
+ if (!cleanedPath) return null;
277
+ const contentType = workspaceImageContentType(cleanedPath);
278
+ if (!contentType) return null;
279
+ const target = path2.resolve(cleanedPath);
280
+ try {
281
+ const stat = await fs2.lstat(target);
282
+ if (!stat.isFile() || stat.size > MAX_WORKSPACE_IMAGE_BYTES) return null;
283
+ return {
284
+ path: target,
285
+ contentType,
286
+ bytes: await fs2.readFile(target),
287
+ sizeBytes: stat.size
288
+ };
289
+ } catch {
290
+ return null;
291
+ }
292
+ }
293
+ function cleanLocalImagePath(value) {
294
+ let cleaned = value.trim();
295
+ if (!cleaned) return null;
296
+ cleaned = cleaned.replace(/^['"`]+|['"`]+$/g, "");
297
+ if (cleaned.startsWith("file://")) {
298
+ try {
299
+ cleaned = decodeURIComponent(new URL(cleaned).pathname);
300
+ } catch {
301
+ cleaned = cleaned.replace(/^file:\/\//, "");
302
+ }
303
+ }
304
+ if (!path2.isAbsolute(cleaned)) return null;
305
+ return cleaned;
306
+ }
307
+ function isReadableTextWorkspaceFile(filePath, sizeBytes) {
308
+ if (sizeBytes > MAX_READABLE_FILE_BYTES) return false;
309
+ return !BINARY_WORKSPACE_EXTENSIONS.has(path2.extname(filePath).toLowerCase());
310
+ }
311
+ async function isWorkspaceDirectory(repoPath, filePath) {
312
+ const target = path2.resolve(repoPath, filePath);
313
+ const root = path2.resolve(repoPath);
314
+ if (target !== root && !target.startsWith(`${root}${path2.sep}`)) return false;
315
+ try {
316
+ const stat = await fs2.lstat(target);
317
+ return stat.isDirectory();
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+ function normalizeWorkspaceFilePath(input) {
323
+ const normalized = input.replace(/\\/g, "/").replace(/^\.\/+/, "");
324
+ if (!normalized.trim() || normalized.startsWith("/")) return null;
325
+ const parts = normalized.split("/").filter(Boolean);
326
+ if (parts.length === 0 || parts.some((part) => part === ".." || part === ".git")) return null;
327
+ return parts.join("/");
328
+ }
329
+ function isGeneratedWorkspacePath(filePath) {
330
+ const normalized = normalizeWorkspaceFilePath(filePath);
331
+ if (!normalized) return true;
332
+ return normalized.split("/").some((part) => GENERATED_WORKSPACE_DIRS.has(part) || part === ".env" || part.startsWith(".env.") || part.endsWith(".egg-info") || part.endsWith(".pyc") || part.endsWith(".pyo"));
333
+ }
334
+ async function isVisibleGitWorkspaceFile(repoPath, filePath) {
335
+ if (isGeneratedWorkspacePath(filePath)) return false;
336
+ const result = await runCommand("git", ["ls-files", "--cached", "--others", "--exclude-standard", "--", filePath], repoPath);
337
+ if (result.code !== 0) return false;
338
+ return result.stdout.split("\n").map((line) => line.trim()).some((line) => line === filePath);
339
+ }
340
+ function parseStatusLine(line) {
341
+ if (!line.trim() || line.startsWith("## ")) return null;
342
+ const code = line.slice(0, 2);
343
+ const rawPath = line.slice(3).trim();
344
+ const path4 = rawPath.includes(" -> ") ? rawPath.split(" -> ").pop().trim() : rawPath;
345
+ if (!path4) return null;
346
+ if (code === "??") return { path: path4, status: "untracked" };
347
+ if (code.includes("A")) return { path: path4, status: "added" };
348
+ if (code.includes("D")) return { path: path4, status: "deleted" };
349
+ if (code.includes("R")) return { path: path4, status: "renamed" };
350
+ return { path: path4, status: "modified" };
351
+ }
352
+ function parseBranchStatusLine(line) {
353
+ const trackingPart = line.replace(/^##\s*/, "").split("...")[1]?.trim();
354
+ if (!trackingPart) return { upstreamBranch: null, ahead: 0, behind: 0 };
355
+ const bracketIndex = trackingPart.indexOf("[");
356
+ const upstreamBranch = (bracketIndex >= 0 ? trackingPart.slice(0, bracketIndex) : trackingPart).trim() || null;
357
+ const aheadMatch = trackingPart.match(/ahead\s+(\d+)/);
358
+ const behindMatch = trackingPart.match(/behind\s+(\d+)/);
359
+ return {
360
+ upstreamBranch,
361
+ ahead: aheadMatch ? Number(aheadMatch[1]) || 0 : 0,
362
+ behind: behindMatch ? Number(behindMatch[1]) || 0 : 0
363
+ };
364
+ }
365
+ function parseNumstat(stdout) {
366
+ const stats = /* @__PURE__ */ new Map();
367
+ for (const line of stdout.split("\n")) {
368
+ if (!line.trim()) continue;
369
+ const [additionsRaw, deletionsRaw, ...pathParts] = line.split(" ");
370
+ const filePath = pathParts.join(" ").trim();
371
+ if (!filePath) continue;
372
+ stats.set(filePath, {
373
+ additions: additionsRaw === "-" ? 0 : Number(additionsRaw) || 0,
374
+ deletions: deletionsRaw === "-" ? 0 : Number(deletionsRaw) || 0
375
+ });
376
+ }
377
+ return stats;
378
+ }
379
+ function uniqueSortedPaths(paths) {
380
+ return Array.from(new Set(paths.filter(Boolean))).sort((left, right) => left.localeCompare(right));
381
+ }
382
+ var MAX_PLAIN_WORKSPACE_FILES = 2e3;
383
+ async function listPlainWorkspaceFiles(rootPath) {
384
+ const files = [];
385
+ async function walk(currentPath, prefix = "") {
386
+ if (files.length >= MAX_PLAIN_WORKSPACE_FILES) return;
387
+ let entries;
388
+ try {
389
+ entries = await fs2.readdir(currentPath, { withFileTypes: true });
390
+ } catch {
391
+ return;
392
+ }
393
+ entries.sort((left, right) => left.name.localeCompare(right.name));
394
+ for (const entry of entries) {
395
+ if (files.length >= MAX_PLAIN_WORKSPACE_FILES) return;
396
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
397
+ if (isGeneratedWorkspacePath(relativePath)) continue;
398
+ const absolutePath = path2.join(currentPath, entry.name);
399
+ if (entry.isDirectory()) {
400
+ await walk(absolutePath, relativePath);
401
+ } else if (entry.isFile()) {
402
+ files.push(relativePath);
403
+ }
404
+ }
405
+ }
406
+ await walk(rootPath);
407
+ return uniqueSortedPaths(files);
408
+ }
409
+ function normalizeGitHost(host) {
410
+ const trimmed = host?.trim();
411
+ if (!trimmed) return null;
412
+ try {
413
+ const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
414
+ return url.host.replace(/\/+$/, "");
415
+ } catch {
416
+ return trimmed.replace(/^https?:\/\//i, "").replace(/\/+$/, "");
417
+ }
418
+ }
419
+ function normalizeGitBaseUrl(baseUrl) {
420
+ const trimmed = baseUrl?.trim();
421
+ if (!trimmed) return null;
422
+ try {
423
+ const url = new URL(trimmed);
424
+ return { protocol: url.protocol || "https:", host: url.host };
425
+ } catch {
426
+ return null;
427
+ }
428
+ }
429
+ function isDefaultOpenPondGitHost(host) {
430
+ const normalized = normalizeGitHost(host);
431
+ return !normalized || normalized === "openpond.ai" || normalized === "www.openpond.ai";
432
+ }
433
+ function expectedRemoteUrl(workspace, gitBaseUrl) {
434
+ const owner = workspace.gitOwner?.trim();
435
+ const repo = workspace.gitRepo?.trim();
436
+ if (!owner || !repo) return null;
437
+ const accountGitBase = normalizeGitBaseUrl(gitBaseUrl);
438
+ const appGitHost = normalizeGitHost(workspace.gitHost);
439
+ const host = accountGitBase && isDefaultOpenPondGitHost(appGitHost) ? accountGitBase.host : appGitHost || accountGitBase?.host || "openpond.ai";
440
+ const protocol = accountGitBase && host === accountGitBase.host ? accountGitBase.protocol : "https:";
441
+ const normalizedRepo = repo.endsWith(".git") ? repo : `${repo}.git`;
442
+ return `${protocol}//${host}/${owner}/${normalizedRepo}`;
443
+ }
444
+ function isOpenPondRemote(remoteUrl) {
445
+ try {
446
+ const host = new URL(remoteUrl).hostname.toLowerCase();
447
+ return host === "openpond.ai" || host.endsWith(".openpond.ai");
448
+ } catch {
449
+ return false;
450
+ }
451
+ }
452
+ function gitBasicAuthEnv(remoteUrl, token) {
453
+ if (!token || !isOpenPondRemote(remoteUrl)) return {};
454
+ const encoded = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
455
+ return {
456
+ GIT_CONFIG_COUNT: "1",
457
+ GIT_CONFIG_KEY_0: "http.extraHeader",
458
+ GIT_CONFIG_VALUE_0: `Authorization: Basic ${encoded}`
459
+ };
460
+ }
461
+ function assertSafeBranchName(branch) {
462
+ const trimmed = branch.trim();
463
+ if (!trimmed) throw new Error("Branch name is required");
464
+ if (trimmed.includes("..")) throw new Error("Branch name cannot contain '..'");
465
+ if (/[~^:\\\s]/.test(trimmed)) throw new Error("Branch name contains unsupported characters");
466
+ if (trimmed.startsWith("/") || trimmed.endsWith("/") || trimmed.startsWith("-")) {
467
+ throw new Error("Branch name is not safe");
468
+ }
469
+ }
470
+ function appWorkspacePaths(storeDir, appId) {
471
+ const workspacePath = path2.join(storeDir, "workspaces", safeAppSegment(appId));
472
+ return {
473
+ workspacePath,
474
+ repoPath: path2.join(workspacePath, "repo")
475
+ };
476
+ }
477
+ async function cloneWorkspace(repoPath, workspacePath, remoteUrl, token) {
478
+ await fs2.mkdir(workspacePath, { recursive: true });
479
+ const alreadyExists = await pathExists(repoPath);
480
+ const result = await runCommand("git", ["clone", "--quiet", remoteUrl, repoPath], workspacePath, gitBasicAuthEnv(remoteUrl, token));
481
+ if (result.code !== 0) {
482
+ if (!alreadyExists) await fs2.rm(repoPath, { recursive: true, force: true }).catch(() => null);
483
+ throw new Error(redactRemoteUrl(result.stderr.trim() || result.stdout.trim() || "git clone failed"));
484
+ }
485
+ const sanitize = await runCommand("git", ["remote", "set-url", "origin", remoteUrl], repoPath);
486
+ if (sanitize.code !== 0) {
487
+ throw new Error(redactRemoteUrl(sanitize.stderr.trim() || sanitize.stdout.trim() || "git remote set-url failed"));
488
+ }
489
+ }
490
+ async function lastFetchAt(repoPath) {
491
+ const fetchHead = await runCommand("git", ["rev-parse", "--git-path", "FETCH_HEAD"], repoPath);
492
+ const fetchHeadPath = fetchHead.code === 0 && fetchHead.stdout.trim() ? path2.resolve(repoPath, fetchHead.stdout.trim()) : path2.join(repoPath, ".git", "FETCH_HEAD");
493
+ try {
494
+ const stat = await fs2.stat(fetchHeadPath);
495
+ return stat.mtime.toISOString();
496
+ } catch {
497
+ return null;
498
+ }
499
+ }
500
+
501
+ // ../server/src/workspace/workspace-state.ts
502
+ import { promises as fs3 } from "node:fs";
503
+ async function loadWorkspaceState(storeDir, app, options = {}) {
504
+ return loadWorkspaceStateAtPath(appWorkspacePaths(storeDir, app.id), app, options);
505
+ }
506
+ async function loadWorkspaceStateAtPath(paths, workspace, options = {}) {
507
+ const { workspacePath, repoPath } = paths;
508
+ const expected = expectedRemoteUrl(workspace, options.gitBaseUrl);
509
+ await fs3.mkdir(workspacePath, { recursive: true });
510
+ let initialized = await isGitRepo(repoPath);
511
+ let error = null;
512
+ if (!initialized && options.clone && expected) {
513
+ try {
514
+ await cloneWorkspace(repoPath, workspacePath, expected, options.token);
515
+ initialized = true;
516
+ } catch (cloneError) {
517
+ error = cloneError instanceof Error ? cloneError.message : String(cloneError);
518
+ }
519
+ }
520
+ if (!initialized) {
521
+ if (options.allowPlainFolder && await pathExists(repoPath)) {
522
+ return {
523
+ appId: workspace.id,
524
+ source: "local_folder",
525
+ workspacePath,
526
+ repoPath,
527
+ initialized: true,
528
+ remoteUrl: null,
529
+ expectedRemoteUrl: expected,
530
+ currentBranch: null,
531
+ headCommit: null,
532
+ upstreamBranch: null,
533
+ ahead: 0,
534
+ behind: 0,
535
+ diverged: false,
536
+ linkedSourceHeadCommit: options.linkedSourceHeadCommit?.trim() || null,
537
+ aheadOfLinkedSource: 0,
538
+ behindLinkedSource: 0,
539
+ divergedFromLinkedSource: false,
540
+ linkedSourceComparisonError: null,
541
+ lastFetchAt: null,
542
+ defaultBranch: workspace.defaultBranch ?? null,
543
+ branches: [],
544
+ dirty: false,
545
+ changedFilesCount: 0,
546
+ untrackedFilesCount: 0,
547
+ error,
548
+ updatedAt: now()
549
+ };
550
+ }
551
+ return {
552
+ appId: workspace.id,
553
+ source: sourceFromRemote(expected),
554
+ workspacePath,
555
+ repoPath,
556
+ initialized,
557
+ remoteUrl: null,
558
+ expectedRemoteUrl: expected,
559
+ currentBranch: null,
560
+ headCommit: null,
561
+ upstreamBranch: null,
562
+ ahead: 0,
563
+ behind: 0,
564
+ diverged: false,
565
+ linkedSourceHeadCommit: options.linkedSourceHeadCommit?.trim() || null,
566
+ aheadOfLinkedSource: 0,
567
+ behindLinkedSource: 0,
568
+ divergedFromLinkedSource: false,
569
+ linkedSourceComparisonError: null,
570
+ lastFetchAt: null,
571
+ defaultBranch: workspace.defaultBranch ?? null,
572
+ branches: [],
573
+ dirty: false,
574
+ changedFilesCount: 0,
575
+ untrackedFilesCount: 0,
576
+ error,
577
+ updatedAt: now()
578
+ };
579
+ }
580
+ const [branchResult, branchesResult, statusResult, remoteResult, headResult] = await Promise.all([
581
+ runCommand("git", ["branch", "--show-current"], repoPath),
582
+ runCommand("git", ["branch", "--format=%(refname:short)"], repoPath),
583
+ runCommand("git", ["status", "--porcelain=v1", "-b"], repoPath),
584
+ runCommand("git", ["config", "--get", "remote.origin.url"], repoPath),
585
+ runCommand("git", ["rev-parse", "--verify", "HEAD"], repoPath)
586
+ ]);
587
+ const remoteUrl = remoteResult.code === 0 ? redactRemoteUrl(remoteResult.stdout.trim()) || null : null;
588
+ const statusLines = statusResult.code === 0 ? statusResult.stdout.split("\n").filter((line) => line.trim()) : [];
589
+ const branchStatus = parseBranchStatusLine(statusLines.find((line) => line.startsWith("## ")) ?? "");
590
+ const changedLines = statusLines.filter((line) => {
591
+ if (line.startsWith("## ")) return false;
592
+ const entry = parseStatusLine(line);
593
+ const normalizedPath = entry ? normalizeWorkspaceFilePath(entry.path) : null;
594
+ return Boolean(normalizedPath && !isGeneratedWorkspacePath(normalizedPath));
595
+ });
596
+ const untrackedFilesCount = changedLines.filter((line) => line.startsWith("??")).length;
597
+ const branch = branchResult.code === 0 && branchResult.stdout.trim() ? branchResult.stdout.trim() : null;
598
+ const headCommit = headResult.code === 0 ? headResult.stdout.trim() || null : null;
599
+ const branches = branchesResult.code === 0 ? branchesResult.stdout.split("\n").map((line) => line.trim()).filter(Boolean) : [];
600
+ const remoteSource = sourceFromRemote(remoteUrl);
601
+ const effectiveSourceUrl = remoteSource === "unknown" && expected ? expected : remoteUrl ?? expected;
602
+ const linkedSourceComparison = await compareLinkedSourceCommit(
603
+ repoPath,
604
+ options.linkedSourceHeadCommit,
605
+ headCommit
606
+ );
607
+ return {
608
+ appId: workspace.id,
609
+ source: effectiveSourceUrl ? sourceFromRemote(effectiveSourceUrl) : "local_git",
610
+ workspacePath,
611
+ repoPath,
612
+ initialized,
613
+ remoteUrl,
614
+ expectedRemoteUrl: expected,
615
+ currentBranch: branch,
616
+ headCommit,
617
+ upstreamBranch: branchStatus.upstreamBranch,
618
+ ahead: branchStatus.ahead,
619
+ behind: branchStatus.behind,
620
+ diverged: branchStatus.ahead > 0 && branchStatus.behind > 0,
621
+ linkedSourceHeadCommit: linkedSourceComparison.linkedSourceHeadCommit,
622
+ aheadOfLinkedSource: linkedSourceComparison.aheadOfLinkedSource,
623
+ behindLinkedSource: linkedSourceComparison.behindLinkedSource,
624
+ divergedFromLinkedSource: linkedSourceComparison.divergedFromLinkedSource,
625
+ linkedSourceComparisonError: linkedSourceComparison.error,
626
+ lastFetchAt: await lastFetchAt(repoPath),
627
+ defaultBranch: workspace.defaultBranch ?? null,
628
+ branches,
629
+ dirty: changedLines.length > 0,
630
+ changedFilesCount: changedLines.length,
631
+ untrackedFilesCount,
632
+ error,
633
+ updatedAt: now()
634
+ };
635
+ }
636
+ async function compareLinkedSourceCommit(repoPath, linkedSourceHeadCommit, headCommit) {
637
+ const linkedCommit = linkedSourceHeadCommit?.trim() || null;
638
+ const empty = {
639
+ linkedSourceHeadCommit: linkedCommit,
640
+ aheadOfLinkedSource: 0,
641
+ behindLinkedSource: 0,
642
+ divergedFromLinkedSource: false,
643
+ error: null
644
+ };
645
+ if (!linkedCommit || !headCommit || linkedCommit === headCommit) return empty;
646
+ const result = await runCommand(
647
+ "git",
648
+ ["rev-list", "--left-right", "--count", `${linkedCommit}...${headCommit}`],
649
+ repoPath
650
+ );
651
+ if (result.code !== 0) {
652
+ return {
653
+ ...empty,
654
+ error: result.stderr.trim() || result.stdout.trim() || "Unable to compare linked source commit."
655
+ };
656
+ }
657
+ const [behindRaw, aheadRaw] = result.stdout.trim().split(/\s+/, 2);
658
+ const behindLinkedSource = Number(behindRaw) || 0;
659
+ const aheadOfLinkedSource = Number(aheadRaw) || 0;
660
+ return {
661
+ linkedSourceHeadCommit: linkedCommit,
662
+ aheadOfLinkedSource,
663
+ behindLinkedSource,
664
+ divergedFromLinkedSource: aheadOfLinkedSource > 0 && behindLinkedSource > 0,
665
+ error: null
666
+ };
667
+ }
668
+ async function createAndCheckoutBranch(storeDir, app, branch) {
669
+ return createAndCheckoutBranchAtPath(appWorkspacePaths(storeDir, app.id), app, branch);
670
+ }
671
+ async function createAndCheckoutBranchAtPath(paths, workspace, branch, options = {}) {
672
+ assertSafeBranchName(branch);
673
+ const state = await loadWorkspaceStateAtPath(paths, workspace, { ...options, clone: false });
674
+ if (!state.initialized) throw new Error(state.error || "Workspace is not initialized");
675
+ const result = await runCommand("git", ["checkout", "-b", branch.trim()], state.repoPath);
676
+ if (result.code !== 0) {
677
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Unable to create branch");
678
+ }
679
+ return loadWorkspaceStateAtPath(paths, workspace, { ...options, clone: false });
680
+ }
681
+ async function checkoutBranch(storeDir, app, branch) {
682
+ return checkoutBranchAtPath(appWorkspacePaths(storeDir, app.id), app, branch);
683
+ }
684
+ async function checkoutBranchAtPath(paths, workspace, branch, options = {}) {
685
+ assertSafeBranchName(branch);
686
+ const state = await loadWorkspaceStateAtPath(paths, workspace, { ...options, clone: false });
687
+ if (!state.initialized) throw new Error(state.error || "Workspace is not initialized");
688
+ if (state.dirty && state.currentBranch !== branch.trim()) {
689
+ throw new Error("Commit or discard local changes before switching branches");
690
+ }
691
+ const result = await runCommand("git", ["checkout", branch.trim()], state.repoPath);
692
+ if (result.code !== 0) {
693
+ throw new Error(result.stderr.trim() || result.stdout.trim() || "Unable to switch branch");
694
+ }
695
+ return loadWorkspaceStateAtPath(paths, workspace, { ...options, clone: false });
696
+ }
697
+
698
+ // ../server/src/workspace/workspace-diff.ts
699
+ import path3 from "node:path";
700
+ import { promises as fs4 } from "node:fs";
701
+ var WORKSPACE_DIFF_FILE_CONCURRENCY = 6;
702
+ var WORKSPACE_DIFF_CACHE_MAX_ENTRIES = 60;
703
+ var WORKSPACE_EXPLICIT_FILE_SEARCH_MAX_ENTRIES = 1e4;
704
+ var workspaceDiffCache = /* @__PURE__ */ new Map();
705
+ async function mapWorkspaceDiffEntriesWithConcurrency(entries, mapper, concurrency = WORKSPACE_DIFF_FILE_CONCURRENCY) {
706
+ const limit = Math.max(1, Math.floor(concurrency));
707
+ const results = new Array(entries.length);
708
+ let nextIndex = 0;
709
+ async function worker() {
710
+ while (nextIndex < entries.length) {
711
+ const index = nextIndex;
712
+ nextIndex += 1;
713
+ results[index] = await mapper(entries[index], index);
714
+ }
715
+ }
716
+ await Promise.all(
717
+ Array.from({ length: Math.min(limit, entries.length) }, () => worker())
718
+ );
719
+ return results;
720
+ }
721
+ function clearWorkspaceDiffCacheForTests() {
722
+ workspaceDiffCache.clear();
723
+ }
724
+ async function loadWorkspaceDiff(storeDir, app, options = {}) {
725
+ const { repoPath } = appWorkspacePaths(storeDir, app.id);
726
+ return loadWorkspaceDiffAtPath(repoPath, app.id, options);
727
+ }
728
+ async function loadWorkspaceDiffAtPath(repoPath, workspaceId, options = {}) {
729
+ const initialized = await isGitRepo(repoPath);
730
+ if (!initialized) {
731
+ const repoFiles2 = await listPlainWorkspaceFiles(repoPath);
732
+ return {
733
+ appId: workspaceId,
734
+ repoPath,
735
+ initialized: repoFiles2.length > 0 || await pathExists(repoPath),
736
+ dirty: false,
737
+ filesChanged: 0,
738
+ additions: 0,
739
+ deletions: 0,
740
+ repoFiles: repoFiles2,
741
+ files: [],
742
+ error: null,
743
+ updatedAt: now()
744
+ };
745
+ }
746
+ const gitContext = await gitWorkspaceContext(repoPath);
747
+ const pathspec = gitContext.pathPrefix || ".";
748
+ const [headResult, statusResult] = await Promise.all([
749
+ runCommand("git", ["rev-parse", "HEAD"], gitContext.commandCwd),
750
+ runCommand("git", ["status", "--porcelain=v1", "-uall", "--", pathspec], gitContext.commandCwd)
751
+ ]);
752
+ if (statusResult.code !== 0) {
753
+ return {
754
+ appId: workspaceId,
755
+ repoPath,
756
+ initialized,
757
+ dirty: false,
758
+ filesChanged: 0,
759
+ additions: 0,
760
+ deletions: 0,
761
+ repoFiles: [],
762
+ files: [],
763
+ error: statusResult.stderr.trim() || statusResult.stdout.trim() || "Unable to read workspace diff",
764
+ updatedAt: now()
765
+ };
766
+ }
767
+ const entries = statusResult.stdout.split("\n").map(parseStatusLine).filter((entry) => Boolean(entry)).map((entry) => {
768
+ const scopedPath = pathInWorkspace(entry.path, gitContext.pathPrefix);
769
+ if (!scopedPath) return null;
770
+ const normalizedPath = normalizeWorkspaceFilePath(scopedPath);
771
+ return normalizedPath ? { ...entry, path: normalizedPath } : null;
772
+ }).filter((entry) => {
773
+ if (!entry) return false;
774
+ return !isGeneratedWorkspacePath(entry.path);
775
+ });
776
+ const worktreeSignature = await workspaceDiffWorktreeSignature(repoPath, entries);
777
+ const cacheKey = workspaceDiffCacheKey({
778
+ repoPath,
779
+ workspaceId,
780
+ includeFileDetails: Boolean(options.includeFileDetails),
781
+ head: headResult.code === 0 ? headResult.stdout.trim() : "unborn",
782
+ pathPrefix: gitContext.pathPrefix,
783
+ status: statusResult.stdout,
784
+ worktreeSignature
785
+ });
786
+ const cached = workspaceDiffCache.get(cacheKey);
787
+ if (cached) return cloneWorkspaceDiffSummary(cached);
788
+ const fileEntries = (await mapWorkspaceDiffEntriesWithConcurrency(entries, async (entry) => entry.status === "deleted" || !await isWorkspaceDirectory(repoPath, entry.path) ? entry : null)).filter((entry) => Boolean(entry));
789
+ const trackedFilesResult = await runCommand("git", ["ls-files", "--", pathspec], gitContext.commandCwd);
790
+ const repoFiles = uniqueSortedPaths([
791
+ ...trackedFilesResult.code === 0 ? trackedFilesResult.stdout.split("\n").map((line) => pathInWorkspace(line.trim(), gitContext.pathPrefix)).filter((line) => Boolean(line)) : [],
792
+ ...fileEntries.map((entry) => entry.path)
793
+ ].filter((filePath) => !isGeneratedWorkspacePath(filePath)));
794
+ const numstatResult = await runCommand("git", ["diff", "--numstat", "HEAD", "--", pathspec], gitContext.commandCwd);
795
+ const stats = numstatResult.code === 0 ? scopedNumstat(parseNumstat(numstatResult.stdout), gitContext.pathPrefix) : /* @__PURE__ */ new Map();
796
+ const trackedPatchMap = options.includeFileDetails ? await loadTrackedGitPatchMap(gitContext, fileEntries.filter((entry) => entry.status !== "untracked")) : /* @__PURE__ */ new Map();
797
+ const files = await mapWorkspaceDiffEntriesWithConcurrency(fileEntries, async (entry) => {
798
+ const shouldReadContent = options.includeFileDetails || entry.status === "untracked";
799
+ const content = shouldReadContent && entry.status !== "deleted" ? await readWorkspaceFile(repoPath, entry.path) : null;
800
+ const fileStats = entry.status === "untracked" ? { additions: countTextLines(content), deletions: 0 } : stats.get(entry.path) ?? { additions: 0, deletions: 0 };
801
+ if (options.includeFileDetails) {
802
+ return loadGitWorkspaceFileDetail(repoPath, entry, gitContext, fileStats, {
803
+ content,
804
+ patch: entry.status === "untracked" ? null : trackedPatchMap.get(entry.path) ?? null
805
+ });
806
+ }
807
+ return {
808
+ path: entry.path,
809
+ status: entry.status,
810
+ additions: fileStats.additions,
811
+ deletions: fileStats.deletions,
812
+ patch: "",
813
+ content: null
814
+ };
815
+ });
816
+ const additions = files.reduce((sum, file) => sum + file.additions, 0);
817
+ const deletions = files.reduce((sum, file) => sum + file.deletions, 0);
818
+ const summary = {
819
+ appId: workspaceId,
820
+ repoPath,
821
+ initialized,
822
+ dirty: files.length > 0,
823
+ filesChanged: files.length,
824
+ additions,
825
+ deletions,
826
+ repoFiles,
827
+ files,
828
+ error: null,
829
+ updatedAt: now()
830
+ };
831
+ writeWorkspaceDiffCache(cacheKey, summary);
832
+ return cloneWorkspaceDiffSummary(summary);
833
+ }
834
+ async function loadGitCommitDiffAtPath(repoPath, workspaceId, baseCommit, headCommit) {
835
+ const initialized = await isGitRepo(repoPath);
836
+ if (!initialized) throw new Error("Candidate source is not a Git repository.");
837
+ const gitContext = await gitWorkspaceContext(repoPath);
838
+ const range = `${baseCommit}..${headCommit}`;
839
+ const [base, head, statusResult, patchResult, repoFilesResult] = await Promise.all([
840
+ runCommand("git", ["rev-parse", "--verify", `${baseCommit}^{commit}`], gitContext.commandCwd),
841
+ runCommand("git", ["rev-parse", "--verify", `${headCommit}^{commit}`], gitContext.commandCwd),
842
+ runCommand(
843
+ "git",
844
+ ["-c", "core.quotePath=false", "diff", "--name-status", "--find-renames", range, "--", "."],
845
+ gitContext.commandCwd
846
+ ),
847
+ runCommand(
848
+ "git",
849
+ ["-c", "core.quotePath=false", "diff", "--no-ext-diff", "--unified=80", range, "--", "."],
850
+ gitContext.commandCwd
851
+ ),
852
+ runCommand(
853
+ "git",
854
+ ["-c", "core.quotePath=false", "ls-tree", "-r", "--name-only", headCommit],
855
+ gitContext.commandCwd
856
+ )
857
+ ]);
858
+ if (base.code !== 0 || head.code !== 0) {
859
+ throw new Error("Candidate Git history is no longer available.");
860
+ }
861
+ if (statusResult.code !== 0) {
862
+ throw new Error(statusResult.stderr.trim() || "Unable to read the candidate change.");
863
+ }
864
+ const entries = parseCommitDiffEntries(statusResult.stdout).filter((entry) => !isGeneratedWorkspacePath(entry.path));
865
+ const patchMap = patchResult.code === 0 ? parseGitPatchStreamByWorkspacePath(patchResult.stdout || patchResult.stderr, "") : /* @__PURE__ */ new Map();
866
+ const files = await mapWorkspaceDiffEntriesWithConcurrency(entries, async (entry) => {
867
+ const statsResult = await runCommand(
868
+ "git",
869
+ ["diff", "--numstat", range, "--", entry.path],
870
+ gitContext.commandCwd
871
+ );
872
+ const stats = statsResult.code === 0 ? [...parseNumstat(statsResult.stdout).values()][0] ?? { additions: 0, deletions: 0 } : { additions: 0, deletions: 0 };
873
+ const contentResult = entry.status === "deleted" ? null : await runCommand("git", ["show", `${headCommit}:${entry.path}`], gitContext.commandCwd);
874
+ return {
875
+ path: entry.path,
876
+ status: entry.status,
877
+ additions: stats.additions,
878
+ deletions: stats.deletions,
879
+ patch: truncatePatch(patchMap.get(entry.path) ?? ""),
880
+ content: contentResult?.code === 0 ? contentResult.stdout : null
881
+ };
882
+ });
883
+ const repoFiles = repoFilesResult.code === 0 ? uniqueSortedPaths(
884
+ repoFilesResult.stdout.split("\n").map((value) => value.trim()).filter((value) => value && !isGeneratedWorkspacePath(value))
885
+ ) : files.map((file) => file.path);
886
+ return {
887
+ appId: workspaceId,
888
+ repoPath,
889
+ initialized: true,
890
+ dirty: files.length > 0,
891
+ filesChanged: files.length,
892
+ additions: files.reduce((sum, file) => sum + file.additions, 0),
893
+ deletions: files.reduce((sum, file) => sum + file.deletions, 0),
894
+ repoFiles,
895
+ files,
896
+ error: null,
897
+ updatedAt: now()
898
+ };
899
+ }
900
+ function parseCommitDiffEntries(output) {
901
+ return output.split(/\r?\n/).filter(Boolean).flatMap((line) => {
902
+ const [rawStatus, firstPath, secondPath] = line.split(" ");
903
+ const statusCode = rawStatus?.[0] ?? "M";
904
+ const rawPath = statusCode === "R" || statusCode === "C" ? secondPath : firstPath;
905
+ const normalized = rawPath ? normalizeWorkspaceFilePath(rawPath) : null;
906
+ if (!normalized) return [];
907
+ const status = statusCode === "A" || statusCode === "C" ? "added" : statusCode === "D" ? "deleted" : statusCode === "R" ? "renamed" : "modified";
908
+ return [{ path: normalized, status }];
909
+ });
910
+ }
911
+ function countTextLines(content) {
912
+ if (!content) return 0;
913
+ const withoutTrailingNewline = content.endsWith("\n") ? content.slice(0, -1) : content;
914
+ return withoutTrailingNewline ? withoutTrailingNewline.split("\n").length : 0;
915
+ }
916
+ async function workspaceDiffWorktreeSignature(repoPath, entries) {
917
+ const rows = await mapWorkspaceDiffEntriesWithConcurrency(entries, async (entry) => {
918
+ if (entry.status === "deleted") return `${entry.path} ${entry.status} deleted`;
919
+ const target = path3.resolve(repoPath, entry.path);
920
+ const root = path3.resolve(repoPath);
921
+ if (target !== root && !target.startsWith(`${root}${path3.sep}`)) {
922
+ return `${entry.path} ${entry.status} outside`;
923
+ }
924
+ try {
925
+ const stat = await fs4.lstat(target);
926
+ const kind = stat.isDirectory() ? "dir" : stat.isFile() ? "file" : "other";
927
+ return `${entry.path} ${entry.status} ${kind} ${stat.size} ${Math.trunc(stat.mtimeMs)} ${Math.trunc(stat.ctimeMs)}`;
928
+ } catch {
929
+ return `${entry.path} ${entry.status} missing`;
930
+ }
931
+ });
932
+ return rows.sort((left, right) => left.localeCompare(right)).join("\n");
933
+ }
934
+ function workspaceDiffCacheKey(input) {
935
+ return JSON.stringify(input);
936
+ }
937
+ function writeWorkspaceDiffCache(key, summary) {
938
+ if (workspaceDiffCache.has(key)) workspaceDiffCache.delete(key);
939
+ workspaceDiffCache.set(key, cloneWorkspaceDiffSummary(summary));
940
+ while (workspaceDiffCache.size > WORKSPACE_DIFF_CACHE_MAX_ENTRIES) {
941
+ const oldest = workspaceDiffCache.keys().next().value;
942
+ if (!oldest) break;
943
+ workspaceDiffCache.delete(oldest);
944
+ }
945
+ }
946
+ function cloneWorkspaceDiffSummary(summary) {
947
+ return {
948
+ ...summary,
949
+ repoFiles: [...summary.repoFiles],
950
+ files: summary.files.map((file) => ({ ...file }))
951
+ };
952
+ }
953
+ async function gitWorkspaceContext(repoPath) {
954
+ const result = await runCommand("git", ["rev-parse", "--show-toplevel"], repoPath);
955
+ if (result.code !== 0) return { commandCwd: repoPath, pathPrefix: "" };
956
+ const topLevel = result.stdout.trim();
957
+ if (!topLevel) return { commandCwd: repoPath, pathPrefix: "" };
958
+ const relative = path3.relative(path3.resolve(topLevel), path3.resolve(repoPath));
959
+ const pathPrefix = normalizeWorkspaceFilePath(relative.replace(/\\/g, "/")) ?? "";
960
+ return {
961
+ commandCwd: topLevel,
962
+ pathPrefix
963
+ };
964
+ }
965
+ function pathInWorkspace(filePath, pathPrefix) {
966
+ const normalizedPath = normalizeWorkspaceFilePath(filePath);
967
+ if (!normalizedPath) return null;
968
+ if (!pathPrefix) return normalizedPath;
969
+ if (normalizedPath === pathPrefix) return null;
970
+ return normalizedPath.startsWith(`${pathPrefix}/`) ? normalizedPath.slice(pathPrefix.length + 1) : null;
971
+ }
972
+ function gitPathForWorkspacePath(filePath, pathPrefix) {
973
+ return pathPrefix ? `${pathPrefix}/${filePath}` : filePath;
974
+ }
975
+ function scopedNumstat(stats, pathPrefix) {
976
+ const scoped = /* @__PURE__ */ new Map();
977
+ for (const [filePath, fileStats] of stats) {
978
+ const workspacePath = pathInWorkspace(filePath, pathPrefix);
979
+ if (workspacePath) scoped.set(workspacePath, fileStats);
980
+ }
981
+ return scoped;
982
+ }
983
+ async function loadTrackedGitPatchMap(gitContext, entries) {
984
+ if (entries.length === 0) return /* @__PURE__ */ new Map();
985
+ const pathspec = gitContext.pathPrefix || ".";
986
+ const result = await runCommand(
987
+ "git",
988
+ ["-c", "core.quotePath=false", "diff", "--no-ext-diff", "--unified=80", "HEAD", "--", pathspec],
989
+ gitContext.commandCwd
990
+ );
991
+ if (result.code !== 0) return /* @__PURE__ */ new Map();
992
+ return parseGitPatchStreamByWorkspacePath(result.stdout || result.stderr, gitContext.pathPrefix);
993
+ }
994
+ function parseGitPatchStreamByWorkspacePath(stdout, pathPrefix) {
995
+ const patches = /* @__PURE__ */ new Map();
996
+ for (const chunk of stdout.split(/(?=^diff --git )/m)) {
997
+ if (!chunk.trim()) continue;
998
+ const workspacePath = workspacePathForGitPatchChunk(chunk, pathPrefix);
999
+ if (workspacePath) patches.set(workspacePath, chunk);
1000
+ }
1001
+ return patches;
1002
+ }
1003
+ function workspacePathForGitPatchChunk(chunk, pathPrefix) {
1004
+ const lines = chunk.split("\n");
1005
+ for (const line of lines) {
1006
+ const newPath = gitPatchMarkerPath(line, "+++ ");
1007
+ if (newPath) return pathInWorkspace(newPath, pathPrefix);
1008
+ }
1009
+ for (const line of lines) {
1010
+ const oldPath = gitPatchMarkerPath(line, "--- ");
1011
+ if (oldPath) return pathInWorkspace(oldPath, pathPrefix);
1012
+ }
1013
+ for (const line of lines) {
1014
+ if (line.startsWith("rename to ")) {
1015
+ const renamedPath = normalizeWorkspaceFilePath(line.slice("rename to ".length).trim());
1016
+ if (renamedPath) return pathInWorkspace(renamedPath, pathPrefix);
1017
+ }
1018
+ }
1019
+ const header = lines.find((line) => line.startsWith("diff --git "));
1020
+ if (!header) return null;
1021
+ const destinationIndex = header.lastIndexOf(" b/");
1022
+ if (destinationIndex < 0) return null;
1023
+ const destinationPath = normalizeWorkspaceFilePath(header.slice(destinationIndex + 3).trim());
1024
+ return destinationPath ? pathInWorkspace(destinationPath, pathPrefix) : null;
1025
+ }
1026
+ function gitPatchMarkerPath(line, prefix) {
1027
+ if (!line.startsWith(prefix)) return null;
1028
+ const value = line.slice(prefix.length).trim();
1029
+ if (!value || value === "/dev/null") return null;
1030
+ const withoutGitPrefix = value.startsWith("a/") || value.startsWith("b/") ? value.slice(2) : value;
1031
+ return normalizeWorkspaceFilePath(withoutGitPrefix);
1032
+ }
1033
+ async function loadWorkspaceFile(storeDir, app, filePath) {
1034
+ const { repoPath } = appWorkspacePaths(storeDir, app.id);
1035
+ return loadWorkspaceFileAtPath(repoPath, filePath);
1036
+ }
1037
+ async function loadWorkspaceImageFile(storeDir, app, filePath) {
1038
+ const { repoPath } = appWorkspacePaths(storeDir, app.id);
1039
+ return loadWorkspaceImageFileAtPath(repoPath, filePath);
1040
+ }
1041
+ async function loadWorkspaceImageFileAtPath(repoPath, filePath) {
1042
+ const initialized = await isGitRepo(repoPath);
1043
+ const normalizedPath = normalizeWorkspaceFileRequestPath(repoPath, filePath);
1044
+ if (!normalizedPath) throw new Error("Image not found");
1045
+ if (initialized && !await isVisibleGitWorkspaceFile(repoPath, normalizedPath)) throw new Error("Image not found");
1046
+ const image = await readWorkspaceImageFile(repoPath, normalizedPath);
1047
+ if (!image) throw new Error("Image not found");
1048
+ return image;
1049
+ }
1050
+ function normalizeWorkspaceFileRequestPath(repoPath, filePath) {
1051
+ const cleaned = cleanWorkspaceFileRequestPath(filePath);
1052
+ if (!cleaned) return null;
1053
+ if (!path3.isAbsolute(cleaned)) return normalizeWorkspaceFilePath(cleaned);
1054
+ const relative = path3.relative(path3.resolve(repoPath), path3.resolve(cleaned));
1055
+ if (!relative || relative.startsWith("..") || path3.isAbsolute(relative)) return null;
1056
+ return normalizeWorkspaceFilePath(relative);
1057
+ }
1058
+ function cleanWorkspaceFileRequestPath(value) {
1059
+ let cleaned = value.trim();
1060
+ if (!cleaned) return null;
1061
+ cleaned = cleaned.replace(/^['"`]+|['"`]+$/g, "");
1062
+ const hashIndex = cleaned.indexOf("#");
1063
+ if (hashIndex >= 0) cleaned = cleaned.slice(0, hashIndex);
1064
+ const queryIndex = cleaned.indexOf("?");
1065
+ if (queryIndex >= 0) cleaned = cleaned.slice(0, queryIndex);
1066
+ if (cleaned.startsWith("file://")) {
1067
+ try {
1068
+ cleaned = decodeURIComponent(new URL(cleaned).pathname);
1069
+ } catch {
1070
+ cleaned = cleaned.replace(/^file:\/\//, "");
1071
+ }
1072
+ }
1073
+ return cleaned.trim() || null;
1074
+ }
1075
+ async function loadWorkspaceFileAtPath(repoPath, filePath) {
1076
+ const initialized = await isGitRepo(repoPath);
1077
+ const normalizedPath = normalizeWorkspaceFileRequestPath(repoPath, filePath);
1078
+ if (!normalizedPath) throw new Error("File not found");
1079
+ if (isGeneratedWorkspacePath(normalizedPath)) throw new Error("File not found");
1080
+ if (initialized) {
1081
+ const detail = await loadGitWorkspaceFileAtPath(repoPath, normalizedPath);
1082
+ if (detail) return detail;
1083
+ }
1084
+ let resolvedPath = normalizedPath;
1085
+ let content = await readWorkspaceFile(repoPath, resolvedPath);
1086
+ if (content === null) {
1087
+ const basenameMatch = await resolveExplicitMarkdownBasenamePath(repoPath, normalizedPath);
1088
+ if (basenameMatch) {
1089
+ resolvedPath = basenameMatch;
1090
+ content = await readWorkspaceFile(repoPath, resolvedPath);
1091
+ }
1092
+ }
1093
+ if (content === null) throw new Error("File not found");
1094
+ return {
1095
+ path: resolvedPath,
1096
+ status: "unchanged",
1097
+ additions: 0,
1098
+ deletions: 0,
1099
+ patch: "",
1100
+ content
1101
+ };
1102
+ }
1103
+ async function loadGitWorkspaceFileAtPath(repoPath, filePath) {
1104
+ const gitContext = await gitWorkspaceContext(repoPath);
1105
+ const gitPath = gitPathForWorkspacePath(filePath, gitContext.pathPrefix);
1106
+ const statusResult = await runCommand("git", ["status", "--porcelain=v1", "-uall", "--", gitPath], gitContext.commandCwd);
1107
+ if (statusResult.code !== 0) return null;
1108
+ const entry = statusResult.stdout.split("\n").map(parseStatusLine).filter((item) => Boolean(item)).map((item) => {
1109
+ const scopedPath = pathInWorkspace(item.path, gitContext.pathPrefix);
1110
+ if (!scopedPath) return null;
1111
+ const normalizedScopedPath = normalizeWorkspaceFilePath(scopedPath);
1112
+ return normalizedScopedPath ? { ...item, path: normalizedScopedPath } : null;
1113
+ }).find((item) => Boolean(item && item.path === filePath));
1114
+ if (!entry) return null;
1115
+ const fileStats = entry.status === "untracked" ? { additions: countTextLines(await readWorkspaceFile(repoPath, entry.path)), deletions: 0 } : await gitNumstatForFile(gitContext, entry.path);
1116
+ return loadGitWorkspaceFileDetail(repoPath, entry, gitContext, fileStats);
1117
+ }
1118
+ async function resolveExplicitMarkdownBasenamePath(repoPath, normalizedPath) {
1119
+ if (normalizedPath.includes("/") || !isMarkdownWorkspacePath(normalizedPath)) return null;
1120
+ const targetName = normalizedPath.toLowerCase();
1121
+ const matches = [];
1122
+ const queue = [{ absolutePath: repoPath, prefix: "" }];
1123
+ let visitedEntries = 0;
1124
+ while (queue.length > 0 && visitedEntries < WORKSPACE_EXPLICIT_FILE_SEARCH_MAX_ENTRIES) {
1125
+ const current = queue.shift();
1126
+ let entries;
1127
+ try {
1128
+ entries = await fs4.readdir(current.absolutePath, { withFileTypes: true });
1129
+ } catch {
1130
+ continue;
1131
+ }
1132
+ entries.sort((left, right) => left.name.localeCompare(right.name));
1133
+ for (const entry of entries) {
1134
+ if (visitedEntries >= WORKSPACE_EXPLICIT_FILE_SEARCH_MAX_ENTRIES) break;
1135
+ const relativePath = current.prefix ? `${current.prefix}/${entry.name}` : entry.name;
1136
+ if (isGeneratedWorkspacePath(relativePath)) continue;
1137
+ visitedEntries += 1;
1138
+ const absolutePath = path3.join(current.absolutePath, entry.name);
1139
+ if (entry.isDirectory()) {
1140
+ queue.push({ absolutePath, prefix: relativePath });
1141
+ } else if (entry.isFile() && entry.name.toLowerCase() === targetName) {
1142
+ matches.push(relativePath);
1143
+ }
1144
+ }
1145
+ }
1146
+ if (matches.length === 1) return matches[0] ?? null;
1147
+ const workingDocMatches = matches.filter((candidate) => candidate.startsWith("docs/working-docs/"));
1148
+ return workingDocMatches.length === 1 ? workingDocMatches[0] ?? null : null;
1149
+ }
1150
+ function isMarkdownWorkspacePath(filePath) {
1151
+ return /\.(?:md|markdown|mdx)$/i.test(filePath);
1152
+ }
1153
+ async function loadGitWorkspaceFileDetail(repoPath, entry, gitContext, fileStats, overrides = {}) {
1154
+ const content = overrides.content !== void 0 ? overrides.content : entry.status === "deleted" ? null : await readWorkspaceFile(repoPath, entry.path);
1155
+ const gitPath = gitPathForWorkspacePath(entry.path, gitContext.pathPrefix);
1156
+ const diffArgs = entry.status === "untracked" ? ["diff", "--no-index", "--unified=80", "--", "/dev/null", path3.join(repoPath, entry.path)] : ["diff", "--no-ext-diff", "--unified=80", "HEAD", "--", gitPath];
1157
+ let patch = overrides.patch ?? "";
1158
+ if (overrides.patch === null || overrides.patch === void 0) {
1159
+ const patchResult = await runCommand("git", diffArgs, gitContext.commandCwd);
1160
+ patch = patchResult.stdout || patchResult.stderr;
1161
+ }
1162
+ return {
1163
+ path: entry.path,
1164
+ status: entry.status,
1165
+ additions: fileStats.additions,
1166
+ deletions: fileStats.deletions,
1167
+ patch: truncatePatch(patch),
1168
+ content
1169
+ };
1170
+ }
1171
+ async function gitNumstatForFile(gitContext, filePath) {
1172
+ const gitPath = gitPathForWorkspacePath(filePath, gitContext.pathPrefix);
1173
+ const result = await runCommand("git", ["diff", "--numstat", "HEAD", "--", gitPath], gitContext.commandCwd);
1174
+ if (result.code !== 0) return { additions: 0, deletions: 0 };
1175
+ return scopedNumstat(parseNumstat(result.stdout), gitContext.pathPrefix).get(filePath) ?? { additions: 0, deletions: 0 };
1176
+ }
1177
+
1178
+ export {
1179
+ MACOS_GIT_MISSING_DEVELOPER_TOOLS_ERROR,
1180
+ runCommand,
1181
+ isMacOSGitDeveloperToolsMissing,
1182
+ checkWorkspaceGitAvailability,
1183
+ startMacOSCommandLineToolsInstall,
1184
+ sourceFromRemote,
1185
+ redactRemoteUrl,
1186
+ pathExists,
1187
+ isGitRepo,
1188
+ truncatePatch,
1189
+ readWorkspaceFile,
1190
+ workspaceImageContentType,
1191
+ readWorkspaceImageFile,
1192
+ readLocalImageFile,
1193
+ isWorkspaceDirectory,
1194
+ normalizeWorkspaceFilePath,
1195
+ isGeneratedWorkspacePath,
1196
+ isVisibleGitWorkspaceFile,
1197
+ parseStatusLine,
1198
+ parseBranchStatusLine,
1199
+ parseNumstat,
1200
+ uniqueSortedPaths,
1201
+ listPlainWorkspaceFiles,
1202
+ expectedRemoteUrl,
1203
+ assertSafeBranchName,
1204
+ appWorkspacePaths,
1205
+ cloneWorkspace,
1206
+ lastFetchAt,
1207
+ loadWorkspaceState,
1208
+ loadWorkspaceStateAtPath,
1209
+ createAndCheckoutBranch,
1210
+ createAndCheckoutBranchAtPath,
1211
+ checkoutBranch,
1212
+ checkoutBranchAtPath,
1213
+ mapWorkspaceDiffEntriesWithConcurrency,
1214
+ clearWorkspaceDiffCacheForTests,
1215
+ loadWorkspaceDiff,
1216
+ loadWorkspaceDiffAtPath,
1217
+ loadGitCommitDiffAtPath,
1218
+ loadWorkspaceFile,
1219
+ loadWorkspaceImageFile,
1220
+ loadWorkspaceImageFileAtPath,
1221
+ loadWorkspaceFileAtPath
1222
+ };