@skaile/workspaces 0.53.0 → 1.0.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 (137) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/{asset-feeds-OEGJ6NOF.js → asset-feeds-677S5NX7.js} +7 -7
  3. package/dist/{asset-feeds-OEGJ6NOF.js.map → asset-feeds-677S5NX7.js.map} +1 -1
  4. package/dist/asset-manager/index.js +5 -5
  5. package/dist/asset-manager/installer.js +4 -4
  6. package/dist/{chunk-VGT2R662.js → chunk-43JDPQJ4.js} +3 -3
  7. package/dist/{chunk-VGT2R662.js.map → chunk-43JDPQJ4.js.map} +1 -1
  8. package/dist/{chunk-EOINYN5T.js → chunk-DW5MIJK3.js} +3 -3
  9. package/dist/{chunk-EOINYN5T.js.map → chunk-DW5MIJK3.js.map} +1 -1
  10. package/dist/{chunk-VE2UIJ7Y.js → chunk-EXNTTJGP.js} +3 -43
  11. package/dist/chunk-EXNTTJGP.js.map +1 -0
  12. package/dist/{chunk-7ZWLZ5H3.js → chunk-FLRHP3UC.js} +3 -3
  13. package/dist/{chunk-7ZWLZ5H3.js.map → chunk-FLRHP3UC.js.map} +1 -1
  14. package/dist/{chunk-V4BGVOJC.js → chunk-K5JIAHDV.js} +6 -6
  15. package/dist/{chunk-V4BGVOJC.js.map → chunk-K5JIAHDV.js.map} +1 -1
  16. package/dist/{chunk-3IHBGDCG.js → chunk-KSISPZV3.js} +2 -2
  17. package/dist/{chunk-3IHBGDCG.js.map → chunk-KSISPZV3.js.map} +1 -1
  18. package/dist/{chunk-WD2NRI3T.js → chunk-PLQVAEM2.js} +2 -2
  19. package/dist/{chunk-WD2NRI3T.js.map → chunk-PLQVAEM2.js.map} +1 -1
  20. package/dist/{chunk-TCNYSHPM.js → chunk-SFVEHSCI.js} +4 -4
  21. package/dist/{chunk-TCNYSHPM.js.map → chunk-SFVEHSCI.js.map} +1 -1
  22. package/dist/{chunk-QLLFCQU5.js → chunk-WSQ5J7CE.js} +403 -1469
  23. package/dist/chunk-WSQ5J7CE.js.map +1 -0
  24. package/dist/{chunk-BXC5547R.js → chunk-XD7GYMAU.js} +3 -3
  25. package/dist/{chunk-BXC5547R.js.map → chunk-XD7GYMAU.js.map} +1 -1
  26. package/dist/cli/index.js +30 -32
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/connectors/config.js +4 -4
  29. package/dist/connectors/index.js +5 -7
  30. package/dist/connectors/src/config.d.ts +1 -74
  31. package/dist/connectors/src/config.d.ts.map +1 -1
  32. package/dist/connectors/src/connector-manager.d.ts +13 -137
  33. package/dist/connectors/src/connector-manager.d.ts.map +1 -1
  34. package/dist/connectors/src/connector-types.d.ts +3 -25
  35. package/dist/connectors/src/connector-types.d.ts.map +1 -1
  36. package/dist/connectors/src/fleet-utils.d.ts +2 -30
  37. package/dist/connectors/src/fleet-utils.d.ts.map +1 -1
  38. package/dist/connectors/src/fs-utils.d.ts +1 -2
  39. package/dist/connectors/src/fs-utils.d.ts.map +1 -1
  40. package/dist/connectors/src/index.d.ts +2 -12
  41. package/dist/connectors/src/index.d.ts.map +1 -1
  42. package/dist/connectors/src/watcher.d.ts +1 -5
  43. package/dist/connectors/src/watcher.d.ts.map +1 -1
  44. package/dist/core/index.js +3 -3
  45. package/dist/deploy/index.js +3 -3
  46. package/dist/{ensure-sources-GJOXWVWO.js → ensure-sources-OCMHK7LT.js} +5 -5
  47. package/dist/{ensure-sources-GJOXWVWO.js.map → ensure-sources-OCMHK7LT.js.map} +1 -1
  48. package/dist/factory-assets/connectors/deploy.js +5 -7
  49. package/dist/factory-assets/connectors/devserver.js +5 -7
  50. package/dist/factory-assets/connectors/flow/adapter.js +5 -7
  51. package/dist/factory-assets/connectors/flow/engine.js +3 -3
  52. package/dist/factory-assets/connectors/flow/run-flow.js +6 -8
  53. package/dist/factory-assets/connectors/flow.js +5 -7
  54. package/dist/factory-assets/connectors/git.js +5 -7
  55. package/dist/factory-assets/connectors/gmail.js +5 -7
  56. package/dist/factory-assets/connectors/googledrive/CONNECTOR.md +14 -128
  57. package/dist/factory-assets/connectors/googledrive/driver.d.ts +6 -13
  58. package/dist/factory-assets/connectors/googledrive/driver.d.ts.map +1 -1
  59. package/dist/factory-assets/connectors/googledrive.js +5 -7
  60. package/dist/factory-assets/connectors/local.js +5 -7
  61. package/dist/factory-assets/connectors/mattermost.js +5 -7
  62. package/dist/factory-assets/connectors/memory.js +5 -7
  63. package/dist/factory-assets/connectors/minio.js +5 -7
  64. package/dist/factory-assets/connectors/postgres.js +5 -7
  65. package/dist/factory-assets/connectors/s3.js +5 -7
  66. package/dist/factory-assets/connectors/sharepoint/CONNECTOR.md +13 -159
  67. package/dist/factory-assets/connectors/sharepoint/driver.d.ts +6 -12
  68. package/dist/factory-assets/connectors/sharepoint/driver.d.ts.map +1 -1
  69. package/dist/factory-assets/connectors/sharepoint.js +5 -7
  70. package/dist/factory-assets/connectors/sqlite.js +5 -7
  71. package/dist/factory-assets/connectors/static-server.js +5 -7
  72. package/dist/factory-assets/connectors/tunnel.js +5 -7
  73. package/dist/factory-assets/connectors/webdav/CONNECTOR.md +13 -118
  74. package/dist/factory-assets/connectors/webdav/driver.d.ts +6 -15
  75. package/dist/factory-assets/connectors/webdav/driver.d.ts.map +1 -1
  76. package/dist/factory-assets/connectors/webdav.js +5 -7
  77. package/dist/factory-assets/connectors/xstate-store.js +5 -7
  78. package/dist/factory-assets/connectors/xstate.js +5 -7
  79. package/dist/{flows-AGPIE6JM.js → flows-36PZESON.js} +4 -4
  80. package/dist/{flows-AGPIE6JM.js.map → flows-36PZESON.js.map} +1 -1
  81. package/dist/library/index.js +4 -4
  82. package/dist/library/install.js +3 -3
  83. package/dist/{open-library-ERKRGH4B.js → open-library-WRFYABFP.js} +7 -7
  84. package/dist/{open-library-ERKRGH4B.js.map → open-library-WRFYABFP.js.map} +1 -1
  85. package/dist/{paths-D6RUQ522.js → paths-EUNVMONC.js} +5 -5
  86. package/dist/{paths-D6RUQ522.js.map → paths-EUNVMONC.js.map} +1 -1
  87. package/dist/{plugin-store-SGNJTBYS.js → plugin-store-35KIMX6W.js} +5 -5
  88. package/dist/{plugin-store-SGNJTBYS.js.map → plugin-store-35KIMX6W.js.map} +1 -1
  89. package/dist/runner/index.js +7 -9
  90. package/dist/sdk/asset-manager.js +5 -5
  91. package/dist/sdk/core.js +3 -3
  92. package/dist/sdk/flow.js +3 -3
  93. package/dist/sdk/index.js +7 -9
  94. package/dist/sdk/index.js.map +1 -1
  95. package/dist/sdk/runner.js +7 -9
  96. package/dist/{setup-IV2JFW3T.js → setup-HM6HATAC.js} +5 -5
  97. package/dist/{setup-IV2JFW3T.js.map → setup-HM6HATAC.js.map} +1 -1
  98. package/dist/{source-FGOMQMZT.js → source-EZ5Y3CKP.js} +8 -8
  99. package/dist/{source-FGOMQMZT.js.map → source-EZ5Y3CKP.js.map} +1 -1
  100. package/dist/{store-client-GJT2XQ5Z.js → store-client-BUUF7BPK.js} +5 -5
  101. package/dist/{store-client-GJT2XQ5Z.js.map → store-client-BUUF7BPK.js.map} +1 -1
  102. package/dist/tui/index.js +7 -9
  103. package/dist/tui/index.js.map +1 -1
  104. package/dist/workspace-plugin/adapters/mcp.js +3 -3
  105. package/dist/workspace-plugin/adapters/omp.js +3 -3
  106. package/dist/workspace-plugin/index.js +1 -1
  107. package/package.json +1 -13
  108. package/dist/chunk-OVRSNIKJ.js +0 -72
  109. package/dist/chunk-OVRSNIKJ.js.map +0 -1
  110. package/dist/chunk-QLLFCQU5.js.map +0 -1
  111. package/dist/chunk-RAIRWFAN.js +0 -843
  112. package/dist/chunk-RAIRWFAN.js.map +0 -1
  113. package/dist/chunk-VE2UIJ7Y.js.map +0 -1
  114. package/dist/connectors/rclone-config.d.ts +0 -2
  115. package/dist/connectors/rclone-config.js +0 -4
  116. package/dist/connectors/rclone-config.js.map +0 -1
  117. package/dist/connectors/rclone.d.ts +0 -2
  118. package/dist/connectors/rclone.js +0 -5
  119. package/dist/connectors/rclone.js.map +0 -1
  120. package/dist/connectors/src/fleet-health-gate.d.ts +0 -105
  121. package/dist/connectors/src/fleet-health-gate.d.ts.map +0 -1
  122. package/dist/connectors/src/mount-placeholder.d.ts +0 -131
  123. package/dist/connectors/src/mount-placeholder.d.ts.map +0 -1
  124. package/dist/connectors/src/rclone-config/googledrive.d.ts +0 -59
  125. package/dist/connectors/src/rclone-config/googledrive.d.ts.map +0 -1
  126. package/dist/connectors/src/rclone-config/index.d.ts +0 -9
  127. package/dist/connectors/src/rclone-config/index.d.ts.map +0 -1
  128. package/dist/connectors/src/rclone-config/onedrive.d.ts +0 -48
  129. package/dist/connectors/src/rclone-config/onedrive.d.ts.map +0 -1
  130. package/dist/connectors/src/rclone-config/sanitize.d.ts +0 -9
  131. package/dist/connectors/src/rclone-config/sanitize.d.ts.map +0 -1
  132. package/dist/connectors/src/rclone-config/webdav.d.ts +0 -50
  133. package/dist/connectors/src/rclone-config/webdav.d.ts.map +0 -1
  134. package/dist/connectors/src/rclone-process-manager.d.ts +0 -316
  135. package/dist/connectors/src/rclone-process-manager.d.ts.map +0 -1
  136. package/dist/connectors/src/rclone-token-refresh.d.ts +0 -84
  137. package/dist/connectors/src/rclone-token-refresh.d.ts.map +0 -1
@@ -1,6 +1,4 @@
1
- import { renderGoogleDriveConfig, renderOneDriveConfig, renderWebDAVConfig } from './chunk-OVRSNIKJ.js';
2
- import { resolveAuthRef, resolveMountCredentialString, resolveMountCredential } from './chunk-VE2UIJ7Y.js';
3
- import { PortPool, createFsWatcher, RcloneProcessManager, ensureDirMode } from './chunk-RAIRWFAN.js';
1
+ import { resolveAuthRef } from './chunk-EXNTTJGP.js';
4
2
  import { pluginRegistry } from './chunk-6E6PKKAD.js';
5
3
  import { computeFlowStateFromSnapshots } from './chunk-SKADPHAM.js';
6
4
  import { renderStimulusPrompt } from './chunk-DEYYRFQU.js';
@@ -8,15 +6,16 @@ import { computeStimulus } from './chunk-IFGTRL2I.js';
8
6
  import { portableSpawn } from './chunk-3KPNDTMR.js';
9
7
  import { createLogger } from './chunk-JB35MVBN.js';
10
8
  import { existsSync, mkdirSync, renameSync, writeFileSync, readFileSync, cpSync, statSync, createReadStream, readdirSync, openSync, writeSync, closeSync, chmodSync } from 'fs';
11
- import path2, { join, dirname, resolve, sep } from 'path';
9
+ import { join, dirname, resolve, sep, relative } from 'path';
12
10
  import * as zNS from 'zod';
13
- import { execFile, execSync, spawn } from 'child_process';
11
+ import { execSync } from 'child_process';
14
12
  import { createServer } from 'http';
15
13
  import mime from 'mime';
16
- import { promisify } from 'util';
17
- import { stat, readdir, writeFile, chmod, rm, mkdir, readFile } from 'fs/promises';
14
+ import { stat, readdir, mkdir, chmod } from 'fs/promises';
18
15
  import { createRequire } from 'module';
19
16
  import { pathToFileURL } from 'url';
17
+ import { createConnection } from 'net';
18
+ import chokidar from 'chokidar';
20
19
 
21
20
  // connectors/src/connector-base.ts
22
21
  var AbstractConnector = class {
@@ -2110,198 +2109,54 @@ function createConnector5() {
2110
2109
  }
2111
2110
 
2112
2111
  // factory-assets/connectors/googledrive/driver.ts
2113
- var DEFAULT_CACHE_BASE = "/var/cache/skaile";
2114
- function cacheBase() {
2115
- return process.env.SKAILE_RCLONE_CACHE_BASE ?? DEFAULT_CACHE_BASE;
2116
- }
2117
- var ALLOWED_SCOPES = /* @__PURE__ */ new Set([
2118
- "drive",
2119
- "drive.readonly",
2120
- "drive.file",
2121
- "drive.appfolder",
2122
- "drive.metadata.readonly"
2123
- ]);
2112
+ var CLOUD_FLEET_REQUIRED = "Google Drive cloud connectors require host fleet-managed mounts. Add this connector id to SKAILE_FLEET_MANAGED_MOUNTS and bind the host-managed mount into the workspace container.";
2124
2113
  var GoogleDriveConnector = class extends AbstractConnector {
2125
2114
  name = "googledrive";
2126
- rclone = new RcloneProcessManager();
2127
2115
  filesystem = {
2128
- deferMount: true,
2129
- sync: (handle, options) => {
2130
- return this._sync(handle, options);
2131
- }
2116
+ sync: (_handle, options) => syncFleetManaged(options)
2132
2117
  };
2133
2118
  async connect(declaration, ctx) {
2134
2119
  const targetDir = ctx.mountTarget;
2135
2120
  if (!targetDir) {
2136
2121
  throw new Error("GoogleDriveConnector requires ctx.mountTarget to be set by the manager");
2137
2122
  }
2138
- const secrets = ctx.secrets;
2139
- const tokenMediator = ctx.tokenMediator;
2140
- const opts = declaration.options;
2141
- this.log.info("mount called", {
2142
- optionKeys: opts ? Object.keys(opts) : []
2143
- });
2144
- if (isFleetManaged(declaration.id)) {
2145
- this.log.info("mount handled by host fleet \u2014 skipping in-container rclone spawn", {
2146
- connectorId: declaration.id,
2147
- targetDir
2148
- });
2149
- await ensureFleetMounted(targetDir, this.log);
2150
- const fleetState = {
2151
- rcloneHandle: null,
2152
- fleetManaged: true
2153
- };
2154
- return this.makeHandle(declaration, fleetState, targetDir);
2155
- }
2156
- const scopeRaw = opts?.scope !== void 0 ? String(opts.scope) : "drive";
2157
- if (!ALLOWED_SCOPES.has(scopeRaw)) {
2158
- throw new Error(
2159
- `Google Drive connector: invalid scope "${scopeRaw}" (allowed: ${[...ALLOWED_SCOPES].join(", ")})`
2160
- );
2123
+ if (!isFleetManaged(declaration.id)) {
2124
+ throw new Error(CLOUD_FLEET_REQUIRED);
2161
2125
  }
2162
- const scope = scopeRaw;
2163
- const auth = parseAuthBlob(
2164
- await resolveMountCredentialString(declaration, secrets, tokenMediator),
2165
- this.log
2166
- );
2167
- if (!auth.accessToken && !auth.serviceAccountFile) {
2168
- throw new Error(
2169
- "Google Drive connector: no access token or serviceAccountFile resolved. Use 'auth: backend' (platform) or 'auth: env:GDRIVE_TOKEN' (standalone)."
2170
- );
2171
- }
2172
- const rootFolderId = opts?.rootFolderId !== void 0 ? String(opts.rootFolderId) : null;
2173
- const teamDrive = opts?.teamDrive !== void 0 ? String(opts.teamDrive) : null;
2174
- const basePath = String(opts?.basePath ?? "").replace(/^\/+|\/+$/g, "");
2175
- const remoteName = `skaile-${declaration.id}`;
2176
- const remotePath = basePath.length > 0 ? `/${basePath}` : "/";
2177
- const remote = `${remoteName}:${remotePath}`;
2178
- const base = cacheBase();
2179
- const cacheDir = `${base}/${declaration.id}`;
2180
- const vfsCacheMode = declaration.access === "read-only" ? "minimal" : "full";
2181
- this.log.info("mount params resolved", {
2182
- scope,
2183
- remote,
2184
- cacheDir,
2185
- vfsCacheMode,
2186
- hasRefresh: auth.refreshToken !== null,
2187
- hasServiceAccount: auth.serviceAccountFile !== null,
2188
- teamDrive
2189
- });
2190
- const rcloneConfig = renderGoogleDriveConfig({
2191
- remoteName,
2192
- accessToken: auth.accessToken,
2193
- refreshToken: auth.refreshToken,
2194
- expiresAtMs: auth.expiresAtMs,
2195
- clientId: auth.clientId,
2196
- clientSecret: auth.clientSecret,
2197
- scope,
2198
- rootFolderId,
2199
- teamDrive,
2200
- serviceAccountFile: auth.serviceAccountFile
2201
- });
2202
- await ensureDirMode(base, this.log);
2203
- await ensureDirMode(cacheDir, this.log);
2204
- await ensureDirMode(targetDir, this.log);
2205
- const cacheMaxSize = opts?.cacheMaxSize !== void 0 ? String(opts.cacheMaxSize) : void 0;
2206
- const cacheMaxAge = opts?.cacheMaxAge !== void 0 ? String(opts.cacheMaxAge) : void 0;
2207
- const vfsWriteBack = opts?.vfsWriteBack !== void 0 ? String(opts.vfsWriteBack) : void 0;
2208
- const rcloneHandle = await this.rclone.spawn({
2209
- mountId: declaration.id,
2210
- driverName: "googledrive",
2211
- mountPoint: targetDir,
2212
- cacheDir,
2213
- rcloneConfig,
2214
- remote,
2215
- vfsCacheMode,
2216
- ...cacheMaxSize !== void 0 ? { cacheMaxSize } : {},
2217
- ...cacheMaxAge !== void 0 ? { cacheMaxAge } : {},
2218
- ...vfsWriteBack !== void 0 ? { vfsWriteBack } : {},
2219
- log: this.log
2126
+ this.log.info("mount handled by host fleet", {
2127
+ connectorId: declaration.id,
2128
+ targetDir
2220
2129
  });
2221
- this.log.info("mount ready", { pid: rcloneHandle.pid });
2222
- const state = {
2223
- rcloneHandle,
2224
- // Safe to close over the handle directly: GoogleDrive has no token-refresh
2225
- // recycle (unlike sharepoint/webdav, which dereference a handleRef so the
2226
- // hook follows the recycled handle). If a refresh/reconfigure path is ever
2227
- // added here, switch this to the handleRef indirection or it will query a
2228
- // stale handle.
2229
- authoritativeSize: (relPath) => this.rclone.statSize(rcloneHandle, relPath)
2230
- };
2231
- return this.makeHandle(declaration, state, targetDir);
2232
- }
2233
- async disconnect(handle) {
2234
- const s = handle.state;
2235
- if (s.fleetManaged || !s.rcloneHandle) return;
2236
- await this.rclone.stop(s.rcloneHandle, { graceMs: 3e4 });
2130
+ await ensureFleetMounted(targetDir, this.log);
2131
+ return this.makeHandle(
2132
+ declaration,
2133
+ { fleetManaged: true },
2134
+ targetDir
2135
+ );
2237
2136
  }
2238
- async _sync(handle, options) {
2239
- const s = handle.state;
2240
- if (s.fleetManaged || !s.rcloneHandle) {
2241
- options?.onSyncStatus?.({
2242
- phase: "initial_sync_complete",
2243
- totalFiles: 0,
2244
- downloadedFiles: 0,
2245
- stubFiles: 0,
2246
- message: "fleet-managed mount \u2014 flush handled by host"
2247
- });
2248
- return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
2249
- }
2250
- await this.rclone.flush(s.rcloneHandle);
2251
- options?.onSyncStatus?.({
2252
- phase: "initial_sync_complete",
2253
- totalFiles: 0,
2254
- downloadedFiles: 0,
2255
- stubFiles: 0,
2256
- message: "rclone flush complete"
2257
- });
2258
- return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
2137
+ async disconnect(_handle) {
2259
2138
  }
2260
2139
  async onHibernate(handle) {
2261
- const s = handle.state;
2262
- if (s.fleetManaged) return "flushed";
2263
- await this._sync(handle);
2140
+ await this.filesystem.sync(handle);
2264
2141
  return "flushed";
2265
2142
  }
2266
2143
  async onSessionClose(handle) {
2267
- const s = handle.state;
2268
- if (s.fleetManaged) return "flushed";
2269
- await this._sync(handle);
2144
+ await this.filesystem.sync(handle);
2270
2145
  return "flushed";
2271
2146
  }
2272
2147
  };
2273
2148
  function createConnector6() {
2274
2149
  return new GoogleDriveConnector();
2275
2150
  }
2276
- function parseAuthBlob(raw, log) {
2277
- const empty = {
2278
- accessToken: "",
2279
- refreshToken: null,
2280
- expiresAtMs: null,
2281
- clientId: null,
2282
- clientSecret: null,
2283
- serviceAccountFile: null
2284
- };
2285
- if (!raw) return empty;
2286
- if (!raw.startsWith("{")) {
2287
- return { ...empty, accessToken: raw };
2288
- }
2289
- try {
2290
- const parsed = JSON.parse(raw);
2291
- return {
2292
- accessToken: parsed.accessToken ?? "",
2293
- refreshToken: parsed.refreshToken ?? null,
2294
- expiresAtMs: parsed.expiresAt ?? null,
2295
- clientId: parsed.clientId ?? null,
2296
- clientSecret: parsed.clientSecret ?? null,
2297
- serviceAccountFile: parsed.serviceAccountFile ?? null
2298
- };
2299
- } catch (err) {
2300
- log?.warn("auth blob starts with '{' but is not valid JSON \u2014 treating as raw token", {
2301
- error: err instanceof Error ? err.message : String(err)
2302
- });
2303
- return { ...empty, accessToken: raw };
2304
- }
2151
+ function syncFleetManaged(options) {
2152
+ options?.onSyncStatus?.({
2153
+ phase: "initial_sync_complete",
2154
+ totalFiles: 0,
2155
+ downloadedFiles: 0,
2156
+ stubFiles: 0,
2157
+ message: "fleet-managed mount \u2014 flush handled by host"
2158
+ });
2159
+ return Promise.resolve({ filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 });
2305
2160
  }
2306
2161
  var LocalConnector = class extends AbstractConnector {
2307
2162
  name = "local";
@@ -2381,8 +2236,8 @@ var MattermostConnector = class extends AbstractConnector {
2381
2236
  constructor() {
2382
2237
  super();
2383
2238
  this.tools = {
2384
- read: (handle, path3) => this._read(handle, path3),
2385
- write: (handle, path3, content) => this._write(handle, path3, content),
2239
+ read: (handle, path) => this._read(handle, path),
2240
+ write: (handle, path, content) => this._write(handle, path, content),
2386
2241
  describeOperations: (handle) => this._describeOperations(handle),
2387
2242
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args),
2388
2243
  describeSkill: (handle, declaration) => this._describeSkill(handle, declaration)
@@ -2454,23 +2309,23 @@ var MattermostConnector = class extends AbstractConnector {
2454
2309
  handle.status = "disconnected";
2455
2310
  }
2456
2311
  // ── Tool face methods (private, wired in constructor) ─────────────────────
2457
- async _read(handle, path3) {
2312
+ async _read(handle, path) {
2458
2313
  const state = handle.state;
2459
2314
  try {
2460
- const post = await this._apiGet(state.url, state.token, `/api/v4/posts/${path3}`);
2315
+ const post = await this._apiGet(state.url, state.token, `/api/v4/posts/${path}`);
2461
2316
  this._assertChannelAccess(String(post.channel_id ?? ""), state.channelFilter);
2462
2317
  return { data: JSON.stringify(post, null, 2), contentType: "application/json" };
2463
2318
  } catch {
2464
2319
  return null;
2465
2320
  }
2466
2321
  }
2467
- async _write(handle, path3, content) {
2322
+ async _write(handle, path, content) {
2468
2323
  if (handle.access === "read-only")
2469
2324
  throw new Error("MattermostConnector: write denied \u2014 read-only");
2470
2325
  const state = handle.state;
2471
2326
  const message = typeof content === "string" ? content : String(content.data ?? "");
2472
- await this._apiPut(state.url, state.token, `/api/v4/posts/${path3}`, { id: path3, message });
2473
- this.emitChange?.({ path: path3, action: "edit", source: "operation" });
2327
+ await this._apiPut(state.url, state.token, `/api/v4/posts/${path}`, { id: path, message });
2328
+ this.emitChange?.({ path, action: "edit", source: "operation" });
2474
2329
  }
2475
2330
  _describeSkill(handle, declaration) {
2476
2331
  const state = handle.state;
@@ -2728,31 +2583,31 @@ var MattermostConnector = class extends AbstractConnector {
2728
2583
  return typeof id === "string" && filter.has(id);
2729
2584
  });
2730
2585
  }
2731
- async _apiGet(baseUrl, token, path3) {
2732
- const res = await fetch(baseUrl + path3, { headers: { Authorization: `Bearer ${token}` } });
2586
+ async _apiGet(baseUrl, token, path) {
2587
+ const res = await fetch(baseUrl + path, { headers: { Authorization: `Bearer ${token}` } });
2733
2588
  if (!res.ok) {
2734
2589
  const body = await res.text();
2735
- this.log.warn("REST error", { method: "GET", endpoint: path3, status: res.status });
2736
- throw new Error(`Mattermost GET ${path3}: ${res.status} ${body}`);
2590
+ this.log.warn("REST error", { method: "GET", endpoint: path, status: res.status });
2591
+ throw new Error(`Mattermost GET ${path}: ${res.status} ${body}`);
2737
2592
  }
2738
2593
  return res.json();
2739
2594
  }
2740
- async _apiPost(baseUrl, token, path3, body) {
2741
- const res = await fetch(baseUrl + path3, {
2595
+ async _apiPost(baseUrl, token, path, body) {
2596
+ const res = await fetch(baseUrl + path, {
2742
2597
  method: "POST",
2743
2598
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
2744
2599
  body: JSON.stringify(body)
2745
2600
  });
2746
- if (!res.ok) throw new Error(`Mattermost POST ${path3}: ${res.status} ${await res.text()}`);
2601
+ if (!res.ok) throw new Error(`Mattermost POST ${path}: ${res.status} ${await res.text()}`);
2747
2602
  return res.json();
2748
2603
  }
2749
- async _apiPut(baseUrl, token, path3, body) {
2750
- const res = await fetch(baseUrl + path3, {
2604
+ async _apiPut(baseUrl, token, path, body) {
2605
+ const res = await fetch(baseUrl + path, {
2751
2606
  method: "PUT",
2752
2607
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
2753
2608
  body: JSON.stringify(body)
2754
2609
  });
2755
- if (!res.ok) throw new Error(`Mattermost PUT ${path3}: ${res.status} ${await res.text()}`);
2610
+ if (!res.ok) throw new Error(`Mattermost PUT ${path}: ${res.status} ${await res.text()}`);
2756
2611
  return res.json();
2757
2612
  }
2758
2613
  };
@@ -2767,10 +2622,10 @@ var MemoryConnector = class extends AbstractConnector {
2767
2622
  constructor() {
2768
2623
  super();
2769
2624
  this.tools = {
2770
- read: (handle, path3) => this._read(handle, path3),
2771
- write: (handle, path3, content) => this._write(handle, path3, content),
2772
- delete: (handle, path3) => this._delete(handle, path3),
2773
- list: (handle, path3, options) => this._list(handle, path3, options),
2625
+ read: (handle, path) => this._read(handle, path),
2626
+ write: (handle, path, content) => this._write(handle, path, content),
2627
+ delete: (handle, path) => this._delete(handle, path),
2628
+ list: (handle, path, options) => this._list(handle, path, options),
2774
2629
  search: (handle, query, options) => this._search(handle, query, options),
2775
2630
  describeOperations: (handle) => this._describeOperations(handle),
2776
2631
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args)
@@ -2789,26 +2644,26 @@ var MemoryConnector = class extends AbstractConnector {
2789
2644
  store(handle) {
2790
2645
  return handle.state.store;
2791
2646
  }
2792
- async _read(handle, path3) {
2793
- const value = this.store(handle).get(path3);
2647
+ async _read(handle, path) {
2648
+ const value = this.store(handle).get(path);
2794
2649
  if (value === void 0) return null;
2795
2650
  return { data: value, contentType: "application/json" };
2796
2651
  }
2797
- async _write(handle, path3, content) {
2798
- const isNew = !this.store(handle).has(path3);
2652
+ async _write(handle, path, content) {
2653
+ const isNew = !this.store(handle).has(path);
2799
2654
  this.store(handle).set(
2800
- path3,
2655
+ path,
2801
2656
  typeof content.data === "string" ? content.data : content.data.toString("utf-8")
2802
2657
  );
2803
- this.emitChange?.({ path: path3, action: isNew ? "create" : "edit", source: "operation" });
2658
+ this.emitChange?.({ path, action: isNew ? "create" : "edit", source: "operation" });
2804
2659
  }
2805
- async _delete(handle, path3) {
2806
- const deleted = this.store(handle).delete(path3);
2807
- if (deleted) this.emitChange?.({ path: path3, action: "delete", source: "operation" });
2660
+ async _delete(handle, path) {
2661
+ const deleted = this.store(handle).delete(path);
2662
+ if (deleted) this.emitChange?.({ path, action: "delete", source: "operation" });
2808
2663
  return deleted;
2809
2664
  }
2810
- async _list(handle, path3, options) {
2811
- const prefix = path3 ?? "";
2665
+ async _list(handle, path, options) {
2666
+ const prefix = path ?? "";
2812
2667
  const entries = [];
2813
2668
  for (const key of this.store(handle).keys()) {
2814
2669
  if (prefix && !key.startsWith(prefix)) continue;
@@ -2921,8 +2776,8 @@ var MinIOConnector = class extends AbstractConnector {
2921
2776
  constructor() {
2922
2777
  super();
2923
2778
  this.tools = {
2924
- list: (handle, path3) => this._list(handle, path3),
2925
- read: (handle, path3) => this._read(handle, path3),
2779
+ list: (handle, path) => this._list(handle, path),
2780
+ read: (handle, path) => this._read(handle, path),
2926
2781
  describeOperations: (handle) => this._describeOperations(handle),
2927
2782
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args),
2928
2783
  describeSkill: (handle, declaration) => this._describeSkill(handle, declaration)
@@ -3100,10 +2955,10 @@ var MinIOConnector = class extends AbstractConnector {
3100
2955
  }
3101
2956
  return ops;
3102
2957
  }
3103
- async _list(handle, path3, recursive = false) {
2958
+ async _list(handle, path, recursive = false) {
3104
2959
  const { client, bucket } = handle.state;
3105
2960
  const entries = [];
3106
- const stream = client.listObjects(bucket, path3 ?? "", recursive);
2961
+ const stream = client.listObjects(bucket, path ?? "", recursive);
3107
2962
  return new Promise((resolve5, reject) => {
3108
2963
  stream.on("data", (obj) => {
3109
2964
  entries.push({
@@ -3118,10 +2973,10 @@ var MinIOConnector = class extends AbstractConnector {
3118
2973
  stream.on("error", reject);
3119
2974
  });
3120
2975
  }
3121
- async _read(handle, path3) {
2976
+ async _read(handle, path) {
3122
2977
  const { client, bucket } = handle.state;
3123
2978
  const chunks = [];
3124
- const stream = await client.getObject(bucket, path3);
2979
+ const stream = await client.getObject(bucket, path);
3125
2980
  return new Promise((resolve5, reject) => {
3126
2981
  stream.on("data", (chunk) => chunks.push(chunk));
3127
2982
  stream.on("end", () => resolve5({ data: Buffer.concat(chunks).toString() }));
@@ -3179,8 +3034,8 @@ var PostgresConnector = class extends AbstractConnector {
3179
3034
  constructor() {
3180
3035
  super();
3181
3036
  this.tools = {
3182
- read: (handle, path3) => this._read(handle, path3),
3183
- list: (handle, path3, options) => this._list(handle, path3, options),
3037
+ read: (handle, path) => this._read(handle, path),
3038
+ list: (handle, path, options) => this._list(handle, path, options),
3184
3039
  search: (handle, query, options) => this._search(handle, query, options),
3185
3040
  describeOperations: (handle) => this._describeOperations(handle),
3186
3041
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args),
@@ -3268,8 +3123,8 @@ var PostgresConnector = class extends AbstractConnector {
3268
3123
  return handle.state.pool;
3269
3124
  }
3270
3125
  // Base ops map to table/row access via path convention: <table>/<pk>
3271
- async _read(handle, path3) {
3272
- const parts = path3.split("/");
3126
+ async _read(handle, path) {
3127
+ const parts = path.split("/");
3273
3128
  if (parts.length === 1) {
3274
3129
  const table2 = parts[0];
3275
3130
  const result2 = await this.pool(handle).query(
@@ -3288,8 +3143,8 @@ var PostgresConnector = class extends AbstractConnector {
3288
3143
  if (result.rows.length === 0) return null;
3289
3144
  return { data: JSON.stringify(result.rows[0], null, 2), contentType: "application/json" };
3290
3145
  }
3291
- async _list(handle, path3, options) {
3292
- if (!path3) {
3146
+ async _list(handle, path, options) {
3147
+ if (!path) {
3293
3148
  const result2 = await this.pool(handle).query(
3294
3149
  `SELECT table_name FROM information_schema.tables
3295
3150
  WHERE table_schema = 'public' ORDER BY table_name`
@@ -3301,12 +3156,12 @@ var PostgresConnector = class extends AbstractConnector {
3301
3156
  }));
3302
3157
  }
3303
3158
  const limit = options?.limit ?? 100;
3304
- const result = await this.pool(handle).query(`SELECT * FROM "${quoteIdent(path3)}" LIMIT $1`, [
3159
+ const result = await this.pool(handle).query(`SELECT * FROM "${quoteIdent(path)}" LIMIT $1`, [
3305
3160
  limit
3306
3161
  ]);
3307
3162
  return result.rows.map((row, i) => ({
3308
3163
  name: String(row.id ?? i),
3309
- path: `${path3}/${row.id ?? i}`,
3164
+ path: `${path}/${row.id ?? i}`,
3310
3165
  type: "row"
3311
3166
  }));
3312
3167
  }
@@ -3586,216 +3441,54 @@ function createConnector12(sdkOverride) {
3586
3441
  }
3587
3442
 
3588
3443
  // factory-assets/connectors/sharepoint/driver.ts
3589
- var DEFAULT_CACHE_BASE2 = "/var/cache/skaile";
3590
- function cacheBase2() {
3591
- return process.env.SKAILE_RCLONE_CACHE_BASE ?? DEFAULT_CACHE_BASE2;
3592
- }
3444
+ var CLOUD_FLEET_REQUIRED2 = "SharePoint cloud connectors require host fleet-managed mounts. Add this connector id to SKAILE_FLEET_MANAGED_MOUNTS and bind the host-managed mount into the workspace container.";
3593
3445
  var SharePointConnector = class extends AbstractConnector {
3594
3446
  name = "sharepoint";
3595
- rclone = new RcloneProcessManager();
3596
3447
  filesystem = {
3597
- deferMount: true,
3598
- sync: (handle, options) => {
3599
- return this._sync(handle, options);
3600
- }
3448
+ sync: (_handle, options) => syncFleetManaged2(options)
3601
3449
  };
3602
3450
  async connect(declaration, ctx) {
3603
3451
  const targetDir = ctx.mountTarget;
3604
3452
  if (!targetDir) {
3605
3453
  throw new Error("SharePointConnector requires ctx.mountTarget to be set by the manager");
3606
3454
  }
3607
- const secrets = ctx.secrets;
3608
- const tokenMediator = ctx.tokenMediator;
3609
- const opts = declaration.options;
3610
- this.log.info("mount called", {
3611
- optionKeys: opts ? Object.keys(opts) : []
3612
- });
3613
- if (isFleetManaged(declaration.id)) {
3614
- this.log.info("mount handled by host fleet \u2014 skipping in-container rclone spawn", {
3615
- connectorId: declaration.id,
3616
- targetDir
3617
- });
3618
- await ensureFleetMounted(targetDir, this.log);
3619
- const fleetState = {
3620
- handleRef: { current: null },
3621
- fleetManaged: true
3622
- };
3623
- return this.makeHandle(declaration, fleetState, targetDir);
3624
- }
3625
- const driveId = String(opts?.driveId ?? "");
3626
- if (!driveId) {
3627
- throw new Error("SharePoint connector requires options.driveId");
3455
+ if (!isFleetManaged(declaration.id)) {
3456
+ throw new Error(CLOUD_FLEET_REQUIRED2);
3628
3457
  }
3629
- const folderPath = String(opts?.folderPath ?? "").trim();
3630
- if (!folderPath) {
3631
- throw new Error(
3632
- "SharePoint connector requires options.folderPath (rclone backend needs a path string, not just folderId)"
3633
- );
3634
- }
3635
- const basePath = String(opts?.basePath ?? "").replace(/^\/+|\/+$/g, "");
3636
- const cred = await resolveMountCredential(declaration, secrets, tokenMediator, "initial");
3637
- const auth = parseAuthBlob2(cred.token, this.log);
3638
- if (!auth.accessToken) {
3639
- throw new Error(
3640
- "SharePoint connector: no access token resolved. Use 'auth: backend' (platform) or 'auth: env:SHAREPOINT_TOKEN' (standalone)."
3641
- );
3642
- }
3643
- const remoteName = `skaile-${declaration.id}`;
3644
- const remotePath = buildRemotePath(folderPath, basePath);
3645
- const remote = `${remoteName}:${remotePath}`;
3646
- const base = cacheBase2();
3647
- const cacheDir = `${base}/${declaration.id}`;
3648
- const vfsCacheMode = declaration.access === "read-only" ? "minimal" : "full";
3649
- this.log.info("mount params resolved", {
3650
- driveId,
3651
- remote,
3652
- cacheDir,
3653
- vfsCacheMode,
3654
- hasRefresh: auth.refreshToken !== null
3655
- });
3656
- const renderConfig = (rawToken) => {
3657
- const a = parseAuthBlob2(rawToken, this.log);
3658
- return renderOneDriveConfig({
3659
- remoteName,
3660
- accessToken: a.accessToken,
3661
- refreshToken: a.refreshToken,
3662
- expiresAtMs: a.expiresAtMs,
3663
- tenantId: a.tenantId,
3664
- clientId: a.clientId,
3665
- clientSecret: a.clientSecret,
3666
- driveId
3667
- });
3668
- };
3669
- const handleRef = { current: null };
3670
- const refresh = declaration.auth === "backend" && tokenMediator ? createRcloneTokenRefresh({
3458
+ this.log.info("mount handled by host fleet", {
3671
3459
  connectorId: declaration.id,
3672
- tokenMediator,
3673
- renderConfig,
3674
- rclone: this.rclone,
3675
- handleRef,
3676
- log: (line) => this.log.info(line)
3677
- }) : void 0;
3678
- const rcloneConfig = renderConfig(cred.token ?? "");
3679
- await ensureDirMode(base, this.log);
3680
- await ensureDirMode(cacheDir, this.log);
3681
- await ensureDirMode(targetDir, this.log);
3682
- const cacheMaxSize = opts?.cacheMaxSize !== void 0 ? String(opts.cacheMaxSize) : void 0;
3683
- const cacheMaxAge = opts?.cacheMaxAge !== void 0 ? String(opts.cacheMaxAge) : void 0;
3684
- const vfsWriteBack = opts?.vfsWriteBack !== void 0 ? String(opts.vfsWriteBack) : void 0;
3685
- const ignoreSizeCheck = opts?.ignoreSizeCheck === true;
3686
- if (ignoreSizeCheck) {
3687
- this.log.warn(
3688
- "ignoreSizeCheck enabled \u2014 rclone post-transfer size/checksum verification disabled for this mount",
3689
- { connectorId: declaration.id }
3690
- );
3691
- }
3692
- const rcloneHandle = await this.rclone.spawn({
3693
- mountId: declaration.id,
3694
- driverName: "sharepoint",
3695
- mountPoint: targetDir,
3696
- cacheDir,
3697
- rcloneConfig,
3698
- remote,
3699
- vfsCacheMode,
3700
- ...cacheMaxSize !== void 0 ? { cacheMaxSize } : {},
3701
- ...cacheMaxAge !== void 0 ? { cacheMaxAge } : {},
3702
- ...vfsWriteBack !== void 0 ? { vfsWriteBack } : {},
3703
- ...ignoreSizeCheck ? { ignoreSizeCheck: true } : {},
3704
- ...refresh ? { onAuthError: () => refresh.onAuthError() } : {},
3705
- log: this.log
3460
+ targetDir
3706
3461
  });
3707
- this.log.info("mount ready", { pid: rcloneHandle.pid });
3708
- handleRef.current = rcloneHandle;
3709
- refresh?.schedule(cred.expiresAt);
3710
- const state = {
3711
- handleRef,
3712
- refresh,
3713
- authoritativeSize: (relPath) => {
3714
- const h = handleRef.current;
3715
- return h ? this.rclone.statSize(h, relPath) : Promise.resolve(null);
3716
- }
3717
- };
3718
- return this.makeHandle(declaration, state, targetDir);
3719
- }
3720
- async disconnect(handle) {
3721
- const s = handle.state;
3722
- s.refresh?.cancel();
3723
- if (s.fleetManaged || !s.handleRef.current) return;
3724
- await this.rclone.stop(s.handleRef.current, { graceMs: 3e4 });
3462
+ await ensureFleetMounted(targetDir, this.log);
3463
+ return this.makeHandle(
3464
+ declaration,
3465
+ { fleetManaged: true },
3466
+ targetDir
3467
+ );
3725
3468
  }
3726
- async _sync(handle, options) {
3727
- const s = handle.state;
3728
- if (s.fleetManaged || !s.handleRef.current) {
3729
- options?.onSyncStatus?.({
3730
- phase: "initial_sync_complete",
3731
- totalFiles: 0,
3732
- downloadedFiles: 0,
3733
- stubFiles: 0,
3734
- message: "fleet-managed mount \u2014 flush handled by host"
3735
- });
3736
- return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
3737
- }
3738
- await this.rclone.flush(s.handleRef.current);
3739
- options?.onSyncStatus?.({
3740
- phase: "initial_sync_complete",
3741
- totalFiles: 0,
3742
- downloadedFiles: 0,
3743
- stubFiles: 0,
3744
- message: "rclone flush complete"
3745
- });
3746
- return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
3469
+ async disconnect(_handle) {
3747
3470
  }
3748
3471
  async onHibernate(handle) {
3749
- const s = handle.state;
3750
- if (s.fleetManaged) return "flushed";
3751
- await this._sync(handle);
3472
+ await this.filesystem.sync(handle);
3752
3473
  return "flushed";
3753
3474
  }
3754
3475
  async onSessionClose(handle) {
3755
- const s = handle.state;
3756
- if (s.fleetManaged) return "flushed";
3757
- await this._sync(handle);
3476
+ await this.filesystem.sync(handle);
3758
3477
  return "flushed";
3759
3478
  }
3760
3479
  };
3761
3480
  function createConnector13() {
3762
3481
  return new SharePointConnector();
3763
3482
  }
3764
- function parseAuthBlob2(raw, log) {
3765
- const empty = {
3766
- accessToken: "",
3767
- refreshToken: null,
3768
- expiresAtMs: null,
3769
- tenantId: null,
3770
- clientId: null,
3771
- clientSecret: null
3772
- };
3773
- if (!raw) return empty;
3774
- if (!raw.startsWith("{")) {
3775
- return { ...empty, accessToken: raw };
3776
- }
3777
- try {
3778
- const parsed = JSON.parse(raw);
3779
- return {
3780
- accessToken: parsed.accessToken ?? "",
3781
- refreshToken: parsed.refreshToken ?? null,
3782
- expiresAtMs: parsed.expiresAt ?? null,
3783
- tenantId: parsed.tenantId ?? null,
3784
- clientId: parsed.clientId ?? null,
3785
- clientSecret: parsed.clientSecret ?? null
3786
- };
3787
- } catch (err) {
3788
- log?.warn("auth blob starts with '{' but is not valid JSON \u2014 treating as raw token", {
3789
- error: err instanceof Error ? err.message : String(err)
3790
- });
3791
- return { ...empty, accessToken: raw };
3792
- }
3793
- }
3794
- function buildRemotePath(folderPath, basePath) {
3795
- const folder = folderPath.replace(/^\/+|\/+$/g, "");
3796
- const base = basePath.replace(/^\/+|\/+$/g, "");
3797
- const joined = [folder, base].filter((p) => p.length > 0).join("/");
3798
- return joined.length > 0 ? `/${joined}` : "/";
3483
+ function syncFleetManaged2(options) {
3484
+ options?.onSyncStatus?.({
3485
+ phase: "initial_sync_complete",
3486
+ totalFiles: 0,
3487
+ downloadedFiles: 0,
3488
+ stubFiles: 0,
3489
+ message: "fleet-managed mount \u2014 flush handled by host"
3490
+ });
3491
+ return Promise.resolve({ filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 });
3799
3492
  }
3800
3493
  function quoteIdent2(ident) {
3801
3494
  return ident.replace(/"/g, '""');
@@ -3806,8 +3499,8 @@ var SQLiteConnector = class extends AbstractConnector {
3806
3499
  constructor() {
3807
3500
  super();
3808
3501
  this.tools = {
3809
- read: (handle, path3) => this._read(handle, path3),
3810
- list: (handle, path3, options) => this._list(handle, path3, options),
3502
+ read: (handle, path) => this._read(handle, path),
3503
+ list: (handle, path, options) => this._list(handle, path, options),
3811
3504
  search: (handle, query, options) => this._search(handle, query, options),
3812
3505
  describeOperations: (handle) => this._describeOperations(handle),
3813
3506
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args)
@@ -3891,8 +3584,8 @@ var SQLiteConnector = class extends AbstractConnector {
3891
3584
  const changes = db.getRowsModified();
3892
3585
  return changes;
3893
3586
  }
3894
- async _read(handle, path3) {
3895
- const parts = path3.split("/");
3587
+ async _read(handle, path) {
3588
+ const parts = path.split("/");
3896
3589
  if (parts.length === 1) {
3897
3590
  const table2 = parts[0];
3898
3591
  const info = this.queryRows(this.db(handle), `PRAGMA table_info("${quoteIdent2(table2)}")`);
@@ -3908,8 +3601,8 @@ var SQLiteConnector = class extends AbstractConnector {
3908
3601
  if (rows.length === 0) return null;
3909
3602
  return { data: JSON.stringify(rows[0], null, 2), contentType: "application/json" };
3910
3603
  }
3911
- async _list(handle, path3, options) {
3912
- if (!path3) {
3604
+ async _list(handle, path, options) {
3605
+ if (!path) {
3913
3606
  const tables = this.queryRows(
3914
3607
  this.db(handle),
3915
3608
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
@@ -3923,12 +3616,12 @@ var SQLiteConnector = class extends AbstractConnector {
3923
3616
  const limit = options?.limit ?? 100;
3924
3617
  const rows = this.queryRows(
3925
3618
  this.db(handle),
3926
- `SELECT rowid, * FROM "${quoteIdent2(path3)}" LIMIT ?`,
3619
+ `SELECT rowid, * FROM "${quoteIdent2(path)}" LIMIT ?`,
3927
3620
  [limit]
3928
3621
  );
3929
3622
  return rows.map((row) => ({
3930
3623
  name: String(row.rowid ?? row.id ?? "?"),
3931
- path: `${path3}/${row.rowid ?? row.id ?? "?"}`,
3624
+ path: `${path}/${row.rowid ?? row.id ?? "?"}`,
3932
3625
  type: "row"
3933
3626
  }));
3934
3627
  }
@@ -4538,215 +4231,52 @@ async function spawnNgrokTunnel(port) {
4538
4231
  function createConnector16() {
4539
4232
  return new TunnelConnector();
4540
4233
  }
4541
- var DEFAULT_CACHE_BASE3 = "/var/cache/skaile";
4542
- function cacheBase3() {
4543
- return process.env.SKAILE_RCLONE_CACHE_BASE ?? DEFAULT_CACHE_BASE3;
4544
- }
4545
- var ALLOWED_VENDORS = /* @__PURE__ */ new Set([
4546
- "nextcloud",
4547
- "owncloud",
4548
- "sharepoint",
4549
- "fastmail",
4550
- "other"
4551
- ]);
4552
- var execFileP = promisify(execFile);
4234
+
4235
+ // factory-assets/connectors/webdav/driver.ts
4236
+ var CLOUD_FLEET_REQUIRED3 = "WebDAV cloud connectors require host fleet-managed mounts. Add this connector id to SKAILE_FLEET_MANAGED_MOUNTS and bind the host-managed mount into the workspace container.";
4553
4237
  var WebDAVConnector = class extends AbstractConnector {
4554
- // Injectable so tests can stub the `rclone obscure` shell-out; production
4555
- // wires the real binary via createConnector().
4556
- constructor(obscure = obscurePassword) {
4557
- super();
4558
- this.obscure = obscure;
4559
- }
4560
- obscure;
4561
4238
  name = "webdav";
4562
- rclone = new RcloneProcessManager();
4563
4239
  filesystem = {
4564
- deferMount: true,
4565
- sync: (handle, options) => {
4566
- return this._sync(handle, options);
4567
- }
4240
+ sync: (_handle, options) => syncFleetManaged3(options)
4568
4241
  };
4569
4242
  async connect(declaration, ctx) {
4570
4243
  const targetDir = ctx.mountTarget;
4571
4244
  if (!targetDir) {
4572
4245
  throw new Error("WebDAVConnector requires ctx.mountTarget to be set by the manager");
4573
4246
  }
4574
- const secrets = ctx.secrets;
4575
- const tokenMediator = ctx.tokenMediator;
4576
- const opts = declaration.options;
4577
- this.log.info("mount called", {
4578
- optionKeys: opts ? Object.keys(opts) : [],
4579
- hasAuth: declaration.auth !== void 0
4580
- });
4581
- if (isFleetManaged(declaration.id)) {
4582
- this.log.info("mount handled by host fleet \u2014 skipping in-container rclone spawn", {
4583
- connectorId: declaration.id,
4584
- targetDir
4585
- });
4586
- await ensureFleetMounted(targetDir, this.log);
4587
- const fleetState = {
4588
- handleRef: { current: null },
4589
- fleetManaged: true
4590
- };
4591
- return this.makeHandle(declaration, fleetState, targetDir);
4592
- }
4593
- const url = String(declaration.mount?.source ?? "").trim();
4594
- if (!url) {
4595
- throw new Error("WebDAV connector requires declaration.mount.source (server URL)");
4596
- }
4597
- const vendorRaw = opts?.vendor !== void 0 ? String(opts.vendor) : "other";
4598
- if (!ALLOWED_VENDORS.has(vendorRaw)) {
4599
- throw new Error(
4600
- `WebDAV connector: invalid vendor "${vendorRaw}" (allowed: ${[...ALLOWED_VENDORS].join(", ")})`
4601
- );
4247
+ if (!isFleetManaged(declaration.id)) {
4248
+ throw new Error(CLOUD_FLEET_REQUIRED3);
4602
4249
  }
4603
- const vendor = vendorRaw;
4604
- const basePath = String(opts?.basePath ?? "").replace(/^\/+|\/+$/g, "");
4605
- const remotePath = basePath.length > 0 ? `/${basePath}` : "/";
4606
- const cred = await resolveMountCredential(declaration, secrets, tokenMediator, "initial");
4607
- const auth = parseAuth(cred.token);
4608
- const remoteName = `skaile-${declaration.id}`;
4609
- const remote = `${remoteName}:${remotePath}`;
4610
- const base = cacheBase3();
4611
- const cacheDir = `${base}/${declaration.id}`;
4612
- const vfsCacheMode = declaration.access === "read-only" ? "minimal" : "full";
4613
- this.log.info("mount params resolved", {
4614
- url,
4615
- vendor,
4616
- remote,
4617
- cacheDir,
4618
- vfsCacheMode,
4619
- authKind: auth.bearerToken ? "bearer" : auth.username ? "basic" : "anonymous"
4620
- });
4621
- const renderConfig = async (rawToken) => {
4622
- const a = parseAuth(rawToken);
4623
- const obscured = a.password ? await this.obscure(a.password) : null;
4624
- return renderWebDAVConfig({
4625
- remoteName,
4626
- url,
4627
- vendor,
4628
- username: a.username,
4629
- obscuredPassword: obscured,
4630
- bearerToken: a.bearerToken
4631
- });
4632
- };
4633
- const handleRef = { current: null };
4634
- const refresh = declaration.auth === "backend" && tokenMediator ? createRcloneTokenRefresh({
4250
+ this.log.info("mount handled by host fleet", {
4635
4251
  connectorId: declaration.id,
4636
- tokenMediator,
4637
- renderConfig,
4638
- rclone: this.rclone,
4639
- handleRef,
4640
- log: (line) => this.log.info(line)
4641
- }) : void 0;
4642
- const rcloneConfig = await renderConfig(cred.token ?? "");
4643
- await ensureDirMode(base, this.log);
4644
- await ensureDirMode(cacheDir, this.log);
4645
- await ensureDirMode(targetDir, this.log);
4646
- const cacheMaxSize = opts?.cacheMaxSize !== void 0 ? String(opts.cacheMaxSize) : void 0;
4647
- const cacheMaxAge = opts?.cacheMaxAge !== void 0 ? String(opts.cacheMaxAge) : void 0;
4648
- const vfsWriteBack = opts?.vfsWriteBack !== void 0 ? String(opts.vfsWriteBack) : void 0;
4649
- const rcloneHandle = await this.rclone.spawn({
4650
- mountId: declaration.id,
4651
- driverName: "webdav",
4652
- mountPoint: targetDir,
4653
- cacheDir,
4654
- rcloneConfig,
4655
- remote,
4656
- vfsCacheMode,
4657
- ...cacheMaxSize !== void 0 ? { cacheMaxSize } : {},
4658
- ...cacheMaxAge !== void 0 ? { cacheMaxAge } : {},
4659
- ...vfsWriteBack !== void 0 ? { vfsWriteBack } : {},
4660
- ...refresh ? { onAuthError: () => refresh.onAuthError() } : {},
4661
- log: this.log
4252
+ targetDir
4662
4253
  });
4663
- this.log.info("mount ready", { pid: rcloneHandle.pid });
4664
- handleRef.current = rcloneHandle;
4665
- refresh?.schedule(cred.expiresAt);
4666
- const state = {
4667
- handleRef,
4668
- refresh,
4669
- authoritativeSize: (relPath) => {
4670
- const h = handleRef.current;
4671
- return h ? this.rclone.statSize(h, relPath) : Promise.resolve(null);
4672
- }
4673
- };
4674
- return this.makeHandle(declaration, state, targetDir);
4675
- }
4676
- async disconnect(handle) {
4677
- const s = handle.state;
4678
- s.refresh?.cancel();
4679
- if (s.fleetManaged || !s.handleRef.current) return;
4680
- await this.rclone.stop(s.handleRef.current, { graceMs: 3e4 });
4254
+ await ensureFleetMounted(targetDir, this.log);
4255
+ return this.makeHandle(declaration, { fleetManaged: true }, targetDir);
4681
4256
  }
4682
- async _sync(handle, options) {
4683
- const s = handle.state;
4684
- if (s.fleetManaged || !s.handleRef.current) {
4685
- options?.onSyncStatus?.({
4686
- phase: "initial_sync_complete",
4687
- totalFiles: 0,
4688
- downloadedFiles: 0,
4689
- stubFiles: 0,
4690
- message: "fleet-managed mount \u2014 flush handled by host"
4691
- });
4692
- return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
4693
- }
4694
- await this.rclone.flush(s.handleRef.current);
4695
- options?.onSyncStatus?.({
4696
- phase: "initial_sync_complete",
4697
- totalFiles: 0,
4698
- downloadedFiles: 0,
4699
- stubFiles: 0,
4700
- message: "rclone flush complete"
4701
- });
4702
- return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
4257
+ async disconnect(_handle) {
4703
4258
  }
4704
4259
  async onHibernate(handle) {
4705
- const s = handle.state;
4706
- if (s.fleetManaged) return "flushed";
4707
- await this._sync(handle);
4260
+ await this.filesystem.sync(handle);
4708
4261
  return "flushed";
4709
4262
  }
4710
4263
  async onSessionClose(handle) {
4711
- const s = handle.state;
4712
- if (s.fleetManaged) return "flushed";
4713
- await this._sync(handle);
4264
+ await this.filesystem.sync(handle);
4714
4265
  return "flushed";
4715
4266
  }
4716
4267
  };
4717
4268
  function createConnector17() {
4718
4269
  return new WebDAVConnector();
4719
4270
  }
4720
- function parseAuth(raw) {
4721
- const empty = {
4722
- username: null,
4723
- password: null,
4724
- bearerToken: null
4725
- };
4726
- if (!raw) return empty;
4727
- const colonIdx = raw.indexOf(":");
4728
- if (colonIdx === -1) {
4729
- return { ...empty, bearerToken: raw };
4730
- }
4731
- const username = raw.slice(0, colonIdx);
4732
- const password = raw.slice(colonIdx + 1);
4733
- return { username, password, bearerToken: null };
4734
- }
4735
- async function obscurePassword(plain) {
4736
- try {
4737
- const { stdout } = await execFileP("rclone", ["obscure", plain]);
4738
- return stdout.trim();
4739
- } catch (err) {
4740
- const code = err?.code;
4741
- if (code === "ENOENT") {
4742
- throw new Error(
4743
- "WebDAV connector: rclone binary not found on PATH (install rclone to use the webdav connector)"
4744
- );
4745
- }
4746
- throw new Error(
4747
- `WebDAV connector: 'rclone obscure' failed: ${err instanceof Error ? err.message : String(err)}`
4748
- );
4749
- }
4271
+ function syncFleetManaged3(options) {
4272
+ options?.onSyncStatus?.({
4273
+ phase: "initial_sync_complete",
4274
+ totalFiles: 0,
4275
+ downloadedFiles: 0,
4276
+ stubFiles: 0,
4277
+ message: "fleet-managed mount \u2014 flush handled by host"
4278
+ });
4279
+ return Promise.resolve({ filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 });
4750
4280
  }
4751
4281
  var XStateConnector = class extends AbstractConnector {
4752
4282
  name = "xstate";
@@ -4754,8 +4284,8 @@ var XStateConnector = class extends AbstractConnector {
4754
4284
  constructor() {
4755
4285
  super();
4756
4286
  this.tools = {
4757
- read: (handle, path3) => this._read(handle, path3),
4758
- list: (handle, path3, options) => this._list(handle, path3, options),
4287
+ read: (handle, path) => this._read(handle, path),
4288
+ list: (handle, path, options) => this._list(handle, path, options),
4759
4289
  search: (handle, query) => this._search(handle, query),
4760
4290
  describeOperations: (handle) => this._describeOperations(handle),
4761
4291
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args)
@@ -4857,15 +4387,15 @@ var XStateConnector = class extends AbstractConnector {
4857
4387
  this.s(handle).onChange = cb;
4858
4388
  }
4859
4389
  // ── Tool face methods (private, wired in constructor) ─────────────────────
4860
- async _read(handle, path3) {
4390
+ async _read(handle, path) {
4861
4391
  const snapshot = this.s(handle).actor.getSnapshot();
4862
- if (path3 === "" || path3 === "state") {
4392
+ if (path === "" || path === "state") {
4863
4393
  return { data: JSON.stringify(snapshot.value), contentType: "application/json" };
4864
4394
  }
4865
- if (path3 === "context") {
4395
+ if (path === "context") {
4866
4396
  return { data: JSON.stringify(snapshot.context, null, 2), contentType: "application/json" };
4867
4397
  }
4868
- const value = snapshot.context?.[path3];
4398
+ const value = snapshot.context?.[path];
4869
4399
  if (value === void 0) return null;
4870
4400
  return { data: JSON.stringify(value), contentType: "application/json" };
4871
4401
  }
@@ -5059,10 +4589,10 @@ var XStateStoreConnector = class extends AbstractConnector {
5059
4589
  constructor() {
5060
4590
  super();
5061
4591
  this.tools = {
5062
- read: (handle, path3) => this._read(handle, path3),
5063
- write: (handle, path3, content) => this._write(handle, path3, content),
5064
- delete: (handle, path3) => this._delete(handle, path3),
5065
- list: (handle, path3, options) => this._list(handle, path3, options),
4592
+ read: (handle, path) => this._read(handle, path),
4593
+ write: (handle, path, content) => this._write(handle, path, content),
4594
+ delete: (handle, path) => this._delete(handle, path),
4595
+ list: (handle, path, options) => this._list(handle, path, options),
5066
4596
  search: (handle, query, options) => this._search(handle, query, options),
5067
4597
  describeOperations: (handle) => this._describeOperations(handle),
5068
4598
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args)
@@ -5116,17 +4646,17 @@ var XStateStoreConnector = class extends AbstractConnector {
5116
4646
  handle.state.onChange = cb;
5117
4647
  }
5118
4648
  // ── Tool face methods (private, wired in constructor) ─────────────────────
5119
- async _read(handle, path3) {
4649
+ async _read(handle, path) {
5120
4650
  const ctx = this.context(handle);
5121
- if (path3 === "*") {
4651
+ if (path === "*") {
5122
4652
  if (Object.keys(ctx).length === 0) return null;
5123
4653
  return { data: JSON.stringify(ctx, null, 2), contentType: "application/json" };
5124
4654
  }
5125
- const value = ctx[path3];
4655
+ const value = ctx[path];
5126
4656
  if (value === void 0) return null;
5127
4657
  return { data: JSON.stringify(value), contentType: "application/json" };
5128
4658
  }
5129
- async _write(handle, path3, content) {
4659
+ async _write(handle, path, content) {
5130
4660
  const data = typeof content.data === "string" ? content.data : content.data.toString("utf-8");
5131
4661
  let value;
5132
4662
  try {
@@ -5134,14 +4664,14 @@ var XStateStoreConnector = class extends AbstractConnector {
5134
4664
  } catch {
5135
4665
  value = data;
5136
4666
  }
5137
- this.storeState(handle).store.send({ type: "set", key: path3, value });
5138
- this._recordHistory(handle, "set", { key: path3, value });
4667
+ this.storeState(handle).store.send({ type: "set", key: path, value });
4668
+ this._recordHistory(handle, "set", { key: path, value });
5139
4669
  }
5140
- async _delete(handle, path3) {
4670
+ async _delete(handle, path) {
5141
4671
  const ctx = this.context(handle);
5142
- if (!(path3 in ctx)) return false;
5143
- this.storeState(handle).store.send({ type: "delete", key: path3 });
5144
- this._recordHistory(handle, "delete", { key: path3 });
4672
+ if (!(path in ctx)) return false;
4673
+ this.storeState(handle).store.send({ type: "delete", key: path });
4674
+ this._recordHistory(handle, "delete", { key: path });
5145
4675
  return true;
5146
4676
  }
5147
4677
  async _list(handle, _path, options) {
@@ -5446,418 +4976,8 @@ var ConnectorStartupError = class extends Error {
5446
4976
  }
5447
4977
  };
5448
4978
 
5449
- // connectors/src/fleet-health-gate.ts
5450
- function verdictToSurface(verdict) {
5451
- if (verdict === "healthy") return "open";
5452
- if (verdict === "degraded") return "ro";
5453
- return "masked";
5454
- }
5455
- var FleetHealthGate = class {
5456
- constructor(target, barrier, log, opts) {
5457
- this.target = target;
5458
- this.barrier = barrier;
5459
- this.log = log;
5460
- this.unhealthyThreshold = Math.max(1, opts?.unhealthyThreshold ?? 2);
5461
- this.healthyThreshold = Math.max(1, opts?.healthyThreshold ?? 2);
5462
- const initial = opts?.initialSurface ?? "open";
5463
- this.surface = initial;
5464
- this.pendingSurface = initial;
5465
- this.runSurface = initial;
5466
- }
5467
- target;
5468
- barrier;
5469
- log;
5470
- unhealthyThreshold;
5471
- healthyThreshold;
5472
- /** The barrier surface actually applied to the target right now. */
5473
- surface;
5474
- /**
5475
- * The surface the most recently *enqueued* transition will settle on.
5476
- * Hysteresis and re-enqueue de-duping are measured against this, not
5477
- * `surface`, so verdicts arriving while a (slow, async) barrier op is still in
5478
- * flight are counted toward the post-transition surface, not the stale one.
5479
- */
5480
- pendingSurface;
5481
- /** The surface of the *current* same-verdict run, and its length. */
5482
- runSurface;
5483
- runLength = 0;
5484
- /** Serialises barrier ops so overlapping observe() calls can't race. */
5485
- transition = Promise.resolve();
5486
- /** True while any read-only barrier (ro or masked) is established. */
5487
- get isGated() {
5488
- return this.surface !== "open";
5489
- }
5490
- /** The currently-applied surface (test/observability hook). */
5491
- get currentSurface() {
5492
- return this.surface;
5493
- }
5494
- /** The mount target this gate guards (recovery-probe lookup). */
5495
- get mountTarget() {
5496
- return this.target;
5497
- }
5498
- /**
5499
- * Feed one health verdict. Applies hysteresis, then drives the barrier toward
5500
- * the target surface on a change. Resolves once the (possibly no-op)
5501
- * transition is applied. Never throws — a failed barrier op stays gated
5502
- * (fail-closed).
5503
- */
5504
- observe(verdict) {
5505
- const desired = verdictToSurface(verdict);
5506
- if (desired === this.runSurface) {
5507
- this.runLength += 1;
5508
- } else {
5509
- this.runSurface = desired;
5510
- this.runLength = 1;
5511
- }
5512
- const threshold = desired === "open" ? this.healthyThreshold : this.unhealthyThreshold;
5513
- if (desired !== this.pendingSurface && this.runLength >= threshold) {
5514
- this.pendingSurface = desired;
5515
- this.runLength = 0;
5516
- return this.enqueue(() => this.apply(desired));
5517
- }
5518
- return this.transition;
5519
- }
5520
- /**
5521
- * Tear the gate down. If any barrier is established (or in flight), removes it
5522
- * so a leftover read-only surface is not leaked. Call only after the connector
5523
- * above the target is disconnected (top-down, no leaked mount).
5524
- */
5525
- dispose() {
5526
- if (this.surface === "open" && this.pendingSurface === "open") return this.transition;
5527
- this.pendingSurface = "open";
5528
- return this.enqueue(() => this.apply("open"));
5529
- }
5530
- enqueue(step) {
5531
- this.transition = this.transition.then(step, step);
5532
- return this.transition;
5533
- }
5534
- /** Drive the barrier to `desired`, fail-closed to `masked` on any throw. */
5535
- async apply(desired) {
5536
- if (this.surface === desired) {
5537
- this.pendingSurface = desired;
5538
- return;
5539
- }
5540
- try {
5541
- if (desired === "open") {
5542
- await this.barrier.remove(this.target, this.log);
5543
- this.surface = "open";
5544
- this.log?.info("fleet mount recovered \u2014 gate lifted, writable again", {
5545
- target: this.target
5546
- });
5547
- return;
5548
- }
5549
- if (desired === "ro") {
5550
- if (this.barrier.exposeReadOnly) {
5551
- await this.barrier.exposeReadOnly(this.target, this.log);
5552
- } else {
5553
- await this.barrier.establish(this.target, this.log);
5554
- }
5555
- this.surface = "ro";
5556
- this.log?.warn("fleet mount upload-degraded \u2014 gated read-only (content visible)", {
5557
- target: this.target
5558
- });
5559
- return;
5560
- }
5561
- await this.barrier.establish(this.target, this.log);
5562
- this.surface = "masked";
5563
- this.log?.warn("fleet mount unhealthy \u2014 gated read-only (masked)", { target: this.target });
5564
- } catch (err) {
5565
- const safe = "masked";
5566
- this.surface = safe;
5567
- this.pendingSurface = safe;
5568
- this.log?.error(
5569
- `fleet read-only gate transition to '${desired}' threw \u2014 biasing to '${safe}' (fail-closed)`,
5570
- err,
5571
- { target: this.target }
5572
- );
5573
- }
5574
- }
5575
- };
5576
- function isFleetManaged(connectorId) {
5577
- const raw = process.env.SKAILE_FLEET_MANAGED_MOUNTS ?? "";
5578
- if (raw.length === 0) return false;
5579
- return raw.split(",").map((segment) => segment.trim()).filter((segment) => segment.length > 0).includes(connectorId);
5580
- }
5581
- async function ensureFleetMounted(dir, log, options) {
5582
- const timeoutMs = options?.timeoutMs ?? 3e3;
5583
- const pollIntervalMs = options?.pollIntervalMs ?? 100;
5584
- const deadline = Date.now() + timeoutMs;
5585
- let lastErr = null;
5586
- while (Date.now() < deadline) {
5587
- try {
5588
- const st = await stat(dir);
5589
- if (!st.isDirectory()) {
5590
- throw new Error(`fleet mountpoint ${dir} is not a directory`);
5591
- }
5592
- await readdir(dir);
5593
- log.info("fleet bind mount verified", { dir });
5594
- return;
5595
- } catch (err) {
5596
- lastErr = err;
5597
- await new Promise((resolve5) => setTimeout(resolve5, pollIntervalMs));
5598
- }
5599
- }
5600
- throw new Error(
5601
- `fleet mount ${dir} not ready after ${timeoutMs}ms \u2014 check SKAILE_FLEET_MANAGED_MOUNTS env var and host bind: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
5602
- );
5603
- }
5604
- async function probeFleetHealth(dir) {
5605
- try {
5606
- const st = await stat(dir);
5607
- if (!st.isDirectory()) return "error";
5608
- await readdir(dir);
5609
- return "healthy";
5610
- } catch {
5611
- return "error";
5612
- }
5613
- }
5614
- async function probeFleetMountTablePresent(dir) {
5615
- try {
5616
- const text = await readFile("/proc/self/mounts", "utf8");
5617
- const resolved = path2.resolve(dir);
5618
- for (const raw of text.split("\n")) {
5619
- if (raw.length === 0) continue;
5620
- const fields = raw.split(/\s+/);
5621
- const mp = fields[1];
5622
- const fstype = fields[2];
5623
- if (!mp || !fstype) continue;
5624
- if (mp.replace(/\\040/g, " ") !== resolved) continue;
5625
- if (fstype !== "tmpfs") return true;
5626
- }
5627
- return false;
5628
- } catch {
5629
- return false;
5630
- }
5631
- }
5632
- var NOT_READY_SENTINEL = ".skaile-workspace-not-ready";
5633
- var TARGET_ALREADY_PRESENT_CODES = /* @__PURE__ */ new Set(["EEXIST", "ENOTCONN", "EBUSY", "ENOTDIR"]);
5634
- function errnoCode(err) {
5635
- return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
5636
- }
5637
- var SENTINEL_BODY = [
5638
- "This workspace is backed by a remote mount that is not live yet.",
5639
- "It is mounted read-only on purpose: writing here would create a phantom",
5640
- "workspace that diverges from the remote once the mount comes up.",
5641
- "Wait for the mount to finish syncing (watch connector_status) before writing.",
5642
- ""
5643
- ].join("\n");
5644
- var defaultMkdir = async (target) => {
5645
- await mkdir(target, { recursive: true });
5646
- };
5647
- var defaultRunner = (cmd, args) => new Promise((resolve5, reject) => {
5648
- const proc = spawn(cmd, args, { stdio: "ignore" });
5649
- proc.once("error", reject);
5650
- proc.once("close", (code, signal) => {
5651
- if (code === 0) resolve5();
5652
- else reject(new Error(`${cmd} ${args.join(" ")} exited ${code ?? `signal ${signal}`}`));
5653
- });
5654
- });
5655
- var RoMountPlaceholderBarrier = class {
5656
- run;
5657
- mkdirFn;
5658
- modes = /* @__PURE__ */ new Map();
5659
- constructor(opts) {
5660
- this.run = opts?.run ?? defaultRunner;
5661
- this.mkdirFn = opts?.mkdirFn ?? defaultMkdir;
5662
- }
5663
- async establish(target, log) {
5664
- if (this.modes.has(target)) return;
5665
- try {
5666
- await this.mkdirFn(target);
5667
- } catch (err) {
5668
- const code = errnoCode(err);
5669
- if (!code || !TARGET_ALREADY_PRESENT_CODES.has(code)) {
5670
- await this.chmodFallback(target, err, log);
5671
- return;
5672
- }
5673
- log?.debug("barrier target already present (live/broken mount) \u2014 protecting in place", {
5674
- target,
5675
- code
5676
- });
5677
- }
5678
- let tmpfsMounted = false;
5679
- try {
5680
- await this.run("mount", [
5681
- "-t",
5682
- "tmpfs",
5683
- "-o",
5684
- "mode=0755,size=1m",
5685
- "skaile-not-ready",
5686
- target
5687
- ]);
5688
- tmpfsMounted = true;
5689
- await writeFile(path2.join(target, NOT_READY_SENTINEL), SENTINEL_BODY);
5690
- await this.run("mount", ["-o", "remount,ro", target]);
5691
- this.modes.set(target, "mount");
5692
- log?.info("deferred mount barrier established (read-only tmpfs)", { target });
5693
- } catch (err) {
5694
- if (tmpfsMounted) await this.run("umount", [target]).catch(() => {
5695
- });
5696
- await this.chmodFallback(target, err, log);
5697
- }
5698
- }
5699
- async prepareForStackedMount(target, log) {
5700
- const mode = this.modes.get(target);
5701
- if (mode === "mount") {
5702
- try {
5703
- await this.run("mount", ["-o", "remount,rw", target]);
5704
- log?.debug("deferred mount barrier lifted to read-write for stacked mount", { target });
5705
- } catch (err) {
5706
- log?.error(
5707
- "deferred mount barrier: remount read-write before stacked mount failed \u2014 the mount may be rejected with EACCES",
5708
- err instanceof Error ? err : void 0,
5709
- { target }
5710
- );
5711
- }
5712
- return;
5713
- }
5714
- }
5715
- async restoreBarrier(target, log) {
5716
- const mode = this.modes.get(target);
5717
- if (mode === "mount") {
5718
- let restored = false;
5719
- await this.run("mount", ["-o", "remount,ro", target]).then(() => {
5720
- restored = true;
5721
- }).catch((err) => {
5722
- log?.error(
5723
- "deferred mount barrier: remount read-only after failed mount failed \u2014 target may be writable",
5724
- err instanceof Error ? err : void 0,
5725
- { target }
5726
- );
5727
- });
5728
- if (restored) log?.debug("deferred mount barrier re-asserted read-only after failed mount", { target });
5729
- }
5730
- }
5731
- async onMountLive(target, log) {
5732
- const mode = this.modes.get(target);
5733
- if (mode === "mount") {
5734
- log?.debug("deferred mount live; placeholder shadowed", { target });
5735
- return;
5736
- }
5737
- if (mode === "chmod" || mode === "unprotected") {
5738
- if (mode === "chmod") await chmod(target, 493).catch(() => {
5739
- });
5740
- await rm(path2.join(target, NOT_READY_SENTINEL), { force: true }).catch(() => {
5741
- });
5742
- log?.debug("deferred mount live; degraded barrier lifted", { target, mode });
5743
- }
5744
- }
5745
- async exposeReadOnly(target, log) {
5746
- const mode = this.modes.get(target);
5747
- if (mode === "ro-bind") return;
5748
- if (mode === "mount") {
5749
- let unmounted = await this.run("umount", [target]).then(
5750
- () => true,
5751
- () => false
5752
- );
5753
- if (!unmounted) {
5754
- unmounted = await this.run("umount", ["-l", target]).then(
5755
- () => true,
5756
- () => false
5757
- );
5758
- }
5759
- if (!unmounted) {
5760
- log?.error(
5761
- "deferred mount barrier: could not unmount opaque mask to expose read-only surface \u2014 staying masked",
5762
- void 0,
5763
- { target }
5764
- );
5765
- return;
5766
- }
5767
- }
5768
- try {
5769
- await this.run("mount", ["--bind", target, target]);
5770
- await this.run("mount", ["-o", "remount,ro,bind", target]);
5771
- this.modes.set(target, "ro-bind");
5772
- log?.info("deferred mount barrier downgraded to read-only (content visible)", { target });
5773
- } catch (err) {
5774
- const chmodOk = await chmod(target, 365).then(
5775
- () => true,
5776
- () => false
5777
- );
5778
- this.modes.set(target, chmodOk ? "chmod" : "unprotected");
5779
- log?.error(
5780
- chmodOk ? "deferred mount read-only-readable surface degraded to chmod (ro bind unavailable) \u2014 a write by the owning uid could bypass it" : "deferred mount read-only-readable surface FAILED (ro bind and chmod both unavailable) \u2014 target may be writable",
5781
- err instanceof Error ? err : void 0,
5782
- { target }
5783
- );
5784
- }
5785
- }
5786
- isOpaque(target) {
5787
- return this.modes.get(target) === "mount";
5788
- }
5789
- async remove(target, log) {
5790
- const mode = this.modes.get(target);
5791
- if (!mode) return;
5792
- this.modes.delete(target);
5793
- if (mode === "mount" || mode === "ro-bind") {
5794
- let unmounted = false;
5795
- try {
5796
- await this.run("umount", [target]);
5797
- unmounted = true;
5798
- } catch {
5799
- await this.run("umount", ["-l", target]).then(
5800
- () => {
5801
- unmounted = true;
5802
- },
5803
- () => {
5804
- }
5805
- );
5806
- }
5807
- if (unmounted) {
5808
- log?.debug("deferred mount barrier removed (tmpfs unmounted)", { target });
5809
- } else {
5810
- log?.error(
5811
- "deferred mount barrier: umount and umount -l both failed \u2014 tmpfs may be leaked",
5812
- void 0,
5813
- { target }
5814
- );
5815
- }
5816
- return;
5817
- }
5818
- if (mode === "chmod") await chmod(target, 493).catch(() => {
5819
- });
5820
- await rm(path2.join(target, NOT_READY_SENTINEL), { force: true }).catch(() => {
5821
- });
5822
- log?.debug(
5823
- mode === "chmod" ? "deferred mount barrier removed (chmod restored)" : "deferred mount barrier removed (was never enforced \u2014 target stayed writable)",
5824
- { target }
5825
- );
5826
- }
5827
- async chmodFallback(target, cause, log) {
5828
- await writeFile(path2.join(target, NOT_READY_SENTINEL), SENTINEL_BODY).catch(() => {
5829
- });
5830
- const chmodOk = await chmod(target, 365).then(
5831
- () => true,
5832
- () => false
5833
- );
5834
- this.modes.set(target, chmodOk ? "chmod" : "unprotected");
5835
- const detail = { target, error: cause instanceof Error ? cause.message : String(cause) };
5836
- if (chmodOk) {
5837
- log?.error(
5838
- "deferred mount read-only barrier degraded to chmod (mount unavailable) \u2014 a write by the owning uid could bypass it",
5839
- void 0,
5840
- detail
5841
- );
5842
- } else {
5843
- log?.error(
5844
- "deferred mount read-only barrier FAILED (mount and chmod both unavailable) \u2014 target is WRITABLE",
5845
- void 0,
5846
- detail
5847
- );
5848
- }
5849
- }
5850
- };
5851
- var defaultMountPlaceholderBarrier = new RoMountPlaceholderBarrier();
5852
-
5853
4979
  // connectors/src/connector-manager.ts
5854
4980
  var KNOWN_PREFIXES = ["env:", "forge:", "op:", "kp:", "oauth:"];
5855
- var DEFAULT_FLEET_HEALTH_POLL_MS = 5e3;
5856
- function parseFleetHealthPollMs(raw = process.env.SKAILE_FLEET_HEALTH_POLL_MS) {
5857
- if (raw === void 0) return DEFAULT_FLEET_HEALTH_POLL_MS;
5858
- const n = Number(raw);
5859
- return Number.isFinite(n) && n >= 0 ? n : DEFAULT_FLEET_HEALTH_POLL_MS;
5860
- }
5861
4981
  function hasKnownPrefix(ref) {
5862
4982
  return KNOWN_PREFIXES.some((p) => ref.startsWith(p));
5863
4983
  }
@@ -5960,9 +5080,6 @@ var ConnectorManager = class {
5960
5080
  this.catalogEntries = opts.catalogEntries ?? [];
5961
5081
  this.tokenMediator = opts.tokenMediator;
5962
5082
  this.preMintedSecrets = opts.preMintedSecrets;
5963
- this.placeholderBarrier = opts.placeholderBarrier ?? defaultMountPlaceholderBarrier;
5964
- this.fleetHealthPollMs = opts.fleetHealthPollMs ?? parseFleetHealthPollMs();
5965
- this.fleetMountRecoveryProbe = opts.fleetMountRecoveryProbe ?? probeFleetMountTablePresent;
5966
5083
  const secrets = opts.secrets;
5967
5084
  if (secrets && typeof secrets === "object" && "prepareRef" in secrets) {
5968
5085
  this.chain = secrets;
@@ -5985,20 +5102,9 @@ var ConnectorManager = class {
5985
5102
  catalogEntries;
5986
5103
  tokenMediator;
5987
5104
  preMintedSecrets;
5988
- placeholderBarrier;
5989
5105
  mgrLog;
5990
- /** Set once `disconnectAll()` starts, so in-flight deferred mounts bail. */
5991
- disposed = false;
5992
5106
  /** Aborted on dispose; threaded into every `connect()` via `ctx.abortSignal`. */
5993
5107
  disposeController = new AbortController();
5994
- /** Tracks the background task driving deferred (rclone-backed) mounts. */
5995
- deferredConnect;
5996
- /** Periodic local fleet-health probe; runs only while a fleet gate is active. */
5997
- fleetHealthTimer;
5998
- /** Local fleet-health poll interval (ms). 0 disables the local probe. */
5999
- fleetHealthPollMs;
6000
- /** Host-mount recovery detector under an opaque placeholder (injectable for tests). */
6001
- fleetMountRecoveryProbe;
6002
5108
  // ── Private helpers ──────────────────────────────────────────────────────────
6003
5109
  /**
6004
5110
  * Wrap the user-supplied `TokenMediator` so `reason: 'initial'` calls for an
@@ -6128,8 +5234,8 @@ var ConnectorManager = class {
6128
5234
  }
6129
5235
  /** Release every mount target owned by `id` (on disconnect). */
6130
5236
  releaseMountTarget(id) {
6131
- for (const [path3, owner] of [...this.mountTargets]) {
6132
- if (owner === id) this.mountTargets.delete(path3);
5237
+ for (const [path, owner] of [...this.mountTargets]) {
5238
+ if (owner === id) this.mountTargets.delete(path);
6133
5239
  }
6134
5240
  }
6135
5241
  /**
@@ -6193,21 +5299,13 @@ var ConnectorManager = class {
6193
5299
  * connector with a `mount` config failed → throws `ConnectorStartupError`
6194
5300
  * (fail-fast lifecycle semantics for mountable connectors).
6195
5301
  *
6196
- * When `options.deferMounts` is set, connectors whose filesystem face is
6197
- * marked `deferMount` (rclone-backed FUSE mounts) are NOT awaited here:
6198
- * their target is pre-registered (so the mount path renders in the system
6199
- * prompt immediately) and the actual mount is driven on a background task.
6200
- * The returned report therefore covers only the eagerly-connected set, so a
6201
- * slow remote can never gate whatever the caller does next (e.g. emitting
6202
- * `session_init_ack`). Deferred connectors surface their progress through
6203
- * `onStatusChange` (connecting → connected | error).
6204
- *
6205
5302
  * @param declarations - Connector declarations from `skaile.yaml`.
6206
5303
  * @param options.failOnReadWriteError - Throw `ConnectorStartupError` if any
6207
5304
  * read-write connector fails. Read-only failures are always tolerated.
6208
5305
  * Only the eager set is considered — deferred mounts never throw.
6209
- * @param options.deferMounts - Bring `deferMount` filesystem-face connectors
6210
- * up in the background instead of awaiting them.
5306
+ * @param options.deferMounts - Deprecated compatibility option. Cloud
5307
+ * connectors now require already-live host fleet bind mounts and connect
5308
+ * synchronously.
6211
5309
  */
6212
5310
  async connectAll(declarations, options) {
6213
5311
  this.mgrLog.info("connectAll start", { count: declarations.length });
@@ -6215,36 +5313,8 @@ var ConnectorManager = class {
6215
5313
  if (decl.mount != null) this.reserveMountTarget(decl.id, this.resolveMountTarget(decl));
6216
5314
  }
6217
5315
  const results = [];
6218
- const eager = [];
6219
- const deferred = [];
6220
- if (options?.deferMounts) {
6221
- for (const decl of declarations) {
6222
- let connector;
6223
- try {
6224
- connector = await this.resolveConnector(decl.driver);
6225
- } catch {
6226
- }
6227
- if (connector?.filesystem?.deferMount === true) {
6228
- deferred.push({ decl, connector });
6229
- } else {
6230
- eager.push({ decl, connector });
6231
- }
6232
- }
6233
- } else {
6234
- for (const decl of declarations) eager.push({ decl });
6235
- }
6236
- for (const { decl, connector } of eager) {
6237
- results.push(await this.attemptConnect(decl, connector));
6238
- }
6239
- if (deferred.length > 0) {
6240
- for (const { decl, connector } of deferred) {
6241
- await this.preRegisterDeferred(decl, connector);
6242
- }
6243
- const prior = this.deferredConnect;
6244
- this.deferredConnect = (async () => {
6245
- if (prior) await prior;
6246
- await this.connectDeferred(deferred);
6247
- })();
5316
+ for (const decl of declarations) {
5317
+ results.push(await this.attemptConnect(decl));
6248
5318
  }
6249
5319
  const report = { results };
6250
5320
  this.mgrLog.info("connectAll complete", {
@@ -6273,7 +5343,7 @@ var ConnectorManager = class {
6273
5343
  /**
6274
5344
  * Connect a single declaration, emitting `connecting` → `connected | error`.
6275
5345
  * Never throws — failures are recorded in the returned result so `connectAll`
6276
- * can keep going. Shared by the eager loop and the deferred background task.
5346
+ * can keep going.
6277
5347
  */
6278
5348
  async attemptConnect(decl, preResolved) {
6279
5349
  this.opts.onStatusChange?.({
@@ -6299,20 +5369,10 @@ var ConnectorManager = class {
6299
5369
  },
6300
5370
  () => connector.connect(decl, ctx)
6301
5371
  );
6302
- const previous = this.active.get(decl.id);
6303
- if (previous?.watchHandle) {
6304
- try {
6305
- await previous.watchHandle.close();
6306
- } catch {
6307
- }
6308
- }
6309
5372
  this.active.set(decl.id, {
6310
5373
  connector,
6311
5374
  handle,
6312
- declaration: decl,
6313
- placeholderTarget: previous?.placeholderTarget,
6314
- fleetHealthGate: previous?.fleetHealthGate,
6315
- fleetHealthTarget: previous?.fleetHealthTarget
5375
+ declaration: decl
6316
5376
  });
6317
5377
  this.opts.onStatusChange?.({
6318
5378
  type: "connector_status",
@@ -6343,216 +5403,26 @@ var ConnectorManager = class {
6343
5403
  }
6344
5404
  }
6345
5405
  /**
6346
- * Register a provisional entry for a deferred filesystem-face connector so
6347
- * its (deterministic) mount path shows up in `listConnectors()` / the system
6348
- * prompt before the background mount completes.
6349
- *
6350
- * Before exposing the target, a read-only barrier is established so the agent
6351
- * cannot write a phantom workspace into a not-yet-mounted remote.
6352
- *
6353
- * Fleet-managed mounts skip the *static* boot-time barrier (the host already
6354
- * bound the live share at the target — a static barrier would shadow real
6355
- * content) but instead get a *dynamic* health gate: while the host fleet rclone
6356
- * is healthy the target stays writable exactly as before; when it wedges, the
6357
- * gate establishes the same read-only barrier until the mount recovers.
6358
- */
6359
- async preRegisterDeferred(decl, connector) {
6360
- const mountTarget = this.resolveMountTarget(decl);
6361
- if (!existsSync(mountTarget)) mkdirSync(mountTarget, { recursive: true });
6362
- const fleetManaged = isFleetManaged(decl.id);
6363
- let placeholderTarget;
6364
- let fleetHealthGate;
6365
- let fleetHealthTarget;
6366
- if (fleetManaged) {
6367
- fleetHealthGate = new FleetHealthGate(mountTarget, this.placeholderBarrier, this.mgrLog);
6368
- fleetHealthTarget = mountTarget;
6369
- } else {
6370
- try {
6371
- await this.placeholderBarrier.establish(mountTarget, this.mgrLog);
6372
- placeholderTarget = mountTarget;
6373
- } catch (err) {
6374
- this.mgrLog.error("deferred mount barrier establish failed", err, { id: decl.id });
6375
- }
6376
- }
6377
- const handle = {
6378
- id: decl.id,
6379
- driver: connector.name ?? decl.driver,
6380
- access: decl.access,
6381
- status: "connecting",
6382
- mountPath: mountTarget,
6383
- // Empty (not null) so a best-effort disconnect of a still-provisional
6384
- // entry — e.g. a deferred mount that errored — finds no live handle and
6385
- // returns cleanly instead of dereferencing null state.
6386
- state: {}
6387
- };
6388
- this.active.set(decl.id, {
6389
- connector,
6390
- handle,
6391
- declaration: decl,
6392
- placeholderTarget,
6393
- fleetHealthGate,
6394
- fleetHealthTarget
6395
- });
6396
- if (fleetHealthGate) this.ensureFleetHealthPoll();
6397
- }
6398
- /**
6399
- * Start the periodic local fleet-health probe if it is not already running and
6400
- * the poll interval is enabled. Each tick probes every fleet target and feeds
6401
- * the verdict to its gate (hysteresis lives in the gate). The probe catches both
6402
- * directly-detectable failures (EIO / dead bind) AND host-mount recovery while a
6403
- * placeholder is up; silent degradation is covered by the platform signal via
6404
- * `reportFleetHealth`.
6405
- */
6406
- ensureFleetHealthPoll() {
6407
- if (this.fleetHealthTimer || this.fleetHealthPollMs <= 0 || this.disposed) return;
6408
- this.fleetHealthTimer = setInterval(() => {
6409
- void this.runFleetHealthProbe();
6410
- }, this.fleetHealthPollMs);
6411
- this.fleetHealthTimer.unref?.();
6412
- }
6413
- async runFleetHealthProbe() {
6414
- if (this.disposed) return;
6415
- const gates = [...this.active.values()].filter(
6416
- (e) => e.fleetHealthGate != null && e.fleetHealthTarget != null
6417
- );
6418
- await Promise.all(
6419
- gates.map(async (e) => {
6420
- const verdict = e.fleetHealthGate.isGated ? await this.probeGatedRecovery(e.fleetHealthGate) : await probeFleetHealth(e.fleetHealthTarget);
6421
- await e.fleetHealthGate.observe(verdict).catch(() => {
6422
- });
6423
- })
6424
- );
6425
- }
6426
- /**
6427
- * Detect host-mount RECOVERY for an already-gated target so the placeholder lifts
6428
- * edge-triggered (no restart). Over an opaque mask a stat/readdir reads the 0555
6429
- * mask and falsely reads healthy, so recovery is detected via the mount table;
6430
- * non-opaque surfaces hold their current surface until the platform signal lifts.
6431
- * Rationale: `_devlog/notes/2026-06-17-fleet-gate-edge-triggered-recovery.md`.
6432
- */
6433
- async probeGatedRecovery(gate) {
6434
- const target = gate.mountTarget;
6435
- if (!(this.placeholderBarrier.isOpaque?.(target) ?? false)) {
6436
- return gate.currentSurface === "ro" ? "degraded" : "error";
6437
- }
6438
- return await this.fleetMountRecoveryProbe(target) ? "healthy" : "error";
6439
- }
6440
- /**
6441
- * Feed an externally-derived fleet-health verdict for a connector into its gate.
6442
- *
6443
- * This is the consume side of the platform fleet-health signal: the host fleet
6444
- * health poll knows `core/stats.errors` and can detect a *silently-empty-but-
6445
- * writable* degraded mount that the local probe cannot. The runner watches the
6446
- * `<workspace>/.skaile/fleet-health/<connectorId>` flag and forwards its verdict
6447
- * here. Unknown / non-fleet ids are ignored.
6448
- *
6449
- * The verdict drives the gate across three surfaces (writable / read-only-
6450
- * readable / masked): `healthy` lifts any barrier, `degraded` downgrades to a
6451
- * read-only surface that keeps the live content visible, `error` keeps the
6452
- * opaque mask.
6453
- *
6454
- * A deferred remote mount may have been brought up with the *static* boot-time
6455
- * barrier (it was not fleet-managed at connect — e.g. the host bind landed
6456
- * late, after `connectAll` placed the not-ready barrier). When a fleet verdict
6457
- * later arrives for such a connector, a gate is created lazily over that same
6458
- * barrier (seeded `masked`, matching the static barrier already in place) so the
6459
- * authoritative platform signal can lift or downgrade it — otherwise a late-
6460
- * arriving live mount stays masked forever even after it goes healthy.
6461
- */
6462
- async reportFleetHealth(connectorId, verdict) {
6463
- const entry = this.active.get(connectorId);
6464
- if (!entry) return;
6465
- if (!entry.fleetHealthGate) {
6466
- if (!entry.placeholderTarget) return;
6467
- entry.fleetHealthTarget = entry.placeholderTarget;
6468
- entry.fleetHealthGate = new FleetHealthGate(
6469
- entry.placeholderTarget,
6470
- this.placeholderBarrier,
6471
- this.mgrLog,
6472
- { initialSurface: "masked" }
6473
- );
6474
- }
6475
- await entry.fleetHealthGate.observe(verdict).catch(() => {
6476
- });
6477
- }
6478
- /** Drive all deferred mounts concurrently; resolves when every one settles. */
6479
- async connectDeferred(deferred) {
6480
- await Promise.allSettled(
6481
- deferred.map(({ decl, connector }) => this.connectDeferredOne(decl, connector))
6482
- );
6483
- }
6484
- /**
6485
- * Background-connect one deferred mount. On success `attemptConnect` swaps the
6486
- * provisional entry for the real handle; on failure the provisional entry is
6487
- * marked `error` so the mount path keeps rendering as "still syncing". If the
6488
- * manager was disposed mid-flight, the connector that came up is torn down
6489
- * immediately so no rclone process leaks.
5406
+ * Compatibility no-op for the removed in-container fleet health gate. Host
5407
+ * fleet-managed cloud mounts are verified at connect time by their drivers.
6490
5408
  */
6491
- async connectDeferredOne(decl, connector) {
6492
- if (this.disposed) return;
6493
- const preEntry = this.active.get(decl.id);
6494
- if (preEntry?.placeholderTarget && this.placeholderBarrier.prepareForStackedMount) {
6495
- await this.placeholderBarrier.prepareForStackedMount(preEntry.placeholderTarget, this.mgrLog).catch(() => {
6496
- });
6497
- }
6498
- const result = await this.attemptConnect(decl, connector);
6499
- if (!result.connected) {
6500
- const entry2 = this.active.get(decl.id);
6501
- if (entry2 && entry2.handle.status === "connecting") entry2.handle.status = "error";
6502
- const failTarget = entry2?.placeholderTarget ?? preEntry?.placeholderTarget;
6503
- if (failTarget && this.placeholderBarrier.restoreBarrier) {
6504
- await this.placeholderBarrier.restoreBarrier(failTarget, this.mgrLog).catch(() => {
6505
- });
6506
- }
6507
- return;
6508
- }
6509
- const entry = this.active.get(decl.id);
6510
- if (entry?.placeholderTarget) {
6511
- await this.placeholderBarrier.onMountLive(entry.placeholderTarget, this.mgrLog).catch(() => {
6512
- });
6513
- }
6514
- if (this.disposed && this.active.has(decl.id)) {
6515
- await this.disconnect(decl.id).catch(() => {
6516
- });
6517
- }
5409
+ async reportFleetHealth(_connectorId, _verdict) {
6518
5410
  }
6519
5411
  /**
6520
- * Resolve once every background (deferred) mount started by `connectAll` has
6521
- * settled. Resolves immediately when none are in flight. Useful for tests and
6522
- * for callers that want to await full readiness.
5412
+ * Compatibility no-op for the removed deferred rclone mount path.
6523
5413
  */
6524
5414
  async whenDeferredMountsSettled() {
6525
- if (this.deferredConnect) await this.deferredConnect.catch(() => {
6526
- });
6527
5415
  }
6528
5416
  /** Disconnect all connectors and stop all watchers. Best-effort — ignores disconnect errors. */
6529
5417
  async disconnectAll() {
6530
5418
  this.mgrLog.info("disconnectAll start", { count: this.active.size });
6531
- this.disposed = true;
6532
5419
  this.disposeController.abort();
6533
- if (this.fleetHealthTimer) {
6534
- clearInterval(this.fleetHealthTimer);
6535
- this.fleetHealthTimer = void 0;
6536
- }
6537
- if (this.deferredConnect) {
6538
- try {
6539
- await this.deferredConnect;
6540
- } catch {
6541
- }
6542
- this.deferredConnect = void 0;
6543
- }
6544
5420
  await this.unwatchAll();
6545
5421
  for (const [id, entry] of this.active) {
6546
5422
  try {
6547
5423
  await entry.connector.disconnect(entry.handle);
6548
5424
  } catch {
6549
5425
  }
6550
- if (entry.placeholderTarget) {
6551
- await this.placeholderBarrier.remove(entry.placeholderTarget, this.mgrLog).catch(() => {
6552
- });
6553
- }
6554
- if (entry.fleetHealthGate) await entry.fleetHealthGate.dispose().catch(() => {
6555
- });
6556
5426
  this.opts.onStatusChange?.({
6557
5427
  type: "connector_status",
6558
5428
  connectorId: id,
@@ -6568,10 +5438,6 @@ var ConnectorManager = class {
6568
5438
  * Connect a single connector (hot-plug). Resolves fields and allocates
6569
5439
  * mount target if the connector has a filesystem face.
6570
5440
  *
6571
- * This path connects immediately and does NOT establish the read-only
6572
- * deferred-mount barrier — bring `deferMount` (rclone-backed) connectors up
6573
- * through `connectAll({ deferMounts: true })` so the not-ready guarantee holds.
6574
- *
6575
5441
  * @returns The `ConnectorHandle` for subsequent operations.
6576
5442
  */
6577
5443
  async connect(declaration) {
@@ -6641,12 +5507,6 @@ var ConnectorManager = class {
6641
5507
  try {
6642
5508
  await entry.connector.disconnect(entry.handle);
6643
5509
  } finally {
6644
- if (entry.placeholderTarget) {
6645
- await this.placeholderBarrier.remove(entry.placeholderTarget, this.mgrLog).catch(() => {
6646
- });
6647
- }
6648
- if (entry.fleetHealthGate) await entry.fleetHealthGate.dispose().catch(() => {
6649
- });
6650
5510
  this.active.delete(id);
6651
5511
  this.releaseMountTarget(id);
6652
5512
  this.opts.onStatusChange?.({
@@ -6822,7 +5682,7 @@ var ConnectorManager = class {
6822
5682
  }
6823
5683
  return entry.connector.tools;
6824
5684
  }
6825
- async read(id, path3) {
5685
+ async read(id, path) {
6826
5686
  const entry = this.get(id);
6827
5687
  const tools = this.requireToolFace(id, entry);
6828
5688
  if (!tools.read) {
@@ -6835,10 +5695,10 @@ var ConnectorManager = class {
6835
5695
  "skaile.connector.driver": entry.connector.name,
6836
5696
  "skaile.connector.operation": "read"
6837
5697
  },
6838
- () => tools.read(entry.handle, path3)
5698
+ () => tools.read(entry.handle, path)
6839
5699
  );
6840
5700
  }
6841
- async write(id, path3, content) {
5701
+ async write(id, path, content) {
6842
5702
  const entry = this.get(id);
6843
5703
  if (entry.declaration.access === "read-only") {
6844
5704
  throw new Error(`Connector "${id}" is read-only`);
@@ -6854,10 +5714,10 @@ var ConnectorManager = class {
6854
5714
  "skaile.connector.driver": entry.connector.name,
6855
5715
  "skaile.connector.operation": "write"
6856
5716
  },
6857
- () => tools.write(entry.handle, path3, content)
5717
+ () => tools.write(entry.handle, path, content)
6858
5718
  );
6859
5719
  }
6860
- async delete(id, path3) {
5720
+ async delete(id, path) {
6861
5721
  const entry = this.get(id);
6862
5722
  if (entry.declaration.access === "read-only") {
6863
5723
  throw new Error(`Connector "${id}" is read-only`);
@@ -6873,10 +5733,10 @@ var ConnectorManager = class {
6873
5733
  "skaile.connector.driver": entry.connector.name,
6874
5734
  "skaile.connector.operation": "delete"
6875
5735
  },
6876
- () => tools.delete(entry.handle, path3)
5736
+ () => tools.delete(entry.handle, path)
6877
5737
  );
6878
5738
  }
6879
- async list(id, path3, options) {
5739
+ async list(id, path, options) {
6880
5740
  const entry = this.get(id);
6881
5741
  const tools = this.requireToolFace(id, entry);
6882
5742
  if (!tools.list) {
@@ -6889,7 +5749,7 @@ var ConnectorManager = class {
6889
5749
  "skaile.connector.driver": entry.connector.name,
6890
5750
  "skaile.connector.operation": "list"
6891
5751
  },
6892
- () => tools.list(entry.handle, path3, options)
5752
+ () => tools.list(entry.handle, path, options)
6893
5753
  );
6894
5754
  }
6895
5755
  async search(id, query, options) {
@@ -7030,9 +5890,9 @@ async function buildSdkConnectorTools(manager, options) {
7030
5890
  connector_id: z4.string().describe("Connector ID"),
7031
5891
  path: z4.string().describe("Path within the connector")
7032
5892
  },
7033
- handler: async ({ connector_id, path: path3 }) => {
5893
+ handler: async ({ connector_id, path }) => {
7034
5894
  try {
7035
- const content = await manager.read(connector_id, path3);
5895
+ const content = await manager.read(connector_id, path);
7036
5896
  return {
7037
5897
  content: [{ type: "text", text: content?.data?.toString() ?? "(empty)" }]
7038
5898
  };
@@ -7054,11 +5914,11 @@ async function buildSdkConnectorTools(manager, options) {
7054
5914
  },
7055
5915
  handler: async ({
7056
5916
  connector_id,
7057
- path: path3,
5917
+ path,
7058
5918
  content
7059
5919
  }) => {
7060
5920
  try {
7061
- await manager.write(connector_id, path3, { data: content });
5921
+ await manager.write(connector_id, path, { data: content });
7062
5922
  return { content: [{ type: "text", text: "Written." }] };
7063
5923
  } catch (err) {
7064
5924
  return {
@@ -7078,11 +5938,11 @@ async function buildSdkConnectorTools(manager, options) {
7078
5938
  },
7079
5939
  handler: async ({
7080
5940
  connector_id,
7081
- path: path3,
5941
+ path,
7082
5942
  recursive
7083
5943
  }) => {
7084
5944
  try {
7085
- const entries = await manager.list(connector_id, path3, { recursive });
5945
+ const entries = await manager.list(connector_id, path, { recursive });
7086
5946
  return { content: [{ type: "text", text: JSON.stringify(entries, null, 2) }] };
7087
5947
  } catch (err) {
7088
5948
  return {
@@ -7118,9 +5978,9 @@ async function buildSdkConnectorTools(manager, options) {
7118
5978
  connector_id: z4.string().describe("Connector ID"),
7119
5979
  path: z4.string().describe("Path to delete")
7120
5980
  },
7121
- handler: async ({ connector_id, path: path3 }) => {
5981
+ handler: async ({ connector_id, path }) => {
7122
5982
  try {
7123
- const ok = await manager.delete(connector_id, path3);
5983
+ const ok = await manager.delete(connector_id, path);
7124
5984
  return { content: [{ type: "text", text: ok ? "Deleted." : "Not found." }] };
7125
5985
  } catch (err) {
7126
5986
  return {
@@ -7383,6 +6243,45 @@ printf 'password=%s\\n' "$pass"
7383
6243
  printf '\\n'
7384
6244
  `;
7385
6245
  }
6246
+ function isFleetManaged(connectorId) {
6247
+ const raw = process.env.SKAILE_FLEET_MANAGED_MOUNTS ?? "";
6248
+ if (raw.length === 0) return false;
6249
+ return raw.split(",").map((segment) => segment.trim()).filter((segment) => segment.length > 0).includes(connectorId);
6250
+ }
6251
+ async function ensureFleetMounted(dir, log, options) {
6252
+ const timeoutMs = options?.timeoutMs ?? 3e3;
6253
+ const pollIntervalMs = options?.pollIntervalMs ?? 100;
6254
+ const deadline = Date.now() + timeoutMs;
6255
+ let lastErr = null;
6256
+ while (Date.now() < deadline) {
6257
+ try {
6258
+ const st = await stat(dir);
6259
+ if (!st.isDirectory()) {
6260
+ throw new Error(`fleet mountpoint ${dir} is not a directory`);
6261
+ }
6262
+ await readdir(dir);
6263
+ log.info("fleet bind mount verified", { dir });
6264
+ return;
6265
+ } catch (err) {
6266
+ lastErr = err;
6267
+ await new Promise((resolve5) => setTimeout(resolve5, pollIntervalMs));
6268
+ }
6269
+ }
6270
+ throw new Error(
6271
+ `fleet mount ${dir} not ready after ${timeoutMs}ms \u2014 check SKAILE_FLEET_MANAGED_MOUNTS env var and host bind: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
6272
+ );
6273
+ }
6274
+ async function ensureDirMode(dir, log) {
6275
+ await mkdir(dir, { recursive: true });
6276
+ try {
6277
+ await chmod(dir, 504);
6278
+ } catch (err) {
6279
+ log?.debug("chmod 0770 skipped (EPERM \u2014 entrypoint heal expected)", {
6280
+ path: dir,
6281
+ error: err instanceof Error ? err.message : String(err)
6282
+ });
6283
+ }
6284
+ }
7386
6285
 
7387
6286
  // connectors/src/log-buffer.ts
7388
6287
  var LogBuffer = class {
@@ -7436,19 +6335,19 @@ var ManagedGitconfigCollisionError = class extends Error {
7436
6335
  var blockStart = (mountId) => `# skaile-mount: ${mountId}`;
7437
6336
  var blockEnd = (mountId) => `# skaile-mount: ${mountId} (end)`;
7438
6337
  var credentialTag = (mountId) => `# skaile-mount: ${mountId}`;
7439
- function readFileOrEmpty(path3) {
6338
+ function readFileOrEmpty(path) {
7440
6339
  try {
7441
- return readFileSync(path3, "utf-8");
6340
+ return readFileSync(path, "utf-8");
7442
6341
  } catch {
7443
6342
  return "";
7444
6343
  }
7445
6344
  }
7446
- function atomicWrite(path3, content, mode) {
7447
- const dir = dirname(path3);
6345
+ function atomicWrite(path, content, mode) {
6346
+ const dir = dirname(path);
7448
6347
  if (!existsSync(dir)) {
7449
6348
  mkdirSync(dir, { recursive: true, mode: 448 });
7450
6349
  }
7451
- const tmp = `${path3}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
6350
+ const tmp = `${path}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
7452
6351
  const fd = openSync(tmp, "w", mode);
7453
6352
  try {
7454
6353
  writeSync(fd, content);
@@ -7456,7 +6355,7 @@ function atomicWrite(path3, content, mode) {
7456
6355
  closeSync(fd);
7457
6356
  }
7458
6357
  chmodSync(tmp, mode);
7459
- renameSync(tmp, path3);
6358
+ renameSync(tmp, path);
7460
6359
  }
7461
6360
  function appendForeignLine(blocks, line) {
7462
6361
  const last = blocks[blocks.length - 1];
@@ -7522,13 +6421,13 @@ function buildCredentialLine(urlPrefix, token, mountId) {
7522
6421
  const encodedToken = encodeURIComponent(token);
7523
6422
  return `${scheme}://x-access-token:${encodedToken}@${host} ${credentialTag(mountId)}`;
7524
6423
  }
7525
- function readCredentialLines(path3) {
7526
- return readFileOrEmpty(path3).split("\n").filter((l) => l.length > 0);
6424
+ function readCredentialLines(path) {
6425
+ return readFileOrEmpty(path).split("\n").filter((l) => l.length > 0);
7527
6426
  }
7528
- function writeCredentialLines(path3, lines) {
6427
+ function writeCredentialLines(path, lines) {
7529
6428
  const content = lines.length > 0 ? `${lines.join("\n")}
7530
6429
  ` : "";
7531
- atomicWrite(path3, content, 416);
6430
+ atomicWrite(path, content, 416);
7532
6431
  }
7533
6432
  function findExistingBlock(blocks, mountId) {
7534
6433
  return blocks.find((b) => b.mountId === mountId);
@@ -7632,156 +6531,191 @@ function isPackageResolvable(pkg, projectDir) {
7632
6531
  function findMissingPackages(packages, projectDir) {
7633
6532
  return packages.filter((p) => !isPackageResolvable(p, projectDir));
7634
6533
  }
7635
-
7636
- // connectors/src/rclone-token-refresh.ts
7637
- var PROACTIVE_RCLONE_REFRESH_MARGIN_MS = 5 * 60 * 1e3;
7638
- var DEFAULT_REACTIVE_COOLDOWN_MS = 30 * 1e3;
7639
- var MAX_TIMER_MS = 2147483647;
7640
- function createRcloneTokenRefresh(deps) {
7641
- const now2 = deps.now ?? (() => Date.now());
7642
- const setTimeoutImpl = deps.setTimeoutImpl ?? ((handler, ms) => setTimeout(handler, ms));
7643
- const clearTimeoutImpl = deps.clearTimeoutImpl ?? ((handle) => {
7644
- clearTimeout(handle);
7645
- });
7646
- const marginMs = deps.marginMs ?? PROACTIVE_RCLONE_REFRESH_MARGIN_MS;
7647
- const reactiveCooldownMs = deps.reactiveCooldownMs ?? DEFAULT_REACTIVE_COOLDOWN_MS;
7648
- let timer = null;
7649
- let refreshing = false;
7650
- let disposed = false;
7651
- let lastRefreshAt = Number.NEGATIVE_INFINITY;
7652
- function clearTimer() {
7653
- if (timer !== null) {
7654
- clearTimeoutImpl(timer);
7655
- timer = null;
7656
- }
7657
- }
7658
- function cancel() {
7659
- disposed = true;
7660
- clearTimer();
7661
- }
7662
- async function fire(reason) {
7663
- if (refreshing || disposed) return;
7664
- refreshing = true;
7665
- try {
7666
- let result;
7667
- try {
7668
- result = await deps.tokenMediator({ connectorId: deps.connectorId, reason });
7669
- } catch (err) {
7670
- deps.log(
7671
- `[rclone-token-refresh] mediator threw: ${err instanceof Error ? err.message : String(err)}; reactive auth-error path remains as safety net`
7672
- );
7673
- return;
7674
- }
7675
- if (disposed) return;
7676
- if (!result.ok) {
7677
- deps.log(
7678
- `[rclone-token-refresh] mediator refused (${result.code}: ${result.message}); reactive auth-error path remains as safety net`
7679
- );
7680
- return;
7681
- }
7682
- if (!result.token) {
7683
- deps.log(
7684
- "[rclone-token-refresh] mediator returned an empty token; reactive auth-error path remains as safety net"
7685
- );
7686
- return;
7687
- }
7688
- const handle = deps.handleRef.current;
7689
- if (!handle) {
7690
- deps.log("[rclone-token-refresh] no live rclone handle to recycle; skipping refresh");
7691
- return;
7692
- }
7693
- const rcloneConfig = await deps.renderConfig(result.token);
7694
- if (disposed) return;
7695
- let next;
7696
- try {
7697
- next = await deps.rclone.reconfigure(handle, rcloneConfig);
7698
- } catch (err) {
7699
- deps.log(
7700
- `[rclone-token-refresh] reconfigure failed: ${err instanceof Error ? err.message : String(err)}; reactive auth-error path remains as safety net`
7701
- );
7702
- return;
6534
+ var PortPool = class _PortPool {
6535
+ min;
6536
+ max;
6537
+ inUse = /* @__PURE__ */ new Set();
6538
+ constructor(range) {
6539
+ this.min = range[0];
6540
+ this.max = range[1];
6541
+ }
6542
+ /** Allocate the next free port from the range. Checks OS for collisions. */
6543
+ async allocate() {
6544
+ for (let port = this.min; port <= this.max; port++) {
6545
+ if (this.inUse.has(port)) continue;
6546
+ if (!await _PortPool.isPortFree(port)) {
6547
+ this.inUse.add(port);
6548
+ continue;
7703
6549
  }
7704
- if (disposed) {
7705
- deps.log("[rclone-token-refresh] disposed mid-recycle; stopping freshly-spawned process");
7706
- try {
7707
- await deps.rclone.stop(next);
7708
- } catch {
6550
+ this.inUse.add(port);
6551
+ return port;
6552
+ }
6553
+ throw new Error(`No free port in range ${this.min}-${this.max}`);
6554
+ }
6555
+ /** Mark a port as in-use (for explicit port args or registered instances). */
6556
+ reserve(port) {
6557
+ this.inUse.add(port);
6558
+ }
6559
+ /** Return a port to the pool. */
6560
+ release(port) {
6561
+ this.inUse.delete(port);
6562
+ }
6563
+ /** Check if a port is currently tracked as in-use. */
6564
+ isInUse(port) {
6565
+ return this.inUse.has(port);
6566
+ }
6567
+ /** Quick TCP connect test — returns true if nothing is listening. */
6568
+ static isPortFree(port, host = "127.0.0.1") {
6569
+ return new Promise((resolve5) => {
6570
+ const socket = createConnection({ port, host });
6571
+ socket.once("connect", () => {
6572
+ socket.destroy();
6573
+ resolve5(false);
6574
+ });
6575
+ socket.once("error", () => {
6576
+ socket.destroy();
6577
+ resolve5(true);
6578
+ });
6579
+ socket.setTimeout(500, () => {
6580
+ socket.destroy();
6581
+ resolve5(true);
6582
+ });
6583
+ });
6584
+ }
6585
+ };
6586
+ function globToMatcher(pattern) {
6587
+ const segmentMatch = pattern.match(/^\*\*\/([^*/?]+)\/\*\*$/);
6588
+ if (segmentMatch) {
6589
+ const name = segmentMatch[1];
6590
+ return (p) => p.includes(`/${name}/`) || p.endsWith(`/${name}`) || p === name;
6591
+ }
6592
+ const dotSegment = pattern.match(/^\*\*\/(\.[\w#]+)(?:\/\*\*)?$/);
6593
+ if (dotSegment) {
6594
+ const name = dotSegment[1];
6595
+ return (p) => p.includes(`/${name}/`) || p.endsWith(`/${name}`) || p === name;
6596
+ }
6597
+ const extMatch = pattern.match(/^\*\*\/\*(\.\w+(?:\.\*)?)/);
6598
+ if (extMatch) {
6599
+ const suffix = extMatch[1];
6600
+ if (suffix.endsWith(".*")) {
6601
+ const base = suffix.slice(0, -2);
6602
+ return (p) => p.includes(base);
6603
+ }
6604
+ return (p) => p.endsWith(suffix);
6605
+ }
6606
+ const suffixMatch = pattern.match(/^\*\*\/\*(.+)$/);
6607
+ if (suffixMatch) {
6608
+ const sfx = suffixMatch[1];
6609
+ return (p) => p.endsWith(sfx);
6610
+ }
6611
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
6612
+ const re = new RegExp(`^${escaped}$`);
6613
+ return (p) => re.test(p);
6614
+ }
6615
+ var DEFAULT_IGNORED = [
6616
+ (p) => p.includes("/.git/") || p.endsWith("/.git"),
6617
+ (p) => p.includes("/node_modules/") || p.endsWith("/node_modules"),
6618
+ (p) => p.includes("/.connectors/") || p.endsWith("/.connectors"),
6619
+ // Atomic-write tempfiles (Bun.write creates `<name>.tmp.<pid>.<ts>` then renames).
6620
+ // These vanish between readdir and fs.watch, causing EINVAL that crashes the process.
6621
+ (p) => /\.tmp\.\d/.test(p),
6622
+ // Editor swap/backup files that also race with save operations.
6623
+ (p) => p.endsWith(".swp"),
6624
+ (p) => p.endsWith(".swx"),
6625
+ (p) => p.includes("/.#"),
6626
+ (p) => p.endsWith("~")
6627
+ ];
6628
+ var DEFAULT_DEBOUNCE_MS = 150;
6629
+ function parseGitignore(rootDir) {
6630
+ const gitignorePath = join(rootDir, ".gitignore");
6631
+ if (!existsSync(gitignorePath)) return [];
6632
+ try {
6633
+ const content = readFileSync(gitignorePath, "utf-8");
6634
+ const matchers = [];
6635
+ for (const raw of content.split("\n")) {
6636
+ const line = raw.trim();
6637
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
6638
+ let pattern = line;
6639
+ if (pattern.startsWith("/")) {
6640
+ pattern = pattern.slice(1);
6641
+ } else if (!pattern.includes("/")) {
6642
+ pattern = `**/${pattern}`;
6643
+ }
6644
+ if (pattern.endsWith("/")) {
6645
+ matchers.push(globToMatcher(`${pattern}**`));
6646
+ } else {
6647
+ matchers.push(globToMatcher(pattern));
6648
+ if (!pattern.includes("*") && !pattern.includes(".")) {
6649
+ matchers.push(globToMatcher(`${pattern}/**`));
7709
6650
  }
7710
- return;
7711
6651
  }
7712
- deps.handleRef.current = next;
7713
- lastRefreshAt = now2();
7714
- deps.log(
7715
- `[rclone-token-refresh] recycled mount with refreshed token (reason=${reason}, expiresAt=${result.expiresAt ?? "none"})`
7716
- );
7717
- schedule(result.expiresAt);
7718
- } finally {
7719
- refreshing = false;
7720
- }
7721
- }
7722
- function armTimer(fireAtMs) {
7723
- const delayMs = Math.min(Math.max(0, fireAtMs - now2()), MAX_TIMER_MS);
7724
- timer = setTimeoutImpl(() => {
7725
- timer = null;
7726
- if (now2() < fireAtMs) armTimer(fireAtMs);
7727
- else void fire("refresh");
7728
- }, delayMs);
7729
- timer.unref?.();
7730
- }
7731
- function schedule(expiresAtIso) {
7732
- clearTimer();
7733
- if (disposed || !expiresAtIso) return;
7734
- const expiresAtMs = Date.parse(expiresAtIso);
7735
- if (!Number.isFinite(expiresAtMs)) return;
7736
- const fireAtMs = expiresAtMs - marginMs;
7737
- if (fireAtMs - now2() <= 0) {
7738
- deps.log(`[rclone-token-refresh] token within ${marginMs / 1e3}s of expiry; refreshing now`);
7739
- } else {
7740
- deps.log(
7741
- `[rclone-token-refresh] scheduled in ${Math.round((fireAtMs - now2()) / 1e3)}s (expiresAt=${expiresAtIso})`
7742
- );
7743
6652
  }
7744
- armTimer(fireAtMs);
6653
+ return matchers;
6654
+ } catch {
6655
+ return [];
7745
6656
  }
7746
- function onAuthError() {
7747
- if (refreshing || disposed) return;
7748
- if (now2() - lastRefreshAt < reactiveCooldownMs) {
7749
- deps.log(
7750
- "[rclone-token-refresh] auth-error within refresh cooldown; suppressing reactive refresh"
7751
- );
7752
- return;
7753
- }
7754
- clearTimer();
7755
- void fire("retry-401");
6657
+ }
6658
+ var CHOKIDAR_EVENT_MAP = {
6659
+ add: "create",
6660
+ change: "edit",
6661
+ unlink: "delete",
6662
+ addDir: "create",
6663
+ unlinkDir: "delete"
6664
+ };
6665
+ async function createFsWatcher(rootDir, callback, options) {
6666
+ const log = createLogger({ kind: "connector", subkind: "watcher", instance: rootDir });
6667
+ const gitignorePatterns = parseGitignore(rootDir);
6668
+ const optionsIgnored = (options?.ignored ?? []).map(
6669
+ (entry) => typeof entry === "string" ? globToMatcher(entry) : entry
6670
+ );
6671
+ const ignored = [...DEFAULT_IGNORED, ...gitignorePatterns, ...optionsIgnored];
6672
+ const debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;
6673
+ const watcher = chokidar.watch(rootDir, {
6674
+ ignoreInitial: true,
6675
+ ignored,
6676
+ awaitWriteFinish: { stabilityThreshold: debounceMs, pollInterval: 50 },
6677
+ usePolling: false
6678
+ });
6679
+ watcher.on("error", (err) => {
6680
+ const message = err instanceof Error ? err.message : String(err);
6681
+ log.warn("watcher error (non-fatal)", { message });
6682
+ });
6683
+ for (const [chokidarEvent, action] of Object.entries(CHOKIDAR_EVENT_MAP)) {
6684
+ watcher.on(
6685
+ chokidarEvent,
6686
+ (fullPath) => {
6687
+ const rel = relative(rootDir, fullPath);
6688
+ if (!rel || rel.startsWith("..")) return;
6689
+ const normalizedPath = rel.split("\\").join("/");
6690
+ callback({ path: normalizedPath, action, source: "filesystem" });
6691
+ }
6692
+ );
7756
6693
  }
7757
6694
  return {
7758
- schedule,
7759
- onAuthError,
7760
- cancel,
7761
- isPending: () => timer !== null
6695
+ close: () => watcher.close()
7762
6696
  };
7763
6697
  }
7764
6698
  var WorktreeRegistryImpl = class {
7765
6699
  entries = /* @__PURE__ */ new Map();
7766
- register(path3, owner) {
7767
- let owners = this.entries.get(path3);
6700
+ register(path, owner) {
6701
+ let owners = this.entries.get(path);
7768
6702
  if (!owners) {
7769
6703
  owners = /* @__PURE__ */ new Set();
7770
- this.entries.set(path3, owners);
6704
+ this.entries.set(path, owners);
7771
6705
  }
7772
6706
  owners.add(owner);
7773
6707
  }
7774
- deregister(path3, owner) {
7775
- const owners = this.entries.get(path3);
6708
+ deregister(path, owner) {
6709
+ const owners = this.entries.get(path);
7776
6710
  if (!owners) return;
7777
6711
  owners.delete(owner);
7778
- if (owners.size === 0) this.entries.delete(path3);
6712
+ if (owners.size === 0) this.entries.delete(path);
7779
6713
  }
7780
- isInUse(path3) {
7781
- return (this.entries.get(path3)?.size ?? 0) > 0;
6714
+ isInUse(path) {
6715
+ return (this.entries.get(path)?.size ?? 0) > 0;
7782
6716
  }
7783
- owners(path3) {
7784
- return [...this.entries.get(path3) ?? []];
6717
+ owners(path) {
6718
+ return [...this.entries.get(path) ?? []];
7785
6719
  }
7786
6720
  };
7787
6721
  var worktreeRegistry = new WorktreeRegistryImpl();
@@ -7910,8 +6844,8 @@ var FlowAdapter = class extends AbstractConnector {
7910
6844
  constructor() {
7911
6845
  super();
7912
6846
  this.tools = {
7913
- read: (handle, path3) => this.read(handle, path3),
7914
- list: (handle, path3, options) => this.list(handle, path3, options),
6847
+ read: (handle, path) => this.read(handle, path),
6848
+ list: (handle, path, options) => this.list(handle, path, options),
7915
6849
  search: (handle, query, options) => this.search(handle, query, options),
7916
6850
  describeOperations: (handle) => this.describeOperations(handle),
7917
6851
  executeOp: (handle, operation, args) => this.executeOp(handle, operation, args)
@@ -7955,16 +6889,16 @@ var FlowAdapter = class extends AbstractConnector {
7955
6889
  s.execution = this.recompute(s);
7956
6890
  }
7957
6891
  // ── Base operations (private — wired into tools face in constructor) ──
7958
- async read(handle, path3) {
6892
+ async read(handle, path) {
7959
6893
  const s = this.s(handle);
7960
- if (path3 === "" || path3 === "state") {
6894
+ if (path === "" || path === "state") {
7961
6895
  return {
7962
6896
  data: JSON.stringify(s.execution, null, 2),
7963
6897
  contentType: "application/json"
7964
6898
  };
7965
6899
  }
7966
- if (path3.startsWith("node/")) {
7967
- const nodeId = path3.slice(5);
6900
+ if (path.startsWith("node/")) {
6901
+ const nodeId = path.slice(5);
7968
6902
  const node = s.flowDef.nodes.find((n) => n.id === nodeId);
7969
6903
  if (!node) return null;
7970
6904
  const exec = s.execution.nodes[nodeId];
@@ -8781,6 +7715,6 @@ function createConnector() {
8781
7715
  return new FlowAdapter();
8782
7716
  }
8783
7717
 
8784
- export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, ConnectorFieldMissingError, ConnectorManager, ConnectorStartupError, DEFAULT_REACTIVE_COOLDOWN_MS, DeployConnector, DevServerConnector, FleetHealthGate, FlowAdapter, GitConnector, GmailConnector, GoogleDriveConnector, LocalConnector, LogBuffer, ManagedGitconfigCollisionError, MattermostConnector, MemoryConnector, MinIOConnector, NOT_READY_SENTINEL, PROACTIVE_RCLONE_REFRESH_MARGIN_MS, PostgresConnector, RoMountPlaceholderBarrier, S3Connector, SQLiteConnector, SharePointConnector, StaticServerConnector, TunnelConnector, WebDAVConnector, XStateConnector, XStateStoreConnector, __test, atomicReplaceCredential, buildConnectorPromptSection, buildSdkConnectorTools, createConnector, createConnector10, createConnector11, createConnector12, createConnector13, createConnector14, createConnector15, createConnector16, createConnector17, createConnector18, createConnector19, createConnector2, createConnector3, createConnector4, createConnector5, createConnector6, createConnector7, createConnector8, createConnector9, createRcloneTokenRefresh, createWorktree, defaultMountPlaceholderBarrier, defaultSpawn, ensureFleetMounted, findGitRoot, findMissingPackages, getConnector, installNpmPackages, isFleetManaged, isPackageResolvable, listConnectors, listWorktrees, probeFleetHealth, readGitSyncStatus, registerBuiltinConnectors, removeMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock };
8785
- //# sourceMappingURL=chunk-QLLFCQU5.js.map
8786
- //# sourceMappingURL=chunk-QLLFCQU5.js.map
7718
+ export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, ConnectorFieldMissingError, ConnectorManager, ConnectorStartupError, DeployConnector, DevServerConnector, FlowAdapter, GitConnector, GmailConnector, GoogleDriveConnector, LocalConnector, LogBuffer, ManagedGitconfigCollisionError, MattermostConnector, MemoryConnector, MinIOConnector, PortPool, PostgresConnector, S3Connector, SQLiteConnector, SharePointConnector, StaticServerConnector, TunnelConnector, WebDAVConnector, XStateConnector, XStateStoreConnector, __test, atomicReplaceCredential, buildConnectorPromptSection, buildSdkConnectorTools, createConnector, createConnector10, createConnector11, createConnector12, createConnector13, createConnector14, createConnector15, createConnector16, createConnector17, createConnector18, createConnector19, createConnector2, createConnector3, createConnector4, createConnector5, createConnector6, createConnector7, createConnector8, createConnector9, createFsWatcher, createWorktree, defaultSpawn, ensureDirMode, ensureFleetMounted, findGitRoot, findMissingPackages, getConnector, installNpmPackages, isFleetManaged, isPackageResolvable, listConnectors, listWorktrees, readGitSyncStatus, registerBuiltinConnectors, removeMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock };
7719
+ //# sourceMappingURL=chunk-WSQ5J7CE.js.map
7720
+ //# sourceMappingURL=chunk-WSQ5J7CE.js.map