@skaile/workspaces 0.53.0 → 1.1.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 (224) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/dist/{asset-feeds-OEGJ6NOF.js → asset-feeds-NIAABO6U.js} +9 -9
  3. package/dist/{asset-feeds-OEGJ6NOF.js.map → asset-feeds-NIAABO6U.js.map} +1 -1
  4. package/dist/asset-manager/index.js +6 -6
  5. package/dist/asset-manager/installer.js +5 -5
  6. package/dist/asset-manager/src/index.d.ts +128 -0
  7. package/dist/asset-manager/src/index.d.ts.map +1 -1
  8. package/dist/bridge/drivers/claude-sdk.js +2 -2
  9. package/dist/bridge/drivers/codex.js +1 -1
  10. package/dist/bridge/drivers/continue.js +2 -2
  11. package/dist/bridge/drivers/echo.js +1 -1
  12. package/dist/bridge/drivers/gemini.js +3 -3
  13. package/dist/bridge/drivers/goose.js +2 -2
  14. package/dist/bridge/drivers/omp.js +1 -1
  15. package/dist/bridge/drivers/opencode.js +2 -2
  16. package/dist/bridge/drivers/qwen.js +3 -3
  17. package/dist/bridge/index.js +3 -3
  18. package/dist/{chunk-7ZWLZ5H3.js → chunk-35TLBMHX.js} +4 -4
  19. package/dist/{chunk-7ZWLZ5H3.js.map → chunk-35TLBMHX.js.map} +1 -1
  20. package/dist/{chunk-6VUP6XHK.js → chunk-52XHXSBN.js} +3 -3
  21. package/dist/{chunk-6VUP6XHK.js.map → chunk-52XHXSBN.js.map} +1 -1
  22. package/dist/{chunk-NXC6IEL4.js → chunk-5AGARSQT.js} +267 -261
  23. package/dist/chunk-5AGARSQT.js.map +1 -0
  24. package/dist/{chunk-QLLFCQU5.js → chunk-B4IOSXRZ.js} +485 -1502
  25. package/dist/chunk-B4IOSXRZ.js.map +1 -0
  26. package/dist/{chunk-VE2UIJ7Y.js → chunk-BCJL2UNH.js} +4 -44
  27. package/dist/chunk-BCJL2UNH.js.map +1 -0
  28. package/dist/{chunk-W53D5Q52.js → chunk-BM37QD6U.js} +3 -3
  29. package/dist/{chunk-W53D5Q52.js.map → chunk-BM37QD6U.js.map} +1 -1
  30. package/dist/{chunk-WD2NRI3T.js → chunk-DMHVL5MC.js} +5 -5
  31. package/dist/{chunk-WD2NRI3T.js.map → chunk-DMHVL5MC.js.map} +1 -1
  32. package/dist/{chunk-V4BGVOJC.js → chunk-EE4NL25D.js} +28 -19
  33. package/dist/chunk-EE4NL25D.js.map +1 -0
  34. package/dist/{chunk-VGT2R662.js → chunk-G2X3THJV.js} +5 -5
  35. package/dist/{chunk-VGT2R662.js.map → chunk-G2X3THJV.js.map} +1 -1
  36. package/dist/{chunk-3IHBGDCG.js → chunk-INUQVXUE.js} +2 -2
  37. package/dist/{chunk-3IHBGDCG.js.map → chunk-INUQVXUE.js.map} +1 -1
  38. package/dist/{chunk-UTHEIPEY.js → chunk-KVZ6XKVN.js} +56 -14
  39. package/dist/chunk-KVZ6XKVN.js.map +1 -0
  40. package/dist/{chunk-TCNYSHPM.js → chunk-L3SLC3S4.js} +253 -14
  41. package/dist/chunk-L3SLC3S4.js.map +1 -0
  42. package/dist/{chunk-3KPNDTMR.js → chunk-MG6UIS7G.js} +3 -3
  43. package/dist/{chunk-3KPNDTMR.js.map → chunk-MG6UIS7G.js.map} +1 -1
  44. package/dist/{chunk-LKOMH3QC.js → chunk-OIGYDVCM.js} +3 -3
  45. package/dist/{chunk-LKOMH3QC.js.map → chunk-OIGYDVCM.js.map} +1 -1
  46. package/dist/{chunk-N5AE5SOC.js → chunk-QVE3ILCY.js} +2 -2
  47. package/dist/{chunk-N5AE5SOC.js.map → chunk-QVE3ILCY.js.map} +1 -1
  48. package/dist/{chunk-GYPA2Q7F.js → chunk-SHOKXRL4.js} +21 -4
  49. package/dist/chunk-SHOKXRL4.js.map +1 -0
  50. package/dist/{chunk-6AD3NS7R.js → chunk-U5J77IIZ.js} +4 -4
  51. package/dist/{chunk-6AD3NS7R.js.map → chunk-U5J77IIZ.js.map} +1 -1
  52. package/dist/{chunk-OYMSCV7T.js → chunk-UY5Q2NZH.js} +3 -3
  53. package/dist/{chunk-OYMSCV7T.js.map → chunk-UY5Q2NZH.js.map} +1 -1
  54. package/dist/{chunk-2D5YE3FL.js → chunk-VYIK5IE5.js} +85 -6
  55. package/dist/chunk-VYIK5IE5.js.map +1 -0
  56. package/dist/{chunk-EOINYN5T.js → chunk-YI5OZ7QO.js} +3 -3
  57. package/dist/{chunk-EOINYN5T.js.map → chunk-YI5OZ7QO.js.map} +1 -1
  58. package/dist/{chunk-BXC5547R.js → chunk-Z4EJOBEL.js} +7 -7
  59. package/dist/{chunk-BXC5547R.js.map → chunk-Z4EJOBEL.js.map} +1 -1
  60. package/dist/{chunk-2ZK7M2SG.js → chunk-ZUE2PIJ5.js} +3 -3
  61. package/dist/{chunk-2ZK7M2SG.js.map → chunk-ZUE2PIJ5.js.map} +1 -1
  62. package/dist/cli/index.js +189 -56
  63. package/dist/cli/index.js.map +1 -1
  64. package/dist/cli/src/commands/catalog.d.ts +18 -0
  65. package/dist/cli/src/commands/catalog.d.ts.map +1 -1
  66. package/dist/cli/src/commands/manage.d.ts.map +1 -1
  67. package/dist/cli/src/commands/tree.d.ts.map +1 -1
  68. package/dist/cli/src/commands/why.d.ts.map +1 -1
  69. package/dist/cli/src/helpers.d.ts +25 -0
  70. package/dist/cli/src/helpers.d.ts.map +1 -1
  71. package/dist/connectors/config.js +5 -5
  72. package/dist/connectors/index.js +6 -8
  73. package/dist/connectors/src/config.d.ts +1 -74
  74. package/dist/connectors/src/config.d.ts.map +1 -1
  75. package/dist/connectors/src/connector-manager.d.ts +13 -137
  76. package/dist/connectors/src/connector-manager.d.ts.map +1 -1
  77. package/dist/connectors/src/connector-registry.d.ts.map +1 -1
  78. package/dist/connectors/src/connector-types.d.ts +3 -25
  79. package/dist/connectors/src/connector-types.d.ts.map +1 -1
  80. package/dist/connectors/src/fleet-utils.d.ts +2 -30
  81. package/dist/connectors/src/fleet-utils.d.ts.map +1 -1
  82. package/dist/connectors/src/fs-utils.d.ts +1 -2
  83. package/dist/connectors/src/fs-utils.d.ts.map +1 -1
  84. package/dist/connectors/src/index.d.ts +2 -12
  85. package/dist/connectors/src/index.d.ts.map +1 -1
  86. package/dist/connectors/src/watcher.d.ts +1 -5
  87. package/dist/connectors/src/watcher.d.ts.map +1 -1
  88. package/dist/core/index.js +4 -4
  89. package/dist/core/runtime-assets.js +2 -2
  90. package/dist/core/src/index.d.ts +1 -1
  91. package/dist/core/src/index.d.ts.map +1 -1
  92. package/dist/core/src/repo-manager.d.ts +19 -1
  93. package/dist/core/src/repo-manager.d.ts.map +1 -1
  94. package/dist/core/src/walker.d.ts +7 -0
  95. package/dist/core/src/walker.d.ts.map +1 -1
  96. package/dist/core/workspace-config.js +1 -1
  97. package/dist/deploy/index.js +4 -4
  98. package/dist/discovery/index.js +1 -1
  99. package/dist/discovery/src/discover.d.ts.map +1 -1
  100. package/dist/discovery/src/index.d.ts +1 -1
  101. package/dist/discovery/src/index.d.ts.map +1 -1
  102. package/dist/discovery/src/reference-lint.d.ts +30 -0
  103. package/dist/discovery/src/reference-lint.d.ts.map +1 -1
  104. package/dist/discovery/src/tree-entries.d.ts.map +1 -1
  105. package/dist/{ensure-sources-GJOXWVWO.js → ensure-sources-MJYHZ77Y.js} +14 -7
  106. package/dist/ensure-sources-MJYHZ77Y.js.map +1 -0
  107. package/dist/factory-assets/connectors/box/CONNECTOR.md +33 -0
  108. package/dist/factory-assets/connectors/box/driver.d.ts +27 -0
  109. package/dist/factory-assets/connectors/box/driver.d.ts.map +1 -0
  110. package/dist/{connectors/rclone-config.d.ts → factory-assets/connectors/box.d.ts} +1 -1
  111. package/dist/factory-assets/connectors/box.js +20 -0
  112. package/dist/factory-assets/connectors/box.js.map +1 -0
  113. package/dist/factory-assets/connectors/deploy.js +6 -8
  114. package/dist/factory-assets/connectors/devserver.js +6 -8
  115. package/dist/factory-assets/connectors/flow/adapter.js +6 -8
  116. package/dist/factory-assets/connectors/flow/engine.js +4 -4
  117. package/dist/factory-assets/connectors/flow/run-flow.js +7 -9
  118. package/dist/factory-assets/connectors/flow.js +6 -8
  119. package/dist/factory-assets/connectors/git.js +6 -8
  120. package/dist/factory-assets/connectors/gmail.js +6 -8
  121. package/dist/factory-assets/connectors/googledrive/CONNECTOR.md +14 -128
  122. package/dist/factory-assets/connectors/googledrive/driver.d.ts +6 -13
  123. package/dist/factory-assets/connectors/googledrive/driver.d.ts.map +1 -1
  124. package/dist/factory-assets/connectors/googledrive.js +6 -8
  125. package/dist/factory-assets/connectors/local.js +6 -8
  126. package/dist/factory-assets/connectors/mattermost.js +6 -8
  127. package/dist/factory-assets/connectors/memory.js +6 -8
  128. package/dist/factory-assets/connectors/minio.js +6 -8
  129. package/dist/factory-assets/connectors/postgres.js +6 -8
  130. package/dist/factory-assets/connectors/s3.js +6 -8
  131. package/dist/factory-assets/connectors/sharepoint/CONNECTOR.md +13 -159
  132. package/dist/factory-assets/connectors/sharepoint/driver.d.ts +6 -12
  133. package/dist/factory-assets/connectors/sharepoint/driver.d.ts.map +1 -1
  134. package/dist/factory-assets/connectors/sharepoint.js +6 -8
  135. package/dist/factory-assets/connectors/sqlite.js +6 -8
  136. package/dist/factory-assets/connectors/static-server.js +6 -8
  137. package/dist/factory-assets/connectors/tunnel.js +6 -8
  138. package/dist/factory-assets/connectors/webdav/CONNECTOR.md +13 -118
  139. package/dist/factory-assets/connectors/webdav/driver.d.ts +6 -15
  140. package/dist/factory-assets/connectors/webdav/driver.d.ts.map +1 -1
  141. package/dist/factory-assets/connectors/webdav.js +6 -8
  142. package/dist/factory-assets/connectors/xstate-store.js +6 -8
  143. package/dist/factory-assets/connectors/xstate.js +6 -8
  144. package/dist/factory-assets/skaile.manifest.yaml +4 -1
  145. package/dist/{flows-AGPIE6JM.js → flows-4Z74KY2H.js} +4 -4
  146. package/dist/{flows-AGPIE6JM.js.map → flows-4Z74KY2H.js.map} +1 -1
  147. package/dist/helpers-7KOVCMHW.js +22 -0
  148. package/dist/{helpers-F4NZ66VD.js.map → helpers-7KOVCMHW.js.map} +1 -1
  149. package/dist/library/index.js +7 -7
  150. package/dist/library/install.js +4 -4
  151. package/dist/library/src/library.d.ts +6 -0
  152. package/dist/library/src/library.d.ts.map +1 -1
  153. package/dist/library/src/local/db.d.ts.map +1 -1
  154. package/dist/library/src/local/library.d.ts +9 -0
  155. package/dist/library/src/local/library.d.ts.map +1 -1
  156. package/dist/library/src/local/local-catalog-source.d.ts +9 -0
  157. package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
  158. package/dist/library/src/local/schema.d.ts +19 -0
  159. package/dist/library/src/local/schema.d.ts.map +1 -1
  160. package/dist/open-library-PGTL332T.js +29 -0
  161. package/dist/{open-library-ERKRGH4B.js.map → open-library-PGTL332T.js.map} +1 -1
  162. package/dist/{paths-D6RUQ522.js → paths-S7QNXYMB.js} +7 -7
  163. package/dist/{paths-D6RUQ522.js.map → paths-S7QNXYMB.js.map} +1 -1
  164. package/dist/{plugin-store-SGNJTBYS.js → plugin-store-3DAVGKHN.js} +6 -6
  165. package/dist/{plugin-store-SGNJTBYS.js.map → plugin-store-3DAVGKHN.js.map} +1 -1
  166. package/dist/runner/index.js +11 -13
  167. package/dist/runner/src/serve.d.ts.map +1 -1
  168. package/dist/sdk/asset-manager.js +6 -6
  169. package/dist/sdk/bridge.js +3 -3
  170. package/dist/sdk/core.js +4 -4
  171. package/dist/sdk/flow.js +4 -4
  172. package/dist/sdk/index.js +11 -13
  173. package/dist/sdk/index.js.map +1 -1
  174. package/dist/sdk/runner.js +11 -13
  175. package/dist/{setup-IV2JFW3T.js → setup-WBGDT2WY.js} +9 -9
  176. package/dist/{setup-IV2JFW3T.js.map → setup-WBGDT2WY.js.map} +1 -1
  177. package/dist/{source-FGOMQMZT.js → source-FJXRQ2NU.js} +18 -12
  178. package/dist/{source-FGOMQMZT.js.map → source-FJXRQ2NU.js.map} +1 -1
  179. package/dist/{store-client-GJT2XQ5Z.js → store-client-YGDN4L2G.js} +7 -7
  180. package/dist/{store-client-GJT2XQ5Z.js.map → store-client-YGDN4L2G.js.map} +1 -1
  181. package/dist/tui/index.js +11 -13
  182. package/dist/tui/index.js.map +1 -1
  183. package/dist/workspace-plugin/adapters/mcp.js +2 -2
  184. package/dist/workspace-plugin/adapters/omp.js +3 -3
  185. package/dist/workspace-plugin/index.js +1 -1
  186. package/package.json +7 -13
  187. package/dist/chunk-2D5YE3FL.js.map +0 -1
  188. package/dist/chunk-GYPA2Q7F.js.map +0 -1
  189. package/dist/chunk-NXC6IEL4.js.map +0 -1
  190. package/dist/chunk-OVRSNIKJ.js +0 -72
  191. package/dist/chunk-OVRSNIKJ.js.map +0 -1
  192. package/dist/chunk-QLLFCQU5.js.map +0 -1
  193. package/dist/chunk-RAIRWFAN.js +0 -843
  194. package/dist/chunk-RAIRWFAN.js.map +0 -1
  195. package/dist/chunk-TCNYSHPM.js.map +0 -1
  196. package/dist/chunk-UTHEIPEY.js.map +0 -1
  197. package/dist/chunk-V4BGVOJC.js.map +0 -1
  198. package/dist/chunk-VE2UIJ7Y.js.map +0 -1
  199. package/dist/connectors/rclone-config.js +0 -4
  200. package/dist/connectors/rclone-config.js.map +0 -1
  201. package/dist/connectors/rclone.d.ts +0 -2
  202. package/dist/connectors/rclone.js +0 -5
  203. package/dist/connectors/rclone.js.map +0 -1
  204. package/dist/connectors/src/fleet-health-gate.d.ts +0 -105
  205. package/dist/connectors/src/fleet-health-gate.d.ts.map +0 -1
  206. package/dist/connectors/src/mount-placeholder.d.ts +0 -131
  207. package/dist/connectors/src/mount-placeholder.d.ts.map +0 -1
  208. package/dist/connectors/src/rclone-config/googledrive.d.ts +0 -59
  209. package/dist/connectors/src/rclone-config/googledrive.d.ts.map +0 -1
  210. package/dist/connectors/src/rclone-config/index.d.ts +0 -9
  211. package/dist/connectors/src/rclone-config/index.d.ts.map +0 -1
  212. package/dist/connectors/src/rclone-config/onedrive.d.ts +0 -48
  213. package/dist/connectors/src/rclone-config/onedrive.d.ts.map +0 -1
  214. package/dist/connectors/src/rclone-config/sanitize.d.ts +0 -9
  215. package/dist/connectors/src/rclone-config/sanitize.d.ts.map +0 -1
  216. package/dist/connectors/src/rclone-config/webdav.d.ts +0 -50
  217. package/dist/connectors/src/rclone-config/webdav.d.ts.map +0 -1
  218. package/dist/connectors/src/rclone-process-manager.d.ts +0 -316
  219. package/dist/connectors/src/rclone-process-manager.d.ts.map +0 -1
  220. package/dist/connectors/src/rclone-token-refresh.d.ts +0 -84
  221. package/dist/connectors/src/rclone-token-refresh.d.ts.map +0 -1
  222. package/dist/ensure-sources-GJOXWVWO.js.map +0 -1
  223. package/dist/helpers-F4NZ66VD.js +0 -4
  224. package/dist/open-library-ERKRGH4B.js +0 -23
@@ -1,22 +1,21 @@
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-BCJL2UNH.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';
7
5
  import { computeStimulus } from './chunk-IFGTRL2I.js';
8
- import { portableSpawn } from './chunk-3KPNDTMR.js';
6
+ import { portableSpawn } from './chunk-MG6UIS7G.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 {
@@ -713,6 +712,53 @@ function instanceToStatus(inst) {
713
712
  function createConnector3() {
714
713
  return new DevServerConnector();
715
714
  }
715
+
716
+ // factory-assets/connectors/box/driver.ts
717
+ var CLOUD_FLEET_REQUIRED = "Box 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.";
718
+ var BoxConnector = class extends AbstractConnector {
719
+ name = "box";
720
+ filesystem = {
721
+ sync: (_handle, options) => syncFleetManaged(options)
722
+ };
723
+ async connect(declaration, ctx) {
724
+ const targetDir = ctx.mountTarget;
725
+ if (!targetDir) {
726
+ throw new Error("BoxConnector requires ctx.mountTarget to be set by the manager");
727
+ }
728
+ if (!isFleetManaged(declaration.id)) {
729
+ throw new Error(CLOUD_FLEET_REQUIRED);
730
+ }
731
+ this.log.info("mount handled by host fleet", {
732
+ connectorId: declaration.id,
733
+ targetDir
734
+ });
735
+ await ensureFleetMounted(targetDir, this.log);
736
+ return this.makeHandle(declaration, { fleetManaged: true }, targetDir);
737
+ }
738
+ async disconnect(_handle) {
739
+ }
740
+ async onHibernate(handle) {
741
+ await this.filesystem.sync(handle);
742
+ return "flushed";
743
+ }
744
+ async onSessionClose(handle) {
745
+ await this.filesystem.sync(handle);
746
+ return "flushed";
747
+ }
748
+ };
749
+ function createConnector4() {
750
+ return new BoxConnector();
751
+ }
752
+ function syncFleetManaged(options) {
753
+ options?.onSyncStatus?.({
754
+ phase: "initial_sync_complete",
755
+ totalFiles: 0,
756
+ downloadedFiles: 0,
757
+ stubFiles: 0,
758
+ message: "fleet-managed mount \u2014 flush handled by host"
759
+ });
760
+ return Promise.resolve({ filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 });
761
+ }
716
762
  var LEGACY_DEFAULTS = {
717
763
  session: { enabled: false },
718
764
  sync: { fetchIntervalSec: 0, autoRebase: false, autoPull: false },
@@ -1897,7 +1943,7 @@ var GitConnector = class extends AbstractConnector {
1897
1943
  }
1898
1944
  }
1899
1945
  };
1900
- function createConnector4() {
1946
+ function createConnector5() {
1901
1947
  return new GitConnector();
1902
1948
  }
1903
1949
  var __test = { resolveGitConfig };
@@ -2105,203 +2151,59 @@ var GmailConnector = class extends AbstractConnector {
2105
2151
  return Buffer.from(message).toString("base64url");
2106
2152
  }
2107
2153
  };
2108
- function createConnector5() {
2154
+ function createConnector6() {
2109
2155
  return new GmailConnector();
2110
2156
  }
2111
2157
 
2112
2158
  // 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
- ]);
2159
+ var CLOUD_FLEET_REQUIRED2 = "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
2160
  var GoogleDriveConnector = class extends AbstractConnector {
2125
2161
  name = "googledrive";
2126
- rclone = new RcloneProcessManager();
2127
2162
  filesystem = {
2128
- deferMount: true,
2129
- sync: (handle, options) => {
2130
- return this._sync(handle, options);
2131
- }
2163
+ sync: (_handle, options) => syncFleetManaged2(options)
2132
2164
  };
2133
2165
  async connect(declaration, ctx) {
2134
2166
  const targetDir = ctx.mountTarget;
2135
2167
  if (!targetDir) {
2136
2168
  throw new Error("GoogleDriveConnector requires ctx.mountTarget to be set by the manager");
2137
2169
  }
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
- );
2161
- }
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
- );
2170
+ if (!isFleetManaged(declaration.id)) {
2171
+ throw new Error(CLOUD_FLEET_REQUIRED2);
2171
2172
  }
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
2173
+ this.log.info("mount handled by host fleet", {
2174
+ connectorId: declaration.id,
2175
+ targetDir
2220
2176
  });
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 });
2177
+ await ensureFleetMounted(targetDir, this.log);
2178
+ return this.makeHandle(
2179
+ declaration,
2180
+ { fleetManaged: true },
2181
+ targetDir
2182
+ );
2237
2183
  }
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 };
2184
+ async disconnect(_handle) {
2259
2185
  }
2260
2186
  async onHibernate(handle) {
2261
- const s = handle.state;
2262
- if (s.fleetManaged) return "flushed";
2263
- await this._sync(handle);
2187
+ await this.filesystem.sync(handle);
2264
2188
  return "flushed";
2265
2189
  }
2266
2190
  async onSessionClose(handle) {
2267
- const s = handle.state;
2268
- if (s.fleetManaged) return "flushed";
2269
- await this._sync(handle);
2191
+ await this.filesystem.sync(handle);
2270
2192
  return "flushed";
2271
2193
  }
2272
2194
  };
2273
- function createConnector6() {
2195
+ function createConnector7() {
2274
2196
  return new GoogleDriveConnector();
2275
2197
  }
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
- }
2198
+ function syncFleetManaged2(options) {
2199
+ options?.onSyncStatus?.({
2200
+ phase: "initial_sync_complete",
2201
+ totalFiles: 0,
2202
+ downloadedFiles: 0,
2203
+ stubFiles: 0,
2204
+ message: "fleet-managed mount \u2014 flush handled by host"
2205
+ });
2206
+ return Promise.resolve({ filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 });
2305
2207
  }
2306
2208
  var LocalConnector = class extends AbstractConnector {
2307
2209
  name = "local";
@@ -2370,7 +2272,7 @@ var LocalConnector = class extends AbstractConnector {
2370
2272
  };
2371
2273
  }
2372
2274
  };
2373
- function createConnector7() {
2275
+ function createConnector8() {
2374
2276
  return new LocalConnector();
2375
2277
  }
2376
2278
 
@@ -2381,8 +2283,8 @@ var MattermostConnector = class extends AbstractConnector {
2381
2283
  constructor() {
2382
2284
  super();
2383
2285
  this.tools = {
2384
- read: (handle, path3) => this._read(handle, path3),
2385
- write: (handle, path3, content) => this._write(handle, path3, content),
2286
+ read: (handle, path) => this._read(handle, path),
2287
+ write: (handle, path, content) => this._write(handle, path, content),
2386
2288
  describeOperations: (handle) => this._describeOperations(handle),
2387
2289
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args),
2388
2290
  describeSkill: (handle, declaration) => this._describeSkill(handle, declaration)
@@ -2454,23 +2356,23 @@ var MattermostConnector = class extends AbstractConnector {
2454
2356
  handle.status = "disconnected";
2455
2357
  }
2456
2358
  // ── Tool face methods (private, wired in constructor) ─────────────────────
2457
- async _read(handle, path3) {
2359
+ async _read(handle, path) {
2458
2360
  const state = handle.state;
2459
2361
  try {
2460
- const post = await this._apiGet(state.url, state.token, `/api/v4/posts/${path3}`);
2362
+ const post = await this._apiGet(state.url, state.token, `/api/v4/posts/${path}`);
2461
2363
  this._assertChannelAccess(String(post.channel_id ?? ""), state.channelFilter);
2462
2364
  return { data: JSON.stringify(post, null, 2), contentType: "application/json" };
2463
2365
  } catch {
2464
2366
  return null;
2465
2367
  }
2466
2368
  }
2467
- async _write(handle, path3, content) {
2369
+ async _write(handle, path, content) {
2468
2370
  if (handle.access === "read-only")
2469
2371
  throw new Error("MattermostConnector: write denied \u2014 read-only");
2470
2372
  const state = handle.state;
2471
2373
  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" });
2374
+ await this._apiPut(state.url, state.token, `/api/v4/posts/${path}`, { id: path, message });
2375
+ this.emitChange?.({ path, action: "edit", source: "operation" });
2474
2376
  }
2475
2377
  _describeSkill(handle, declaration) {
2476
2378
  const state = handle.state;
@@ -2728,35 +2630,35 @@ var MattermostConnector = class extends AbstractConnector {
2728
2630
  return typeof id === "string" && filter.has(id);
2729
2631
  });
2730
2632
  }
2731
- async _apiGet(baseUrl, token, path3) {
2732
- const res = await fetch(baseUrl + path3, { headers: { Authorization: `Bearer ${token}` } });
2633
+ async _apiGet(baseUrl, token, path) {
2634
+ const res = await fetch(baseUrl + path, { headers: { Authorization: `Bearer ${token}` } });
2733
2635
  if (!res.ok) {
2734
2636
  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}`);
2637
+ this.log.warn("REST error", { method: "GET", endpoint: path, status: res.status });
2638
+ throw new Error(`Mattermost GET ${path}: ${res.status} ${body}`);
2737
2639
  }
2738
2640
  return res.json();
2739
2641
  }
2740
- async _apiPost(baseUrl, token, path3, body) {
2741
- const res = await fetch(baseUrl + path3, {
2642
+ async _apiPost(baseUrl, token, path, body) {
2643
+ const res = await fetch(baseUrl + path, {
2742
2644
  method: "POST",
2743
2645
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
2744
2646
  body: JSON.stringify(body)
2745
2647
  });
2746
- if (!res.ok) throw new Error(`Mattermost POST ${path3}: ${res.status} ${await res.text()}`);
2648
+ if (!res.ok) throw new Error(`Mattermost POST ${path}: ${res.status} ${await res.text()}`);
2747
2649
  return res.json();
2748
2650
  }
2749
- async _apiPut(baseUrl, token, path3, body) {
2750
- const res = await fetch(baseUrl + path3, {
2651
+ async _apiPut(baseUrl, token, path, body) {
2652
+ const res = await fetch(baseUrl + path, {
2751
2653
  method: "PUT",
2752
2654
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
2753
2655
  body: JSON.stringify(body)
2754
2656
  });
2755
- if (!res.ok) throw new Error(`Mattermost PUT ${path3}: ${res.status} ${await res.text()}`);
2657
+ if (!res.ok) throw new Error(`Mattermost PUT ${path}: ${res.status} ${await res.text()}`);
2756
2658
  return res.json();
2757
2659
  }
2758
2660
  };
2759
- function createConnector8() {
2661
+ function createConnector9() {
2760
2662
  return new MattermostConnector();
2761
2663
  }
2762
2664
 
@@ -2767,10 +2669,10 @@ var MemoryConnector = class extends AbstractConnector {
2767
2669
  constructor() {
2768
2670
  super();
2769
2671
  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),
2672
+ read: (handle, path) => this._read(handle, path),
2673
+ write: (handle, path, content) => this._write(handle, path, content),
2674
+ delete: (handle, path) => this._delete(handle, path),
2675
+ list: (handle, path, options) => this._list(handle, path, options),
2774
2676
  search: (handle, query, options) => this._search(handle, query, options),
2775
2677
  describeOperations: (handle) => this._describeOperations(handle),
2776
2678
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args)
@@ -2789,26 +2691,26 @@ var MemoryConnector = class extends AbstractConnector {
2789
2691
  store(handle) {
2790
2692
  return handle.state.store;
2791
2693
  }
2792
- async _read(handle, path3) {
2793
- const value = this.store(handle).get(path3);
2694
+ async _read(handle, path) {
2695
+ const value = this.store(handle).get(path);
2794
2696
  if (value === void 0) return null;
2795
2697
  return { data: value, contentType: "application/json" };
2796
2698
  }
2797
- async _write(handle, path3, content) {
2798
- const isNew = !this.store(handle).has(path3);
2699
+ async _write(handle, path, content) {
2700
+ const isNew = !this.store(handle).has(path);
2799
2701
  this.store(handle).set(
2800
- path3,
2702
+ path,
2801
2703
  typeof content.data === "string" ? content.data : content.data.toString("utf-8")
2802
2704
  );
2803
- this.emitChange?.({ path: path3, action: isNew ? "create" : "edit", source: "operation" });
2705
+ this.emitChange?.({ path, action: isNew ? "create" : "edit", source: "operation" });
2804
2706
  }
2805
- async _delete(handle, path3) {
2806
- const deleted = this.store(handle).delete(path3);
2807
- if (deleted) this.emitChange?.({ path: path3, action: "delete", source: "operation" });
2707
+ async _delete(handle, path) {
2708
+ const deleted = this.store(handle).delete(path);
2709
+ if (deleted) this.emitChange?.({ path, action: "delete", source: "operation" });
2808
2710
  return deleted;
2809
2711
  }
2810
- async _list(handle, path3, options) {
2811
- const prefix = path3 ?? "";
2712
+ async _list(handle, path, options) {
2713
+ const prefix = path ?? "";
2812
2714
  const entries = [];
2813
2715
  for (const key of this.store(handle).keys()) {
2814
2716
  if (prefix && !key.startsWith(prefix)) continue;
@@ -2910,7 +2812,7 @@ function matchGlob(str, pattern) {
2910
2812
  const regex = new RegExp(`^${escaped.replace(/\\\*/g, ".*").replace(/\\\?/g, ".")}$`);
2911
2813
  return regex.test(str);
2912
2814
  }
2913
- function createConnector9() {
2815
+ function createConnector10() {
2914
2816
  return new MemoryConnector();
2915
2817
  }
2916
2818
 
@@ -2921,8 +2823,8 @@ var MinIOConnector = class extends AbstractConnector {
2921
2823
  constructor() {
2922
2824
  super();
2923
2825
  this.tools = {
2924
- list: (handle, path3) => this._list(handle, path3),
2925
- read: (handle, path3) => this._read(handle, path3),
2826
+ list: (handle, path) => this._list(handle, path),
2827
+ read: (handle, path) => this._read(handle, path),
2926
2828
  describeOperations: (handle) => this._describeOperations(handle),
2927
2829
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args),
2928
2830
  describeSkill: (handle, declaration) => this._describeSkill(handle, declaration)
@@ -3100,10 +3002,10 @@ var MinIOConnector = class extends AbstractConnector {
3100
3002
  }
3101
3003
  return ops;
3102
3004
  }
3103
- async _list(handle, path3, recursive = false) {
3005
+ async _list(handle, path, recursive = false) {
3104
3006
  const { client, bucket } = handle.state;
3105
3007
  const entries = [];
3106
- const stream = client.listObjects(bucket, path3 ?? "", recursive);
3008
+ const stream = client.listObjects(bucket, path ?? "", recursive);
3107
3009
  return new Promise((resolve5, reject) => {
3108
3010
  stream.on("data", (obj) => {
3109
3011
  entries.push({
@@ -3118,10 +3020,10 @@ var MinIOConnector = class extends AbstractConnector {
3118
3020
  stream.on("error", reject);
3119
3021
  });
3120
3022
  }
3121
- async _read(handle, path3) {
3023
+ async _read(handle, path) {
3122
3024
  const { client, bucket } = handle.state;
3123
3025
  const chunks = [];
3124
- const stream = await client.getObject(bucket, path3);
3026
+ const stream = await client.getObject(bucket, path);
3125
3027
  return new Promise((resolve5, reject) => {
3126
3028
  stream.on("data", (chunk) => chunks.push(chunk));
3127
3029
  stream.on("end", () => resolve5({ data: Buffer.concat(chunks).toString() }));
@@ -3165,7 +3067,7 @@ var MinIOConnector = class extends AbstractConnector {
3165
3067
  }
3166
3068
  }
3167
3069
  };
3168
- function createConnector10() {
3070
+ function createConnector11() {
3169
3071
  return new MinIOConnector();
3170
3072
  }
3171
3073
 
@@ -3179,8 +3081,8 @@ var PostgresConnector = class extends AbstractConnector {
3179
3081
  constructor() {
3180
3082
  super();
3181
3083
  this.tools = {
3182
- read: (handle, path3) => this._read(handle, path3),
3183
- list: (handle, path3, options) => this._list(handle, path3, options),
3084
+ read: (handle, path) => this._read(handle, path),
3085
+ list: (handle, path, options) => this._list(handle, path, options),
3184
3086
  search: (handle, query, options) => this._search(handle, query, options),
3185
3087
  describeOperations: (handle) => this._describeOperations(handle),
3186
3088
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args),
@@ -3268,8 +3170,8 @@ var PostgresConnector = class extends AbstractConnector {
3268
3170
  return handle.state.pool;
3269
3171
  }
3270
3172
  // Base ops map to table/row access via path convention: <table>/<pk>
3271
- async _read(handle, path3) {
3272
- const parts = path3.split("/");
3173
+ async _read(handle, path) {
3174
+ const parts = path.split("/");
3273
3175
  if (parts.length === 1) {
3274
3176
  const table2 = parts[0];
3275
3177
  const result2 = await this.pool(handle).query(
@@ -3288,8 +3190,8 @@ var PostgresConnector = class extends AbstractConnector {
3288
3190
  if (result.rows.length === 0) return null;
3289
3191
  return { data: JSON.stringify(result.rows[0], null, 2), contentType: "application/json" };
3290
3192
  }
3291
- async _list(handle, path3, options) {
3292
- if (!path3) {
3193
+ async _list(handle, path, options) {
3194
+ if (!path) {
3293
3195
  const result2 = await this.pool(handle).query(
3294
3196
  `SELECT table_name FROM information_schema.tables
3295
3197
  WHERE table_schema = 'public' ORDER BY table_name`
@@ -3301,12 +3203,12 @@ var PostgresConnector = class extends AbstractConnector {
3301
3203
  }));
3302
3204
  }
3303
3205
  const limit = options?.limit ?? 100;
3304
- const result = await this.pool(handle).query(`SELECT * FROM "${quoteIdent(path3)}" LIMIT $1`, [
3206
+ const result = await this.pool(handle).query(`SELECT * FROM "${quoteIdent(path)}" LIMIT $1`, [
3305
3207
  limit
3306
3208
  ]);
3307
3209
  return result.rows.map((row, i) => ({
3308
3210
  name: String(row.id ?? i),
3309
- path: `${path3}/${row.id ?? i}`,
3211
+ path: `${path}/${row.id ?? i}`,
3310
3212
  type: "row"
3311
3213
  }));
3312
3214
  }
@@ -3455,7 +3357,7 @@ var PostgresConnector = class extends AbstractConnector {
3455
3357
  return JSON.stringify({ rowCount: result.rowCount, command: result.command });
3456
3358
  }
3457
3359
  };
3458
- function createConnector11() {
3360
+ function createConnector12() {
3459
3361
  return new PostgresConnector();
3460
3362
  }
3461
3363
  var s3Pkg = "@aws-sdk/client-s3";
@@ -3581,221 +3483,59 @@ var S3Connector = class extends AbstractConnector {
3581
3483
  return { filesWritten, filesDeleted: 0, bytesTransferred };
3582
3484
  }
3583
3485
  };
3584
- function createConnector12(sdkOverride) {
3486
+ function createConnector13(sdkOverride) {
3585
3487
  return new S3Connector(sdkOverride);
3586
3488
  }
3587
3489
 
3588
3490
  // 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
- }
3491
+ var CLOUD_FLEET_REQUIRED3 = "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
3492
  var SharePointConnector = class extends AbstractConnector {
3594
3493
  name = "sharepoint";
3595
- rclone = new RcloneProcessManager();
3596
3494
  filesystem = {
3597
- deferMount: true,
3598
- sync: (handle, options) => {
3599
- return this._sync(handle, options);
3600
- }
3495
+ sync: (_handle, options) => syncFleetManaged3(options)
3601
3496
  };
3602
3497
  async connect(declaration, ctx) {
3603
3498
  const targetDir = ctx.mountTarget;
3604
3499
  if (!targetDir) {
3605
3500
  throw new Error("SharePointConnector requires ctx.mountTarget to be set by the manager");
3606
3501
  }
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");
3628
- }
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
- );
3502
+ if (!isFleetManaged(declaration.id)) {
3503
+ throw new Error(CLOUD_FLEET_REQUIRED3);
3642
3504
  }
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({
3505
+ this.log.info("mount handled by host fleet", {
3671
3506
  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
3507
+ targetDir
3706
3508
  });
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 });
3509
+ await ensureFleetMounted(targetDir, this.log);
3510
+ return this.makeHandle(
3511
+ declaration,
3512
+ { fleetManaged: true },
3513
+ targetDir
3514
+ );
3725
3515
  }
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 };
3516
+ async disconnect(_handle) {
3747
3517
  }
3748
3518
  async onHibernate(handle) {
3749
- const s = handle.state;
3750
- if (s.fleetManaged) return "flushed";
3751
- await this._sync(handle);
3519
+ await this.filesystem.sync(handle);
3752
3520
  return "flushed";
3753
3521
  }
3754
3522
  async onSessionClose(handle) {
3755
- const s = handle.state;
3756
- if (s.fleetManaged) return "flushed";
3757
- await this._sync(handle);
3523
+ await this.filesystem.sync(handle);
3758
3524
  return "flushed";
3759
3525
  }
3760
3526
  };
3761
- function createConnector13() {
3527
+ function createConnector14() {
3762
3528
  return new SharePointConnector();
3763
3529
  }
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}` : "/";
3530
+ function syncFleetManaged3(options) {
3531
+ options?.onSyncStatus?.({
3532
+ phase: "initial_sync_complete",
3533
+ totalFiles: 0,
3534
+ downloadedFiles: 0,
3535
+ stubFiles: 0,
3536
+ message: "fleet-managed mount \u2014 flush handled by host"
3537
+ });
3538
+ return Promise.resolve({ filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 });
3799
3539
  }
3800
3540
  function quoteIdent2(ident) {
3801
3541
  return ident.replace(/"/g, '""');
@@ -3806,8 +3546,8 @@ var SQLiteConnector = class extends AbstractConnector {
3806
3546
  constructor() {
3807
3547
  super();
3808
3548
  this.tools = {
3809
- read: (handle, path3) => this._read(handle, path3),
3810
- list: (handle, path3, options) => this._list(handle, path3, options),
3549
+ read: (handle, path) => this._read(handle, path),
3550
+ list: (handle, path, options) => this._list(handle, path, options),
3811
3551
  search: (handle, query, options) => this._search(handle, query, options),
3812
3552
  describeOperations: (handle) => this._describeOperations(handle),
3813
3553
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args)
@@ -3891,8 +3631,8 @@ var SQLiteConnector = class extends AbstractConnector {
3891
3631
  const changes = db.getRowsModified();
3892
3632
  return changes;
3893
3633
  }
3894
- async _read(handle, path3) {
3895
- const parts = path3.split("/");
3634
+ async _read(handle, path) {
3635
+ const parts = path.split("/");
3896
3636
  if (parts.length === 1) {
3897
3637
  const table2 = parts[0];
3898
3638
  const info = this.queryRows(this.db(handle), `PRAGMA table_info("${quoteIdent2(table2)}")`);
@@ -3908,8 +3648,8 @@ var SQLiteConnector = class extends AbstractConnector {
3908
3648
  if (rows.length === 0) return null;
3909
3649
  return { data: JSON.stringify(rows[0], null, 2), contentType: "application/json" };
3910
3650
  }
3911
- async _list(handle, path3, options) {
3912
- if (!path3) {
3651
+ async _list(handle, path, options) {
3652
+ if (!path) {
3913
3653
  const tables = this.queryRows(
3914
3654
  this.db(handle),
3915
3655
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
@@ -3923,12 +3663,12 @@ var SQLiteConnector = class extends AbstractConnector {
3923
3663
  const limit = options?.limit ?? 100;
3924
3664
  const rows = this.queryRows(
3925
3665
  this.db(handle),
3926
- `SELECT rowid, * FROM "${quoteIdent2(path3)}" LIMIT ?`,
3666
+ `SELECT rowid, * FROM "${quoteIdent2(path)}" LIMIT ?`,
3927
3667
  [limit]
3928
3668
  );
3929
3669
  return rows.map((row) => ({
3930
3670
  name: String(row.rowid ?? row.id ?? "?"),
3931
- path: `${path3}/${row.rowid ?? row.id ?? "?"}`,
3671
+ path: `${path}/${row.rowid ?? row.id ?? "?"}`,
3932
3672
  type: "row"
3933
3673
  }));
3934
3674
  }
@@ -4047,7 +3787,7 @@ var SQLiteConnector = class extends AbstractConnector {
4047
3787
  }
4048
3788
  }
4049
3789
  };
4050
- function createConnector14() {
3790
+ function createConnector15() {
4051
3791
  return new SQLiteConnector();
4052
3792
  }
4053
3793
  var DEFAULT_PORT_RANGE = [14e3, 14099];
@@ -4303,7 +4043,7 @@ function instanceToStatus2(instance) {
4303
4043
  uptime_ms: Date.now() - new Date(instance.startedAt).getTime()
4304
4044
  };
4305
4045
  }
4306
- function createConnector15() {
4046
+ function createConnector16() {
4307
4047
  return new StaticServerConnector();
4308
4048
  }
4309
4049
 
@@ -4535,218 +4275,55 @@ async function spawnNgrokTunnel(port) {
4535
4275
  proc.kill();
4536
4276
  throw new Error("ngrok: timed out");
4537
4277
  }
4538
- function createConnector16() {
4278
+ function createConnector17() {
4539
4279
  return new TunnelConnector();
4540
4280
  }
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);
4281
+
4282
+ // factory-assets/connectors/webdav/driver.ts
4283
+ var CLOUD_FLEET_REQUIRED4 = "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
4284
  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
4285
  name = "webdav";
4562
- rclone = new RcloneProcessManager();
4563
4286
  filesystem = {
4564
- deferMount: true,
4565
- sync: (handle, options) => {
4566
- return this._sync(handle, options);
4567
- }
4287
+ sync: (_handle, options) => syncFleetManaged4(options)
4568
4288
  };
4569
4289
  async connect(declaration, ctx) {
4570
4290
  const targetDir = ctx.mountTarget;
4571
4291
  if (!targetDir) {
4572
4292
  throw new Error("WebDAVConnector requires ctx.mountTarget to be set by the manager");
4573
4293
  }
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)");
4294
+ if (!isFleetManaged(declaration.id)) {
4295
+ throw new Error(CLOUD_FLEET_REQUIRED4);
4596
4296
  }
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
- );
4602
- }
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({
4297
+ this.log.info("mount handled by host fleet", {
4635
4298
  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
4299
+ targetDir
4662
4300
  });
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 });
4301
+ await ensureFleetMounted(targetDir, this.log);
4302
+ return this.makeHandle(declaration, { fleetManaged: true }, targetDir);
4681
4303
  }
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 };
4304
+ async disconnect(_handle) {
4703
4305
  }
4704
4306
  async onHibernate(handle) {
4705
- const s = handle.state;
4706
- if (s.fleetManaged) return "flushed";
4707
- await this._sync(handle);
4307
+ await this.filesystem.sync(handle);
4708
4308
  return "flushed";
4709
4309
  }
4710
4310
  async onSessionClose(handle) {
4711
- const s = handle.state;
4712
- if (s.fleetManaged) return "flushed";
4713
- await this._sync(handle);
4311
+ await this.filesystem.sync(handle);
4714
4312
  return "flushed";
4715
4313
  }
4716
4314
  };
4717
- function createConnector17() {
4315
+ function createConnector18() {
4718
4316
  return new WebDAVConnector();
4719
4317
  }
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
- }
4318
+ function syncFleetManaged4(options) {
4319
+ options?.onSyncStatus?.({
4320
+ phase: "initial_sync_complete",
4321
+ totalFiles: 0,
4322
+ downloadedFiles: 0,
4323
+ stubFiles: 0,
4324
+ message: "fleet-managed mount \u2014 flush handled by host"
4325
+ });
4326
+ return Promise.resolve({ filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 });
4750
4327
  }
4751
4328
  var XStateConnector = class extends AbstractConnector {
4752
4329
  name = "xstate";
@@ -4754,8 +4331,8 @@ var XStateConnector = class extends AbstractConnector {
4754
4331
  constructor() {
4755
4332
  super();
4756
4333
  this.tools = {
4757
- read: (handle, path3) => this._read(handle, path3),
4758
- list: (handle, path3, options) => this._list(handle, path3, options),
4334
+ read: (handle, path) => this._read(handle, path),
4335
+ list: (handle, path, options) => this._list(handle, path, options),
4759
4336
  search: (handle, query) => this._search(handle, query),
4760
4337
  describeOperations: (handle) => this._describeOperations(handle),
4761
4338
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args)
@@ -4857,15 +4434,15 @@ var XStateConnector = class extends AbstractConnector {
4857
4434
  this.s(handle).onChange = cb;
4858
4435
  }
4859
4436
  // ── Tool face methods (private, wired in constructor) ─────────────────────
4860
- async _read(handle, path3) {
4437
+ async _read(handle, path) {
4861
4438
  const snapshot = this.s(handle).actor.getSnapshot();
4862
- if (path3 === "" || path3 === "state") {
4439
+ if (path === "" || path === "state") {
4863
4440
  return { data: JSON.stringify(snapshot.value), contentType: "application/json" };
4864
4441
  }
4865
- if (path3 === "context") {
4442
+ if (path === "context") {
4866
4443
  return { data: JSON.stringify(snapshot.context, null, 2), contentType: "application/json" };
4867
4444
  }
4868
- const value = snapshot.context?.[path3];
4445
+ const value = snapshot.context?.[path];
4869
4446
  if (value === void 0) return null;
4870
4447
  return { data: JSON.stringify(value), contentType: "application/json" };
4871
4448
  }
@@ -5048,7 +4625,7 @@ var XStateConnector = class extends AbstractConnector {
5048
4625
  return [...names];
5049
4626
  }
5050
4627
  };
5051
- function createConnector18() {
4628
+ function createConnector19() {
5052
4629
  return new XStateConnector();
5053
4630
  }
5054
4631
 
@@ -5059,10 +4636,10 @@ var XStateStoreConnector = class extends AbstractConnector {
5059
4636
  constructor() {
5060
4637
  super();
5061
4638
  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),
4639
+ read: (handle, path) => this._read(handle, path),
4640
+ write: (handle, path, content) => this._write(handle, path, content),
4641
+ delete: (handle, path) => this._delete(handle, path),
4642
+ list: (handle, path, options) => this._list(handle, path, options),
5066
4643
  search: (handle, query, options) => this._search(handle, query, options),
5067
4644
  describeOperations: (handle) => this._describeOperations(handle),
5068
4645
  executeOp: (handle, operation, args) => this._executeOp(handle, operation, args)
@@ -5116,17 +4693,17 @@ var XStateStoreConnector = class extends AbstractConnector {
5116
4693
  handle.state.onChange = cb;
5117
4694
  }
5118
4695
  // ── Tool face methods (private, wired in constructor) ─────────────────────
5119
- async _read(handle, path3) {
4696
+ async _read(handle, path) {
5120
4697
  const ctx = this.context(handle);
5121
- if (path3 === "*") {
4698
+ if (path === "*") {
5122
4699
  if (Object.keys(ctx).length === 0) return null;
5123
4700
  return { data: JSON.stringify(ctx, null, 2), contentType: "application/json" };
5124
4701
  }
5125
- const value = ctx[path3];
4702
+ const value = ctx[path];
5126
4703
  if (value === void 0) return null;
5127
4704
  return { data: JSON.stringify(value), contentType: "application/json" };
5128
4705
  }
5129
- async _write(handle, path3, content) {
4706
+ async _write(handle, path, content) {
5130
4707
  const data = typeof content.data === "string" ? content.data : content.data.toString("utf-8");
5131
4708
  let value;
5132
4709
  try {
@@ -5134,14 +4711,14 @@ var XStateStoreConnector = class extends AbstractConnector {
5134
4711
  } catch {
5135
4712
  value = data;
5136
4713
  }
5137
- this.storeState(handle).store.send({ type: "set", key: path3, value });
5138
- this._recordHistory(handle, "set", { key: path3, value });
4714
+ this.storeState(handle).store.send({ type: "set", key: path, value });
4715
+ this._recordHistory(handle, "set", { key: path, value });
5139
4716
  }
5140
- async _delete(handle, path3) {
4717
+ async _delete(handle, path) {
5141
4718
  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 });
4719
+ if (!(path in ctx)) return false;
4720
+ this.storeState(handle).store.send({ type: "delete", key: path });
4721
+ this._recordHistory(handle, "delete", { key: path });
5145
4722
  return true;
5146
4723
  }
5147
4724
  async _list(handle, _path, options) {
@@ -5334,7 +4911,7 @@ var XStateStoreConnector = class extends AbstractConnector {
5334
4911
  if (s.history.length > 500) s.history = s.history.slice(-500);
5335
4912
  }
5336
4913
  };
5337
- function createConnector19() {
4914
+ function createConnector20() {
5338
4915
  return new XStateStoreConnector();
5339
4916
  }
5340
4917
  var BUILTIN_CONNECTOR_CATALOG = [
@@ -5344,6 +4921,7 @@ var BUILTIN_CONNECTOR_CATALOG = [
5344
4921
  { id: "webdav", displayName: "webdav" },
5345
4922
  { id: "sharepoint", displayName: "sharepoint" },
5346
4923
  { id: "googledrive", displayName: "googledrive" },
4924
+ { id: "box", displayName: "box" },
5347
4925
  { id: "memory", displayName: "memory" },
5348
4926
  { id: "postgres", displayName: "postgres" },
5349
4927
  { id: "sqlite", displayName: "sqlite" },
@@ -5374,25 +4952,26 @@ function registerBuiltinConnectors(registry = pluginRegistry) {
5374
4952
  if (registry.get("connector", id)) return;
5375
4953
  registry.register("connector", connectorTargetFromFactory(id, factory));
5376
4954
  };
5377
- reg("git", () => createConnector4());
5378
- reg("local", () => createConnector7());
5379
- reg("s3", () => createConnector12());
5380
- reg("webdav", () => createConnector17());
5381
- reg("sharepoint", () => createConnector13());
5382
- reg("googledrive", () => createConnector6());
5383
- reg("memory", () => createConnector9());
5384
- reg("postgres", () => createConnector11());
5385
- reg("sqlite", () => createConnector14());
5386
- reg("minio", () => createConnector10());
4955
+ reg("git", () => createConnector5());
4956
+ reg("local", () => createConnector8());
4957
+ reg("s3", () => createConnector13());
4958
+ reg("webdav", () => createConnector18());
4959
+ reg("sharepoint", () => createConnector14());
4960
+ reg("googledrive", () => createConnector7());
4961
+ reg("box", () => createConnector4());
4962
+ reg("memory", () => createConnector10());
4963
+ reg("postgres", () => createConnector12());
4964
+ reg("sqlite", () => createConnector15());
4965
+ reg("minio", () => createConnector11());
5387
4966
  reg("flow", () => createConnector());
5388
- reg("xstate", () => createConnector18());
5389
- reg("xstate-store", () => createConnector19());
4967
+ reg("xstate", () => createConnector19());
4968
+ reg("xstate-store", () => createConnector20());
5390
4969
  reg("devserver", () => createConnector3());
5391
- reg("static-server", () => createConnector15());
5392
- reg("tunnel", () => createConnector16());
4970
+ reg("static-server", () => createConnector16());
4971
+ reg("tunnel", () => createConnector17());
5393
4972
  reg("deploy", () => createConnector2());
5394
- reg("gmail", () => createConnector5());
5395
- reg("mattermost", () => createConnector8());
4973
+ reg("gmail", () => createConnector6());
4974
+ reg("mattermost", () => createConnector9());
5396
4975
  if (registry === pluginRegistry) _connectorsRegistered = true;
5397
4976
  }
5398
4977
  function getConnector(name) {
@@ -5446,418 +5025,8 @@ var ConnectorStartupError = class extends Error {
5446
5025
  }
5447
5026
  };
5448
5027
 
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
5028
  // connectors/src/connector-manager.ts
5854
5029
  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
5030
  function hasKnownPrefix(ref) {
5862
5031
  return KNOWN_PREFIXES.some((p) => ref.startsWith(p));
5863
5032
  }
@@ -5960,9 +5129,6 @@ var ConnectorManager = class {
5960
5129
  this.catalogEntries = opts.catalogEntries ?? [];
5961
5130
  this.tokenMediator = opts.tokenMediator;
5962
5131
  this.preMintedSecrets = opts.preMintedSecrets;
5963
- this.placeholderBarrier = opts.placeholderBarrier ?? defaultMountPlaceholderBarrier;
5964
- this.fleetHealthPollMs = opts.fleetHealthPollMs ?? parseFleetHealthPollMs();
5965
- this.fleetMountRecoveryProbe = opts.fleetMountRecoveryProbe ?? probeFleetMountTablePresent;
5966
5132
  const secrets = opts.secrets;
5967
5133
  if (secrets && typeof secrets === "object" && "prepareRef" in secrets) {
5968
5134
  this.chain = secrets;
@@ -5985,20 +5151,9 @@ var ConnectorManager = class {
5985
5151
  catalogEntries;
5986
5152
  tokenMediator;
5987
5153
  preMintedSecrets;
5988
- placeholderBarrier;
5989
5154
  mgrLog;
5990
- /** Set once `disconnectAll()` starts, so in-flight deferred mounts bail. */
5991
- disposed = false;
5992
5155
  /** Aborted on dispose; threaded into every `connect()` via `ctx.abortSignal`. */
5993
5156
  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
5157
  // ── Private helpers ──────────────────────────────────────────────────────────
6003
5158
  /**
6004
5159
  * Wrap the user-supplied `TokenMediator` so `reason: 'initial'` calls for an
@@ -6128,8 +5283,8 @@ var ConnectorManager = class {
6128
5283
  }
6129
5284
  /** Release every mount target owned by `id` (on disconnect). */
6130
5285
  releaseMountTarget(id) {
6131
- for (const [path3, owner] of [...this.mountTargets]) {
6132
- if (owner === id) this.mountTargets.delete(path3);
5286
+ for (const [path, owner] of [...this.mountTargets]) {
5287
+ if (owner === id) this.mountTargets.delete(path);
6133
5288
  }
6134
5289
  }
6135
5290
  /**
@@ -6193,21 +5348,13 @@ var ConnectorManager = class {
6193
5348
  * connector with a `mount` config failed → throws `ConnectorStartupError`
6194
5349
  * (fail-fast lifecycle semantics for mountable connectors).
6195
5350
  *
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
5351
  * @param declarations - Connector declarations from `skaile.yaml`.
6206
5352
  * @param options.failOnReadWriteError - Throw `ConnectorStartupError` if any
6207
5353
  * read-write connector fails. Read-only failures are always tolerated.
6208
5354
  * 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.
5355
+ * @param options.deferMounts - Deprecated compatibility option. Cloud
5356
+ * connectors now require already-live host fleet bind mounts and connect
5357
+ * synchronously.
6211
5358
  */
6212
5359
  async connectAll(declarations, options) {
6213
5360
  this.mgrLog.info("connectAll start", { count: declarations.length });
@@ -6215,36 +5362,8 @@ var ConnectorManager = class {
6215
5362
  if (decl.mount != null) this.reserveMountTarget(decl.id, this.resolveMountTarget(decl));
6216
5363
  }
6217
5364
  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
- })();
5365
+ for (const decl of declarations) {
5366
+ results.push(await this.attemptConnect(decl));
6248
5367
  }
6249
5368
  const report = { results };
6250
5369
  this.mgrLog.info("connectAll complete", {
@@ -6273,7 +5392,7 @@ var ConnectorManager = class {
6273
5392
  /**
6274
5393
  * Connect a single declaration, emitting `connecting` → `connected | error`.
6275
5394
  * 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.
5395
+ * can keep going.
6277
5396
  */
6278
5397
  async attemptConnect(decl, preResolved) {
6279
5398
  this.opts.onStatusChange?.({
@@ -6299,20 +5418,10 @@ var ConnectorManager = class {
6299
5418
  },
6300
5419
  () => connector.connect(decl, ctx)
6301
5420
  );
6302
- const previous = this.active.get(decl.id);
6303
- if (previous?.watchHandle) {
6304
- try {
6305
- await previous.watchHandle.close();
6306
- } catch {
6307
- }
6308
- }
6309
5421
  this.active.set(decl.id, {
6310
5422
  connector,
6311
5423
  handle,
6312
- declaration: decl,
6313
- placeholderTarget: previous?.placeholderTarget,
6314
- fleetHealthGate: previous?.fleetHealthGate,
6315
- fleetHealthTarget: previous?.fleetHealthTarget
5424
+ declaration: decl
6316
5425
  });
6317
5426
  this.opts.onStatusChange?.({
6318
5427
  type: "connector_status",
@@ -6343,216 +5452,26 @@ var ConnectorManager = class {
6343
5452
  }
6344
5453
  }
6345
5454
  /**
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.
5455
+ * Compatibility no-op for the removed in-container fleet health gate. Host
5456
+ * fleet-managed cloud mounts are verified at connect time by their drivers.
6358
5457
  */
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
- );
5458
+ async reportFleetHealth(_connectorId, _verdict) {
6425
5459
  }
6426
5460
  /**
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.
6490
- */
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
- }
6518
- }
6519
- /**
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.
5461
+ * Compatibility no-op for the removed deferred rclone mount path.
6523
5462
  */
6524
5463
  async whenDeferredMountsSettled() {
6525
- if (this.deferredConnect) await this.deferredConnect.catch(() => {
6526
- });
6527
5464
  }
6528
5465
  /** Disconnect all connectors and stop all watchers. Best-effort — ignores disconnect errors. */
6529
5466
  async disconnectAll() {
6530
5467
  this.mgrLog.info("disconnectAll start", { count: this.active.size });
6531
- this.disposed = true;
6532
5468
  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
5469
  await this.unwatchAll();
6545
5470
  for (const [id, entry] of this.active) {
6546
5471
  try {
6547
5472
  await entry.connector.disconnect(entry.handle);
6548
5473
  } catch {
6549
5474
  }
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
5475
  this.opts.onStatusChange?.({
6557
5476
  type: "connector_status",
6558
5477
  connectorId: id,
@@ -6568,10 +5487,6 @@ var ConnectorManager = class {
6568
5487
  * Connect a single connector (hot-plug). Resolves fields and allocates
6569
5488
  * mount target if the connector has a filesystem face.
6570
5489
  *
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
5490
  * @returns The `ConnectorHandle` for subsequent operations.
6576
5491
  */
6577
5492
  async connect(declaration) {
@@ -6641,12 +5556,6 @@ var ConnectorManager = class {
6641
5556
  try {
6642
5557
  await entry.connector.disconnect(entry.handle);
6643
5558
  } 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
5559
  this.active.delete(id);
6651
5560
  this.releaseMountTarget(id);
6652
5561
  this.opts.onStatusChange?.({
@@ -6822,7 +5731,7 @@ var ConnectorManager = class {
6822
5731
  }
6823
5732
  return entry.connector.tools;
6824
5733
  }
6825
- async read(id, path3) {
5734
+ async read(id, path) {
6826
5735
  const entry = this.get(id);
6827
5736
  const tools = this.requireToolFace(id, entry);
6828
5737
  if (!tools.read) {
@@ -6835,10 +5744,10 @@ var ConnectorManager = class {
6835
5744
  "skaile.connector.driver": entry.connector.name,
6836
5745
  "skaile.connector.operation": "read"
6837
5746
  },
6838
- () => tools.read(entry.handle, path3)
5747
+ () => tools.read(entry.handle, path)
6839
5748
  );
6840
5749
  }
6841
- async write(id, path3, content) {
5750
+ async write(id, path, content) {
6842
5751
  const entry = this.get(id);
6843
5752
  if (entry.declaration.access === "read-only") {
6844
5753
  throw new Error(`Connector "${id}" is read-only`);
@@ -6854,10 +5763,10 @@ var ConnectorManager = class {
6854
5763
  "skaile.connector.driver": entry.connector.name,
6855
5764
  "skaile.connector.operation": "write"
6856
5765
  },
6857
- () => tools.write(entry.handle, path3, content)
5766
+ () => tools.write(entry.handle, path, content)
6858
5767
  );
6859
5768
  }
6860
- async delete(id, path3) {
5769
+ async delete(id, path) {
6861
5770
  const entry = this.get(id);
6862
5771
  if (entry.declaration.access === "read-only") {
6863
5772
  throw new Error(`Connector "${id}" is read-only`);
@@ -6873,10 +5782,10 @@ var ConnectorManager = class {
6873
5782
  "skaile.connector.driver": entry.connector.name,
6874
5783
  "skaile.connector.operation": "delete"
6875
5784
  },
6876
- () => tools.delete(entry.handle, path3)
5785
+ () => tools.delete(entry.handle, path)
6877
5786
  );
6878
5787
  }
6879
- async list(id, path3, options) {
5788
+ async list(id, path, options) {
6880
5789
  const entry = this.get(id);
6881
5790
  const tools = this.requireToolFace(id, entry);
6882
5791
  if (!tools.list) {
@@ -6889,7 +5798,7 @@ var ConnectorManager = class {
6889
5798
  "skaile.connector.driver": entry.connector.name,
6890
5799
  "skaile.connector.operation": "list"
6891
5800
  },
6892
- () => tools.list(entry.handle, path3, options)
5801
+ () => tools.list(entry.handle, path, options)
6893
5802
  );
6894
5803
  }
6895
5804
  async search(id, query, options) {
@@ -7030,9 +5939,9 @@ async function buildSdkConnectorTools(manager, options) {
7030
5939
  connector_id: z4.string().describe("Connector ID"),
7031
5940
  path: z4.string().describe("Path within the connector")
7032
5941
  },
7033
- handler: async ({ connector_id, path: path3 }) => {
5942
+ handler: async ({ connector_id, path }) => {
7034
5943
  try {
7035
- const content = await manager.read(connector_id, path3);
5944
+ const content = await manager.read(connector_id, path);
7036
5945
  return {
7037
5946
  content: [{ type: "text", text: content?.data?.toString() ?? "(empty)" }]
7038
5947
  };
@@ -7054,11 +5963,11 @@ async function buildSdkConnectorTools(manager, options) {
7054
5963
  },
7055
5964
  handler: async ({
7056
5965
  connector_id,
7057
- path: path3,
5966
+ path,
7058
5967
  content
7059
5968
  }) => {
7060
5969
  try {
7061
- await manager.write(connector_id, path3, { data: content });
5970
+ await manager.write(connector_id, path, { data: content });
7062
5971
  return { content: [{ type: "text", text: "Written." }] };
7063
5972
  } catch (err) {
7064
5973
  return {
@@ -7078,11 +5987,11 @@ async function buildSdkConnectorTools(manager, options) {
7078
5987
  },
7079
5988
  handler: async ({
7080
5989
  connector_id,
7081
- path: path3,
5990
+ path,
7082
5991
  recursive
7083
5992
  }) => {
7084
5993
  try {
7085
- const entries = await manager.list(connector_id, path3, { recursive });
5994
+ const entries = await manager.list(connector_id, path, { recursive });
7086
5995
  return { content: [{ type: "text", text: JSON.stringify(entries, null, 2) }] };
7087
5996
  } catch (err) {
7088
5997
  return {
@@ -7118,9 +6027,9 @@ async function buildSdkConnectorTools(manager, options) {
7118
6027
  connector_id: z4.string().describe("Connector ID"),
7119
6028
  path: z4.string().describe("Path to delete")
7120
6029
  },
7121
- handler: async ({ connector_id, path: path3 }) => {
6030
+ handler: async ({ connector_id, path }) => {
7122
6031
  try {
7123
- const ok = await manager.delete(connector_id, path3);
6032
+ const ok = await manager.delete(connector_id, path);
7124
6033
  return { content: [{ type: "text", text: ok ? "Deleted." : "Not found." }] };
7125
6034
  } catch (err) {
7126
6035
  return {
@@ -7383,6 +6292,45 @@ printf 'password=%s\\n' "$pass"
7383
6292
  printf '\\n'
7384
6293
  `;
7385
6294
  }
6295
+ function isFleetManaged(connectorId) {
6296
+ const raw = process.env.SKAILE_FLEET_MANAGED_MOUNTS ?? "";
6297
+ if (raw.length === 0) return false;
6298
+ return raw.split(",").map((segment) => segment.trim()).filter((segment) => segment.length > 0).includes(connectorId);
6299
+ }
6300
+ async function ensureFleetMounted(dir, log, options) {
6301
+ const timeoutMs = options?.timeoutMs ?? 3e3;
6302
+ const pollIntervalMs = options?.pollIntervalMs ?? 100;
6303
+ const deadline = Date.now() + timeoutMs;
6304
+ let lastErr = null;
6305
+ while (Date.now() < deadline) {
6306
+ try {
6307
+ const st = await stat(dir);
6308
+ if (!st.isDirectory()) {
6309
+ throw new Error(`fleet mountpoint ${dir} is not a directory`);
6310
+ }
6311
+ await readdir(dir);
6312
+ log.info("fleet bind mount verified", { dir });
6313
+ return;
6314
+ } catch (err) {
6315
+ lastErr = err;
6316
+ await new Promise((resolve5) => setTimeout(resolve5, pollIntervalMs));
6317
+ }
6318
+ }
6319
+ throw new Error(
6320
+ `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)}`
6321
+ );
6322
+ }
6323
+ async function ensureDirMode(dir, log) {
6324
+ await mkdir(dir, { recursive: true });
6325
+ try {
6326
+ await chmod(dir, 504);
6327
+ } catch (err) {
6328
+ log?.debug("chmod 0770 skipped (EPERM \u2014 entrypoint heal expected)", {
6329
+ path: dir,
6330
+ error: err instanceof Error ? err.message : String(err)
6331
+ });
6332
+ }
6333
+ }
7386
6334
 
7387
6335
  // connectors/src/log-buffer.ts
7388
6336
  var LogBuffer = class {
@@ -7436,19 +6384,19 @@ var ManagedGitconfigCollisionError = class extends Error {
7436
6384
  var blockStart = (mountId) => `# skaile-mount: ${mountId}`;
7437
6385
  var blockEnd = (mountId) => `# skaile-mount: ${mountId} (end)`;
7438
6386
  var credentialTag = (mountId) => `# skaile-mount: ${mountId}`;
7439
- function readFileOrEmpty(path3) {
6387
+ function readFileOrEmpty(path) {
7440
6388
  try {
7441
- return readFileSync(path3, "utf-8");
6389
+ return readFileSync(path, "utf-8");
7442
6390
  } catch {
7443
6391
  return "";
7444
6392
  }
7445
6393
  }
7446
- function atomicWrite(path3, content, mode) {
7447
- const dir = dirname(path3);
6394
+ function atomicWrite(path, content, mode) {
6395
+ const dir = dirname(path);
7448
6396
  if (!existsSync(dir)) {
7449
6397
  mkdirSync(dir, { recursive: true, mode: 448 });
7450
6398
  }
7451
- const tmp = `${path3}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
6399
+ const tmp = `${path}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
7452
6400
  const fd = openSync(tmp, "w", mode);
7453
6401
  try {
7454
6402
  writeSync(fd, content);
@@ -7456,7 +6404,7 @@ function atomicWrite(path3, content, mode) {
7456
6404
  closeSync(fd);
7457
6405
  }
7458
6406
  chmodSync(tmp, mode);
7459
- renameSync(tmp, path3);
6407
+ renameSync(tmp, path);
7460
6408
  }
7461
6409
  function appendForeignLine(blocks, line) {
7462
6410
  const last = blocks[blocks.length - 1];
@@ -7522,13 +6470,13 @@ function buildCredentialLine(urlPrefix, token, mountId) {
7522
6470
  const encodedToken = encodeURIComponent(token);
7523
6471
  return `${scheme}://x-access-token:${encodedToken}@${host} ${credentialTag(mountId)}`;
7524
6472
  }
7525
- function readCredentialLines(path3) {
7526
- return readFileOrEmpty(path3).split("\n").filter((l) => l.length > 0);
6473
+ function readCredentialLines(path) {
6474
+ return readFileOrEmpty(path).split("\n").filter((l) => l.length > 0);
7527
6475
  }
7528
- function writeCredentialLines(path3, lines) {
6476
+ function writeCredentialLines(path, lines) {
7529
6477
  const content = lines.length > 0 ? `${lines.join("\n")}
7530
6478
  ` : "";
7531
- atomicWrite(path3, content, 416);
6479
+ atomicWrite(path, content, 416);
7532
6480
  }
7533
6481
  function findExistingBlock(blocks, mountId) {
7534
6482
  return blocks.find((b) => b.mountId === mountId);
@@ -7632,156 +6580,191 @@ function isPackageResolvable(pkg, projectDir) {
7632
6580
  function findMissingPackages(packages, projectDir) {
7633
6581
  return packages.filter((p) => !isPackageResolvable(p, projectDir));
7634
6582
  }
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;
6583
+ var PortPool = class _PortPool {
6584
+ min;
6585
+ max;
6586
+ inUse = /* @__PURE__ */ new Set();
6587
+ constructor(range) {
6588
+ this.min = range[0];
6589
+ this.max = range[1];
6590
+ }
6591
+ /** Allocate the next free port from the range. Checks OS for collisions. */
6592
+ async allocate() {
6593
+ for (let port = this.min; port <= this.max; port++) {
6594
+ if (this.inUse.has(port)) continue;
6595
+ if (!await _PortPool.isPortFree(port)) {
6596
+ this.inUse.add(port);
6597
+ continue;
7703
6598
  }
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 {
6599
+ this.inUse.add(port);
6600
+ return port;
6601
+ }
6602
+ throw new Error(`No free port in range ${this.min}-${this.max}`);
6603
+ }
6604
+ /** Mark a port as in-use (for explicit port args or registered instances). */
6605
+ reserve(port) {
6606
+ this.inUse.add(port);
6607
+ }
6608
+ /** Return a port to the pool. */
6609
+ release(port) {
6610
+ this.inUse.delete(port);
6611
+ }
6612
+ /** Check if a port is currently tracked as in-use. */
6613
+ isInUse(port) {
6614
+ return this.inUse.has(port);
6615
+ }
6616
+ /** Quick TCP connect test — returns true if nothing is listening. */
6617
+ static isPortFree(port, host = "127.0.0.1") {
6618
+ return new Promise((resolve5) => {
6619
+ const socket = createConnection({ port, host });
6620
+ socket.once("connect", () => {
6621
+ socket.destroy();
6622
+ resolve5(false);
6623
+ });
6624
+ socket.once("error", () => {
6625
+ socket.destroy();
6626
+ resolve5(true);
6627
+ });
6628
+ socket.setTimeout(500, () => {
6629
+ socket.destroy();
6630
+ resolve5(true);
6631
+ });
6632
+ });
6633
+ }
6634
+ };
6635
+ function globToMatcher(pattern) {
6636
+ const segmentMatch = pattern.match(/^\*\*\/([^*/?]+)\/\*\*$/);
6637
+ if (segmentMatch) {
6638
+ const name = segmentMatch[1];
6639
+ return (p) => p.includes(`/${name}/`) || p.endsWith(`/${name}`) || p === name;
6640
+ }
6641
+ const dotSegment = pattern.match(/^\*\*\/(\.[\w#]+)(?:\/\*\*)?$/);
6642
+ if (dotSegment) {
6643
+ const name = dotSegment[1];
6644
+ return (p) => p.includes(`/${name}/`) || p.endsWith(`/${name}`) || p === name;
6645
+ }
6646
+ const extMatch = pattern.match(/^\*\*\/\*(\.\w+(?:\.\*)?)/);
6647
+ if (extMatch) {
6648
+ const suffix = extMatch[1];
6649
+ if (suffix.endsWith(".*")) {
6650
+ const base = suffix.slice(0, -2);
6651
+ return (p) => p.includes(base);
6652
+ }
6653
+ return (p) => p.endsWith(suffix);
6654
+ }
6655
+ const suffixMatch = pattern.match(/^\*\*\/\*(.+)$/);
6656
+ if (suffixMatch) {
6657
+ const sfx = suffixMatch[1];
6658
+ return (p) => p.endsWith(sfx);
6659
+ }
6660
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
6661
+ const re = new RegExp(`^${escaped}$`);
6662
+ return (p) => re.test(p);
6663
+ }
6664
+ var DEFAULT_IGNORED = [
6665
+ (p) => p.includes("/.git/") || p.endsWith("/.git"),
6666
+ (p) => p.includes("/node_modules/") || p.endsWith("/node_modules"),
6667
+ (p) => p.includes("/.connectors/") || p.endsWith("/.connectors"),
6668
+ // Atomic-write tempfiles (Bun.write creates `<name>.tmp.<pid>.<ts>` then renames).
6669
+ // These vanish between readdir and fs.watch, causing EINVAL that crashes the process.
6670
+ (p) => /\.tmp\.\d/.test(p),
6671
+ // Editor swap/backup files that also race with save operations.
6672
+ (p) => p.endsWith(".swp"),
6673
+ (p) => p.endsWith(".swx"),
6674
+ (p) => p.includes("/.#"),
6675
+ (p) => p.endsWith("~")
6676
+ ];
6677
+ var DEFAULT_DEBOUNCE_MS = 150;
6678
+ function parseGitignore(rootDir) {
6679
+ const gitignorePath = join(rootDir, ".gitignore");
6680
+ if (!existsSync(gitignorePath)) return [];
6681
+ try {
6682
+ const content = readFileSync(gitignorePath, "utf-8");
6683
+ const matchers = [];
6684
+ for (const raw of content.split("\n")) {
6685
+ const line = raw.trim();
6686
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
6687
+ let pattern = line;
6688
+ if (pattern.startsWith("/")) {
6689
+ pattern = pattern.slice(1);
6690
+ } else if (!pattern.includes("/")) {
6691
+ pattern = `**/${pattern}`;
6692
+ }
6693
+ if (pattern.endsWith("/")) {
6694
+ matchers.push(globToMatcher(`${pattern}**`));
6695
+ } else {
6696
+ matchers.push(globToMatcher(pattern));
6697
+ if (!pattern.includes("*") && !pattern.includes(".")) {
6698
+ matchers.push(globToMatcher(`${pattern}/**`));
7709
6699
  }
7710
- return;
7711
6700
  }
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
6701
  }
7744
- armTimer(fireAtMs);
6702
+ return matchers;
6703
+ } catch {
6704
+ return [];
7745
6705
  }
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");
6706
+ }
6707
+ var CHOKIDAR_EVENT_MAP = {
6708
+ add: "create",
6709
+ change: "edit",
6710
+ unlink: "delete",
6711
+ addDir: "create",
6712
+ unlinkDir: "delete"
6713
+ };
6714
+ async function createFsWatcher(rootDir, callback, options) {
6715
+ const log = createLogger({ kind: "connector", subkind: "watcher", instance: rootDir });
6716
+ const gitignorePatterns = parseGitignore(rootDir);
6717
+ const optionsIgnored = (options?.ignored ?? []).map(
6718
+ (entry) => typeof entry === "string" ? globToMatcher(entry) : entry
6719
+ );
6720
+ const ignored = [...DEFAULT_IGNORED, ...gitignorePatterns, ...optionsIgnored];
6721
+ const debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;
6722
+ const watcher = chokidar.watch(rootDir, {
6723
+ ignoreInitial: true,
6724
+ ignored,
6725
+ awaitWriteFinish: { stabilityThreshold: debounceMs, pollInterval: 50 },
6726
+ usePolling: false
6727
+ });
6728
+ watcher.on("error", (err) => {
6729
+ const message = err instanceof Error ? err.message : String(err);
6730
+ log.warn("watcher error (non-fatal)", { message });
6731
+ });
6732
+ for (const [chokidarEvent, action] of Object.entries(CHOKIDAR_EVENT_MAP)) {
6733
+ watcher.on(
6734
+ chokidarEvent,
6735
+ (fullPath) => {
6736
+ const rel = relative(rootDir, fullPath);
6737
+ if (!rel || rel.startsWith("..")) return;
6738
+ const normalizedPath = rel.split("\\").join("/");
6739
+ callback({ path: normalizedPath, action, source: "filesystem" });
6740
+ }
6741
+ );
7756
6742
  }
7757
6743
  return {
7758
- schedule,
7759
- onAuthError,
7760
- cancel,
7761
- isPending: () => timer !== null
6744
+ close: () => watcher.close()
7762
6745
  };
7763
6746
  }
7764
6747
  var WorktreeRegistryImpl = class {
7765
6748
  entries = /* @__PURE__ */ new Map();
7766
- register(path3, owner) {
7767
- let owners = this.entries.get(path3);
6749
+ register(path, owner) {
6750
+ let owners = this.entries.get(path);
7768
6751
  if (!owners) {
7769
6752
  owners = /* @__PURE__ */ new Set();
7770
- this.entries.set(path3, owners);
6753
+ this.entries.set(path, owners);
7771
6754
  }
7772
6755
  owners.add(owner);
7773
6756
  }
7774
- deregister(path3, owner) {
7775
- const owners = this.entries.get(path3);
6757
+ deregister(path, owner) {
6758
+ const owners = this.entries.get(path);
7776
6759
  if (!owners) return;
7777
6760
  owners.delete(owner);
7778
- if (owners.size === 0) this.entries.delete(path3);
6761
+ if (owners.size === 0) this.entries.delete(path);
7779
6762
  }
7780
- isInUse(path3) {
7781
- return (this.entries.get(path3)?.size ?? 0) > 0;
6763
+ isInUse(path) {
6764
+ return (this.entries.get(path)?.size ?? 0) > 0;
7782
6765
  }
7783
- owners(path3) {
7784
- return [...this.entries.get(path3) ?? []];
6766
+ owners(path) {
6767
+ return [...this.entries.get(path) ?? []];
7785
6768
  }
7786
6769
  };
7787
6770
  var worktreeRegistry = new WorktreeRegistryImpl();
@@ -7910,8 +6893,8 @@ var FlowAdapter = class extends AbstractConnector {
7910
6893
  constructor() {
7911
6894
  super();
7912
6895
  this.tools = {
7913
- read: (handle, path3) => this.read(handle, path3),
7914
- list: (handle, path3, options) => this.list(handle, path3, options),
6896
+ read: (handle, path) => this.read(handle, path),
6897
+ list: (handle, path, options) => this.list(handle, path, options),
7915
6898
  search: (handle, query, options) => this.search(handle, query, options),
7916
6899
  describeOperations: (handle) => this.describeOperations(handle),
7917
6900
  executeOp: (handle, operation, args) => this.executeOp(handle, operation, args)
@@ -7955,16 +6938,16 @@ var FlowAdapter = class extends AbstractConnector {
7955
6938
  s.execution = this.recompute(s);
7956
6939
  }
7957
6940
  // ── Base operations (private — wired into tools face in constructor) ──
7958
- async read(handle, path3) {
6941
+ async read(handle, path) {
7959
6942
  const s = this.s(handle);
7960
- if (path3 === "" || path3 === "state") {
6943
+ if (path === "" || path === "state") {
7961
6944
  return {
7962
6945
  data: JSON.stringify(s.execution, null, 2),
7963
6946
  contentType: "application/json"
7964
6947
  };
7965
6948
  }
7966
- if (path3.startsWith("node/")) {
7967
- const nodeId = path3.slice(5);
6949
+ if (path.startsWith("node/")) {
6950
+ const nodeId = path.slice(5);
7968
6951
  const node = s.flowDef.nodes.find((n) => n.id === nodeId);
7969
6952
  if (!node) return null;
7970
6953
  const exec = s.execution.nodes[nodeId];
@@ -8781,6 +7764,6 @@ function createConnector() {
8781
7764
  return new FlowAdapter();
8782
7765
  }
8783
7766
 
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
7767
+ export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, BoxConnector, 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, createConnector20, 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 };
7768
+ //# sourceMappingURL=chunk-B4IOSXRZ.js.map
7769
+ //# sourceMappingURL=chunk-B4IOSXRZ.js.map