@skaile/workspaces 0.22.0 → 0.23.0

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 (124) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/{asset-feeds-Y2CDCM3W.js → asset-feeds-WKIKSZ6Z.js} +9 -9
  3. package/dist/{asset-feeds-Y2CDCM3W.js.map → asset-feeds-WKIKSZ6Z.js.map} +1 -1
  4. package/dist/asset-manager/index.js +7 -7
  5. package/dist/asset-manager/installer.js +6 -6
  6. package/dist/asset-manager/src/index.d.ts +15 -0
  7. package/dist/asset-manager/src/index.d.ts.map +1 -1
  8. package/dist/base-assets/connectors/deploy.js +8 -8
  9. package/dist/base-assets/connectors/devserver.js +8 -8
  10. package/dist/base-assets/connectors/flow/adapter.js +8 -8
  11. package/dist/base-assets/connectors/flow/run-flow.js +9 -9
  12. package/dist/base-assets/connectors/flow.js +8 -8
  13. package/dist/base-assets/connectors/git.js +8 -8
  14. package/dist/base-assets/connectors/gmail.js +8 -8
  15. package/dist/base-assets/connectors/googledrive/driver.d.ts.map +1 -1
  16. package/dist/base-assets/connectors/googledrive.js +8 -8
  17. package/dist/base-assets/connectors/local.js +8 -8
  18. package/dist/base-assets/connectors/mattermost.js +8 -8
  19. package/dist/base-assets/connectors/memory.js +8 -8
  20. package/dist/base-assets/connectors/minio.js +8 -8
  21. package/dist/base-assets/connectors/postgres.js +8 -8
  22. package/dist/base-assets/connectors/s3.js +8 -8
  23. package/dist/base-assets/connectors/sharepoint/driver.d.ts.map +1 -1
  24. package/dist/base-assets/connectors/sharepoint.js +8 -8
  25. package/dist/base-assets/connectors/sqlite.js +8 -8
  26. package/dist/base-assets/connectors/static-server.js +8 -8
  27. package/dist/base-assets/connectors/tunnel.js +8 -8
  28. package/dist/base-assets/connectors/webdav/driver.d.ts.map +1 -1
  29. package/dist/base-assets/connectors/webdav.js +8 -8
  30. package/dist/base-assets/connectors/xstate-store.js +8 -8
  31. package/dist/base-assets/connectors/xstate.js +8 -8
  32. package/dist/{chunk-MNAHNDUI.js → chunk-2F3RUZXC.js} +3 -3
  33. package/dist/{chunk-MNAHNDUI.js.map → chunk-2F3RUZXC.js.map} +1 -1
  34. package/dist/{chunk-2RYQERIT.js → chunk-2RFOFHSM.js} +4 -4
  35. package/dist/{chunk-2RYQERIT.js.map → chunk-2RFOFHSM.js.map} +1 -1
  36. package/dist/{chunk-K2HDYSAM.js → chunk-46COM7M5.js} +5 -5
  37. package/dist/{chunk-K2HDYSAM.js.map → chunk-46COM7M5.js.map} +1 -1
  38. package/dist/{chunk-V5TBKO5Q.js → chunk-542K7SR6.js} +59 -35
  39. package/dist/chunk-542K7SR6.js.map +1 -0
  40. package/dist/{chunk-PFOXL4SH.js → chunk-5ESCS2OS.js} +4 -4
  41. package/dist/{chunk-PFOXL4SH.js.map → chunk-5ESCS2OS.js.map} +1 -1
  42. package/dist/{chunk-WH2EB2SF.js → chunk-AFLH7B64.js} +3 -3
  43. package/dist/{chunk-WH2EB2SF.js.map → chunk-AFLH7B64.js.map} +1 -1
  44. package/dist/{chunk-7HSXUKNB.js → chunk-BTAC2VYT.js} +19 -18
  45. package/dist/chunk-BTAC2VYT.js.map +1 -0
  46. package/dist/{chunk-K7WPR77X.js → chunk-DH4N5AW4.js} +14 -8
  47. package/dist/chunk-DH4N5AW4.js.map +1 -0
  48. package/dist/{chunk-SKXCTV55.js → chunk-DZCFFTAX.js} +9 -9
  49. package/dist/{chunk-SKXCTV55.js.map → chunk-DZCFFTAX.js.map} +1 -1
  50. package/dist/{chunk-OJN25VJO.js → chunk-E4UJ7CVK.js} +31 -126
  51. package/dist/chunk-E4UJ7CVK.js.map +1 -0
  52. package/dist/{chunk-JN2CUVSU.js → chunk-LJ52ZKIU.js} +3 -3
  53. package/dist/{chunk-JN2CUVSU.js.map → chunk-LJ52ZKIU.js.map} +1 -1
  54. package/dist/{chunk-NBJ5TOEC.js → chunk-ODPII24X.js} +3 -3
  55. package/dist/{chunk-NBJ5TOEC.js.map → chunk-ODPII24X.js.map} +1 -1
  56. package/dist/{chunk-NDD5VMN5.js → chunk-OVQZ5OKL.js} +2 -2
  57. package/dist/{chunk-NDD5VMN5.js.map → chunk-OVQZ5OKL.js.map} +1 -1
  58. package/dist/{chunk-6MB7CRME.js → chunk-QMONOHXT.js} +268 -37
  59. package/dist/chunk-QMONOHXT.js.map +1 -0
  60. package/dist/{chunk-53UNDY6K.js → chunk-QTWA6BZK.js} +5 -5
  61. package/dist/{chunk-53UNDY6K.js.map → chunk-QTWA6BZK.js.map} +1 -1
  62. package/dist/{chunk-4NDWKA64.js → chunk-WSZAFRQL.js} +8 -3
  63. package/dist/chunk-WSZAFRQL.js.map +1 -0
  64. package/dist/{chunk-VUCPJBAG.js → chunk-YX3UWPJ5.js} +53 -28
  65. package/dist/chunk-YX3UWPJ5.js.map +1 -0
  66. package/dist/{chunk-ETMUGBHF.js → chunk-Z3M5K67G.js} +8 -8
  67. package/dist/chunk-Z3M5K67G.js.map +1 -0
  68. package/dist/cli/index.js +38 -38
  69. package/dist/cli/index.js.map +1 -1
  70. package/dist/cli/src/commands/manage.d.ts.map +1 -1
  71. package/dist/cli/src/helpers.d.ts +7 -0
  72. package/dist/cli/src/helpers.d.ts.map +1 -1
  73. package/dist/connectors/config.js +6 -6
  74. package/dist/connectors/index.js +8 -8
  75. package/dist/connectors/rclone.js +2 -1
  76. package/dist/connectors/src/rclone-process-manager.d.ts +91 -3
  77. package/dist/connectors/src/rclone-process-manager.d.ts.map +1 -1
  78. package/dist/connectors/src/watcher.d.ts +6 -0
  79. package/dist/connectors/src/watcher.d.ts.map +1 -1
  80. package/dist/core/index.js +5 -5
  81. package/dist/core/manifest.js +2 -2
  82. package/dist/core/models.js +1 -1
  83. package/dist/core/runtime-assets.js +4 -4
  84. package/dist/core/src/lock.d.ts +6 -6
  85. package/dist/core/src/manifest.d.ts.map +1 -1
  86. package/dist/core/src/models.d.ts +25 -18
  87. package/dist/core/src/models.d.ts.map +1 -1
  88. package/dist/core/src/repo-manager.d.ts +8 -2
  89. package/dist/core/src/repo-manager.d.ts.map +1 -1
  90. package/dist/core/workspace-config.js +3 -3
  91. package/dist/deploy/index.js +5 -5
  92. package/dist/discovery/index.js +3 -3
  93. package/dist/{ensure-sources-REWWBH2K.js → ensure-sources-OJUBGX6Z.js} +10 -10
  94. package/dist/{ensure-sources-REWWBH2K.js.map → ensure-sources-OJUBGX6Z.js.map} +1 -1
  95. package/dist/helpers-LTN3HMD3.js +4 -0
  96. package/dist/{helpers-I3SREIC3.js.map → helpers-LTN3HMD3.js.map} +1 -1
  97. package/dist/library/index.js +4 -4
  98. package/dist/open-library-67FSSQWE.js +13 -0
  99. package/dist/{open-library-CT4VVESU.js.map → open-library-67FSSQWE.js.map} +1 -1
  100. package/dist/{plugin-store-QS7TC5HY.js → plugin-store-IZ5SCRAV.js} +7 -7
  101. package/dist/{plugin-store-QS7TC5HY.js.map → plugin-store-IZ5SCRAV.js.map} +1 -1
  102. package/dist/runner/index.js +10 -10
  103. package/dist/sdk/asset-manager.js +7 -7
  104. package/dist/sdk/core.js +5 -5
  105. package/dist/sdk/index.js +10 -10
  106. package/dist/sdk/runner.js +10 -10
  107. package/dist/{setup-F6DGKL7J.js → setup-J7CYEQOF.js} +7 -7
  108. package/dist/{setup-F6DGKL7J.js.map → setup-J7CYEQOF.js.map} +1 -1
  109. package/dist/store-client-AEI6Y3KD.js +14 -0
  110. package/dist/{store-client-JP642EEI.js.map → store-client-AEI6Y3KD.js.map} +1 -1
  111. package/dist/tui/index.js +10 -10
  112. package/dist/workspace-plugin/index.js +1 -1
  113. package/package.json +1 -1
  114. package/dist/chunk-4NDWKA64.js.map +0 -1
  115. package/dist/chunk-6MB7CRME.js.map +0 -1
  116. package/dist/chunk-7HSXUKNB.js.map +0 -1
  117. package/dist/chunk-ETMUGBHF.js.map +0 -1
  118. package/dist/chunk-K7WPR77X.js.map +0 -1
  119. package/dist/chunk-OJN25VJO.js.map +0 -1
  120. package/dist/chunk-V5TBKO5Q.js.map +0 -1
  121. package/dist/chunk-VUCPJBAG.js.map +0 -1
  122. package/dist/helpers-I3SREIC3.js +0 -4
  123. package/dist/open-library-CT4VVESU.js +0 -13
  124. package/dist/store-client-JP642EEI.js +0 -14
@@ -1,11 +1,13 @@
1
+ import { createLogger } from './chunk-24UIWON4.js';
1
2
  import { spawn } from 'child_process';
2
3
  import { randomBytes } from 'crypto';
3
- import { mkdir, chmod, writeFile, unlink, readdir, stat, readFile } from 'fs/promises';
4
+ import { mkdir, chmod, writeFile, unlink, stat, readdir, readFile } from 'fs/promises';
4
5
  import { tmpdir } from 'os';
5
- import path from 'path';
6
+ import path, { relative, join } from 'path';
6
7
  import { createConnection } from 'net';
8
+ import { existsSync, readFileSync } from 'fs';
9
+ import chokidar from 'chokidar';
7
10
 
8
- // connectors/src/rclone-process-manager.ts
9
11
  async function ensureDirMode(dir, log) {
10
12
  await mkdir(dir, { recursive: true });
11
13
  try {
@@ -69,9 +71,126 @@ var PortPool = class _PortPool {
69
71
  });
70
72
  }
71
73
  };
74
+ function globToMatcher(pattern) {
75
+ const segmentMatch = pattern.match(/^\*\*\/([^*/?]+)\/\*\*$/);
76
+ if (segmentMatch) {
77
+ const name = segmentMatch[1];
78
+ return (p) => p.includes(`/${name}/`) || p.endsWith(`/${name}`) || p === name;
79
+ }
80
+ const dotSegment = pattern.match(/^\*\*\/(\.[\w#]+)(?:\/\*\*)?$/);
81
+ if (dotSegment) {
82
+ const name = dotSegment[1];
83
+ return (p) => p.includes(`/${name}/`) || p.endsWith(`/${name}`) || p === name;
84
+ }
85
+ const extMatch = pattern.match(/^\*\*\/\*(\.\w+(?:\.\*)?)/);
86
+ if (extMatch) {
87
+ const suffix = extMatch[1];
88
+ if (suffix.endsWith(".*")) {
89
+ const base = suffix.slice(0, -2);
90
+ return (p) => p.includes(base);
91
+ }
92
+ return (p) => p.endsWith(suffix);
93
+ }
94
+ const suffixMatch = pattern.match(/^\*\*\/\*(.+)$/);
95
+ if (suffixMatch) {
96
+ const sfx = suffixMatch[1];
97
+ return (p) => p.endsWith(sfx);
98
+ }
99
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
100
+ const re = new RegExp(`^${escaped}$`);
101
+ return (p) => re.test(p);
102
+ }
103
+ var DEFAULT_IGNORED = [
104
+ (p) => p.includes("/.git/") || p.endsWith("/.git"),
105
+ (p) => p.includes("/node_modules/") || p.endsWith("/node_modules"),
106
+ (p) => p.includes("/.connectors/") || p.endsWith("/.connectors"),
107
+ // Atomic-write tempfiles (Bun.write creates `<name>.tmp.<pid>.<ts>` then renames).
108
+ // These vanish between readdir and fs.watch, causing EINVAL that crashes the process.
109
+ (p) => /\.tmp\.\d/.test(p),
110
+ // Editor swap/backup files that also race with save operations.
111
+ (p) => p.endsWith(".swp"),
112
+ (p) => p.endsWith(".swx"),
113
+ (p) => p.includes("/.#"),
114
+ (p) => p.endsWith("~")
115
+ ];
116
+ var DEFAULT_DEBOUNCE_MS = 150;
117
+ function parseGitignore(rootDir) {
118
+ const gitignorePath = join(rootDir, ".gitignore");
119
+ if (!existsSync(gitignorePath)) return [];
120
+ try {
121
+ const content = readFileSync(gitignorePath, "utf-8");
122
+ const matchers = [];
123
+ for (const raw of content.split("\n")) {
124
+ const line = raw.trim();
125
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
126
+ let pattern = line;
127
+ if (pattern.startsWith("/")) {
128
+ pattern = pattern.slice(1);
129
+ } else if (!pattern.includes("/")) {
130
+ pattern = `**/${pattern}`;
131
+ }
132
+ if (pattern.endsWith("/")) {
133
+ matchers.push(globToMatcher(`${pattern}**`));
134
+ } else {
135
+ matchers.push(globToMatcher(pattern));
136
+ if (!pattern.includes("*") && !pattern.includes(".")) {
137
+ matchers.push(globToMatcher(`${pattern}/**`));
138
+ }
139
+ }
140
+ }
141
+ return matchers;
142
+ } catch {
143
+ return [];
144
+ }
145
+ }
146
+ var CHOKIDAR_EVENT_MAP = {
147
+ add: "create",
148
+ change: "edit",
149
+ unlink: "delete",
150
+ addDir: "create",
151
+ unlinkDir: "delete"
152
+ };
153
+ async function createFsWatcher(rootDir, callback, options) {
154
+ const log = createLogger({ kind: "connector", subkind: "watcher", instance: rootDir });
155
+ const gitignorePatterns = parseGitignore(rootDir);
156
+ const optionsIgnored = (options?.ignored ?? []).map(
157
+ (entry) => typeof entry === "string" ? globToMatcher(entry) : entry
158
+ );
159
+ const ignored = [...DEFAULT_IGNORED, ...gitignorePatterns, ...optionsIgnored];
160
+ const debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;
161
+ const watcher = chokidar.watch(rootDir, {
162
+ ignoreInitial: true,
163
+ ignored,
164
+ awaitWriteFinish: { stabilityThreshold: debounceMs, pollInterval: 50 },
165
+ usePolling: false
166
+ });
167
+ watcher.on("error", (err) => {
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ log.warn("watcher error (non-fatal)", { message });
170
+ });
171
+ for (const [chokidarEvent, action] of Object.entries(CHOKIDAR_EVENT_MAP)) {
172
+ watcher.on(
173
+ chokidarEvent,
174
+ (fullPath) => {
175
+ const rel = relative(rootDir, fullPath);
176
+ if (!rel || rel.startsWith("..")) return;
177
+ const normalizedPath = rel.split("\\").join("/");
178
+ callback({ path: normalizedPath, action, source: "filesystem" });
179
+ }
180
+ );
181
+ }
182
+ return {
183
+ close: () => watcher.close()
184
+ };
185
+ }
72
186
 
73
187
  // connectors/src/rclone-process-manager.ts
74
188
  var RCLONE_RC_PORTS = new PortPool([55e3, 56e3]);
189
+ var DEFAULT_VFS_WRITE_BACK = "30s";
190
+ var DEFAULT_UPLOAD_STUCK_THRESHOLD = 3;
191
+ var STABILIZE_THRESHOLD_MS = 1500;
192
+ var STABILIZE_TIMEOUT_MS = 3e4;
193
+ var STABILIZE_POLL_MS = DEFAULT_DEBOUNCE_MS;
75
194
  var HANDLES = /* @__PURE__ */ new Map();
76
195
  var RcloneProcessManager = class {
77
196
  /**
@@ -101,44 +220,31 @@ var RcloneProcessManager = class {
101
220
  remote: config.remote,
102
221
  vfsCacheMode: config.vfsCacheMode
103
222
  });
104
- const args = [
105
- "mount",
106
- config.remote,
107
- config.mountPoint,
108
- "--config",
109
- configPath,
110
- "--cache-dir",
111
- config.cacheDir,
112
- "--vfs-cache-mode",
113
- config.vfsCacheMode,
114
- "--vfs-cache-max-size",
115
- config.cacheMaxSize ?? "5G",
116
- "--vfs-cache-max-age",
117
- config.cacheMaxAge ?? "168h",
118
- "--vfs-write-back",
119
- "5s",
120
- "--vfs-cache-poll-interval",
121
- "60s",
122
- "--rc",
123
- "--rc-addr",
124
- rcAddr,
125
- "--log-level",
126
- "INFO",
127
- "--use-json-log",
128
- "--allow-other"
129
- ];
223
+ const args = _buildRcloneArgs(config, { configPath, rcAddr });
130
224
  const proc = spawn("rclone", args, { stdio: ["ignore", "pipe", "pipe"] });
225
+ const routeCtx = {
226
+ stuckThreshold: config.uploadStuckThreshold ?? DEFAULT_UPLOAD_STUCK_THRESHOLD,
227
+ alertedObjects: /* @__PURE__ */ new Set(),
228
+ onUploadStuck: (object, tries) => {
229
+ rcloneLog.error(
230
+ "rclone upload repeatedly failing \u2014 file may be stranded in this session's cache",
231
+ void 0,
232
+ { object, tries, event: "sync_status", phase: "error" }
233
+ );
234
+ config.onUploadStuck?.(object, tries);
235
+ }
236
+ };
131
237
  const recentStderr = [];
132
238
  const stderrTail = (line) => {
133
239
  recentStderr.push(line);
134
240
  if (recentStderr.length > 20) recentStderr.shift();
135
241
  };
136
242
  const stdoutHandler = (line) => {
137
- _routeRcloneLogLine(line, rcloneLog);
243
+ _routeRcloneLogLine(line, rcloneLog, routeCtx);
138
244
  };
139
245
  const stderrHandler = (line) => {
140
246
  stderrTail(line);
141
- _routeRcloneLogLine(line, rcloneLog);
247
+ _routeRcloneLogLine(line, rcloneLog, routeCtx);
142
248
  };
143
249
  if (proc.stdout) attachLineSplitter(proc.stdout, stdoutHandler);
144
250
  if (proc.stderr) attachLineSplitter(proc.stderr, stderrHandler);
@@ -205,6 +311,10 @@ ${recentStderr.slice(-5).join("\n")}` : "";
205
311
  * Stop an rclone subprocess. Attempts a flush first, then SIGTERM, then
206
312
  * SIGKILL + fusermount3 if `graceMs` elapses without the mountpoint
207
313
  * disappearing.
314
+ *
315
+ * `graceMs` bounds both the flush stabilization gate and the SIGTERM→SIGKILL
316
+ * window — it is *not* a single total wall-clock cap: the flush also waits on
317
+ * rclone's `/vfs/stats` drain (its own 60s ceiling) between the two.
208
318
  */
209
319
  async stop(handle, opts) {
210
320
  const graceMs = opts?.graceMs ?? 3e4;
@@ -213,7 +323,7 @@ ${recentStderr.slice(-5).join("\n")}` : "";
213
323
  return;
214
324
  }
215
325
  try {
216
- await this.flush(handle);
326
+ await this.flush(handle, { stabilizeTimeoutMs: graceMs });
217
327
  } catch (err) {
218
328
  entry.log.warn("rclone flush before stop failed", {
219
329
  pid: handle.pid,
@@ -249,12 +359,35 @@ ${recentStderr.slice(-5).join("\n")}` : "";
249
359
  /**
250
360
  * Force rclone to upload pending writes, then wait until the in-use
251
361
  * counter reaches zero.
362
+ *
363
+ * Before issuing the refresh it waits for the vfs cache dir to go quiescent
364
+ * (no write for `stabilityThresholdMs`, bounded by `stabilizeTimeoutMs`) so a
365
+ * close/hibernate-triggered flush doesn't snapshot a half-written file and
366
+ * trigger rclone's mid-write "sizes differ" abort. Pass `stabilize: false`
367
+ * to skip the gate.
252
368
  */
253
- async flush(handle) {
369
+ async flush(handle, opts) {
254
370
  const entry = HANDLES.get(handle.pid);
255
371
  if (!entry) {
256
372
  throw new Error(`rclone handle not found (pid=${handle.pid})`);
257
373
  }
374
+ if (opts?.stabilize !== false) {
375
+ const thresholdMs = opts?.stabilityThresholdMs ?? STABILIZE_THRESHOLD_MS;
376
+ const timeoutMs = opts?.stabilizeTimeoutMs ?? STABILIZE_TIMEOUT_MS;
377
+ const scanDir = await vfsContentDir(entry.cacheDir);
378
+ const quiescent = await awaitQuiescent(scanDir, {
379
+ thresholdMs,
380
+ timeoutMs,
381
+ pollMs: STABILIZE_POLL_MS
382
+ });
383
+ if (!quiescent) {
384
+ entry.log.warn("rclone cache did not stabilize before flush \u2014 proceeding anyway", {
385
+ pid: handle.pid,
386
+ thresholdMs,
387
+ timeoutMs
388
+ });
389
+ }
390
+ }
258
391
  const refreshRes = await fetch(`http://${handle.rcAddr}/vfs/refresh?recursive=true`, {
259
392
  method: "POST"
260
393
  });
@@ -286,7 +419,9 @@ ${recentStderr.slice(-5).join("\n")}` : "";
286
419
  return { alive, cacheBytes };
287
420
  }
288
421
  };
289
- function _routeRcloneLogLine(line, log) {
422
+ var UPLOAD_FAIL_RE = /failed to upload.*?try #(\d+)/i;
423
+ var UPLOAD_OK_RE = /upload succeeded/i;
424
+ function _routeRcloneLogLine(line, log, ctx) {
290
425
  const trimmed = line.trim();
291
426
  if (trimmed.length === 0) return;
292
427
  let parsed = null;
@@ -297,6 +432,7 @@ function _routeRcloneLogLine(line, log) {
297
432
  parsed = null;
298
433
  }
299
434
  if (!parsed) {
435
+ if (ctx) _trackUploadFailure(trimmed, void 0, ctx);
300
436
  log.info(trimmed);
301
437
  return;
302
438
  }
@@ -305,6 +441,7 @@ function _routeRcloneLogLine(line, log) {
305
441
  const data = {};
306
442
  if (parsed.object !== void 0) data.object = parsed.object;
307
443
  if (parsed.source !== void 0) data.source = parsed.source;
444
+ if (ctx) _trackUploadFailure(msg, parsed.object, ctx);
308
445
  switch (level) {
309
446
  case "DEBUG":
310
447
  log.debug(msg, data);
@@ -327,6 +464,100 @@ function _routeRcloneLogLine(line, log) {
327
464
  break;
328
465
  }
329
466
  }
467
+ function _trackUploadFailure(msg, object, ctx) {
468
+ const failMatch = UPLOAD_FAIL_RE.exec(msg);
469
+ if (failMatch) {
470
+ const tries = Number(failMatch[1]);
471
+ const key = object ?? extractObjectFromMsg(msg) ?? "(unknown)";
472
+ const threshold = ctx.stuckThreshold ?? DEFAULT_UPLOAD_STUCK_THRESHOLD;
473
+ if (!ctx.alertedObjects) ctx.alertedObjects = /* @__PURE__ */ new Set();
474
+ const alerted = ctx.alertedObjects;
475
+ if (tries >= threshold && !alerted.has(key)) {
476
+ alerted.add(key);
477
+ ctx.onUploadStuck?.(key, tries);
478
+ }
479
+ return;
480
+ }
481
+ if (UPLOAD_OK_RE.test(msg)) {
482
+ const key = object ?? extractObjectFromMsg(msg);
483
+ if (key) ctx.alertedObjects?.delete(key);
484
+ }
485
+ }
486
+ function extractObjectFromMsg(msg) {
487
+ const m = /(?:^|\s)([^\s:]+\/[^\s:]+):/.exec(msg);
488
+ return m?.[1];
489
+ }
490
+ function _buildRcloneArgs(config, io) {
491
+ const args = [
492
+ "mount",
493
+ config.remote,
494
+ config.mountPoint,
495
+ "--config",
496
+ io.configPath,
497
+ "--cache-dir",
498
+ config.cacheDir,
499
+ "--vfs-cache-mode",
500
+ config.vfsCacheMode,
501
+ "--vfs-cache-max-size",
502
+ config.cacheMaxSize ?? "5G",
503
+ "--vfs-cache-max-age",
504
+ config.cacheMaxAge ?? "168h",
505
+ "--vfs-write-back",
506
+ config.vfsWriteBack ?? DEFAULT_VFS_WRITE_BACK,
507
+ "--vfs-cache-poll-interval",
508
+ "60s",
509
+ "--rc",
510
+ "--rc-addr",
511
+ io.rcAddr,
512
+ "--log-level",
513
+ "INFO",
514
+ "--use-json-log",
515
+ "--allow-other"
516
+ ];
517
+ if (config.ignoreSizeCheck) {
518
+ args.push("--ignore-size", "--ignore-checksum");
519
+ }
520
+ return args;
521
+ }
522
+ async function awaitQuiescent(dir, opts) {
523
+ const deadline = Date.now() + opts.timeoutMs;
524
+ for (; ; ) {
525
+ const newest = await newestMtimeMs(dir);
526
+ if (newest === null || Date.now() - newest >= opts.thresholdMs) return true;
527
+ if (Date.now() >= deadline) return false;
528
+ await sleep(opts.pollMs);
529
+ }
530
+ }
531
+ async function vfsContentDir(cacheDir) {
532
+ const vfs = path.join(cacheDir, "vfs");
533
+ try {
534
+ return (await stat(vfs)).isDirectory() ? vfs : cacheDir;
535
+ } catch {
536
+ return cacheDir;
537
+ }
538
+ }
539
+ async function newestMtimeMs(dir) {
540
+ let newest = null;
541
+ try {
542
+ const entries = await readdir(dir, { withFileTypes: true });
543
+ for (const e of entries) {
544
+ const full = path.join(dir, String(e.name));
545
+ if (e.isDirectory()) {
546
+ const sub = await newestMtimeMs(full);
547
+ if (sub !== null && (newest === null || sub > newest)) newest = sub;
548
+ } else if (e.isFile()) {
549
+ try {
550
+ const s = await stat(full);
551
+ if (newest === null || s.mtimeMs > newest) newest = s.mtimeMs;
552
+ } catch {
553
+ }
554
+ }
555
+ }
556
+ } catch {
557
+ return null;
558
+ }
559
+ return newest;
560
+ }
330
561
  async function isMountpoint(mountPoint) {
331
562
  try {
332
563
  const resolved = path.resolve(mountPoint);
@@ -399,6 +630,6 @@ async function dirSize(dir) {
399
630
  return total;
400
631
  }
401
632
 
402
- export { PortPool, RcloneProcessManager, _routeRcloneLogLine, ensureDirMode };
403
- //# sourceMappingURL=chunk-6MB7CRME.js.map
404
- //# sourceMappingURL=chunk-6MB7CRME.js.map
633
+ export { DEFAULT_UPLOAD_STUCK_THRESHOLD, DEFAULT_VFS_WRITE_BACK, PortPool, RcloneProcessManager, _buildRcloneArgs, _routeRcloneLogLine, awaitQuiescent, createFsWatcher, ensureDirMode };
634
+ //# sourceMappingURL=chunk-QMONOHXT.js.map
635
+ //# sourceMappingURL=chunk-QMONOHXT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../connectors/src/fs-utils.ts","../connectors/src/port-pool.ts","../connectors/src/watcher.ts","../connectors/src/rclone-process-manager.ts"],"names":[],"mappings":"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","file":"chunk-QMONOHXT.js","sourcesContent":["/**\n * Shared filesystem helpers for connector infrastructure.\n *\n * @docLink packages/connectors/api-reference#fs-utils\n */\n\nimport { chmod, mkdir } from \"node:fs/promises\";\nimport type { Logger } from \"@skaile/workspaces/types\";\n\n/**\n * mkdir(recursive) + chmod 0o770 in one call. The post-mkdir `chmod` is the\n * load-bearing piece: `mkdir(mode:)` is ANDed with the process umask\n * (typically 022 → effective mode 0o750), which strips the group-write bit\n * that `fusermount3` requires on the rclone mountpoint and rclone needs on\n * its vfs cache directory. Explicit `chmod` is umask-immune.\n *\n * EPERM on `chmod` is swallowed: when a foreign uid pre-created the\n * directory (a stray uid-1000 process inside the container or a host-side\n * platform process writing through the project bind-mount), the runner\n * cannot chmod it. The platform's agent container entrypoint runs as root\n * and heals the subtree on the next restart — see\n * `platform/docker/agent/entrypoint.sh` `_skaile_heal_ownership`. A debug\n * line is emitted when a logger is supplied so the rare-but-real heal\n * trigger is observable in session logs without adding noise on the happy\n * path.\n *\n * The chmod is *idempotent* (the syscall always fires, but a no-op when\n * the mode is already 0o770), not a no-op — kernel behaviour for chmod is\n * \"set\", not \"set-if-different\".\n *\n * @param dir Absolute or relative path to ensure exists at mode 0o770.\n * @param log Optional logger. When provided, EPERM-on-chmod emits a\n * `debug`-level entry under whatever taxonomy the caller's logger uses.\n */\nexport async function ensureDirMode(dir: string, log?: Logger): Promise<void> {\n await mkdir(dir, { recursive: true });\n try {\n await chmod(dir, 0o770);\n } catch (err) {\n log?.debug(\"chmod 0770 skipped (EPERM — entrypoint heal expected)\", {\n path: dir,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n","/**\n * PortPool — allocates ports from a [min, max] range, checks OS for collisions.\n */\n\nimport { createConnection } from \"node:net\";\n\n/**\n * Allocates TCP ports from a `[min, max]` range with OS-level collision detection.\n * Used by service connector adapters (devserver, static-server) to assign local ports.\n * @docLink packages/connectors/api-reference#port-pool\n */\nexport class PortPool {\n private readonly min: number;\n private readonly max: number;\n private readonly inUse = new Set<number>();\n\n constructor(range: [number, number]) {\n this.min = range[0];\n this.max = range[1];\n }\n\n /** Allocate the next free port from the range. Checks OS for collisions. */\n async allocate(): Promise<number> {\n for (let port = this.min; port <= this.max; port++) {\n if (this.inUse.has(port)) continue;\n if (!(await PortPool.isPortFree(port))) {\n this.inUse.add(port);\n continue;\n }\n this.inUse.add(port);\n return port;\n }\n throw new Error(`No free port in range ${this.min}-${this.max}`);\n }\n\n /** Mark a port as in-use (for explicit port args or registered instances). */\n reserve(port: number): void {\n this.inUse.add(port);\n }\n\n /** Return a port to the pool. */\n release(port: number): void {\n this.inUse.delete(port);\n }\n\n /** Check if a port is currently tracked as in-use. */\n isInUse(port: number): boolean {\n return this.inUse.has(port);\n }\n\n /** Quick TCP connect test — returns true if nothing is listening. */\n static isPortFree(port: number, host = \"127.0.0.1\"): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = createConnection({ port, host });\n socket.once(\"connect\", () => {\n socket.destroy();\n resolve(false);\n });\n socket.once(\"error\", () => {\n socket.destroy();\n resolve(true);\n });\n socket.setTimeout(500, () => {\n socket.destroy();\n resolve(true);\n });\n });\n }\n}\n","/**\n * Shared filesystem watcher factory for mountable adapters.\n *\n * Uses chokidar for cross-platform, debounced file watching.\n *\n * Automatically reads .gitignore from the watched directory and merges\n * those patterns into chokidar's ignore list. This prevents scanning\n * large generated directories (node_modules, dist, .next, etc.) that\n * most projects already declare as ignored.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport { createLogger } from \"@skaile/workspaces/core/logging\";\n// Static import so bun --compile bundles chokidar into the binary. A dynamic\n// `await import(\"chokidar\")` is not seen by the compiler's static analyzer\n// and fails at runtime inside the /$bunfs/ virtual filesystem.\nimport chokidar from \"chokidar\";\nimport type { ConnectorChangeEvent } from \"./connector-types.js\";\nimport type { WatchHandle, WatchOptions } from \"./shared-types.js\";\n\n/**\n * Convert a glob pattern to a matcher function.\n *\n * Chokidar 5 changed string matchers to exact equality (`===`) instead of\n * glob matching. We convert our patterns to functions so chokidar can still\n * filter correctly.\n */\nfunction globToMatcher(pattern: string): (path: string) => boolean {\n // Simple segment-name patterns: **/NAME/** or **/NAME\n // Match any path that contains /NAME/ or ends with /NAME\n const segmentMatch = pattern.match(/^\\*\\*\\/([^*/?]+)\\/\\*\\*$/);\n if (segmentMatch) {\n const name = segmentMatch[1];\n return (p: string) => p.includes(`/${name}/`) || p.endsWith(`/${name}`) || p === name;\n }\n\n // Dot-prefixed segment: **/.NAME or **/.NAME/**\n const dotSegment = pattern.match(/^\\*\\*\\/(\\.[\\w#]+)(?:\\/\\*\\*)?$/);\n if (dotSegment) {\n const name = dotSegment[1];\n return (p: string) => p.includes(`/${name}/`) || p.endsWith(`/${name}`) || p === name;\n }\n\n // Extension wildcard: **/*.EXT.* or **/*.EXT\n const extMatch = pattern.match(/^\\*\\*\\/\\*(\\.\\w+(?:\\.\\*)?)/);\n if (extMatch) {\n const suffix = extMatch[1];\n if (suffix.endsWith(\".*\")) {\n // e.g. **/*.tmp.* -> match any file with .tmp. in it\n const base = suffix.slice(0, -2);\n return (p: string) => p.includes(base);\n }\n return (p: string) => p.endsWith(suffix);\n }\n\n // Suffix wildcard: **/*SUFFIX (e.g. **/*~)\n const suffixMatch = pattern.match(/^\\*\\*\\/\\*(.+)$/);\n if (suffixMatch) {\n const sfx = suffixMatch[1];\n return (p: string) => p.endsWith(sfx);\n }\n\n // Fallback: convert simple globs to regex\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*\\*/g, \"{{GLOBSTAR}}\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/\\?/g, \"[^/]\")\n .replace(/\\{\\{GLOBSTAR\\}\\}/g, \".*\");\n const re = new RegExp(`^${escaped}$`);\n return (p: string) => re.test(p);\n}\n\nconst DEFAULT_IGNORED: Array<string | ((path: string) => boolean)> = [\n (p: string) => p.includes(\"/.git/\") || p.endsWith(\"/.git\"),\n (p: string) => p.includes(\"/node_modules/\") || p.endsWith(\"/node_modules\"),\n (p: string) => p.includes(\"/.connectors/\") || p.endsWith(\"/.connectors\"),\n // Atomic-write tempfiles (Bun.write creates `<name>.tmp.<pid>.<ts>` then renames).\n // These vanish between readdir and fs.watch, causing EINVAL that crashes the process.\n (p: string) => /\\.tmp\\.\\d/.test(p),\n // Editor swap/backup files that also race with save operations.\n (p: string) => p.endsWith(\".swp\"),\n (p: string) => p.endsWith(\".swx\"),\n (p: string) => p.includes(\"/.#\"),\n (p: string) => p.endsWith(\"~\"),\n];\n/**\n * Default chokidar `awaitWriteFinish` stability threshold (ms). Exported so the\n * rclone process manager can reuse the same write-stabilization concept when\n * gating a flush on a quiescent mount.\n */\nexport const DEFAULT_DEBOUNCE_MS = 150;\n\n/**\n * Parse a .gitignore file into glob patterns suitable for chokidar's `ignored` option.\n * Handles comments, blank lines, directory patterns, and root-relative patterns.\n * Negation patterns (!) are skipped - chokidar cannot un-ignore.\n */\nfunction parseGitignore(rootDir: string): Array<(path: string) => boolean> {\n const gitignorePath = join(rootDir, \".gitignore\");\n if (!existsSync(gitignorePath)) return [];\n\n try {\n const content = readFileSync(gitignorePath, \"utf-8\");\n const matchers: Array<(path: string) => boolean> = [];\n\n for (const raw of content.split(\"\\n\")) {\n const line = raw.trim();\n if (!line || line.startsWith(\"#\") || line.startsWith(\"!\")) continue;\n\n let pattern = line;\n\n // Strip leading / (root-relative -> convert to rootDir-relative)\n if (pattern.startsWith(\"/\")) {\n pattern = pattern.slice(1);\n } else if (!pattern.includes(\"/\")) {\n // Bare name like \"node_modules\" or \"*.log\" -> match anywhere\n pattern = `**/${pattern}`;\n }\n\n // Directory pattern (trailing /) -> ensure it matches contents too\n if (pattern.endsWith(\"/\")) {\n matchers.push(globToMatcher(`${pattern}**`));\n } else {\n matchers.push(globToMatcher(pattern));\n // Also match as directory (e.g. \"dist\" should match \"dist/\" and \"dist/**\")\n if (!pattern.includes(\"*\") && !pattern.includes(\".\")) {\n matchers.push(globToMatcher(`${pattern}/**`));\n }\n }\n }\n\n return matchers;\n } catch {\n return [];\n }\n}\n\ntype ChokidarAction = \"create\" | \"edit\" | \"delete\";\n\nconst CHOKIDAR_EVENT_MAP: Record<string, ChokidarAction> = {\n add: \"create\",\n change: \"edit\",\n unlink: \"delete\",\n addDir: \"create\",\n unlinkDir: \"delete\",\n};\n\n/**\n * Create a cross-platform filesystem watcher on `rootDir` using chokidar.\n * Auto-reads `.gitignore` from `rootDir` and merges those patterns into the ignore list.\n * @param rootDir - Absolute path to the directory to watch.\n * @param callback - Called with each `ConnectorChangeEvent` (create / edit / delete).\n * @param options - Debounce interval and additional ignore patterns.\n * @returns A `WatchHandle` — call `.close()` to stop watching.\n * @docLink packages/connectors/api-reference#create-fs-watcher\n */\nexport async function createFsWatcher(\n rootDir: string,\n callback: (event: ConnectorChangeEvent) => void,\n options?: WatchOptions,\n): Promise<WatchHandle> {\n const log = createLogger({ kind: \"connector\", subkind: \"watcher\", instance: rootDir });\n const gitignorePatterns = parseGitignore(rootDir);\n // Convert any remaining string patterns from options to matcher functions\n // (chokidar 5 treats strings as exact equality, not globs)\n const optionsIgnored = (options?.ignored ?? []).map((entry) =>\n typeof entry === \"string\" ? globToMatcher(entry) : entry,\n );\n const ignored = [...DEFAULT_IGNORED, ...gitignorePatterns, ...optionsIgnored];\n const debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\n const watcher = chokidar.watch(rootDir, {\n ignoreInitial: true,\n ignored,\n awaitWriteFinish: { stabilityThreshold: debounceMs, pollInterval: 50 },\n usePolling: false,\n });\n\n // Swallow transient watch errors (EINVAL/ENOENT when a file vanishes between\n // readdir and fs.watch). Without this handler, chokidar rethrows and the\n // agent process dies.\n watcher.on(\"error\", (err) => {\n const message = err instanceof Error ? err.message : String(err);\n log.warn(\"watcher error (non-fatal)\", { message });\n });\n\n for (const [chokidarEvent, action] of Object.entries(CHOKIDAR_EVENT_MAP)) {\n (watcher as { on(event: string, fn: (fullPath: string) => void): void }).on(\n chokidarEvent,\n (fullPath: string) => {\n const rel = relative(rootDir, fullPath);\n if (!rel || rel.startsWith(\"..\")) return;\n // Normalize to forward slashes\n const normalizedPath = rel.split(\"\\\\\").join(\"/\");\n callback({ path: normalizedPath, action, source: \"filesystem\" });\n },\n );\n }\n\n return {\n close: () => watcher.close(),\n };\n}\n","/**\n * RcloneProcessManager — supervises an rclone FUSE subprocess per mount.\n *\n * Responsibilities:\n * - Spawn rclone with a per-mount config + cache dir, wait for the FUSE\n * mountpoint to appear, then return a handle.\n * - Pipe rclone's JSON-formatted log lines into the parent driver's\n * {@link Logger} via a child source `${driverName}+rclone`.\n * - Coordinate graceful stop (flush → SIGTERM → fusermount fallback).\n * - Expose `flush` (POST /vfs/refresh + poll /vfs/stats) and `status`\n * (process liveness + cache-dir size).\n *\n * Cross-link: see `workspaces/core/CLAUDE.md` § Logging for the\n * `createLogger` / `LogStore` contract, and\n * `_devlog/specs/2026-05-01-debug-logging-design.md` for the source taxonomy.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { readdir, readFile, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport type { Readable } from \"node:stream\";\nimport type { Logger } from \"@skaile/workspaces/types\";\nimport { ensureDirMode } from \"./fs-utils.js\";\nimport { PortPool } from \"./port-pool.js\";\nimport { DEFAULT_DEBOUNCE_MS } from \"./watcher.js\";\n\n/** Module-level pool for rclone remote-control (rc) ports. */\nconst RCLONE_RC_PORTS = new PortPool([55000, 56000]);\n\n/** Default rclone `--vfs-write-back` delay; raised from 5s so a rapidly-rewritten file settles before upload (avoids mid-write \"sizes differ\" aborts). */\nexport const DEFAULT_VFS_WRITE_BACK = \"30s\";\n\n/** Consecutive failed upload retries for one object before a stuck-upload error is surfaced. */\nexport const DEFAULT_UPLOAD_STUCK_THRESHOLD = 3;\n\n/**\n * Stabilization gate defaults for {@link RcloneProcessManager.flush}. Before\n * forcing an upload we wait until the vfs cache dir has had no write for\n * `thresholdMs`, bounded by `timeoutMs` so close/hibernate never blocks\n * forever. `pollMs` reuses the watcher's write-stabilization debounce.\n */\nconst STABILIZE_THRESHOLD_MS = 1_500;\nconst STABILIZE_TIMEOUT_MS = 30_000;\nconst STABILIZE_POLL_MS = DEFAULT_DEBOUNCE_MS;\n\n/**\n * Spawn-time configuration for a single rclone FUSE mount.\n * @docLink packages/connectors/api-reference#rclone-spawn-config\n */\nexport interface RcloneSpawnConfig {\n /** Logical mount id; used as `instance` and as part of the rclone config filename. */\n mountId: string;\n /**\n * Driver name (e.g. \"sharepoint\", \"webdav\"); used to derive the rclone\n * subprocess child logger subkind: `${driverName}+rclone`.\n */\n driverName: string;\n /** Absolute path inside the container where rclone will FUSE-mount. Created if missing. */\n mountPoint: string;\n /** Absolute path inside the container for the vfs cache. Created if missing. */\n cacheDir: string;\n /** Full INI text for rclone.conf (one [remote] section, named `skaile-<mountId>`). */\n rcloneConfig: string;\n /** Remote spec, e.g. `skaile-foo:/Clients/KADK`. */\n remote: string;\n /** rclone --vfs-cache-mode value. */\n vfsCacheMode: \"minimal\" | \"writes\" | \"full\";\n /** Per-mount cache size cap. Default: '5G'. */\n cacheMaxSize?: string;\n /** Per-mount cache age cap. Default: '168h'. */\n cacheMaxAge?: string;\n /**\n * Skip rclone's post-transfer size + checksum verification by appending\n * `--ignore-size --ignore-checksum`. Default: false (verification on).\n *\n * Escape hatch for remotes that mutate uploaded bytes server-side (notably\n * SharePoint Online normalising LF→CRLF on text/html), which makes the\n * remote object a different size/hash than the local source. rclone reads\n * that as `corrupted on transfer`, deletes the upload, and retries forever,\n * so the file never durably lands. Enabling this trades integrity\n * verification for sync-ability and must only be set by drivers that have\n * confirmed the mutation is benign. Off-by-default; wired per-driver, never\n * globally.\n */\n ignoreSizeCheck?: boolean;\n /**\n * rclone `--vfs-write-back` delay (e.g. '30s', '1m'). Controls how long a\n * dirty file sits before rclone starts uploading it. Default:\n * {@link DEFAULT_VFS_WRITE_BACK}. Raise it when the agent rewrites files\n * faster than they upload, to avoid mid-write \"sizes differ\" aborts.\n */\n vfsWriteBack?: string;\n /**\n * Consecutive upload retries for one object before {@link onUploadStuck}\n * fires. Default: {@link DEFAULT_UPLOAD_STUCK_THRESHOLD}.\n */\n uploadStuckThreshold?: number;\n /**\n * Optional hook invoked once per object when its upload has failed\n * `uploadStuckThreshold` times in a row. spawn() always also logs an\n * error-level entry through the `${driverName}+rclone` child logger; this\n * hook lets callers surface the stranded file elsewhere (e.g. a sync event).\n */\n onUploadStuck?: (object: string, tries: number) => void;\n /**\n * Parent driver Logger — already tagged\n * `{kind:\"mount\", subkind:<driver>, instance:<mountId>}` by ConnectorManager.\n * Used for manager-level events; rclone subprocess events go through the\n * derived `child({subkind: \\`${driverName}+rclone\\`})` logger.\n */\n log: Logger;\n}\n\n/**\n * Handle returned by `RcloneProcessManager.spawn()` once the FUSE mountpoint is confirmed.\n * @docLink packages/connectors/api-reference#rclone-handle\n */\nexport interface RcloneHandle {\n /** OS process id of the rclone subprocess. */\n pid: number;\n /** Confirmed FUSE mountpoint (echo of input). */\n mountPoint: string;\n /** Cache directory (echo of input). */\n cacheDir: string;\n /** The local rc API address rclone is bound to, e.g. '127.0.0.1:54321'. */\n rcAddr: string;\n}\n\ninterface InternalEntry {\n proc: ChildProcess;\n configPath: string;\n port: number;\n mountPoint: string;\n cacheDir: string;\n log: Logger;\n rcloneLog: Logger;\n /**\n * Last few stderr lines for diagnostic purposes (used in spawn-failure\n * messages).\n */\n recentStderr: string[];\n}\n\nconst HANDLES = new Map<number, InternalEntry>();\n\n/**\n * Manages rclone FUSE subprocesses for mount drivers. Stateless across instances —\n * the internal handle registry is module-scoped so a fresh `RcloneProcessManager`\n * can still find handles created by a previous instance.\n * @docLink packages/connectors/api-reference#rclone-process-manager\n */\nexport class RcloneProcessManager {\n /**\n * Spawn an rclone FUSE mount. Resolves once the mountpoint is confirmed\n * present in `/proc/self/mounts`. Throws on timeout, on early exit, or\n * if invariants are violated.\n */\n async spawn(config: RcloneSpawnConfig): Promise<RcloneHandle> {\n if (!path.isAbsolute(config.mountPoint)) {\n throw new Error(`mountPoint must be absolute: ${config.mountPoint}`);\n }\n if (!path.isAbsolute(config.cacheDir)) {\n throw new Error(`cacheDir must be absolute: ${config.cacheDir}`);\n }\n\n // Defence-in-depth: the per-connector driver already chmods these via\n // ensureDirMode, but direct callers that bypass the driver still need\n // mountPoint and cacheDir to be group-writable for fusermount3 / vfs.\n await ensureDirMode(config.mountPoint, config.log);\n await ensureDirMode(config.cacheDir, config.log);\n\n const configPath = path.join(\n tmpdir(),\n `rclone-${config.mountId}-${randomBytes(6).toString(\"hex\")}.conf`,\n );\n await writeFile(configPath, config.rcloneConfig, { mode: 0o600 });\n\n const port = await RCLONE_RC_PORTS.allocate();\n const rcAddr = `127.0.0.1:${port}`;\n const rcloneLog = config.log.child({ subkind: `${config.driverName}+rclone` });\n\n config.log.info(\"rclone spawn starting\", {\n mountPoint: config.mountPoint,\n remote: config.remote,\n vfsCacheMode: config.vfsCacheMode,\n });\n\n const args = _buildRcloneArgs(config, { configPath, rcAddr });\n\n const proc = spawn(\"rclone\", args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\n // Per-process tracker for the upload-stuck observability gate. When an\n // object's upload retry count crosses the threshold we log once at error\n // level (the stream otherwise stays at INFO and the failure is silent).\n const routeCtx: RcloneLogRouteContext = {\n stuckThreshold: config.uploadStuckThreshold ?? DEFAULT_UPLOAD_STUCK_THRESHOLD,\n alertedObjects: new Set<string>(),\n onUploadStuck: (object, tries) => {\n rcloneLog.error(\n \"rclone upload repeatedly failing — file may be stranded in this session's cache\",\n undefined,\n { object, tries, event: \"sync_status\", phase: \"error\" },\n );\n config.onUploadStuck?.(object, tries);\n },\n };\n\n const recentStderr: string[] = [];\n const stderrTail = (line: string) => {\n recentStderr.push(line);\n if (recentStderr.length > 20) recentStderr.shift();\n };\n\n const stdoutHandler = (line: string) => {\n _routeRcloneLogLine(line, rcloneLog, routeCtx);\n };\n const stderrHandler = (line: string) => {\n stderrTail(line);\n _routeRcloneLogLine(line, rcloneLog, routeCtx);\n };\n\n if (proc.stdout) attachLineSplitter(proc.stdout, stdoutHandler);\n if (proc.stderr) attachLineSplitter(proc.stderr, stderrHandler);\n\n let earlyExitCode: number | null = null;\n let earlyExitSignal: NodeJS.Signals | null = null;\n const earlyExit = new Promise<void>((resolve) => {\n proc.once(\"exit\", (code, signal) => {\n earlyExitCode = code;\n earlyExitSignal = signal;\n resolve();\n });\n });\n\n const mountReady = waitFor(\n async () => {\n if (earlyExitCode !== null || earlyExitSignal !== null) return false;\n return await isMountpoint(config.mountPoint);\n },\n { timeoutMs: 15_000, intervalMs: 100 },\n );\n\n const ready = await Promise.race([mountReady, earlyExit.then(() => false)]);\n\n if (!ready) {\n try {\n if (proc.exitCode === null && proc.signalCode === null) {\n proc.kill(\"SIGKILL\");\n }\n } catch {\n // ignore\n }\n await unlink(configPath).catch(() => {});\n RCLONE_RC_PORTS.release(port);\n const tail =\n recentStderr.length > 0 ? `\\nstderr tail:\\n${recentStderr.slice(-5).join(\"\\n\")}` : \"\";\n throw new Error(`rclone failed to mount ${config.remote} at ${config.mountPoint}${tail}`);\n }\n\n const pid = proc.pid;\n if (pid === undefined) {\n // Should never happen if mountReady resolved true, but defend anyway.\n proc.kill(\"SIGKILL\");\n await unlink(configPath).catch(() => {});\n RCLONE_RC_PORTS.release(port);\n throw new Error(\"rclone process has no pid after successful mount\");\n }\n\n const entry: InternalEntry = {\n proc,\n configPath,\n port,\n mountPoint: config.mountPoint,\n cacheDir: config.cacheDir,\n log: config.log,\n rcloneLog,\n recentStderr,\n };\n HANDLES.set(pid, entry);\n\n proc.on(\"exit\", (code) => {\n // If the entry is still in the registry, this was unplanned.\n if (HANDLES.has(pid)) {\n config.log.warn(\"rclone exited unexpectedly\", { code, pid });\n }\n });\n\n config.log.info(\"rclone mountpoint confirmed\", { pid });\n\n return { pid, mountPoint: config.mountPoint, cacheDir: config.cacheDir, rcAddr };\n }\n\n /**\n * Stop an rclone subprocess. Attempts a flush first, then SIGTERM, then\n * SIGKILL + fusermount3 if `graceMs` elapses without the mountpoint\n * disappearing.\n *\n * `graceMs` bounds both the flush stabilization gate and the SIGTERM→SIGKILL\n * window — it is *not* a single total wall-clock cap: the flush also waits on\n * rclone's `/vfs/stats` drain (its own 60s ceiling) between the two.\n */\n async stop(handle: RcloneHandle, opts?: { graceMs?: number }): Promise<void> {\n const graceMs = opts?.graceMs ?? 30_000;\n const entry = HANDLES.get(handle.pid);\n if (!entry) {\n return;\n }\n\n try {\n // Bound the stabilization gate by graceMs so stop() doesn't silently add\n // the full default stabilize timeout on top of the caller's budget.\n await this.flush(handle, { stabilizeTimeoutMs: graceMs });\n } catch (err) {\n entry.log.warn(\"rclone flush before stop failed\", {\n pid: handle.pid,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n\n entry.log.info(\"rclone stop initiated\", { pid: handle.pid });\n\n // Remove FIRST so the exit handler doesn't fire the unplanned-exit warning.\n HANDLES.delete(handle.pid);\n\n try {\n entry.proc.kill(\"SIGTERM\");\n } catch {\n // ignore — process may already be gone\n }\n\n const unmounted = await waitFor(async () => !(await isMountpoint(entry.mountPoint)), {\n timeoutMs: graceMs,\n intervalMs: 200,\n });\n\n if (!unmounted) {\n try {\n entry.proc.kill(\"SIGKILL\");\n } catch {\n // ignore\n }\n await new Promise<void>((resolve) => {\n const fuse = spawn(\"fusermount3\", [\"-u\", entry.mountPoint], { stdio: \"ignore\" });\n fuse.once(\"exit\", () => resolve());\n fuse.once(\"error\", () => resolve());\n });\n entry.log.warn(\"rclone hard-killed\", { pid: handle.pid });\n }\n\n RCLONE_RC_PORTS.release(entry.port);\n await unlink(entry.configPath).catch(() => {});\n }\n\n /**\n * Force rclone to upload pending writes, then wait until the in-use\n * counter reaches zero.\n *\n * Before issuing the refresh it waits for the vfs cache dir to go quiescent\n * (no write for `stabilityThresholdMs`, bounded by `stabilizeTimeoutMs`) so a\n * close/hibernate-triggered flush doesn't snapshot a half-written file and\n * trigger rclone's mid-write \"sizes differ\" abort. Pass `stabilize: false`\n * to skip the gate.\n */\n async flush(\n handle: RcloneHandle,\n opts?: { stabilize?: boolean; stabilityThresholdMs?: number; stabilizeTimeoutMs?: number },\n ): Promise<void> {\n const entry = HANDLES.get(handle.pid);\n if (!entry) {\n throw new Error(`rclone handle not found (pid=${handle.pid})`);\n }\n\n if (opts?.stabilize !== false) {\n const thresholdMs = opts?.stabilityThresholdMs ?? STABILIZE_THRESHOLD_MS;\n const timeoutMs = opts?.stabilizeTimeoutMs ?? STABILIZE_TIMEOUT_MS;\n // Scan only the vfs content subtree (agent writes land here). The sibling\n // `vfsMeta/` tree is rclone housekeeping whose churn would never settle.\n const scanDir = await vfsContentDir(entry.cacheDir);\n const quiescent = await awaitQuiescent(scanDir, {\n thresholdMs,\n timeoutMs,\n pollMs: STABILIZE_POLL_MS,\n });\n if (!quiescent) {\n entry.log.warn(\"rclone cache did not stabilize before flush — proceeding anyway\", {\n pid: handle.pid,\n thresholdMs,\n timeoutMs,\n });\n }\n }\n\n const refreshRes = await fetch(`http://${handle.rcAddr}/vfs/refresh?recursive=true`, {\n method: \"POST\",\n });\n if (!refreshRes.ok) {\n const body = await refreshRes.text().catch(() => \"\");\n throw new Error(`rclone /vfs/refresh failed: ${refreshRes.status} ${body}`);\n }\n\n const ok = await waitFor(\n async () => {\n const r = await fetch(`http://${handle.rcAddr}/vfs/stats`, { method: \"GET\" });\n if (!r.ok) return false;\n const body = (await r.json()) as { inUse?: number; [k: string]: unknown };\n return body.inUse === 0;\n },\n { timeoutMs: 60_000, intervalMs: 500 },\n );\n if (!ok) {\n throw new Error(`rclone flush did not drain within 60s (pid=${handle.pid})`);\n }\n }\n\n /** Liveness + cache-dir size. */\n async status(handle: RcloneHandle): Promise<{ alive: boolean; cacheBytes: number }> {\n const entry = HANDLES.get(handle.pid);\n if (!entry) {\n return { alive: false, cacheBytes: 0 };\n }\n const alive = entry.proc.exitCode === null && entry.proc.signalCode === null;\n const cacheBytes = await dirSize(entry.cacheDir);\n return { alive, cacheBytes };\n }\n}\n\ninterface RcloneJsonLine {\n level?: string;\n msg?: string;\n source?: string;\n time?: string;\n object?: string;\n error?: unknown;\n}\n\n/**\n * Per-process state threaded through {@link _routeRcloneLogLine} so it can\n * detect a sustained upload failure (the same object failing to upload K times\n * in a row) and surface it exactly once, instead of letting rclone retry\n * silently at INFO level forever.\n */\nexport interface RcloneLogRouteContext {\n /** Consecutive retries for one object before {@link onUploadStuck} fires. */\n stuckThreshold?: number;\n /** Objects already alerted on, to fire once and not on every later retry. */\n alertedObjects?: Set<string>;\n /** Called the first time an object crosses {@link stuckThreshold}. */\n onUploadStuck?: (object: string, tries: number) => void;\n}\n\n/** rclone's vfs write-back failure line: \"... failed to upload try #3, will retry ...\". */\nconst UPLOAD_FAIL_RE = /failed to upload.*?try #(\\d+)/i;\n/** rclone's vfs write-back success line, used to reset the per-object alert. */\nconst UPLOAD_OK_RE = /upload succeeded/i;\n\n/**\n * @internal\n * Route a single rclone log line (JSON or raw) to the appropriate Logger\n * call. Exported for tests; not part of the public API. When `ctx` is\n * supplied, also tracks consecutive upload failures per object and invokes\n * `ctx.onUploadStuck` once the retry count for an object reaches the threshold.\n */\nexport function _routeRcloneLogLine(line: string, log: Logger, ctx?: RcloneLogRouteContext): void {\n const trimmed = line.trim();\n if (trimmed.length === 0) return;\n\n let parsed: RcloneJsonLine | null = null;\n try {\n const candidate = JSON.parse(trimmed);\n if (candidate && typeof candidate === \"object\") parsed = candidate as RcloneJsonLine;\n } catch {\n parsed = null;\n }\n\n if (!parsed) {\n if (ctx) _trackUploadFailure(trimmed, undefined, ctx);\n log.info(trimmed);\n return;\n }\n\n const level = (parsed.level ?? \"INFO\").toUpperCase();\n const msg = parsed.msg ?? \"\";\n const data: Record<string, unknown> = {};\n if (parsed.object !== undefined) data.object = parsed.object;\n if (parsed.source !== undefined) data.source = parsed.source;\n\n if (ctx) _trackUploadFailure(msg, parsed.object, ctx);\n\n switch (level) {\n case \"DEBUG\":\n log.debug(msg, data);\n break;\n case \"INFO\":\n case \"NOTICE\":\n log.info(msg, data);\n break;\n case \"WARNING\":\n log.warn(msg, data);\n break;\n case \"ERROR\":\n case \"CRITICAL\":\n case \"EMERGENCY\":\n case \"ALERT\":\n log.error(msg, parsed.error, data);\n break;\n default:\n log.info(msg, data);\n break;\n }\n}\n\n/**\n * @internal\n * Inspect one rclone message for an upload retry / success and update the\n * per-object alert state in `ctx`. Fires `onUploadStuck` once when an object's\n * retry count first reaches the threshold; a later success clears the alert so\n * a fresh failure run can re-trigger.\n */\nfunction _trackUploadFailure(\n msg: string,\n object: string | undefined,\n ctx: RcloneLogRouteContext,\n): void {\n const failMatch = UPLOAD_FAIL_RE.exec(msg);\n if (failMatch) {\n const tries = Number(failMatch[1]);\n const key = object ?? extractObjectFromMsg(msg) ?? \"(unknown)\";\n const threshold = ctx.stuckThreshold ?? DEFAULT_UPLOAD_STUCK_THRESHOLD;\n if (!ctx.alertedObjects) ctx.alertedObjects = new Set<string>();\n const alerted = ctx.alertedObjects;\n if (tries >= threshold && !alerted.has(key)) {\n alerted.add(key);\n ctx.onUploadStuck?.(key, tries);\n }\n return;\n }\n if (UPLOAD_OK_RE.test(msg)) {\n const key = object ?? extractObjectFromMsg(msg);\n if (key) ctx.alertedObjects?.delete(key);\n }\n}\n\n/** Best-effort path extraction from a free-text rclone message. */\nfunction extractObjectFromMsg(msg: string): string | undefined {\n const m = /(?:^|\\s)([^\\s:]+\\/[^\\s:]+):/.exec(msg);\n return m?.[1];\n}\n\n/**\n * @internal\n * Build the `rclone mount` argv from a spawn config. Extracted from spawn() so\n * the flag wiring (notably `--vfs-write-back` and the conditional\n * `--ignore-size --ignore-checksum`) is unit-testable without actually\n * launching rclone. Exported for tests; not part of the public API.\n */\nexport function _buildRcloneArgs(\n config: RcloneSpawnConfig,\n io: { configPath: string; rcAddr: string },\n): string[] {\n const args = [\n \"mount\",\n config.remote,\n config.mountPoint,\n \"--config\",\n io.configPath,\n \"--cache-dir\",\n config.cacheDir,\n \"--vfs-cache-mode\",\n config.vfsCacheMode,\n \"--vfs-cache-max-size\",\n config.cacheMaxSize ?? \"5G\",\n \"--vfs-cache-max-age\",\n config.cacheMaxAge ?? \"168h\",\n \"--vfs-write-back\",\n config.vfsWriteBack ?? DEFAULT_VFS_WRITE_BACK,\n \"--vfs-cache-poll-interval\",\n \"60s\",\n \"--rc\",\n \"--rc-addr\",\n io.rcAddr,\n \"--log-level\",\n \"INFO\",\n \"--use-json-log\",\n \"--allow-other\",\n ];\n\n // Remotes that rewrite bytes on upload (SharePoint LF→CRLF) make the\n // post-transfer size/hash check fail and rclone rolls the upload back\n // forever. Both flags are needed: a byte change shifts size AND checksum.\n if (config.ignoreSizeCheck) {\n args.push(\"--ignore-size\", \"--ignore-checksum\");\n }\n\n return args;\n}\n\n/**\n * Poll `dir` until its newest file mtime is at least `thresholdMs` in the past\n * (no write for that long), or `timeoutMs` elapses. Returns true if it went\n * quiescent, false on timeout. An empty/missing dir counts as quiescent.\n */\nexport async function awaitQuiescent(\n dir: string,\n opts: { thresholdMs: number; timeoutMs: number; pollMs: number },\n): Promise<boolean> {\n const deadline = Date.now() + opts.timeoutMs;\n for (;;) {\n const newest = await newestMtimeMs(dir);\n if (newest === null || Date.now() - newest >= opts.thresholdMs) return true;\n if (Date.now() >= deadline) return false;\n await sleep(opts.pollMs);\n }\n}\n\n/**\n * Resolve the rclone vfs content subtree (`<cacheDir>/vfs`) where cached file\n * data — and therefore agent writes — land, falling back to `cacheDir` itself\n * if that subtree doesn't exist yet. Keeps the stabilization gate off rclone's\n * `vfsMeta/` housekeeping tree.\n */\nasync function vfsContentDir(cacheDir: string): Promise<string> {\n const vfs = path.join(cacheDir, \"vfs\");\n try {\n return (await stat(vfs)).isDirectory() ? vfs : cacheDir;\n } catch {\n return cacheDir;\n }\n}\n\n/**\n * Recursively find the most recent file mtime (ms) under `dir`. Returns null if\n * the dir is missing or contains no files. Symlinks are skipped — rclone's\n * cache holds only regular files and dirs.\n */\nasync function newestMtimeMs(dir: string): Promise<number | null> {\n let newest: number | null = null;\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const e of entries) {\n const full = path.join(dir, String(e.name));\n if (e.isDirectory()) {\n const sub = await newestMtimeMs(full);\n if (sub !== null && (newest === null || sub > newest)) newest = sub;\n } else if (e.isFile()) {\n try {\n const s = await stat(full);\n if (newest === null || s.mtimeMs > newest) newest = s.mtimeMs;\n } catch {\n // ignore — file vanished between readdir and stat\n }\n }\n }\n } catch {\n return null;\n }\n return newest;\n}\n\n/** Returns true if `mountPoint` appears as a mount in /proc/self/mounts. */\nasync function isMountpoint(mountPoint: string): Promise<boolean> {\n try {\n const resolved = path.resolve(mountPoint);\n const text = await readFile(\"/proc/self/mounts\", \"utf8\");\n for (const raw of text.split(\"\\n\")) {\n if (raw.length === 0) continue;\n const fields = raw.split(/\\s+/);\n const mp = fields[1];\n if (!mp) continue;\n // /proc/self/mounts encodes spaces as \\040; cheap decode.\n const decoded = mp.replace(/\\\\040/g, \" \");\n if (decoded === resolved) return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\n/**\n * Poll `check` every `intervalMs` until it returns true or `timeoutMs`\n * elapses. Returns true on success, false on timeout.\n */\nasync function waitFor(\n check: () => Promise<boolean>,\n opts: { timeoutMs: number; intervalMs: number },\n): Promise<boolean> {\n const deadline = Date.now() + opts.timeoutMs;\n while (Date.now() < deadline) {\n if (await check()) return true;\n await sleep(opts.intervalMs);\n }\n return await check();\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/** Wire a Readable to a per-line callback, buffering partial chunks. */\nfunction attachLineSplitter(stream: Readable, onLine: (line: string) => void): void {\n let buffer = \"\";\n stream.setEncoding(\"utf8\");\n stream.on(\"data\", (chunk: string) => {\n buffer += chunk;\n let idx = buffer.indexOf(\"\\n\");\n while (idx >= 0) {\n const line = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 1);\n if (line.length > 0) onLine(line);\n idx = buffer.indexOf(\"\\n\");\n }\n });\n stream.on(\"end\", () => {\n if (buffer.length > 0) {\n onLine(buffer);\n buffer = \"\";\n }\n });\n}\n\n/** Recursively sum file sizes under `dir`. Returns 0 if dir does not exist. */\nasync function dirSize(dir: string): Promise<number> {\n let total = 0;\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const e of entries) {\n const full = path.join(dir, String(e.name));\n if (e.isDirectory()) {\n total += await dirSize(full);\n } else if (e.isFile()) {\n try {\n const s = await stat(full);\n total += s.size;\n } catch {\n // ignore\n }\n }\n }\n } catch {\n return 0;\n }\n return total;\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  import { initTelemetry } from './chunk-GCJXPUHG.js';
2
2
  import { parseSkillFrontmatter, validateFlowVersions } from './chunk-IPUYL6TD.js';
3
- import { FlowAdapter } from './chunk-OJN25VJO.js';
3
+ import { FlowAdapter } from './chunk-E4UJ7CVK.js';
4
4
  import { loadFlow } from './chunk-ICS76R4T.js';
5
5
  import { buildOrchestratorPrompt, renderStimulusPrompt } from './chunk-GZWJGNNN.js';
6
- import { resolveSettings } from './chunk-ETMUGBHF.js';
7
- import { resolveAgentDir, resolveSkWorkspaceConfig } from './chunk-V5TBKO5Q.js';
6
+ import { resolveSettings } from './chunk-Z3M5K67G.js';
7
+ import { resolveAgentDir, resolveSkWorkspaceConfig } from './chunk-542K7SR6.js';
8
8
  import { createLogger } from './chunk-24UIWON4.js';
9
9
  import fs from 'fs';
10
10
  import path from 'path';
@@ -389,5 +389,5 @@ function walkForSkill(dir, skillId) {
389
389
  }
390
390
 
391
391
  export { resumeFlow, runFlow };
392
- //# sourceMappingURL=chunk-53UNDY6K.js.map
393
- //# sourceMappingURL=chunk-53UNDY6K.js.map
392
+ //# sourceMappingURL=chunk-QTWA6BZK.js.map
393
+ //# sourceMappingURL=chunk-QTWA6BZK.js.map