@skaile/workspaces 0.32.0 → 0.34.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 (146) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/{asset-feeds-2LQIGNUF.js → asset-feeds-6R2C2NHB.js} +7 -7
  3. package/dist/{asset-feeds-2LQIGNUF.js.map → asset-feeds-6R2C2NHB.js.map} +1 -1
  4. package/dist/asset-manager/index.js +5 -5
  5. package/dist/asset-manager/installer.js +4 -4
  6. package/dist/bridge/drivers/claude-sdk.js +8 -1
  7. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  8. package/dist/bridge/drivers/codex.js +1 -1
  9. package/dist/bridge/drivers/continue.js +2 -2
  10. package/dist/bridge/drivers/echo.js +1 -1
  11. package/dist/bridge/drivers/gemini.js +3 -3
  12. package/dist/bridge/drivers/goose.js +2 -2
  13. package/dist/bridge/drivers/omp.js +1 -1
  14. package/dist/bridge/drivers/opencode.js +2 -2
  15. package/dist/bridge/drivers/qwen.js +3 -3
  16. package/dist/bridge/index.js +2 -2
  17. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  18. package/dist/{chunk-SN2RWSB3.js → chunk-2VM62DX7.js} +3 -3
  19. package/dist/{chunk-SN2RWSB3.js.map → chunk-2VM62DX7.js.map} +1 -1
  20. package/dist/{chunk-SIW5I47F.js → chunk-4RSCLQYX.js} +231 -33
  21. package/dist/chunk-4RSCLQYX.js.map +1 -0
  22. package/dist/{chunk-YHGJBGFU.js → chunk-5JAQVXPV.js} +2 -2
  23. package/dist/{chunk-YHGJBGFU.js.map → chunk-5JAQVXPV.js.map} +1 -1
  24. package/dist/{chunk-NGE6PALH.js → chunk-67AXL4OK.js} +3 -3
  25. package/dist/{chunk-NGE6PALH.js.map → chunk-67AXL4OK.js.map} +1 -1
  26. package/dist/{chunk-CPYXJJOD.js → chunk-BKE2E5AC.js} +6 -6
  27. package/dist/{chunk-CPYXJJOD.js.map → chunk-BKE2E5AC.js.map} +1 -1
  28. package/dist/{chunk-OEXA7BBK.js → chunk-CIFTUSBV.js} +2 -2
  29. package/dist/{chunk-OEXA7BBK.js.map → chunk-CIFTUSBV.js.map} +1 -1
  30. package/dist/{chunk-LO7QEIHT.js → chunk-DHN6VW4B.js} +4 -4
  31. package/dist/{chunk-LO7QEIHT.js.map → chunk-DHN6VW4B.js.map} +1 -1
  32. package/dist/{chunk-7WHBI3I6.js → chunk-DVIIYYD5.js} +3 -3
  33. package/dist/{chunk-7WHBI3I6.js.map → chunk-DVIIYYD5.js.map} +1 -1
  34. package/dist/{chunk-DTJNHYFE.js → chunk-E5VRPYUX.js} +9 -16
  35. package/dist/chunk-E5VRPYUX.js.map +1 -0
  36. package/dist/{chunk-NZJDQYNB.js → chunk-FM2BJYX4.js} +2 -2
  37. package/dist/{chunk-NZJDQYNB.js.map → chunk-FM2BJYX4.js.map} +1 -1
  38. package/dist/{chunk-AOWKMINN.js → chunk-JLSK6HR4.js} +5 -5
  39. package/dist/{chunk-AOWKMINN.js.map → chunk-JLSK6HR4.js.map} +1 -1
  40. package/dist/{chunk-7FLLPKX3.js → chunk-NSVAJWWQ.js} +3 -3
  41. package/dist/{chunk-7FLLPKX3.js.map → chunk-NSVAJWWQ.js.map} +1 -1
  42. package/dist/{chunk-MA7HSL5S.js → chunk-QLHOORR7.js} +2 -2
  43. package/dist/{chunk-MA7HSL5S.js.map → chunk-QLHOORR7.js.map} +1 -1
  44. package/dist/{chunk-P72K36PU.js → chunk-TV2OU2EZ.js} +4 -4
  45. package/dist/{chunk-P72K36PU.js.map → chunk-TV2OU2EZ.js.map} +1 -1
  46. package/dist/{chunk-2RQ3KLY3.js → chunk-UQ5JY2GQ.js} +10 -7
  47. package/dist/chunk-UQ5JY2GQ.js.map +1 -0
  48. package/dist/{chunk-MG4KFVGV.js → chunk-VSLSRDQZ.js} +3 -3
  49. package/dist/{chunk-MG4KFVGV.js.map → chunk-VSLSRDQZ.js.map} +1 -1
  50. package/dist/{chunk-BR7RPEHW.js → chunk-W6TLIJG3.js} +3 -3
  51. package/dist/{chunk-BR7RPEHW.js.map → chunk-W6TLIJG3.js.map} +1 -1
  52. package/dist/{chunk-GXHJDQ37.js → chunk-WQRDLR3K.js} +410 -48
  53. package/dist/chunk-WQRDLR3K.js.map +1 -0
  54. package/dist/{chunk-74UKG2LA.js → chunk-XT6AZWK3.js} +40 -5
  55. package/dist/chunk-XT6AZWK3.js.map +1 -0
  56. package/dist/cli/index.js +31 -31
  57. package/dist/connectors/config.js +4 -4
  58. package/dist/connectors/index.js +6 -6
  59. package/dist/connectors/rclone.js +1 -1
  60. package/dist/connectors/src/config.d.ts +30 -3
  61. package/dist/connectors/src/config.d.ts.map +1 -1
  62. package/dist/connectors/src/connector-manager.d.ts +56 -3
  63. package/dist/connectors/src/connector-manager.d.ts.map +1 -1
  64. package/dist/connectors/src/fleet-health-gate.d.ts +86 -0
  65. package/dist/connectors/src/fleet-health-gate.d.ts.map +1 -0
  66. package/dist/connectors/src/fleet-utils.d.ts +16 -0
  67. package/dist/connectors/src/fleet-utils.d.ts.map +1 -1
  68. package/dist/connectors/src/index.d.ts +9 -3
  69. package/dist/connectors/src/index.d.ts.map +1 -1
  70. package/dist/connectors/src/rclone-process-manager.d.ts +24 -0
  71. package/dist/connectors/src/rclone-process-manager.d.ts.map +1 -1
  72. package/dist/connectors/src/rclone-token-refresh.d.ts +84 -0
  73. package/dist/connectors/src/rclone-token-refresh.d.ts.map +1 -0
  74. package/dist/core/index.js +3 -3
  75. package/dist/core/runtime-assets.js +2 -2
  76. package/dist/core/src/workspace-config.d.ts +20 -6
  77. package/dist/core/src/workspace-config.d.ts.map +1 -1
  78. package/dist/core/workspace-config.js +1 -1
  79. package/dist/deploy/index.js +3 -3
  80. package/dist/{ensure-sources-CRUZDVPG.js → ensure-sources-GYEQ65ET.js} +5 -5
  81. package/dist/{ensure-sources-CRUZDVPG.js.map → ensure-sources-GYEQ65ET.js.map} +1 -1
  82. package/dist/factory-assets/connectors/deploy.js +6 -6
  83. package/dist/factory-assets/connectors/devserver.js +6 -6
  84. package/dist/factory-assets/connectors/flow/adapter.js +6 -6
  85. package/dist/factory-assets/connectors/flow/engine.js +3 -3
  86. package/dist/factory-assets/connectors/flow/run-flow.js +7 -7
  87. package/dist/factory-assets/connectors/flow.js +6 -6
  88. package/dist/factory-assets/connectors/git.js +6 -6
  89. package/dist/factory-assets/connectors/gmail.js +6 -6
  90. package/dist/factory-assets/connectors/googledrive.js +6 -6
  91. package/dist/factory-assets/connectors/local.js +6 -6
  92. package/dist/factory-assets/connectors/mattermost.js +6 -6
  93. package/dist/factory-assets/connectors/memory.js +6 -6
  94. package/dist/factory-assets/connectors/minio.js +6 -6
  95. package/dist/factory-assets/connectors/postgres.js +6 -6
  96. package/dist/factory-assets/connectors/s3.js +6 -6
  97. package/dist/factory-assets/connectors/sharepoint/driver.d.ts.map +1 -1
  98. package/dist/factory-assets/connectors/sharepoint.js +6 -6
  99. package/dist/factory-assets/connectors/sqlite.js +6 -6
  100. package/dist/factory-assets/connectors/static-server.js +6 -6
  101. package/dist/factory-assets/connectors/tunnel.js +6 -6
  102. package/dist/factory-assets/connectors/webdav/driver.d.ts.map +1 -1
  103. package/dist/factory-assets/connectors/webdav.js +6 -6
  104. package/dist/factory-assets/connectors/xstate-store.js +6 -6
  105. package/dist/factory-assets/connectors/xstate.js +6 -6
  106. package/dist/{flows-W7L2576K.js → flows-TJUVSMRR.js} +3 -3
  107. package/dist/{flows-W7L2576K.js.map → flows-TJUVSMRR.js.map} +1 -1
  108. package/dist/library/index.js +3 -3
  109. package/dist/library/install.js +3 -3
  110. package/dist/{open-library-XZZXXVBP.js → open-library-GPZUB7N3.js} +6 -6
  111. package/dist/{open-library-XZZXXVBP.js.map → open-library-GPZUB7N3.js.map} +1 -1
  112. package/dist/{paths-RH5CIFX2.js → paths-R6WA7PKV.js} +6 -6
  113. package/dist/{paths-RH5CIFX2.js.map → paths-R6WA7PKV.js.map} +1 -1
  114. package/dist/{plugin-store-2UZGNS6M.js → plugin-store-TF7GECYJ.js} +5 -5
  115. package/dist/{plugin-store-2UZGNS6M.js.map → plugin-store-TF7GECYJ.js.map} +1 -1
  116. package/dist/runner/index.js +10 -10
  117. package/dist/runner/src/external-mcp.d.ts +45 -4
  118. package/dist/runner/src/external-mcp.d.ts.map +1 -1
  119. package/dist/runner/src/fleet-health-flag-dispatcher.d.ts +40 -0
  120. package/dist/runner/src/fleet-health-flag-dispatcher.d.ts.map +1 -0
  121. package/dist/runner/src/lifecycle-handler.d.ts +17 -0
  122. package/dist/runner/src/lifecycle-handler.d.ts.map +1 -0
  123. package/dist/runner/src/resources.d.ts.map +1 -1
  124. package/dist/runner/src/runner-capabilities.d.ts +1 -1
  125. package/dist/runner/src/runner-capabilities.d.ts.map +1 -1
  126. package/dist/runner/src/serve.d.ts.map +1 -1
  127. package/dist/sdk/asset-manager.js +5 -5
  128. package/dist/sdk/bridge.js +2 -2
  129. package/dist/sdk/core.js +3 -3
  130. package/dist/sdk/flow.js +3 -3
  131. package/dist/sdk/index.js +10 -10
  132. package/dist/sdk/runner.js +10 -10
  133. package/dist/{setup-G5UATRCX.js → setup-YKL43RQJ.js} +7 -7
  134. package/dist/{setup-G5UATRCX.js.map → setup-YKL43RQJ.js.map} +1 -1
  135. package/dist/{store-client-TUC7VSOU.js → store-client-H7MHTZVU.js} +6 -6
  136. package/dist/{store-client-TUC7VSOU.js.map → store-client-H7MHTZVU.js.map} +1 -1
  137. package/dist/tui/index.js +10 -10
  138. package/dist/workspace-plugin/adapters/mcp.js +1 -1
  139. package/dist/workspace-plugin/adapters/omp.js +3 -3
  140. package/dist/workspace-plugin/index.js +1 -1
  141. package/package.json +1 -1
  142. package/dist/chunk-2RQ3KLY3.js.map +0 -1
  143. package/dist/chunk-74UKG2LA.js.map +0 -1
  144. package/dist/chunk-DTJNHYFE.js.map +0 -1
  145. package/dist/chunk-GXHJDQ37.js.map +0 -1
  146. package/dist/chunk-SIW5I47F.js.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { renderGoogleDriveConfig, renderOneDriveConfig, renderWebDAVConfig } from './chunk-OVRSNIKJ.js';
2
- import { resolveAuthRef, resolveAuth, resolveMountCredentialString } from './chunk-2RQ3KLY3.js';
3
- import { PortPool, createFsWatcher, RcloneProcessManager, ensureDirMode } from './chunk-74UKG2LA.js';
2
+ import { resolveAuthRef, resolveAuth, resolveMountCredentialString, resolveMountCredential } from './chunk-UQ5JY2GQ.js';
3
+ import { PortPool, createFsWatcher, RcloneProcessManager, ensureDirMode } from './chunk-XT6AZWK3.js';
4
4
  import { pluginRegistry } from './chunk-6E6PKKAD.js';
5
5
  import { computeFlowStateFromSnapshots } from './chunk-X46FY45B.js';
6
6
  import { renderStimulusPrompt } from './chunk-DEYYRFQU.js';
7
7
  import { computeStimulus } from './chunk-IFGTRL2I.js';
8
- import { portableSpawn } from './chunk-7WHBI3I6.js';
8
+ import { portableSpawn } from './chunk-DVIIYYD5.js';
9
9
  import { createLogger } from './chunk-JB35MVBN.js';
10
10
  import { existsSync, mkdirSync, renameSync, writeFileSync, readFileSync, cpSync, statSync, createReadStream, readdirSync, openSync, writeSync, closeSync, chmodSync } from 'fs';
11
11
  import path, { join, dirname, resolve, sep } from 'path';
@@ -3459,7 +3459,7 @@ var SharePointConnector = class extends AbstractConnector {
3459
3459
  });
3460
3460
  await ensureFleetMounted(targetDir, this.log);
3461
3461
  const fleetState = {
3462
- rcloneHandle: null,
3462
+ handleRef: { current: null },
3463
3463
  fleetManaged: true
3464
3464
  };
3465
3465
  return this.makeHandle(declaration, fleetState, targetDir);
@@ -3475,10 +3475,8 @@ var SharePointConnector = class extends AbstractConnector {
3475
3475
  );
3476
3476
  }
3477
3477
  const basePath = String(opts?.basePath ?? "").replace(/^\/+|\/+$/g, "");
3478
- const auth = parseAuthBlob2(
3479
- await resolveMountCredentialString(declaration, secrets, tokenMediator),
3480
- this.log
3481
- );
3478
+ const cred = await resolveMountCredential(declaration, secrets, tokenMediator, "initial");
3479
+ const auth = parseAuthBlob2(cred.token, this.log);
3482
3480
  if (!auth.accessToken) {
3483
3481
  throw new Error(
3484
3482
  "SharePoint connector: no access token resolved. Use 'auth: backend' (platform) or 'auth: env:SHAREPOINT_TOKEN' (standalone)."
@@ -3497,16 +3495,29 @@ var SharePointConnector = class extends AbstractConnector {
3497
3495
  vfsCacheMode,
3498
3496
  hasRefresh: auth.refreshToken !== null
3499
3497
  });
3500
- const rcloneConfig = renderOneDriveConfig({
3501
- remoteName,
3502
- accessToken: auth.accessToken,
3503
- refreshToken: auth.refreshToken,
3504
- expiresAtMs: auth.expiresAtMs,
3505
- tenantId: auth.tenantId,
3506
- clientId: auth.clientId,
3507
- clientSecret: auth.clientSecret,
3508
- driveId
3509
- });
3498
+ const renderConfig = (rawToken) => {
3499
+ const a = parseAuthBlob2(rawToken, this.log);
3500
+ return renderOneDriveConfig({
3501
+ remoteName,
3502
+ accessToken: a.accessToken,
3503
+ refreshToken: a.refreshToken,
3504
+ expiresAtMs: a.expiresAtMs,
3505
+ tenantId: a.tenantId,
3506
+ clientId: a.clientId,
3507
+ clientSecret: a.clientSecret,
3508
+ driveId
3509
+ });
3510
+ };
3511
+ const handleRef = { current: null };
3512
+ const refresh = declaration.auth === "backend" && tokenMediator ? createRcloneTokenRefresh({
3513
+ connectorId: declaration.id,
3514
+ tokenMediator,
3515
+ renderConfig,
3516
+ rclone: this.rclone,
3517
+ handleRef,
3518
+ log: (line) => this.log.info(line)
3519
+ }) : void 0;
3520
+ const rcloneConfig = renderConfig(cred.token ?? "");
3510
3521
  await ensureDirMode(base, this.log);
3511
3522
  await ensureDirMode(cacheDir, this.log);
3512
3523
  await ensureDirMode(targetDir, this.log);
@@ -3532,20 +3543,24 @@ var SharePointConnector = class extends AbstractConnector {
3532
3543
  ...cacheMaxAge !== void 0 ? { cacheMaxAge } : {},
3533
3544
  ...vfsWriteBack !== void 0 ? { vfsWriteBack } : {},
3534
3545
  ...ignoreSizeCheck ? { ignoreSizeCheck: true } : {},
3546
+ ...refresh ? { onAuthError: () => refresh.onAuthError() } : {},
3535
3547
  log: this.log
3536
3548
  });
3537
3549
  this.log.info("mount ready", { pid: rcloneHandle.pid });
3538
- const state = { rcloneHandle };
3550
+ handleRef.current = rcloneHandle;
3551
+ refresh?.schedule(cred.expiresAt);
3552
+ const state = { handleRef, refresh };
3539
3553
  return this.makeHandle(declaration, state, targetDir);
3540
3554
  }
3541
3555
  async disconnect(handle) {
3542
3556
  const s = handle.state;
3543
- if (s.fleetManaged || !s.rcloneHandle) return;
3544
- await this.rclone.stop(s.rcloneHandle, { graceMs: 3e4 });
3557
+ s.refresh?.cancel();
3558
+ if (s.fleetManaged || !s.handleRef.current) return;
3559
+ await this.rclone.stop(s.handleRef.current, { graceMs: 3e4 });
3545
3560
  }
3546
3561
  async _sync(handle, options) {
3547
3562
  const s = handle.state;
3548
- if (s.fleetManaged || !s.rcloneHandle) {
3563
+ if (s.fleetManaged || !s.handleRef.current) {
3549
3564
  options?.onSyncStatus?.({
3550
3565
  phase: "initial_sync_complete",
3551
3566
  totalFiles: 0,
@@ -3555,7 +3570,7 @@ var SharePointConnector = class extends AbstractConnector {
3555
3570
  });
3556
3571
  return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
3557
3572
  }
3558
- await this.rclone.flush(s.rcloneHandle);
3573
+ await this.rclone.flush(s.handleRef.current);
3559
3574
  options?.onSyncStatus?.({
3560
3575
  phase: "initial_sync_complete",
3561
3576
  totalFiles: 0,
@@ -4405,7 +4420,7 @@ var WebDAVConnector = class extends AbstractConnector {
4405
4420
  });
4406
4421
  await ensureFleetMounted(targetDir, this.log);
4407
4422
  const fleetState = {
4408
- rcloneHandle: null,
4423
+ handleRef: { current: null },
4409
4424
  fleetManaged: true
4410
4425
  };
4411
4426
  return this.makeHandle(declaration, fleetState, targetDir);
@@ -4423,8 +4438,8 @@ var WebDAVConnector = class extends AbstractConnector {
4423
4438
  const vendor = vendorRaw;
4424
4439
  const basePath = String(opts?.basePath ?? "").replace(/^\/+|\/+$/g, "");
4425
4440
  const remotePath = basePath.length > 0 ? `/${basePath}` : "/";
4426
- const auth = parseAuth(await resolveMountCredentialString(declaration, secrets, tokenMediator));
4427
- const obscuredPassword = auth.password ? await this.obscure(auth.password) : null;
4441
+ const cred = await resolveMountCredential(declaration, secrets, tokenMediator, "initial");
4442
+ const auth = parseAuth(cred.token);
4428
4443
  const remoteName = `skaile-${declaration.id}`;
4429
4444
  const remote = `${remoteName}:${remotePath}`;
4430
4445
  const base = cacheBase3();
@@ -4438,14 +4453,28 @@ var WebDAVConnector = class extends AbstractConnector {
4438
4453
  vfsCacheMode,
4439
4454
  authKind: auth.bearerToken ? "bearer" : auth.username ? "basic" : "anonymous"
4440
4455
  });
4441
- const rcloneConfig = renderWebDAVConfig({
4442
- remoteName,
4443
- url,
4444
- vendor,
4445
- username: auth.username,
4446
- obscuredPassword,
4447
- bearerToken: auth.bearerToken
4448
- });
4456
+ const renderConfig = async (rawToken) => {
4457
+ const a = parseAuth(rawToken);
4458
+ const obscured = a.password ? await this.obscure(a.password) : null;
4459
+ return renderWebDAVConfig({
4460
+ remoteName,
4461
+ url,
4462
+ vendor,
4463
+ username: a.username,
4464
+ obscuredPassword: obscured,
4465
+ bearerToken: a.bearerToken
4466
+ });
4467
+ };
4468
+ const handleRef = { current: null };
4469
+ const refresh = declaration.auth === "backend" && tokenMediator ? createRcloneTokenRefresh({
4470
+ connectorId: declaration.id,
4471
+ tokenMediator,
4472
+ renderConfig,
4473
+ rclone: this.rclone,
4474
+ handleRef,
4475
+ log: (line) => this.log.info(line)
4476
+ }) : void 0;
4477
+ const rcloneConfig = await renderConfig(cred.token ?? "");
4449
4478
  await ensureDirMode(base, this.log);
4450
4479
  await ensureDirMode(cacheDir, this.log);
4451
4480
  await ensureDirMode(targetDir, this.log);
@@ -4463,20 +4492,24 @@ var WebDAVConnector = class extends AbstractConnector {
4463
4492
  ...cacheMaxSize !== void 0 ? { cacheMaxSize } : {},
4464
4493
  ...cacheMaxAge !== void 0 ? { cacheMaxAge } : {},
4465
4494
  ...vfsWriteBack !== void 0 ? { vfsWriteBack } : {},
4495
+ ...refresh ? { onAuthError: () => refresh.onAuthError() } : {},
4466
4496
  log: this.log
4467
4497
  });
4468
4498
  this.log.info("mount ready", { pid: rcloneHandle.pid });
4469
- const state = { rcloneHandle };
4499
+ handleRef.current = rcloneHandle;
4500
+ refresh?.schedule(cred.expiresAt);
4501
+ const state = { handleRef, refresh };
4470
4502
  return this.makeHandle(declaration, state, targetDir);
4471
4503
  }
4472
4504
  async disconnect(handle) {
4473
4505
  const s = handle.state;
4474
- if (s.fleetManaged || !s.rcloneHandle) return;
4475
- await this.rclone.stop(s.rcloneHandle, { graceMs: 3e4 });
4506
+ s.refresh?.cancel();
4507
+ if (s.fleetManaged || !s.handleRef.current) return;
4508
+ await this.rclone.stop(s.handleRef.current, { graceMs: 3e4 });
4476
4509
  }
4477
4510
  async _sync(handle, options) {
4478
4511
  const s = handle.state;
4479
- if (s.fleetManaged || !s.rcloneHandle) {
4512
+ if (s.fleetManaged || !s.handleRef.current) {
4480
4513
  options?.onSyncStatus?.({
4481
4514
  phase: "initial_sync_complete",
4482
4515
  totalFiles: 0,
@@ -4486,7 +4519,7 @@ var WebDAVConnector = class extends AbstractConnector {
4486
4519
  });
4487
4520
  return { filesWritten: 0, filesDeleted: 0, bytesTransferred: 0 };
4488
4521
  }
4489
- await this.rclone.flush(s.rcloneHandle);
4522
+ await this.rclone.flush(s.handleRef.current);
4490
4523
  options?.onSyncStatus?.({
4491
4524
  phase: "initial_sync_complete",
4492
4525
  totalFiles: 0,
@@ -5240,6 +5273,115 @@ var ConnectorStartupError = class extends Error {
5240
5273
  this.failedReadWrite = failedReadWrite;
5241
5274
  }
5242
5275
  };
5276
+
5277
+ // connectors/src/fleet-health-gate.ts
5278
+ var FleetHealthGate = class {
5279
+ constructor(target, barrier, log, opts) {
5280
+ this.target = target;
5281
+ this.barrier = barrier;
5282
+ this.log = log;
5283
+ this.unhealthyThreshold = Math.max(1, opts?.unhealthyThreshold ?? 2);
5284
+ this.healthyThreshold = Math.max(1, opts?.healthyThreshold ?? 2);
5285
+ }
5286
+ target;
5287
+ barrier;
5288
+ log;
5289
+ unhealthyThreshold;
5290
+ healthyThreshold;
5291
+ /** True while the read-only barrier is established over the target. */
5292
+ gated = false;
5293
+ /**
5294
+ * The gate state that the most recently *enqueued* transition will settle on.
5295
+ * Hysteresis is measured against this, not `gated`, so that verdicts arriving
5296
+ * while a (slow, async) establish/remove is still in flight are counted toward
5297
+ * the post-transition state rather than the stale pre-transition one. Without
5298
+ * this, fast healthy verdicts arriving during a slow `establish()` could meet
5299
+ * `healthyThreshold` before the gate finished tripping, ungating it on the
5300
+ * first healthy verdict afterwards and defeating the threshold.
5301
+ */
5302
+ pendingGated = false;
5303
+ /** Consecutive same-verdict run length, signed by the *current* run's verdict. */
5304
+ unhealthyRun = 0;
5305
+ healthyRun = 0;
5306
+ /** Serialises establish/remove so overlapping observe() calls can't race. */
5307
+ transition = Promise.resolve();
5308
+ /** True while the read-only barrier is established (test/observability hook). */
5309
+ get isGated() {
5310
+ return this.gated;
5311
+ }
5312
+ /**
5313
+ * Feed one health verdict. Applies hysteresis, then establishes or removes the
5314
+ * barrier on a state change. Resolves once the (possibly no-op) transition is
5315
+ * applied. Never throws — a failed establish is treated as gated (fail-closed).
5316
+ */
5317
+ observe(verdict) {
5318
+ if (verdict === "healthy") {
5319
+ this.healthyRun += 1;
5320
+ this.unhealthyRun = 0;
5321
+ if (this.pendingGated && this.healthyRun >= this.healthyThreshold) {
5322
+ this.pendingGated = false;
5323
+ this.healthyRun = 0;
5324
+ return this.enqueue(() => this.ungate());
5325
+ }
5326
+ } else {
5327
+ this.unhealthyRun += 1;
5328
+ this.healthyRun = 0;
5329
+ if (!this.pendingGated && this.unhealthyRun >= this.unhealthyThreshold) {
5330
+ this.pendingGated = true;
5331
+ this.unhealthyRun = 0;
5332
+ return this.enqueue(() => this.gate());
5333
+ }
5334
+ }
5335
+ return this.transition;
5336
+ }
5337
+ /**
5338
+ * Tear the gate down. If currently gated, removes the barrier so a leftover
5339
+ * read-only tmpfs is not leaked. Call only after the connector above the target
5340
+ * is disconnected (top-down, no leaked mount).
5341
+ */
5342
+ dispose() {
5343
+ if (!this.gated) return this.transition;
5344
+ return this.enqueue(() => this.ungate());
5345
+ }
5346
+ enqueue(step) {
5347
+ this.transition = this.transition.then(step, step);
5348
+ return this.transition;
5349
+ }
5350
+ async gate() {
5351
+ if (this.gated) {
5352
+ this.pendingGated = true;
5353
+ return;
5354
+ }
5355
+ try {
5356
+ await this.barrier.establish(this.target, this.log);
5357
+ this.gated = true;
5358
+ this.log?.warn("fleet mount unhealthy \u2014 gated read-only", { target: this.target });
5359
+ } catch (err) {
5360
+ this.gated = true;
5361
+ this.log?.error(
5362
+ "fleet read-only gate establish threw \u2014 treating as gated (fail-closed)",
5363
+ err,
5364
+ {
5365
+ target: this.target
5366
+ }
5367
+ );
5368
+ }
5369
+ }
5370
+ async ungate() {
5371
+ if (!this.gated) return;
5372
+ try {
5373
+ await this.barrier.remove(this.target, this.log);
5374
+ } catch (err) {
5375
+ this.pendingGated = true;
5376
+ this.log?.error("fleet read-only gate remove failed \u2014 staying gated", err, {
5377
+ target: this.target
5378
+ });
5379
+ return;
5380
+ }
5381
+ this.gated = false;
5382
+ this.log?.info("fleet mount recovered \u2014 gate lifted, writable again", { target: this.target });
5383
+ }
5384
+ };
5243
5385
  function isFleetManaged(connectorId) {
5244
5386
  const raw = process.env.SKAILE_FLEET_MANAGED_MOUNTS ?? "";
5245
5387
  if (raw.length === 0) return false;
@@ -5268,6 +5410,16 @@ async function ensureFleetMounted(dir, log, options) {
5268
5410
  `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)}`
5269
5411
  );
5270
5412
  }
5413
+ async function probeFleetHealth(dir) {
5414
+ try {
5415
+ const st = await stat(dir);
5416
+ if (!st.isDirectory()) return "error";
5417
+ await readdir(dir);
5418
+ return "healthy";
5419
+ } catch {
5420
+ return "error";
5421
+ }
5422
+ }
5271
5423
  var NOT_READY_SENTINEL = ".skaile-workspace-not-ready";
5272
5424
  var SENTINEL_BODY = [
5273
5425
  "This workspace is backed by a remote mount that is not live yet.",
@@ -5394,6 +5546,12 @@ var defaultMountPlaceholderBarrier = new RoMountPlaceholderBarrier();
5394
5546
 
5395
5547
  // connectors/src/connector-manager.ts
5396
5548
  var KNOWN_PREFIXES = ["env:", "forge:", "op:", "kp:", "oauth:"];
5549
+ var DEFAULT_FLEET_HEALTH_POLL_MS = 5e3;
5550
+ function parseFleetHealthPollMs(raw = process.env.SKAILE_FLEET_HEALTH_POLL_MS) {
5551
+ if (raw === void 0) return DEFAULT_FLEET_HEALTH_POLL_MS;
5552
+ const n = Number(raw);
5553
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_FLEET_HEALTH_POLL_MS;
5554
+ }
5397
5555
  function hasKnownPrefix(ref) {
5398
5556
  return KNOWN_PREFIXES.some((p) => ref.startsWith(p));
5399
5557
  }
@@ -5494,6 +5652,7 @@ var ConnectorManager = class {
5494
5652
  this.tokenMediator = opts.tokenMediator;
5495
5653
  this.preMintedSecrets = opts.preMintedSecrets;
5496
5654
  this.placeholderBarrier = opts.placeholderBarrier ?? defaultMountPlaceholderBarrier;
5655
+ this.fleetHealthPollMs = opts.fleetHealthPollMs ?? parseFleetHealthPollMs();
5497
5656
  const secrets = opts.secrets;
5498
5657
  if (secrets && typeof secrets === "object" && "prepareRef" in secrets) {
5499
5658
  this.chain = secrets;
@@ -5522,6 +5681,10 @@ var ConnectorManager = class {
5522
5681
  disposeController = new AbortController();
5523
5682
  /** Tracks the background task driving deferred (rclone-backed) mounts. */
5524
5683
  deferredConnect;
5684
+ /** Periodic local fleet-health probe; runs only while a fleet gate is active. */
5685
+ fleetHealthTimer;
5686
+ /** Local fleet-health poll interval (ms). 0 disables the local probe. */
5687
+ fleetHealthPollMs;
5525
5688
  // ── Private helpers ──────────────────────────────────────────────────────────
5526
5689
  /**
5527
5690
  * Wrap the user-supplied `TokenMediator` so `reason: 'initial'` calls for an
@@ -5793,7 +5956,9 @@ var ConnectorManager = class {
5793
5956
  connector,
5794
5957
  handle,
5795
5958
  declaration: decl,
5796
- placeholderTarget: previous?.placeholderTarget
5959
+ placeholderTarget: previous?.placeholderTarget,
5960
+ fleetHealthGate: previous?.fleetHealthGate,
5961
+ fleetHealthTarget: previous?.fleetHealthTarget
5797
5962
  });
5798
5963
  this.opts.onStatusChange?.({
5799
5964
  type: "connector_status",
@@ -5829,16 +5994,25 @@ var ConnectorManager = class {
5829
5994
  * prompt before the background mount completes.
5830
5995
  *
5831
5996
  * Before exposing the target, a read-only barrier is established so the agent
5832
- * cannot write a phantom workspace into a not-yet-mounted remote. Fleet-managed
5833
- * mounts are skipped: the host already bound the live share at the target, so a
5834
- * barrier on top would shadow real content.
5997
+ * cannot write a phantom workspace into a not-yet-mounted remote.
5998
+ *
5999
+ * Fleet-managed mounts skip the *static* boot-time barrier (the host already
6000
+ * bound the live share at the target — a static barrier would shadow real
6001
+ * content) but instead get a *dynamic* health gate: while the host fleet rclone
6002
+ * is healthy the target stays writable exactly as before; when it wedges, the
6003
+ * gate establishes the same read-only barrier until the mount recovers.
5835
6004
  */
5836
6005
  async preRegisterDeferred(decl, connector) {
5837
6006
  const mountTarget = this.resolveMountTarget(decl);
5838
6007
  if (!existsSync(mountTarget)) mkdirSync(mountTarget, { recursive: true });
5839
6008
  const fleetManaged = isFleetManaged(decl.id);
5840
6009
  let placeholderTarget;
5841
- if (!fleetManaged) {
6010
+ let fleetHealthGate;
6011
+ let fleetHealthTarget;
6012
+ if (fleetManaged) {
6013
+ fleetHealthGate = new FleetHealthGate(mountTarget, this.placeholderBarrier, this.mgrLog);
6014
+ fleetHealthTarget = mountTarget;
6015
+ } else {
5842
6016
  try {
5843
6017
  await this.placeholderBarrier.establish(mountTarget, this.mgrLog);
5844
6018
  placeholderTarget = mountTarget;
@@ -5857,7 +6031,58 @@ var ConnectorManager = class {
5857
6031
  // returns cleanly instead of dereferencing null state.
5858
6032
  state: {}
5859
6033
  };
5860
- this.active.set(decl.id, { connector, handle, declaration: decl, placeholderTarget });
6034
+ this.active.set(decl.id, {
6035
+ connector,
6036
+ handle,
6037
+ declaration: decl,
6038
+ placeholderTarget,
6039
+ fleetHealthGate,
6040
+ fleetHealthTarget
6041
+ });
6042
+ if (fleetHealthGate) this.ensureFleetHealthPoll();
6043
+ }
6044
+ /**
6045
+ * Start the periodic local fleet-health probe if it is not already running and
6046
+ * the poll interval is enabled. Each tick `stat`+`readdir`s every fleet target
6047
+ * and feeds the verdict to its gate (hysteresis lives in the gate). The probe
6048
+ * only catches directly-detectable failures (EIO / dead bind); silent
6049
+ * degradation is covered by the platform signal via `reportFleetHealth`.
6050
+ */
6051
+ ensureFleetHealthPoll() {
6052
+ if (this.fleetHealthTimer || this.fleetHealthPollMs <= 0 || this.disposed) return;
6053
+ this.fleetHealthTimer = setInterval(() => {
6054
+ void this.runFleetHealthProbe();
6055
+ }, this.fleetHealthPollMs);
6056
+ this.fleetHealthTimer.unref?.();
6057
+ }
6058
+ async runFleetHealthProbe() {
6059
+ if (this.disposed) return;
6060
+ const gates = [...this.active.values()].filter(
6061
+ (e) => e.fleetHealthGate != null && e.fleetHealthTarget != null
6062
+ );
6063
+ await Promise.all(
6064
+ gates.map(async (e) => {
6065
+ if (e.fleetHealthGate.isGated) return;
6066
+ const verdict = await probeFleetHealth(e.fleetHealthTarget);
6067
+ await e.fleetHealthGate.observe(verdict).catch(() => {
6068
+ });
6069
+ })
6070
+ );
6071
+ }
6072
+ /**
6073
+ * Feed an externally-derived fleet-health verdict for a connector into its gate.
6074
+ *
6075
+ * This is the consume side of the platform fleet-health signal: the host fleet
6076
+ * health poll knows `core/stats.errors` and can detect a *silently-empty-but-
6077
+ * writable* degraded mount that the local probe cannot. The runner watches the
6078
+ * `<workspace>/.skaile/fleet-health/<connectorId>` flag and forwards its verdict
6079
+ * here. Unknown / non-fleet ids are ignored.
6080
+ */
6081
+ async reportFleetHealth(connectorId, verdict) {
6082
+ const entry = this.active.get(connectorId);
6083
+ if (!entry?.fleetHealthGate) return;
6084
+ await entry.fleetHealthGate.observe(verdict).catch(() => {
6085
+ });
5861
6086
  }
5862
6087
  /** Drive all deferred mounts concurrently; resolves when every one settles. */
5863
6088
  async connectDeferred(deferred) {
@@ -5904,6 +6129,10 @@ var ConnectorManager = class {
5904
6129
  this.mgrLog.info("disconnectAll start", { count: this.active.size });
5905
6130
  this.disposed = true;
5906
6131
  this.disposeController.abort();
6132
+ if (this.fleetHealthTimer) {
6133
+ clearInterval(this.fleetHealthTimer);
6134
+ this.fleetHealthTimer = void 0;
6135
+ }
5907
6136
  if (this.deferredConnect) {
5908
6137
  try {
5909
6138
  await this.deferredConnect;
@@ -5921,6 +6150,8 @@ var ConnectorManager = class {
5921
6150
  await this.placeholderBarrier.remove(entry.placeholderTarget, this.mgrLog).catch(() => {
5922
6151
  });
5923
6152
  }
6153
+ if (entry.fleetHealthGate) await entry.fleetHealthGate.dispose().catch(() => {
6154
+ });
5924
6155
  this.opts.onStatusChange?.({
5925
6156
  type: "connector_status",
5926
6157
  connectorId: id,
@@ -6011,6 +6242,8 @@ var ConnectorManager = class {
6011
6242
  await this.placeholderBarrier.remove(entry.placeholderTarget, this.mgrLog).catch(() => {
6012
6243
  });
6013
6244
  }
6245
+ if (entry.fleetHealthGate) await entry.fleetHealthGate.dispose().catch(() => {
6246
+ });
6014
6247
  this.active.delete(id);
6015
6248
  this.opts.onStatusChange?.({
6016
6249
  type: "connector_status",
@@ -6982,6 +7215,135 @@ function isPackageResolvable(pkg, projectDir) {
6982
7215
  function findMissingPackages(packages, projectDir) {
6983
7216
  return packages.filter((p) => !isPackageResolvable(p, projectDir));
6984
7217
  }
7218
+
7219
+ // connectors/src/rclone-token-refresh.ts
7220
+ var PROACTIVE_RCLONE_REFRESH_MARGIN_MS = 5 * 60 * 1e3;
7221
+ var DEFAULT_REACTIVE_COOLDOWN_MS = 30 * 1e3;
7222
+ var MAX_TIMER_MS = 2147483647;
7223
+ function createRcloneTokenRefresh(deps) {
7224
+ const now2 = deps.now ?? (() => Date.now());
7225
+ const setTimeoutImpl = deps.setTimeoutImpl ?? ((handler, ms) => setTimeout(handler, ms));
7226
+ const clearTimeoutImpl = deps.clearTimeoutImpl ?? ((handle) => {
7227
+ clearTimeout(handle);
7228
+ });
7229
+ const marginMs = deps.marginMs ?? PROACTIVE_RCLONE_REFRESH_MARGIN_MS;
7230
+ const reactiveCooldownMs = deps.reactiveCooldownMs ?? DEFAULT_REACTIVE_COOLDOWN_MS;
7231
+ let timer = null;
7232
+ let refreshing = false;
7233
+ let disposed = false;
7234
+ let lastRefreshAt = Number.NEGATIVE_INFINITY;
7235
+ function clearTimer() {
7236
+ if (timer !== null) {
7237
+ clearTimeoutImpl(timer);
7238
+ timer = null;
7239
+ }
7240
+ }
7241
+ function cancel() {
7242
+ disposed = true;
7243
+ clearTimer();
7244
+ }
7245
+ async function fire(reason) {
7246
+ if (refreshing || disposed) return;
7247
+ refreshing = true;
7248
+ try {
7249
+ let result;
7250
+ try {
7251
+ result = await deps.tokenMediator({ connectorId: deps.connectorId, reason });
7252
+ } catch (err) {
7253
+ deps.log(
7254
+ `[rclone-token-refresh] mediator threw: ${err instanceof Error ? err.message : String(err)}; reactive auth-error path remains as safety net`
7255
+ );
7256
+ return;
7257
+ }
7258
+ if (disposed) return;
7259
+ if (!result.ok) {
7260
+ deps.log(
7261
+ `[rclone-token-refresh] mediator refused (${result.code}: ${result.message}); reactive auth-error path remains as safety net`
7262
+ );
7263
+ return;
7264
+ }
7265
+ if (!result.token) {
7266
+ deps.log(
7267
+ "[rclone-token-refresh] mediator returned an empty token; reactive auth-error path remains as safety net"
7268
+ );
7269
+ return;
7270
+ }
7271
+ const handle = deps.handleRef.current;
7272
+ if (!handle) {
7273
+ deps.log("[rclone-token-refresh] no live rclone handle to recycle; skipping refresh");
7274
+ return;
7275
+ }
7276
+ const rcloneConfig = await deps.renderConfig(result.token);
7277
+ if (disposed) return;
7278
+ let next;
7279
+ try {
7280
+ next = await deps.rclone.reconfigure(handle, rcloneConfig);
7281
+ } catch (err) {
7282
+ deps.log(
7283
+ `[rclone-token-refresh] reconfigure failed: ${err instanceof Error ? err.message : String(err)}; reactive auth-error path remains as safety net`
7284
+ );
7285
+ return;
7286
+ }
7287
+ if (disposed) {
7288
+ deps.log("[rclone-token-refresh] disposed mid-recycle; stopping freshly-spawned process");
7289
+ try {
7290
+ await deps.rclone.stop(next);
7291
+ } catch {
7292
+ }
7293
+ return;
7294
+ }
7295
+ deps.handleRef.current = next;
7296
+ lastRefreshAt = now2();
7297
+ deps.log(
7298
+ `[rclone-token-refresh] recycled mount with refreshed token (reason=${reason}, expiresAt=${result.expiresAt ?? "none"})`
7299
+ );
7300
+ schedule(result.expiresAt);
7301
+ } finally {
7302
+ refreshing = false;
7303
+ }
7304
+ }
7305
+ function armTimer(fireAtMs) {
7306
+ const delayMs = Math.min(Math.max(0, fireAtMs - now2()), MAX_TIMER_MS);
7307
+ timer = setTimeoutImpl(() => {
7308
+ timer = null;
7309
+ if (now2() < fireAtMs) armTimer(fireAtMs);
7310
+ else void fire("refresh");
7311
+ }, delayMs);
7312
+ timer.unref?.();
7313
+ }
7314
+ function schedule(expiresAtIso) {
7315
+ clearTimer();
7316
+ if (disposed || !expiresAtIso) return;
7317
+ const expiresAtMs = Date.parse(expiresAtIso);
7318
+ if (!Number.isFinite(expiresAtMs)) return;
7319
+ const fireAtMs = expiresAtMs - marginMs;
7320
+ if (fireAtMs - now2() <= 0) {
7321
+ deps.log(`[rclone-token-refresh] token within ${marginMs / 1e3}s of expiry; refreshing now`);
7322
+ } else {
7323
+ deps.log(
7324
+ `[rclone-token-refresh] scheduled in ${Math.round((fireAtMs - now2()) / 1e3)}s (expiresAt=${expiresAtIso})`
7325
+ );
7326
+ }
7327
+ armTimer(fireAtMs);
7328
+ }
7329
+ function onAuthError() {
7330
+ if (refreshing || disposed) return;
7331
+ if (now2() - lastRefreshAt < reactiveCooldownMs) {
7332
+ deps.log(
7333
+ "[rclone-token-refresh] auth-error within refresh cooldown; suppressing reactive refresh"
7334
+ );
7335
+ return;
7336
+ }
7337
+ clearTimer();
7338
+ void fire("retry-401");
7339
+ }
7340
+ return {
7341
+ schedule,
7342
+ onAuthError,
7343
+ cancel,
7344
+ isPending: () => timer !== null
7345
+ };
7346
+ }
6985
7347
  var WorktreeRegistryImpl = class {
6986
7348
  entries = /* @__PURE__ */ new Map();
6987
7349
  register(path2, owner) {
@@ -8002,6 +8364,6 @@ function createConnector() {
8002
8364
  return new FlowAdapter();
8003
8365
  }
8004
8366
 
8005
- export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, ConnectorFieldMissingError, ConnectorManager, ConnectorStartupError, DeployConnector, DevServerConnector, FlowAdapter, GitConnector, GmailConnector, GoogleDriveConnector, LocalConnector, LogBuffer, ManagedGitconfigCollisionError, MattermostConnector, MemoryConnector, MinIOConnector, NOT_READY_SENTINEL, 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, createWorktree, defaultMountPlaceholderBarrier, defaultSpawn, ensureFleetMounted, findGitRoot, findMissingPackages, getConnector, installNpmPackages, isFleetManaged, isPackageResolvable, listConnectors, listWorktrees, registerBuiltinConnectors, removeMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock };
8006
- //# sourceMappingURL=chunk-GXHJDQ37.js.map
8007
- //# sourceMappingURL=chunk-GXHJDQ37.js.map
8367
+ 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, registerBuiltinConnectors, removeMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock };
8368
+ //# sourceMappingURL=chunk-WQRDLR3K.js.map
8369
+ //# sourceMappingURL=chunk-WQRDLR3K.js.map