@cursor/july 0.1.31 → 0.1.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 (72) hide show
  1. package/dist/bin/agent-serve.js +0 -0
  2. package/dist/channels/slack/post-update-delivery.d.ts +85 -0
  3. package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
  4. package/dist/docs/404.html +1 -1
  5. package/dist/docs/ab.html +2 -2
  6. package/dist/docs/assets/{app.DigB_9cQ.js → app.C2aVczmo.js} +1 -1
  7. package/dist/docs/assets/chunks/@localSearchIndexroot.xgMnlUSO.js +1 -0
  8. package/dist/docs/assets/chunks/{VPLocalSearchBox.BCMX25Bv.js → VPLocalSearchBox.DLEzetbD.js} +1 -1
  9. package/dist/docs/assets/chunks/{theme.BDDWeELx.js → theme.BkOH1oMp.js} +2 -2
  10. package/dist/docs/building-with-agents.html +2 -2
  11. package/dist/docs/concepts.html +2 -2
  12. package/dist/docs/deployment.html +2 -2
  13. package/dist/docs/evals.html +2 -2
  14. package/dist/docs/example-agents/approval-buddy.html +2 -2
  15. package/dist/docs/example-agents/benny.html +2 -2
  16. package/dist/docs/example-agents/bugbot.html +2 -2
  17. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  18. package/dist/docs/example-agents/codeowners-review.html +2 -2
  19. package/dist/docs/example-agents/concierge.html +2 -2
  20. package/dist/docs/example-agents/fsd.html +2 -2
  21. package/dist/docs/example-agents/index.html +2 -2
  22. package/dist/docs/example-agents/knowledge-base.html +2 -2
  23. package/dist/docs/example-agents/oncall.html +2 -2
  24. package/dist/docs/example-agents/security-reviewer.html +2 -2
  25. package/dist/docs/example-agents/slack-agent.html +2 -2
  26. package/dist/docs/example-agents/weather-agent.html +2 -2
  27. package/dist/docs/guides/agent-to-agent.html +2 -2
  28. package/dist/docs/guides/cloud-runtime.html +2 -2
  29. package/dist/docs/guides/github.html +2 -2
  30. package/dist/docs/guides/human-in-the-loop.html +2 -2
  31. package/dist/docs/guides/mcp-oauth.html +2 -2
  32. package/dist/docs/guides/slack.html +2 -2
  33. package/dist/docs/guides/webhooks.html +2 -2
  34. package/dist/docs/hillclimbing.html +2 -2
  35. package/dist/docs/index.html +2 -2
  36. package/dist/docs/quickstart.html +2 -2
  37. package/dist/docs/reference/agent-config.html +2 -2
  38. package/dist/docs/reference/channels.html +2 -2
  39. package/dist/docs/reference/cli.html +2 -2
  40. package/dist/docs/reference/connections.html +2 -2
  41. package/dist/docs/reference/hooks.html +2 -2
  42. package/dist/docs/reference/http-api.html +2 -2
  43. package/dist/docs/reference/instructions.html +2 -2
  44. package/dist/docs/reference/playground.html +2 -2
  45. package/dist/docs/reference/project-layout.html +2 -2
  46. package/dist/docs/reference/prompt.html +2 -2
  47. package/dist/docs/reference/schedules.html +2 -2
  48. package/dist/docs/reference/sessions.html +2 -2
  49. package/dist/docs/reference/skills.html +2 -2
  50. package/dist/docs/reference/subagents.html +2 -2
  51. package/dist/docs/reference/tools.html +2 -2
  52. package/dist/docs/scaffolding-agents.html +2 -2
  53. package/dist/docs/storage.html +2 -2
  54. package/dist/docs/troubleshooting.html +2 -2
  55. package/dist/files-backends/agent-store-presigned-url.d.ts +3 -100
  56. package/dist/files-backends/agent-store-presigned-url.d.ts.map +1 -1
  57. package/dist/files-backends/agent-store-presigned-url.js +990 -314
  58. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  59. package/dist/files-backends/cursor-hosted.js +25 -2
  60. package/dist/internal/json-dir-store.d.ts +32 -0
  61. package/dist/internal/json-dir-store.d.ts.map +1 -0
  62. package/dist/internal/persistence-coordinator.d.ts +127 -0
  63. package/dist/internal/persistence-coordinator.d.ts.map +1 -0
  64. package/dist/multi-tenant.d.ts +80 -0
  65. package/dist/multi-tenant.d.ts.map +1 -0
  66. package/dist/multi-tenant.js +69 -0
  67. package/dist/persistence.d.ts +184 -0
  68. package/dist/persistence.d.ts.map +1 -0
  69. package/package.json +3 -2
  70. package/src/files-backends/agent-store-presigned-url.ts +6 -400
  71. package/src/files-backends/cursor-hosted.ts +32 -6
  72. package/dist/docs/assets/chunks/@localSearchIndexroot.CnFFl07y.js +0 -1
@@ -1,347 +1,1023 @@
1
- /**
2
- * Vendored from `@anysphere/agent-store-sync` `presigned-url.ts` so the
3
- * published `@cursor/july` tarball does not depend on that private workspace
4
- * package (not on npm). Keep in sync when the source validator changes.
5
- */
6
- export class PresignedUrlRejectedError extends Error {
7
- constructor(options) {
8
- super(options.message);
9
- this.name = "PresignedUrlRejectedError";
10
- this.code = options.code;
11
- this.url = options.url;
12
- }
13
- }
14
- /**
15
- * Strict default: accepts `https:` URLs to an AWS S3 endpoint with no
16
- * userinfo and no private/internal address. Rejects DNS hosts that
17
- * resolve into the private IP space and IPv4-mapped IPv6 literals.
18
- */
19
- export function defaultPresignedUrlValidator(url) {
20
- assertSafeAuthority(url);
21
- if (url.protocol !== "https:") {
22
- throw new PresignedUrlRejectedError({
23
- code: "scheme_not_allowed",
24
- message: `Refused presigned URL with non-https scheme: ${url.protocol}`,
25
- url: redactPresignedUrlString(url),
26
- });
27
- }
28
- const hostname = stripIPv6Brackets(url.hostname);
29
- if (isLocalOrInternalHost(hostname)) {
30
- throw new PresignedUrlRejectedError({
31
- code: "private_host_not_allowed",
32
- message: `Refused https presigned URL with private/internal host: ${hostname}`,
33
- url: redactPresignedUrlString(url),
34
- });
35
- }
36
- if (!isAwsS3Hostname(hostname)) {
37
- throw new PresignedUrlRejectedError({
38
- code: "host_not_allowed",
39
- message: `Refused presigned URL whose host is not an AWS S3 endpoint: ${hostname}`,
40
- url: redactPresignedUrlString(url),
1
+ // ../../node_modules/.pnpm/@connectrpc+connect@1.6.1_patch_hash=c9c7616ccfc0246b19c6537f56676d8501713cb6c94b440d13_ef7275568c8b1f1f499006dc986c70d2/node_modules/@connectrpc/connect/dist/esm/code.js
2
+ var Code;
3
+ (function(Code2) {
4
+ Code2[Code2["Canceled"] = 1] = "Canceled";
5
+ Code2[Code2["Unknown"] = 2] = "Unknown";
6
+ Code2[Code2["InvalidArgument"] = 3] = "InvalidArgument";
7
+ Code2[Code2["DeadlineExceeded"] = 4] = "DeadlineExceeded";
8
+ Code2[Code2["NotFound"] = 5] = "NotFound";
9
+ Code2[Code2["AlreadyExists"] = 6] = "AlreadyExists";
10
+ Code2[Code2["PermissionDenied"] = 7] = "PermissionDenied";
11
+ Code2[Code2["ResourceExhausted"] = 8] = "ResourceExhausted";
12
+ Code2[Code2["FailedPrecondition"] = 9] = "FailedPrecondition";
13
+ Code2[Code2["Aborted"] = 10] = "Aborted";
14
+ Code2[Code2["OutOfRange"] = 11] = "OutOfRange";
15
+ Code2[Code2["Unimplemented"] = 12] = "Unimplemented";
16
+ Code2[Code2["Internal"] = 13] = "Internal";
17
+ Code2[Code2["Unavailable"] = 14] = "Unavailable";
18
+ Code2[Code2["DataLoss"] = 15] = "DataLoss";
19
+ Code2[Code2["Unauthenticated"] = 16] = "Unauthenticated";
20
+ })(Code || (Code = {}));
21
+
22
+ // ../agent-store-sync/dist/bcs-transport.js
23
+ var DIRECTORY_LIST_PAGE_SIZE = 1e3;
24
+ var DIRECTORY_LIST_MAX_PAGES = 10;
25
+ var DIRECTORY_LIST_MAX_ENTRIES = DIRECTORY_LIST_PAGE_SIZE * DIRECTORY_LIST_MAX_PAGES;
26
+
27
+ // ../agent-store-sync/dist/conflict-events.js
28
+ import * as fs2 from "node:fs";
29
+
30
+ // ../agent-store-sync/dist/paths.js
31
+ import * as fs from "node:fs";
32
+ import * as os from "node:os";
33
+ import * as path from "node:path";
34
+ var PRIVATE_DIR_MODE = 448;
35
+ var AgentStorePathError = class extends Error {
36
+ constructor({ code, message, failedPath }) {
37
+ super(message);
38
+ this.name = "AgentStorePathError";
39
+ this.code = code;
40
+ this.failedPath = failedPath;
41
+ }
42
+ };
43
+ function assertNoSymlinkInPath(targetPath, options = {}) {
44
+ const deps = getDeps(options);
45
+ const resolved = deps.path.resolve(targetPath);
46
+ const segments = getPathSegments(resolved, deps.path);
47
+ for (const segment of segments) {
48
+ const stat = tryLstat(segment, deps.fs);
49
+ if (!stat) {
50
+ return;
51
+ }
52
+ if (stat.isSymbolicLink()) {
53
+ throw new AgentStorePathError({
54
+ code: "symlink_refused",
55
+ message: `Refusing to use symlinked agent store path: ${segment}`,
56
+ failedPath: segment
57
+ });
58
+ }
59
+ }
60
+ }
61
+ function ensureSecureDirectoryChain(targetDir, options = {}) {
62
+ const deps = getDeps(options);
63
+ const resolvedTarget = deps.path.resolve(targetDir);
64
+ const trustedBase = options.trustedBase !== void 0 ? deps.path.resolve(options.trustedBase) : findTrustedDirectoryAncestor(resolvedTarget, deps);
65
+ if (resolvedTarget === trustedBase) {
66
+ assertRealDirectorySegment(trustedBase, deps);
67
+ return;
68
+ }
69
+ const relative = deps.path.relative(trustedBase, resolvedTarget);
70
+ if (relative.length === 0 || relative.startsWith("..") || deps.path.isAbsolute(relative)) {
71
+ throw new AgentStorePathError({
72
+ code: "invalid_store_base",
73
+ message: `Refusing to create ${resolvedTarget} outside trusted base ${trustedBase}`,
74
+ failedPath: resolvedTarget
75
+ });
76
+ }
77
+ const segments = relative.split(deps.path.sep).filter((segment) => segment.length > 0);
78
+ let current = trustedBase;
79
+ for (const segment of segments) {
80
+ assertRealDirectorySegment(current, deps);
81
+ current = deps.path.join(current, segment);
82
+ ensureRealDirectorySegment(current, deps);
83
+ }
84
+ }
85
+ function getDeps(options) {
86
+ var _a;
87
+ var _b, _c, _d, _e, _f, _g, _h;
88
+ return {
89
+ env: (_b = options.env) !== null && _b !== void 0 ? _b : process.env,
90
+ fs: (_c = options.fs) !== null && _c !== void 0 ? _c : fs,
91
+ geteuid: (_d = options.geteuid) !== null && _d !== void 0 ? _d : (_a = process.geteuid) === null || _a === void 0 ? void 0 : _a.bind(process),
92
+ osTmpdir: (_e = options.osTmpdir) !== null && _e !== void 0 ? _e : os.tmpdir,
93
+ path: (_f = options.path) !== null && _f !== void 0 ? _f : path,
94
+ platform: (_g = options.platform) !== null && _g !== void 0 ? _g : process.platform,
95
+ windowsAcl: (_h = options.windowsAcl) !== null && _h !== void 0 ? _h : {}
96
+ };
97
+ }
98
+ function assertDirectoryStat(targetPath, stat) {
99
+ if (stat.isSymbolicLink()) {
100
+ throw new AgentStorePathError({
101
+ code: "symlink_refused",
102
+ message: `Refusing to use symlinked agent store path: ${targetPath}`,
103
+ failedPath: targetPath
104
+ });
105
+ }
106
+ if (!stat.isDirectory()) {
107
+ throw new AgentStorePathError({
108
+ code: "not_directory",
109
+ message: `Agent store path is not a directory: ${targetPath}`,
110
+ failedPath: targetPath
111
+ });
112
+ }
113
+ }
114
+ function findTrustedDirectoryAncestor(targetPath, deps) {
115
+ var _a;
116
+ const segments = getPathSegments(deps.path.resolve(targetPath), deps.path);
117
+ let trusted = (_a = segments[0]) !== null && _a !== void 0 ? _a : deps.path.resolve(targetPath);
118
+ for (let i = 1; i < segments.length; i++) {
119
+ const segment = segments[i];
120
+ const stat = tryLstat(segment, deps.fs);
121
+ if (!stat) {
122
+ break;
123
+ }
124
+ if (stat.isSymbolicLink()) {
125
+ throw new AgentStorePathError({
126
+ code: "symlink_refused",
127
+ message: `Refusing to use symlinked agent store path: ${segment}`,
128
+ failedPath: segment
129
+ });
130
+ }
131
+ if (!stat.isDirectory()) {
132
+ throw new AgentStorePathError({
133
+ code: "not_directory",
134
+ message: `Agent store path is not a directory: ${segment}`,
135
+ failedPath: segment
136
+ });
137
+ }
138
+ trusted = segment;
139
+ }
140
+ return trusted;
141
+ }
142
+ function assertRealDirectorySegment(targetPath, deps) {
143
+ const stat = tryLstat(targetPath, deps.fs);
144
+ if (!stat) {
145
+ throw new AgentStorePathError({
146
+ code: "not_directory",
147
+ message: `Trusted directory does not exist: ${targetPath}`,
148
+ failedPath: targetPath
149
+ });
150
+ }
151
+ assertDirectoryStat(targetPath, stat);
152
+ }
153
+ function ensureRealDirectorySegment(targetPath, deps) {
154
+ let stat = tryLstat(targetPath, deps.fs);
155
+ if (!stat) {
156
+ try {
157
+ deps.fs.mkdirSync(targetPath, { mode: PRIVATE_DIR_MODE });
158
+ } catch (error) {
159
+ if (!isNodeError(error) || error.code !== "EEXIST") {
160
+ throw error;
161
+ }
162
+ }
163
+ stat = tryLstat(targetPath, deps.fs);
164
+ if (!stat) {
165
+ throw new AgentStorePathError({
166
+ code: "not_directory",
167
+ message: `Failed to create directory: ${targetPath}`,
168
+ failedPath: targetPath
169
+ });
170
+ }
171
+ }
172
+ assertDirectoryStat(targetPath, stat);
173
+ }
174
+ function getPathSegments(targetPath, pathModule) {
175
+ const root = pathModule.parse(targetPath).root;
176
+ const rest = targetPath.slice(root.length);
177
+ const parts = rest.split(/[\\/]+/).filter(Boolean);
178
+ const segments = [];
179
+ let current = root;
180
+ if (root) {
181
+ segments.push(root);
182
+ }
183
+ for (const part of parts) {
184
+ current = current === root ? pathModule.join(root, part) : pathModule.join(current, part);
185
+ segments.push(current);
186
+ }
187
+ return segments;
188
+ }
189
+ function tryLstat(targetPath, fsModule) {
190
+ try {
191
+ return fsModule.lstatSync(targetPath);
192
+ } catch (err) {
193
+ if (isNodeError(err) && err.code === "ENOENT") {
194
+ return void 0;
195
+ }
196
+ throw err;
197
+ }
198
+ }
199
+ function isNodeError(err) {
200
+ return err instanceof Error && typeof err.code === "string";
201
+ }
202
+
203
+ // ../agent-store-sync/dist/conflict-events.js
204
+ var CONFLICT_JOURNAL_ROTATE_BYTES = 4 * 1024 * 1024;
205
+ var O_NOFOLLOW_FLAG = fs2.constants.O_NOFOLLOW;
206
+
207
+ // ../agent-store-sync/dist/local-index.js
208
+ import * as fs4 from "node:fs";
209
+ import * as path3 from "node:path";
210
+
211
+ // ../agent-store-sync/dist/secure-open.js
212
+ import * as fs3 from "node:fs";
213
+ import * as path2 from "node:path";
214
+ function openSecureSqlitePath(resolvedPath, mode) {
215
+ const parentDir = path2.dirname(resolvedPath);
216
+ const parentDirFd = openParentDirectoryNoFollow(parentDir);
217
+ let cleanupParentFd = true;
218
+ try {
219
+ const parentStat = fs3.fstatSync(parentDirFd);
220
+ if (!parentStat.isDirectory()) {
221
+ throw new AgentStorePathError({
222
+ code: "symlink_refused",
223
+ message: `Refusing to open sqlite under non-directory parent: ${parentDir}`,
224
+ failedPath: parentDir
225
+ });
226
+ }
227
+ const parentIdentity = {
228
+ dev: parentStat.dev,
229
+ ino: parentStat.ino
230
+ };
231
+ assertParentDirInodeMatchesPath(parentIdentity, parentDir);
232
+ const leafFd = openRegularFileNoFollowCreate(resolvedPath, mode);
233
+ let leafIdentity;
234
+ try {
235
+ const leafStat = fs3.fstatSync(leafFd);
236
+ if (!leafStat.isFile()) {
237
+ throw new AgentStorePathError({
238
+ code: "symlink_refused",
239
+ message: `Refusing to open non-regular sqlite path: ${resolvedPath}`,
240
+ failedPath: resolvedPath
41
241
  });
242
+ }
243
+ assertResolvedPathStillCanonical(resolvedPath);
244
+ leafIdentity = { dev: leafStat.dev, ino: leafStat.ino };
245
+ } finally {
246
+ fs3.closeSync(leafFd);
247
+ }
248
+ let verified = false;
249
+ let closed = false;
250
+ const closeParent = () => {
251
+ if (closed) {
252
+ return;
253
+ }
254
+ closed = true;
255
+ fs3.closeSync(parentDirFd);
256
+ };
257
+ const verifyOpenedInode = () => {
258
+ if (verified) {
259
+ return;
260
+ }
261
+ assertParentDirInodeMatchesPath(parentIdentity, parentDir);
262
+ assertLeafInodeUnchanged(resolvedPath, leafIdentity);
263
+ verified = true;
264
+ };
265
+ cleanupParentFd = false;
266
+ return {
267
+ sqlitePath: resolvedPath,
268
+ verifyOpenedInode,
269
+ close: closeParent
270
+ };
271
+ } finally {
272
+ if (cleanupParentFd) {
273
+ fs3.closeSync(parentDirFd);
42
274
  }
275
+ }
43
276
  }
44
- /**
45
- * True when `hostname` is an AWS S3 object-storage endpoint:
46
- * `*.amazonaws.com` with at least one label in
47
- * {@link S3_OBJECT_LABELS}. Covers modern virtual-host, path-style,
48
- * dualstack, accelerate, and FIPS URLs. Rejects the S3-named control
49
- * planes (`s3-control`, `s3-outposts`, `s3-object-lambda`) and the
50
- * legacy regional virtual-host form AWS deprecated in 2019, both of
51
- * which would otherwise let a tampered presign issuer turn the sync
52
- * client into a signed-request gadget against non-storage APIs.
53
- */
54
- export function isAwsS3Hostname(hostname) {
55
- const lowered = hostname.toLowerCase();
56
- if (lowered !== "amazonaws.com" && !lowered.endsWith(".amazonaws.com")) {
57
- return false;
58
- }
59
- const labels = lowered.split(".");
60
- return labels.some(label => S3_OBJECT_LABELS.has(label));
61
- }
62
- const S3_OBJECT_LABELS = new Set(["s3", "s3-accelerate", "s3-fips"]);
63
- /**
64
- * Like {@link defaultPresignedUrlValidator} plus a case-insensitive
65
- * exact-match check against `allowedHosts` (no subdomain wildcards).
66
- */
67
- export function createHostAllowlistValidator(allowedHosts) {
68
- const allowed = new Set(allowedHosts.map(host => host.toLowerCase()));
69
- return (url) => {
70
- defaultPresignedUrlValidator(url);
71
- if (!allowed.has(url.hostname.toLowerCase())) {
72
- throw new PresignedUrlRejectedError({
73
- code: "host_not_allowed",
74
- message: `Refused presigned URL whose host is not in the allowlist: ${url.hostname}`,
75
- url: redactPresignedUrlString(url),
76
- });
77
- }
78
- };
277
+ function openRegularFileNoFollowCreate(filePath, mode) {
278
+ const flags = symlinkSafeOpenCreateFlags();
279
+ try {
280
+ return fs3.openSync(filePath, flags, mode);
281
+ } catch (error) {
282
+ if (isNofollowSymlinkRefusal(error)) {
283
+ throw new AgentStorePathError({
284
+ code: "symlink_refused",
285
+ message: `Refusing to open symlinked path: ${filePath}`,
286
+ failedPath: filePath
287
+ });
288
+ }
289
+ throw error;
290
+ }
79
291
  }
80
- /**
81
- * Virtual-host S3 endpoints for the production `agent-stores` bucket
82
- * (matches backend `getAgentStoreBucket()` in
83
- * `backend/server/src/agent-stores/utils/agentStoreS3.ts`, which presigns
84
- * with a `us-east-1` `S3Client` and the SDK's default virtual-host
85
- * addressing). Both the regional form emitted by modern AWS SDK v3 and
86
- * the legacy global form are listed so a presign issuer upgrade or
87
- * downgrade can't trip the allowlist. Path-style (`s3.amazonaws.com/agent-stores/…`)
88
- * is intentionally not listed: the server never sets `forcePathStyle`,
89
- * and admitting bare `s3.amazonaws.com` would re-open the "any bucket"
90
- * gap this allowlist exists to close.
91
- */
92
- export const BCS_AGENT_STORE_BUCKET_HOSTS = [
93
- "agent-stores.s3.us-east-1.amazonaws.com",
94
- "agent-stores.s3.amazonaws.com",
95
- ];
96
- const PLAYGROUND_AGENT_STORE_BUCKET_HOSTS = [
97
- "agent-stores-928182716709-us-west-2-an.s3.us-west-2.amazonaws.com",
98
- "agent-stores-928182716709-us-west-2-an.s3.amazonaws.com",
99
- ];
100
- /**
101
- * Production validator for the BCS agent-store sync transport: strict
102
- * default (https, S3 endpoint, no private hosts, no userinfo) plus the
103
- * {@link BCS_AGENT_STORE_BUCKET_HOSTS} pin. Pre-built so callers don't
104
- * accidentally pass {@link defaultPresignedUrlValidator}, which would
105
- * accept any AWS S3 bucket on the internet and let a buggy or
106
- * compromised presign issuer redirect sync traffic away from the real
107
- * `agent-stores` bucket.
108
- */
109
- export function createBcsPresignedUrlValidator() {
110
- return createHostAllowlistValidator(BCS_AGENT_STORE_BUCKET_HOSTS);
111
- }
112
- /**
113
- * True when `backendUrl` targets a local dev backend (localhost or
114
- * lclhst.build). Matches IDE checks in `cursor-always-local` /
115
- * `cursorCredsService`.
116
- */
117
- export function isLocalAgentStoreBackendUrl(backendUrl) {
118
- const trimmed = backendUrl.trim();
119
- if (trimmed === "") {
120
- return false;
121
- }
122
- return trimmed.includes("localhost") || trimmed.includes("lclhst.build");
123
- }
124
- function isPlaygroundAgentStoreBackendUrl(backendUrl) {
292
+ function openParentDirectoryNoFollow(parentDir) {
293
+ const flags = symlinkSafeDirectoryOpenFlags();
294
+ try {
295
+ return fs3.openSync(parentDir, flags);
296
+ } catch (error) {
297
+ if (isNofollowSymlinkRefusal(error) || isNodeError2(error) && error.code === "ENOTDIR" && isSymlink(parentDir)) {
298
+ throw new AgentStorePathError({
299
+ code: "symlink_refused",
300
+ message: `Refusing to open sqlite under symlinked parent: ${parentDir}`,
301
+ failedPath: parentDir
302
+ });
303
+ }
304
+ throw error;
305
+ }
306
+ }
307
+ function isSymlink(targetPath) {
308
+ try {
309
+ return fs3.lstatSync(targetPath).isSymbolicLink();
310
+ } catch (_a) {
311
+ return false;
312
+ }
313
+ }
314
+ function assertResolvedPathStillCanonical(resolvedPath) {
315
+ let realpath;
316
+ try {
317
+ realpath = fs3.realpathSync.native(resolvedPath);
318
+ } catch (error) {
319
+ throw new AgentStorePathError({
320
+ code: "symlink_refused",
321
+ message: `Unable to resolve realpath for ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`,
322
+ failedPath: resolvedPath
323
+ });
324
+ }
325
+ if (realpath !== resolvedPath) {
326
+ throw new AgentStorePathError({
327
+ code: "symlink_refused",
328
+ message: `Refusing path with symlinked parent: ${resolvedPath} -> ${realpath}`,
329
+ failedPath: resolvedPath
330
+ });
331
+ }
332
+ }
333
+ function assertLeafInodeUnchanged(resolvedPath, expected) {
334
+ const flags = symlinkSafeReopenFlags();
335
+ let fd;
336
+ try {
337
+ fd = fs3.openSync(resolvedPath, flags);
338
+ } catch (error) {
339
+ if (isNofollowSymlinkRefusal(error)) {
340
+ throw new AgentStorePathError({
341
+ code: "symlink_refused",
342
+ message: `Sqlite path became symlinked between open and verification: ${resolvedPath}`,
343
+ failedPath: resolvedPath
344
+ });
345
+ }
346
+ throw error;
347
+ }
348
+ try {
349
+ const stat = fs3.fstatSync(fd);
350
+ assertResolvedPathStillCanonical(resolvedPath);
351
+ if (stat.dev !== expected.dev || stat.ino !== expected.ino) {
352
+ throw new AgentStorePathError({
353
+ code: "symlink_refused",
354
+ message: `Sqlite path inode drifted between open and verification (expected dev=${expected.dev} ino=${expected.ino}, got dev=${stat.dev} ino=${stat.ino}): ${resolvedPath}`,
355
+ failedPath: resolvedPath
356
+ });
357
+ }
358
+ } finally {
359
+ fs3.closeSync(fd);
360
+ }
361
+ }
362
+ function assertParentDirInodeMatchesPath(expected, parentDir) {
363
+ let pathStat;
364
+ try {
365
+ pathStat = fs3.lstatSync(parentDir);
366
+ } catch (error) {
367
+ throw new AgentStorePathError({
368
+ code: "symlink_refused",
369
+ message: `Unable to lstat parent directory ${parentDir}: ${error instanceof Error ? error.message : String(error)}`,
370
+ failedPath: parentDir
371
+ });
372
+ }
373
+ if (pathStat.isSymbolicLink()) {
374
+ throw new AgentStorePathError({
375
+ code: "symlink_refused",
376
+ message: `Parent directory became symlinked between open and verification: ${parentDir}`,
377
+ failedPath: parentDir
378
+ });
379
+ }
380
+ if (pathStat.dev !== expected.dev || pathStat.ino !== expected.ino) {
381
+ throw new AgentStorePathError({
382
+ code: "symlink_refused",
383
+ message: `Parent directory inode drifted between open and verification (expected dev=${expected.dev} ino=${expected.ino}, got dev=${pathStat.dev} ino=${pathStat.ino}): ${parentDir}`,
384
+ failedPath: parentDir
385
+ });
386
+ }
387
+ }
388
+ function symlinkSafeOpenCreateFlags() {
389
+ var _a;
390
+ const constants3 = fs3.constants;
391
+ let flags = (_a = constants3.O_RDWR) !== null && _a !== void 0 ? _a : 0;
392
+ if (typeof constants3.O_CREAT === "number") {
393
+ flags |= constants3.O_CREAT;
394
+ }
395
+ if (typeof constants3.O_NOFOLLOW === "number") {
396
+ flags |= constants3.O_NOFOLLOW;
397
+ }
398
+ if (typeof constants3.O_CLOEXEC === "number") {
399
+ flags |= constants3.O_CLOEXEC;
400
+ }
401
+ return flags;
402
+ }
403
+ function symlinkSafeReopenFlags() {
404
+ var _a;
405
+ const constants3 = fs3.constants;
406
+ let flags = (_a = constants3.O_RDONLY) !== null && _a !== void 0 ? _a : 0;
407
+ if (typeof constants3.O_NOFOLLOW === "number") {
408
+ flags |= constants3.O_NOFOLLOW;
409
+ }
410
+ if (typeof constants3.O_CLOEXEC === "number") {
411
+ flags |= constants3.O_CLOEXEC;
412
+ }
413
+ return flags;
414
+ }
415
+ function symlinkSafeDirectoryOpenFlags() {
416
+ var _a;
417
+ const constants3 = fs3.constants;
418
+ let flags = (_a = constants3.O_RDONLY) !== null && _a !== void 0 ? _a : 0;
419
+ if (typeof constants3.O_DIRECTORY === "number") {
420
+ flags |= constants3.O_DIRECTORY;
421
+ }
422
+ if (typeof constants3.O_NOFOLLOW === "number") {
423
+ flags |= constants3.O_NOFOLLOW;
424
+ }
425
+ if (typeof constants3.O_CLOEXEC === "number") {
426
+ flags |= constants3.O_CLOEXEC;
427
+ }
428
+ return flags;
429
+ }
430
+ function isNofollowSymlinkRefusal(error) {
431
+ if (!isNodeError2(error)) {
432
+ return false;
433
+ }
434
+ if (error.code === "ELOOP" || error.code === "EMLINK") {
435
+ return true;
436
+ }
437
+ if (error.code === "EPERM" && hasNofollowConstant()) {
438
+ return true;
439
+ }
440
+ return false;
441
+ }
442
+ function hasNofollowConstant() {
443
+ return typeof fs3.constants.O_NOFOLLOW === "number";
444
+ }
445
+ function isNodeError2(error) {
446
+ return error instanceof Error && typeof error.code === "string";
447
+ }
448
+
449
+ // ../agent-store-sync/dist/local-index.js
450
+ var CURRENT_SCHEMA_VERSION = 3;
451
+ var PRIVATE_FILE_MODE = 384;
452
+ var META_DELETION_ARMED = "deletion_armed";
453
+ var META_FILES_ROOT_DEV = "files_root_dev";
454
+ var META_FILES_ROOT_INO = "files_root_ino";
455
+ var LocalIndexError = class extends Error {
456
+ constructor(code, message) {
457
+ super(message);
458
+ this.code = code;
459
+ this.name = "LocalIndexError";
460
+ }
461
+ };
462
+ var LocalAgentStoreIndex = class _LocalAgentStoreIndex {
463
+ static open(indexPath, options) {
464
+ const resolvedPath = path3.resolve(indexPath);
465
+ const existing = _LocalAgentStoreIndex.openHandles.get(resolvedPath);
466
+ if (existing !== void 0 && !existing.closed) {
467
+ return existing;
468
+ }
469
+ assertNoSymlinkInPath(resolvedPath);
470
+ ensureSecureDirectoryChain(path3.dirname(resolvedPath));
471
+ assertNoSymlinkInPath(resolvedPath);
472
+ const opened = openSecureSqlitePath(resolvedPath, PRIVATE_FILE_MODE);
473
+ let db;
125
474
  try {
126
- const hostname = new URL(backendUrl).hostname.toLowerCase();
127
- return (hostname === "playground.cursor.sh" ||
128
- hostname.endsWith(".playground.cursor.sh"));
129
- }
130
- catch (_a) {
131
- return false;
132
- }
475
+ db = new options.sqlite(opened.sqlitePath);
476
+ opened.verifyOpenedInode();
477
+ restrictIndexFileModes(resolvedPath);
478
+ const index = new _LocalAgentStoreIndex(resolvedPath, db);
479
+ index.opened = opened;
480
+ index.migrate();
481
+ index.restrictFileModes();
482
+ _LocalAgentStoreIndex.openHandles.set(resolvedPath, index);
483
+ return index;
484
+ } catch (error) {
485
+ db === null || db === void 0 ? void 0 : db.close();
486
+ opened.close();
487
+ throw error;
488
+ }
489
+ }
490
+ constructor(indexPath, db) {
491
+ this.indexPath = indexPath;
492
+ this.db = db;
493
+ this.closed = false;
494
+ }
495
+ close() {
496
+ var _a;
497
+ if (this.closed) {
498
+ return;
499
+ }
500
+ this.db.close();
501
+ (_a = this.opened) === null || _a === void 0 ? void 0 : _a.close();
502
+ this.opened = void 0;
503
+ this.closed = true;
504
+ _LocalAgentStoreIndex.openHandles.delete(this.indexPath);
505
+ }
506
+ getFile(relPath) {
507
+ const row = this.db.prepare("SELECT rel_path, last_synced_sha, last_seen_etag, size, last_synced_ms, direction, state, tombstone_etag, deleted_at_ms FROM files WHERE rel_path = ?").get(relPath);
508
+ return row === void 0 ? void 0 : fileEntryFromRow(row);
509
+ }
510
+ listFiles() {
511
+ const rows = this.db.prepare("SELECT rel_path, last_synced_sha, last_seen_etag, size, last_synced_ms, direction, state, tombstone_etag, deleted_at_ms FROM files ORDER BY rel_path ASC").all();
512
+ return rows.map(fileEntryFromRow);
513
+ }
514
+ upsertFile(entry) {
515
+ var _a, _b;
516
+ const state = (_a = entry.state) !== null && _a !== void 0 ? _a : "live";
517
+ const tombstoneEtag = state === "tombstoned" ? entry.tombstoneEtag : null;
518
+ const deletedAtMs = state === "tombstoned" ? entry.deletedAtMs : null;
519
+ this.db.prepare(`
520
+ INSERT INTO files (
521
+ rel_path,
522
+ last_synced_sha,
523
+ last_seen_etag,
524
+ size,
525
+ last_synced_ms,
526
+ direction,
527
+ state,
528
+ tombstone_etag,
529
+ deleted_at_ms
530
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
531
+ ON CONFLICT(rel_path) DO UPDATE SET
532
+ last_synced_sha = excluded.last_synced_sha,
533
+ last_seen_etag = excluded.last_seen_etag,
534
+ size = excluded.size,
535
+ last_synced_ms = excluded.last_synced_ms,
536
+ direction = excluded.direction,
537
+ state = excluded.state,
538
+ tombstone_etag = excluded.tombstone_etag,
539
+ deleted_at_ms = excluded.deleted_at_ms
540
+ `).run(entry.relPath, entry.lastSyncedSha, (_b = entry.lastSeenEtag) !== null && _b !== void 0 ? _b : null, entry.size, entry.lastSyncedMs, entry.direction, state, tombstoneEtag !== null && tombstoneEtag !== void 0 ? tombstoneEtag : null, deletedAtMs !== null && deletedAtMs !== void 0 ? deletedAtMs : null);
541
+ this.restrictFileModes();
542
+ }
543
+ listPendingDeletes() {
544
+ const rows = this.db.prepare("SELECT rel_path, base_etag, requested_at_ms, mutation_id FROM pending_deletes ORDER BY rel_path ASC").all();
545
+ return rows.map((row) => {
546
+ var _a;
547
+ return {
548
+ relPath: row.rel_path,
549
+ baseEtag: row.base_etag,
550
+ requestedAtMs: row.requested_at_ms,
551
+ mutationId: (_a = row.mutation_id) !== null && _a !== void 0 ? _a : void 0
552
+ };
553
+ });
554
+ }
555
+ hasPendingDelete(relPath) {
556
+ const row = this.db.prepare("SELECT rel_path FROM pending_deletes WHERE rel_path = ?").get(relPath);
557
+ return row !== void 0;
558
+ }
559
+ upsertPendingDelete(entry) {
560
+ var _a;
561
+ this.db.prepare(`
562
+ INSERT INTO pending_deletes (
563
+ rel_path,
564
+ base_etag,
565
+ requested_at_ms,
566
+ mutation_id
567
+ ) VALUES (?, ?, ?, ?)
568
+ ON CONFLICT(rel_path) DO UPDATE SET
569
+ base_etag = excluded.base_etag,
570
+ requested_at_ms = excluded.requested_at_ms,
571
+ mutation_id = excluded.mutation_id
572
+ `).run(entry.relPath, entry.baseEtag, entry.requestedAtMs, (_a = entry.mutationId) !== null && _a !== void 0 ? _a : null);
573
+ this.restrictFileModes();
574
+ }
575
+ removePendingDelete(relPath) {
576
+ const result = this.db.prepare("DELETE FROM pending_deletes WHERE rel_path = ?").run(relPath);
577
+ return result.changes > 0;
578
+ }
579
+ /** Recovery-mode escape hatch: drop every unflushed deletion intent. */
580
+ clearPendingDeletes() {
581
+ const result = this.db.prepare("DELETE FROM pending_deletes").run();
582
+ return result.changes;
583
+ }
584
+ /**
585
+ * Durable recovery step: unarm scan-based deletion, forget the recorded
586
+ * `files/` root identity, and drop every unflushed deletion intent in one
587
+ * shot. Called when the identity guard trips so a subsequent round starts
588
+ * from the legacy restore semantics until a clean round re-arms. Returns
589
+ * the number of pending deletes cleared.
590
+ */
591
+ disarmDeletionAndClearPending() {
592
+ const disarm = this.db.transaction(() => {
593
+ this.db.prepare(`
594
+ INSERT INTO meta (key, value)
595
+ VALUES (?, ?)
596
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
597
+ `).run(META_DELETION_ARMED, "0");
598
+ this.db.prepare("DELETE FROM meta WHERE key = ?").run(META_FILES_ROOT_DEV);
599
+ this.db.prepare("DELETE FROM meta WHERE key = ?").run(META_FILES_ROOT_INO);
600
+ return this.db.prepare("DELETE FROM pending_deletes").run().changes;
601
+ });
602
+ const cleared = disarm();
603
+ this.restrictFileModes();
604
+ return cleared;
605
+ }
606
+ /**
607
+ * Arms scan-based deletion and records the `files/` root identity in one
608
+ * SQLite transaction so a crash cannot leave `deletion_armed=1` without
609
+ * both identity keys (which the next round would treat as compromised).
610
+ */
611
+ armDeletionWithIdentity(identity) {
612
+ const arm = this.db.transaction(() => {
613
+ const upsert = this.db.prepare(`
614
+ INSERT INTO meta (key, value)
615
+ VALUES (?, ?)
616
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
617
+ `);
618
+ upsert.run(META_DELETION_ARMED, "1");
619
+ upsert.run(META_FILES_ROOT_DEV, String(identity.dev));
620
+ upsert.run(META_FILES_ROOT_INO, String(identity.ino));
621
+ });
622
+ arm();
623
+ this.restrictFileModes();
624
+ }
625
+ deleteFile(relPath) {
626
+ const result = this.db.prepare("DELETE FROM files WHERE rel_path = ?").run(relPath);
627
+ if (result.changes > 0) {
628
+ this.restrictFileModes();
629
+ }
630
+ return result.changes > 0;
631
+ }
632
+ getMeta(key) {
633
+ const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
634
+ return row === null || row === void 0 ? void 0 : row.value;
635
+ }
636
+ setMeta(entry) {
637
+ this.db.prepare(`
638
+ INSERT INTO meta (key, value)
639
+ VALUES (?, ?)
640
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
641
+ `).run(entry.key, entry.value);
642
+ this.restrictFileModes();
643
+ }
644
+ migrate() {
645
+ this.db.pragma("journal_mode = WAL");
646
+ this.db.pragma("synchronous = NORMAL");
647
+ this.db.pragma("busy_timeout = 5000");
648
+ this.db.exec(`
649
+ CREATE TABLE IF NOT EXISTS meta (
650
+ key TEXT PRIMARY KEY,
651
+ value TEXT NOT NULL
652
+ );
653
+ `);
654
+ const version = this.getMeta("schema_version");
655
+ if (version === void 0) {
656
+ this.createCurrentSchema();
657
+ return;
658
+ }
659
+ const parsedVersion = Number(version);
660
+ if (!Number.isInteger(parsedVersion) || parsedVersion < 1 || parsedVersion > CURRENT_SCHEMA_VERSION) {
661
+ throw new LocalIndexError("unsupported_schema_version", `Unsupported agent store index schema version: ${version}`);
662
+ }
663
+ this.createFilesTableIfNeeded();
664
+ if (parsedVersion < 2) {
665
+ this.migrateV1ToV2();
666
+ }
667
+ if (parsedVersion < 3) {
668
+ this.migrateV2ToV3();
669
+ }
670
+ }
671
+ createCurrentSchema() {
672
+ this.createFilesTableIfNeeded();
673
+ this.createPendingDeletesTableIfNeeded();
674
+ this.setMeta({
675
+ key: "schema_version",
676
+ value: String(CURRENT_SCHEMA_VERSION)
677
+ });
678
+ }
679
+ createFilesTableIfNeeded() {
680
+ this.db.exec(`
681
+ CREATE TABLE IF NOT EXISTS files (
682
+ rel_path TEXT PRIMARY KEY,
683
+ last_synced_sha TEXT NOT NULL,
684
+ last_seen_etag TEXT,
685
+ size INTEGER NOT NULL,
686
+ last_synced_ms INTEGER NOT NULL,
687
+ direction TEXT NOT NULL CHECK(direction IN ('pulled', 'pushed')),
688
+ state TEXT NOT NULL DEFAULT 'live' CHECK(state IN ('live', 'tombstoned')),
689
+ tombstone_etag TEXT,
690
+ deleted_at_ms INTEGER
691
+ );
692
+ `);
693
+ }
694
+ createPendingDeletesTableIfNeeded() {
695
+ this.db.exec(`
696
+ CREATE TABLE IF NOT EXISTS pending_deletes (
697
+ rel_path TEXT PRIMARY KEY,
698
+ base_etag TEXT NOT NULL,
699
+ requested_at_ms INTEGER NOT NULL,
700
+ mutation_id TEXT
701
+ );
702
+ `);
703
+ }
704
+ migrateV1ToV2() {
705
+ const columns = this.db.prepare("PRAGMA table_info(files)").all();
706
+ if (columns.some((column) => column.name === "last_seen_sha")) {
707
+ this.db.exec("ALTER TABLE files RENAME COLUMN last_seen_sha TO last_synced_sha");
708
+ }
709
+ if (!columns.some((column) => column.name === "last_seen_etag")) {
710
+ this.db.exec("ALTER TABLE files ADD COLUMN last_seen_etag TEXT");
711
+ }
712
+ this.setMeta({
713
+ key: "schema_version",
714
+ value: "2"
715
+ });
716
+ }
717
+ migrateV2ToV3() {
718
+ const columns = this.db.prepare("PRAGMA table_info(files)").all();
719
+ if (!columns.some((column) => column.name === "state")) {
720
+ this.db.exec("ALTER TABLE files ADD COLUMN state TEXT NOT NULL DEFAULT 'live' CHECK(state IN ('live', 'tombstoned'))");
721
+ }
722
+ if (!columns.some((column) => column.name === "tombstone_etag")) {
723
+ this.db.exec("ALTER TABLE files ADD COLUMN tombstone_etag TEXT");
724
+ }
725
+ if (!columns.some((column) => column.name === "deleted_at_ms")) {
726
+ this.db.exec("ALTER TABLE files ADD COLUMN deleted_at_ms INTEGER");
727
+ }
728
+ this.createPendingDeletesTableIfNeeded();
729
+ this.setMeta({
730
+ key: "schema_version",
731
+ value: String(CURRENT_SCHEMA_VERSION)
732
+ });
733
+ }
734
+ restrictFileModes() {
735
+ restrictIndexFileModes(this.indexPath);
736
+ }
737
+ };
738
+ LocalAgentStoreIndex.openHandles = /* @__PURE__ */ new Map();
739
+ function fileEntryFromRow(row) {
740
+ var _a, _b, _c;
741
+ return {
742
+ relPath: row.rel_path,
743
+ lastSyncedSha: row.last_synced_sha,
744
+ lastSeenEtag: (_a = row.last_seen_etag) !== null && _a !== void 0 ? _a : void 0,
745
+ size: row.size,
746
+ lastSyncedMs: row.last_synced_ms,
747
+ direction: row.direction,
748
+ state: row.state,
749
+ tombstoneEtag: (_b = row.tombstone_etag) !== null && _b !== void 0 ? _b : void 0,
750
+ deletedAtMs: (_c = row.deleted_at_ms) !== null && _c !== void 0 ? _c : void 0
751
+ };
133
752
  }
134
- /**
135
- * Presign validator for the sync client. Production/staging and playground
136
- * backends are pinned to their respective agent-store buckets. For a local dev API (see
137
- * {@link isLocalAgentStoreBackendUrl}), allow `http:` to loopback only
138
- * (LocalStack) and still pin all other URLs to {@link BCS_AGENT_STORE_BUCKET_HOSTS}.
139
- */
140
- export function createAgentStorePresignedUrlValidatorForBackend(backendUrl) {
141
- if (isLocalAgentStoreBackendUrl(backendUrl)) {
142
- return createLocalDevAgentStorePresignedUrlValidator();
753
+ function restrictIndexFileModes(indexPath) {
754
+ for (const filePath of [indexPath, `${indexPath}-wal`, `${indexPath}-shm`]) {
755
+ if (fs4.existsSync(filePath)) {
756
+ fs4.chmodSync(filePath, PRIVATE_FILE_MODE);
143
757
  }
144
- if (isPlaygroundAgentStoreBackendUrl(backendUrl)) {
145
- return createHostAllowlistValidator(PLAYGROUND_AGENT_STORE_BUCKET_HOSTS);
146
- }
147
- return createBcsPresignedUrlValidator();
758
+ }
148
759
  }
149
- function createLocalDevAgentStorePresignedUrlValidator() {
150
- const bcsValidator = createBcsPresignedUrlValidator();
151
- return (url) => {
152
- assertSafeAuthority(url);
153
- const hostname = stripIPv6Brackets(url.hostname);
154
- if (url.protocol === "http:" && isLoopbackHostName(hostname)) {
155
- return;
156
- }
157
- bcsValidator(url);
158
- };
760
+
761
+ // ../agent-store-sync/dist/metrics-emitter.js
762
+ var NOOP_METRICS_EMITTER = Object.freeze({
763
+ onDiskFull() {
764
+ },
765
+ onSymlinkRefused() {
766
+ },
767
+ onNetworkError() {
768
+ },
769
+ onRoundCompleted() {
770
+ },
771
+ onError() {
772
+ },
773
+ onConflict() {
774
+ },
775
+ tokenMinted() {
776
+ },
777
+ tokenRefreshUnauthorized() {
778
+ }
779
+ });
780
+
781
+ // ../agent-store-sync/dist/metrics-aggregator.js
782
+ var DISK_FULL_REPEAT_WINDOW_MS = 5 * 60 * 1e3;
783
+
784
+ // ../agent-store-sync/dist/presigned-url.js
785
+ var PresignedUrlRejectedError = class extends Error {
786
+ constructor(options) {
787
+ super(options.message);
788
+ this.name = "PresignedUrlRejectedError";
789
+ this.code = options.code;
790
+ this.url = options.url;
791
+ }
792
+ };
793
+ function defaultPresignedUrlValidator(url) {
794
+ assertSafeAuthority(url);
795
+ if (url.protocol !== "https:") {
796
+ throw new PresignedUrlRejectedError({
797
+ code: "scheme_not_allowed",
798
+ message: `Refused presigned URL with non-https scheme: ${url.protocol}`,
799
+ url: redactPresignedUrlString(url)
800
+ });
801
+ }
802
+ const hostname = stripIPv6Brackets(url.hostname);
803
+ if (isLocalOrInternalHost(hostname)) {
804
+ throw new PresignedUrlRejectedError({
805
+ code: "private_host_not_allowed",
806
+ message: `Refused https presigned URL with private/internal host: ${hostname}`,
807
+ url: redactPresignedUrlString(url)
808
+ });
809
+ }
810
+ if (!isAwsS3Hostname(hostname)) {
811
+ throw new PresignedUrlRejectedError({
812
+ code: "host_not_allowed",
813
+ message: `Refused presigned URL whose host is not an AWS S3 endpoint: ${hostname}`,
814
+ url: redactPresignedUrlString(url)
815
+ });
816
+ }
817
+ }
818
+ function isAwsS3Hostname(hostname) {
819
+ const lowered = hostname.toLowerCase();
820
+ if (lowered !== "amazonaws.com" && !lowered.endsWith(".amazonaws.com")) {
821
+ return false;
822
+ }
823
+ const labels = lowered.split(".");
824
+ return labels.some((label) => S3_OBJECT_LABELS.has(label));
825
+ }
826
+ var S3_OBJECT_LABELS = /* @__PURE__ */ new Set(["s3", "s3-accelerate", "s3-fips"]);
827
+ function createHostAllowlistValidator(allowedHosts) {
828
+ const allowed = new Set(allowedHosts.map((host) => host.toLowerCase()));
829
+ return (url) => {
830
+ defaultPresignedUrlValidator(url);
831
+ if (!allowed.has(url.hostname.toLowerCase())) {
832
+ throw new PresignedUrlRejectedError({
833
+ code: "host_not_allowed",
834
+ message: `Refused presigned URL whose host is not in the allowlist: ${url.hostname}`,
835
+ url: redactPresignedUrlString(url)
836
+ });
837
+ }
838
+ };
839
+ }
840
+ var BCS_AGENT_STORE_BUCKET_HOSTS = [
841
+ "agent-stores.s3.us-east-1.amazonaws.com",
842
+ "agent-stores.s3.amazonaws.com"
843
+ ];
844
+ function createBcsPresignedUrlValidator() {
845
+ return createHostAllowlistValidator(BCS_AGENT_STORE_BUCKET_HOSTS);
159
846
  }
160
- /**
161
- * Like {@link defaultPresignedUrlValidator} plus `http://localhost` /
162
- * `127.0.0.1` / `[::1]` (any port) for LocalStack-backed unit tests.
163
- * Non-loopback `https:` URLs fall through to the strict default (any S3
164
- * bucket). Do not use for IDE wiring — use
165
- * {@link createAgentStorePresignedUrlValidatorForBackend} instead.
166
- */
167
- export function localhostPermissivePresignedUrlValidator(url) {
847
+ function isLocalAgentStoreBackendUrl(backendUrl) {
848
+ const trimmed = backendUrl.trim();
849
+ if (trimmed === "") {
850
+ return false;
851
+ }
852
+ return trimmed.includes("localhost") || trimmed.includes("lclhst.build");
853
+ }
854
+ function createAgentStorePresignedUrlValidatorForBackend(backendUrl) {
855
+ return isLocalAgentStoreBackendUrl(backendUrl) ? createLocalDevAgentStorePresignedUrlValidator() : createBcsPresignedUrlValidator();
856
+ }
857
+ function createLocalDevAgentStorePresignedUrlValidator() {
858
+ const bcsValidator = createBcsPresignedUrlValidator();
859
+ return (url) => {
168
860
  assertSafeAuthority(url);
169
861
  const hostname = stripIPv6Brackets(url.hostname);
170
862
  if (url.protocol === "http:" && isLoopbackHostName(hostname)) {
171
- return;
863
+ return;
172
864
  }
173
- defaultPresignedUrlValidator(url);
865
+ bcsValidator(url);
866
+ };
174
867
  }
175
868
  function assertSafeAuthority(url) {
176
- if (url.username !== "" || url.password !== "") {
177
- throw new PresignedUrlRejectedError({
178
- code: "userinfo_not_allowed",
179
- message: "Refused presigned URL with embedded userinfo",
180
- url: redactPresignedUrlString(url),
181
- });
182
- }
183
- }
184
- /**
185
- * Strip the query string and userinfo from a presigned URL so the AWS
186
- * SigV4 signature never reaches error messages or telemetry.
187
- */
188
- export function redactPresignedUrlString(url) {
189
- try {
190
- const parsed = typeof url === "string" ? new URL(url) : url;
191
- return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
192
- }
193
- catch (_a) {
194
- return "<unparseable-presigned-url>";
195
- }
869
+ if (url.username !== "" || url.password !== "") {
870
+ throw new PresignedUrlRejectedError({
871
+ code: "userinfo_not_allowed",
872
+ message: "Refused presigned URL with embedded userinfo",
873
+ url: redactPresignedUrlString(url)
874
+ });
875
+ }
196
876
  }
197
- /**
198
- * Parse and validate a server-returned presigned URL before fetch. Uses
199
- * {@link relPath} in error text (never the raw URL) so SigV4 credentials
200
- * cannot leak into messages.
201
- */
202
- export function assertPresignedUrlSafe(args) {
203
- let parsed;
204
- try {
205
- parsed = new URL(args.rawUrl);
206
- }
207
- catch (_a) {
208
- throw new Error(`Refusing unparseable presigned URL for ${args.relPath}`);
209
- }
210
- try {
211
- args.validatePresignedUrl(parsed);
212
- }
213
- catch (error) {
214
- throw new Error(`Refusing presigned URL for ${args.relPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
215
- }
877
+ function redactPresignedUrlString(url) {
878
+ try {
879
+ const parsed = typeof url === "string" ? new URL(url) : url;
880
+ return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
881
+ } catch (_a) {
882
+ return "<unparseable-presigned-url>";
883
+ }
216
884
  }
217
885
  function isLoopbackHostName(hostname) {
218
- const lowered = hostname.toLowerCase();
219
- return (lowered === "localhost" ||
220
- lowered === "ip6-localhost" ||
221
- lowered === "127.0.0.1" ||
222
- lowered === "::1" ||
223
- lowered === "0:0:0:0:0:0:0:1");
886
+ const lowered = hostname.toLowerCase();
887
+ return lowered === "localhost" || lowered === "ip6-localhost" || lowered === "127.0.0.1" || lowered === "::1" || lowered === "0:0:0:0:0:0:0:1";
224
888
  }
225
889
  function isLocalOrInternalHost(hostname) {
226
- if (hostname.length === 0) {
227
- return true;
228
- }
229
- if (isLoopbackHostName(hostname)) {
230
- return true;
231
- }
232
- if (isIPv4Address(hostname)) {
233
- return isPrivateOrInternalIPv4(hostname);
234
- }
235
- if (isIPv6Address(hostname)) {
236
- return isPrivateOrInternalIPv6(hostname);
237
- }
238
- // DNS hostnames can't be classified without resolving; the caller
239
- // relies on transport-level allowlists for those.
240
- return false;
890
+ if (hostname.length === 0) {
891
+ return true;
892
+ }
893
+ if (isLoopbackHostName(hostname)) {
894
+ return true;
895
+ }
896
+ if (isIPv4Address(hostname)) {
897
+ return isPrivateOrInternalIPv4(hostname);
898
+ }
899
+ if (isIPv6Address(hostname)) {
900
+ return isPrivateOrInternalIPv6(hostname);
901
+ }
902
+ return false;
241
903
  }
242
904
  function stripIPv6Brackets(hostname) {
243
- return hostname.startsWith("[") && hostname.endsWith("]")
244
- ? hostname.slice(1, -1)
245
- : hostname;
905
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
246
906
  }
247
907
  function isIPv4Address(hostname) {
248
- const parts = hostname.split(".");
249
- if (parts.length !== 4) {
250
- return false;
251
- }
252
- return parts.every(part => {
253
- if (part.length === 0 || part.length > 3) {
254
- return false;
255
- }
256
- if (!/^\d+$/.test(part)) {
257
- return false;
258
- }
259
- const n = Number(part);
260
- return Number.isInteger(n) && n >= 0 && n <= 255;
261
- });
908
+ const parts = hostname.split(".");
909
+ if (parts.length !== 4) {
910
+ return false;
911
+ }
912
+ return parts.every((part) => {
913
+ if (part.length === 0 || part.length > 3) {
914
+ return false;
915
+ }
916
+ if (!/^\d+$/.test(part)) {
917
+ return false;
918
+ }
919
+ const n = Number(part);
920
+ return Number.isInteger(n) && n >= 0 && n <= 255;
921
+ });
262
922
  }
263
923
  function isPrivateOrInternalIPv4(hostname) {
264
- const [a, b] = hostname.split(".").map(Number);
265
- if (a === undefined || b === undefined) {
266
- return true;
267
- }
268
- // RFC1918, loopback, link-local, multicast, unspecified, broadcast,
269
- // carrier-grade NAT, and reserved blocks.
270
- if (a === 0)
271
- return true;
272
- if (a === 10)
273
- return true;
274
- if (a === 127)
275
- return true;
276
- if (a === 169 && b === 254)
277
- return true;
278
- if (a === 172 && b >= 16 && b <= 31)
279
- return true;
280
- if (a === 192 && b === 168)
281
- return true;
282
- if (a === 100 && b >= 64 && b <= 127)
283
- return true;
284
- if (a >= 224 && a <= 239)
285
- return true;
286
- if (a >= 240)
287
- return true;
288
- return false;
924
+ const [a, b] = hostname.split(".").map(Number);
925
+ if (a === void 0 || b === void 0) {
926
+ return true;
927
+ }
928
+ if (a === 0)
929
+ return true;
930
+ if (a === 10)
931
+ return true;
932
+ if (a === 127)
933
+ return true;
934
+ if (a === 169 && b === 254)
935
+ return true;
936
+ if (a === 172 && b >= 16 && b <= 31)
937
+ return true;
938
+ if (a === 192 && b === 168)
939
+ return true;
940
+ if (a === 100 && b >= 64 && b <= 127)
941
+ return true;
942
+ if (a >= 224 && a <= 239)
943
+ return true;
944
+ if (a >= 240)
945
+ return true;
946
+ return false;
289
947
  }
290
948
  function isIPv6Address(hostname) {
291
- // Loose check — just enough to route the host to the IPv6 classifier.
292
- return hostname.includes(":");
949
+ return hostname.includes(":");
293
950
  }
294
951
  function isPrivateOrInternalIPv6(hostname) {
295
- const lowered = hostname.toLowerCase();
296
- if (lowered === "::" || lowered === "::1") {
297
- return true;
298
- }
299
- // IPv4-mapped IPv6 (RFC4291) routes back through the IPv4 classifier
300
- // so `[::ffff:169.254.169.254]` can't slip past the private-IP check.
301
- const mapped = extractIPv4MappedAddress(lowered);
302
- if (mapped !== undefined) {
303
- return isPrivateOrInternalIPv4(mapped);
304
- }
305
- // ULA fc00::/7, link-local fe80::/10, multicast ff00::/8.
306
- if (lowered.startsWith("fc") || lowered.startsWith("fd")) {
307
- return true;
308
- }
309
- if (lowered.startsWith("fe8") || lowered.startsWith("fe9")) {
310
- return true;
311
- }
312
- if (lowered.startsWith("fea") || lowered.startsWith("feb")) {
313
- return true;
314
- }
315
- if (lowered.startsWith("ff")) {
316
- return true;
317
- }
318
- return false;
952
+ const lowered = hostname.toLowerCase();
953
+ if (lowered === "::" || lowered === "::1") {
954
+ return true;
955
+ }
956
+ const mapped = extractIPv4MappedAddress(lowered);
957
+ if (mapped !== void 0) {
958
+ return isPrivateOrInternalIPv4(mapped);
959
+ }
960
+ if (lowered.startsWith("fc") || lowered.startsWith("fd")) {
961
+ return true;
962
+ }
963
+ if (lowered.startsWith("fe8") || lowered.startsWith("fe9")) {
964
+ return true;
965
+ }
966
+ if (lowered.startsWith("fea") || lowered.startsWith("feb")) {
967
+ return true;
968
+ }
969
+ if (lowered.startsWith("ff")) {
970
+ return true;
971
+ }
972
+ return false;
319
973
  }
320
- // Convert an IPv4-mapped IPv6 literal (`::ffff:a.b.c.d` or
321
- // `::ffff:hhhh:hhhh`) to its dotted-quad form. Returns undefined otherwise.
322
974
  function extractIPv4MappedAddress(hostname) {
323
- const dotted = hostname.match(/^(?:0:0:0:0:0|::):?ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
324
- if (dotted) {
325
- return dotted[1];
326
- }
327
- if (hostname.startsWith("::ffff:")) {
328
- const tail = hostname.slice("::ffff:".length);
329
- if (isIPv4Address(tail)) {
330
- return tail;
331
- }
332
- const hex = tail.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
333
- if (hex) {
334
- const high = Number.parseInt(hex[1], 16);
335
- const low = Number.parseInt(hex[2], 16);
336
- if (Number.isFinite(high) &&
337
- Number.isFinite(low) &&
338
- high >= 0 &&
339
- high <= 0xffff &&
340
- low >= 0 &&
341
- low <= 0xffff) {
342
- return `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;
343
- }
344
- }
345
- }
346
- return undefined;
975
+ const dotted = hostname.match(/^(?:0:0:0:0:0|::):?ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
976
+ if (dotted) {
977
+ return dotted[1];
978
+ }
979
+ if (hostname.startsWith("::ffff:")) {
980
+ const tail = hostname.slice("::ffff:".length);
981
+ if (isIPv4Address(tail)) {
982
+ return tail;
983
+ }
984
+ const hex = tail.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
985
+ if (hex) {
986
+ const high = Number.parseInt(hex[1], 16);
987
+ const low = Number.parseInt(hex[2], 16);
988
+ if (Number.isFinite(high) && Number.isFinite(low) && high >= 0 && high <= 65535 && low >= 0 && low <= 65535) {
989
+ return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
990
+ }
991
+ }
992
+ }
993
+ return void 0;
347
994
  }
995
+
996
+ // ../agent-store-sync/dist/resilience-events.js
997
+ var NOOP_RESILIENCE_LISTENER = Object.freeze({
998
+ onDiskFull() {
999
+ },
1000
+ onSymlinkRefused() {
1001
+ },
1002
+ onNetworkError() {
1003
+ }
1004
+ });
1005
+
1006
+ // ../agent-store-sync/dist/retrying-client.js
1007
+ var RETRYABLE_CONNECT_CODES = /* @__PURE__ */ new Set([
1008
+ Code.Unavailable,
1009
+ Code.ResourceExhausted,
1010
+ Code.Internal,
1011
+ Code.Aborted,
1012
+ Code.DeadlineExceeded
1013
+ ]);
1014
+
1015
+ // ../agent-store-sync/dist/store-lock.js
1016
+ var STORE_LOCK_STALE_MS = 10 * 60 * 1e3;
1017
+
1018
+ // ../agent-store-sync/dist/sync-engine.js
1019
+ var DEFAULT_MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024;
1020
+ var TOMBSTONE_ROW_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
1021
+ export {
1022
+ createAgentStorePresignedUrlValidatorForBackend
1023
+ };