@skaile/workspaces 0.33.0 → 0.35.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 (139) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/{asset-feeds-DX5JPM76.js → asset-feeds-PCLTE2W5.js} +20 -12
  3. package/dist/asset-feeds-PCLTE2W5.js.map +1 -0
  4. package/dist/asset-manager/index.js +8 -8
  5. package/dist/asset-manager/installer.js +7 -7
  6. package/dist/{chunk-PKPUVQJP.js → chunk-2Q6OY3SU.js} +3 -3
  7. package/dist/{chunk-PKPUVQJP.js.map → chunk-2Q6OY3SU.js.map} +1 -1
  8. package/dist/{chunk-LRFZNLB2.js → chunk-52HINMW4.js} +3 -3
  9. package/dist/{chunk-LRFZNLB2.js.map → chunk-52HINMW4.js.map} +1 -1
  10. package/dist/{chunk-AZODLVKH.js → chunk-56YT6CWS.js} +5 -5
  11. package/dist/{chunk-AZODLVKH.js.map → chunk-56YT6CWS.js.map} +1 -1
  12. package/dist/{chunk-RQG5TXIR.js → chunk-5DPQ766L.js} +4 -4
  13. package/dist/{chunk-RQG5TXIR.js.map → chunk-5DPQ766L.js.map} +1 -1
  14. package/dist/{chunk-BUMDC3R2.js → chunk-6422UOMQ.js} +5 -5
  15. package/dist/{chunk-BUMDC3R2.js.map → chunk-6422UOMQ.js.map} +1 -1
  16. package/dist/{chunk-XQRAXAL5.js → chunk-7B3CL5JT.js} +7 -7
  17. package/dist/{chunk-XQRAXAL5.js.map → chunk-7B3CL5JT.js.map} +1 -1
  18. package/dist/{chunk-LFDZYCEO.js → chunk-BM7XANVN.js} +2 -2
  19. package/dist/chunk-BM7XANVN.js.map +1 -0
  20. package/dist/{chunk-QUWXW2N3.js → chunk-EOCKR5TN.js} +3 -3
  21. package/dist/{chunk-QUWXW2N3.js.map → chunk-EOCKR5TN.js.map} +1 -1
  22. package/dist/{chunk-EJZFQCQG.js → chunk-I3TERDTR.js} +2 -2
  23. package/dist/{chunk-EJZFQCQG.js.map → chunk-I3TERDTR.js.map} +1 -1
  24. package/dist/{chunk-N74UQJPN.js → chunk-KYC6JH75.js} +3 -3
  25. package/dist/{chunk-N74UQJPN.js.map → chunk-KYC6JH75.js.map} +1 -1
  26. package/dist/{chunk-4AGIEU7V.js → chunk-MAPRKFBD.js} +3 -3
  27. package/dist/{chunk-4AGIEU7V.js.map → chunk-MAPRKFBD.js.map} +1 -1
  28. package/dist/{chunk-362QBS57.js → chunk-NAMD4AIK.js} +235 -13
  29. package/dist/chunk-NAMD4AIK.js.map +1 -0
  30. package/dist/{chunk-KKERAIPQ.js → chunk-O2OHGE4S.js} +4 -4
  31. package/dist/{chunk-KKERAIPQ.js.map → chunk-O2OHGE4S.js.map} +1 -1
  32. package/dist/{chunk-7MANNJ3U.js → chunk-O4PANZI2.js} +5 -5
  33. package/dist/{chunk-7MANNJ3U.js.map → chunk-O4PANZI2.js.map} +1 -1
  34. package/dist/{chunk-H5YXL4KI.js → chunk-SAXDHVJZ.js} +3 -3
  35. package/dist/{chunk-H5YXL4KI.js.map → chunk-SAXDHVJZ.js.map} +1 -1
  36. package/dist/{chunk-R2I7RHT7.js → chunk-SKVUDDHE.js} +191 -23
  37. package/dist/chunk-SKVUDDHE.js.map +1 -0
  38. package/dist/{chunk-PWXN2KHV.js → chunk-UAYAK7N4.js} +5 -5
  39. package/dist/{chunk-PWXN2KHV.js.map → chunk-UAYAK7N4.js.map} +1 -1
  40. package/dist/{chunk-PTDVTMQL.js → chunk-WEQN6EWW.js} +4 -4
  41. package/dist/{chunk-PTDVTMQL.js.map → chunk-WEQN6EWW.js.map} +1 -1
  42. package/dist/{chunk-VXW2K4YH.js → chunk-XOBPOVFT.js} +3 -3
  43. package/dist/{chunk-VXW2K4YH.js.map → chunk-XOBPOVFT.js.map} +1 -1
  44. package/dist/{chunk-4ZVS6KV4.js → chunk-ZGJE4ZDP.js} +2 -2
  45. package/dist/{chunk-4ZVS6KV4.js.map → chunk-ZGJE4ZDP.js.map} +1 -1
  46. package/dist/cli/index.js +196 -101
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/cli/src/asset-feeds.d.ts +8 -1
  49. package/dist/cli/src/asset-feeds.d.ts.map +1 -1
  50. package/dist/cli/src/commands/manage.d.ts +33 -22
  51. package/dist/cli/src/commands/manage.d.ts.map +1 -1
  52. package/dist/connectors/config.js +7 -7
  53. package/dist/connectors/index.js +8 -8
  54. package/dist/connectors/src/connector-manager.d.ts +56 -3
  55. package/dist/connectors/src/connector-manager.d.ts.map +1 -1
  56. package/dist/connectors/src/fleet-health-gate.d.ts +86 -0
  57. package/dist/connectors/src/fleet-health-gate.d.ts.map +1 -0
  58. package/dist/connectors/src/fleet-utils.d.ts +16 -0
  59. package/dist/connectors/src/fleet-utils.d.ts.map +1 -1
  60. package/dist/connectors/src/index.d.ts +4 -1
  61. package/dist/connectors/src/index.d.ts.map +1 -1
  62. package/dist/connectors/src/mount-placeholder.d.ts +8 -0
  63. package/dist/connectors/src/mount-placeholder.d.ts.map +1 -1
  64. package/dist/core/index.js +6 -6
  65. package/dist/core/manifest.js +2 -2
  66. package/dist/core/models.js +1 -1
  67. package/dist/core/publish-manifest.js +2 -2
  68. package/dist/core/runtime-assets.js +5 -5
  69. package/dist/core/src/index.d.ts +1 -1
  70. package/dist/core/src/index.d.ts.map +1 -1
  71. package/dist/core/src/models.d.ts +28 -0
  72. package/dist/core/src/models.d.ts.map +1 -1
  73. package/dist/core/src/workspace-config.d.ts +13 -0
  74. package/dist/core/src/workspace-config.d.ts.map +1 -1
  75. package/dist/core/workspace-config.js +4 -4
  76. package/dist/deploy/index.js +6 -6
  77. package/dist/discovery/index.js +4 -4
  78. package/dist/{ensure-sources-O6RMWP6C.js → ensure-sources-HMMEQEZC.js} +11 -11
  79. package/dist/{ensure-sources-O6RMWP6C.js.map → ensure-sources-HMMEQEZC.js.map} +1 -1
  80. package/dist/factory-assets/connectors/deploy.js +8 -8
  81. package/dist/factory-assets/connectors/devserver.js +8 -8
  82. package/dist/factory-assets/connectors/flow/adapter.js +8 -8
  83. package/dist/factory-assets/connectors/flow/engine.js +6 -6
  84. package/dist/factory-assets/connectors/flow/run-flow.js +9 -9
  85. package/dist/factory-assets/connectors/flow.js +8 -8
  86. package/dist/factory-assets/connectors/git.js +8 -8
  87. package/dist/factory-assets/connectors/gmail.js +8 -8
  88. package/dist/factory-assets/connectors/googledrive.js +8 -8
  89. package/dist/factory-assets/connectors/local.js +8 -8
  90. package/dist/factory-assets/connectors/mattermost.js +8 -8
  91. package/dist/factory-assets/connectors/memory.js +8 -8
  92. package/dist/factory-assets/connectors/minio.js +8 -8
  93. package/dist/factory-assets/connectors/postgres.js +8 -8
  94. package/dist/factory-assets/connectors/s3.js +8 -8
  95. package/dist/factory-assets/connectors/sharepoint.js +8 -8
  96. package/dist/factory-assets/connectors/sqlite.js +8 -8
  97. package/dist/factory-assets/connectors/static-server.js +8 -8
  98. package/dist/factory-assets/connectors/tunnel.js +8 -8
  99. package/dist/factory-assets/connectors/webdav.js +8 -8
  100. package/dist/factory-assets/connectors/xstate-store.js +8 -8
  101. package/dist/factory-assets/connectors/xstate.js +8 -8
  102. package/dist/{flows-QWYIUDA2.js → flows-O7OXGFIY.js} +3 -3
  103. package/dist/{flows-QWYIUDA2.js.map → flows-O7OXGFIY.js.map} +1 -1
  104. package/dist/library/index.js +9 -9
  105. package/dist/library/install.js +7 -7
  106. package/dist/open-library-5WWTBHQD.js +23 -0
  107. package/dist/{open-library-B7BLYXYK.js.map → open-library-5WWTBHQD.js.map} +1 -1
  108. package/dist/paths-UC3ZHLKC.js +15 -0
  109. package/dist/{paths-5IUR235S.js.map → paths-UC3ZHLKC.js.map} +1 -1
  110. package/dist/{plugin-store-WWOJXWG7.js → plugin-store-R2WLXRT3.js} +8 -8
  111. package/dist/{plugin-store-WWOJXWG7.js.map → plugin-store-R2WLXRT3.js.map} +1 -1
  112. package/dist/runner/index.js +10 -10
  113. package/dist/runner/src/external-mcp.d.ts +45 -4
  114. package/dist/runner/src/external-mcp.d.ts.map +1 -1
  115. package/dist/runner/src/fleet-health-flag-dispatcher.d.ts +40 -0
  116. package/dist/runner/src/fleet-health-flag-dispatcher.d.ts.map +1 -0
  117. package/dist/runner/src/resources.d.ts.map +1 -1
  118. package/dist/runner/src/serve.d.ts.map +1 -1
  119. package/dist/sdk/asset-manager.js +8 -8
  120. package/dist/sdk/core.js +6 -6
  121. package/dist/sdk/flow.js +6 -6
  122. package/dist/sdk/index.js +10 -10
  123. package/dist/sdk/runner.js +10 -10
  124. package/dist/{setup-2RD6EDBZ.js → setup-BSDHVMR6.js} +8 -8
  125. package/dist/{setup-2RD6EDBZ.js.map → setup-BSDHVMR6.js.map} +1 -1
  126. package/dist/store-client-4GKV4LOD.js +15 -0
  127. package/dist/{store-client-XQQRHYDW.js.map → store-client-4GKV4LOD.js.map} +1 -1
  128. package/dist/tui/index.js +10 -10
  129. package/dist/workspace-plugin/adapters/mcp.js +1 -1
  130. package/dist/workspace-plugin/adapters/omp.js +3 -3
  131. package/dist/workspace-plugin/index.js +1 -1
  132. package/package.json +1 -1
  133. package/dist/asset-feeds-DX5JPM76.js.map +0 -1
  134. package/dist/chunk-362QBS57.js.map +0 -1
  135. package/dist/chunk-LFDZYCEO.js.map +0 -1
  136. package/dist/chunk-R2I7RHT7.js.map +0 -1
  137. package/dist/open-library-B7BLYXYK.js +0 -23
  138. package/dist/paths-5IUR235S.js +0 -15
  139. package/dist/store-client-XQQRHYDW.js +0 -15
@@ -1,11 +1,11 @@
1
1
  import { renderGoogleDriveConfig, renderOneDriveConfig, renderWebDAVConfig } from './chunk-OVRSNIKJ.js';
2
- import { resolveAuthRef, resolveAuth, resolveMountCredentialString, resolveMountCredential } from './chunk-LRFZNLB2.js';
2
+ import { resolveAuthRef, resolveAuth, resolveMountCredentialString, resolveMountCredential } from './chunk-52HINMW4.js';
3
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-PKPUVQJP.js';
8
+ import { portableSpawn } from './chunk-2Q6OY3SU.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';
@@ -14,7 +14,7 @@ import { execFile, execSync, spawn } from 'child_process';
14
14
  import { createServer } from 'http';
15
15
  import mime from 'mime';
16
16
  import { promisify } from 'util';
17
- import { stat, readdir, mkdir, writeFile, chmod, rm } from 'fs/promises';
17
+ import { stat, readdir, writeFile, chmod, rm, mkdir } from 'fs/promises';
18
18
  import { createRequire } from 'module';
19
19
  import { pathToFileURL } from 'url';
20
20
 
@@ -5273,6 +5273,116 @@ var ConnectorStartupError = class extends Error {
5273
5273
  this.failedReadWrite = failedReadWrite;
5274
5274
  }
5275
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 && !this.pendingGated) return this.transition;
5344
+ this.pendingGated = false;
5345
+ return this.enqueue(() => this.ungate());
5346
+ }
5347
+ enqueue(step) {
5348
+ this.transition = this.transition.then(step, step);
5349
+ return this.transition;
5350
+ }
5351
+ async gate() {
5352
+ if (this.gated) {
5353
+ this.pendingGated = true;
5354
+ return;
5355
+ }
5356
+ try {
5357
+ await this.barrier.establish(this.target, this.log);
5358
+ this.gated = true;
5359
+ this.log?.warn("fleet mount unhealthy \u2014 gated read-only", { target: this.target });
5360
+ } catch (err) {
5361
+ this.gated = true;
5362
+ this.log?.error(
5363
+ "fleet read-only gate establish threw \u2014 treating as gated (fail-closed)",
5364
+ err,
5365
+ {
5366
+ target: this.target
5367
+ }
5368
+ );
5369
+ }
5370
+ }
5371
+ async ungate() {
5372
+ if (!this.gated) return;
5373
+ try {
5374
+ await this.barrier.remove(this.target, this.log);
5375
+ } catch (err) {
5376
+ this.pendingGated = true;
5377
+ this.log?.error("fleet read-only gate remove failed \u2014 staying gated", err, {
5378
+ target: this.target
5379
+ });
5380
+ return;
5381
+ }
5382
+ this.gated = false;
5383
+ this.log?.info("fleet mount recovered \u2014 gate lifted, writable again", { target: this.target });
5384
+ }
5385
+ };
5276
5386
  function isFleetManaged(connectorId) {
5277
5387
  const raw = process.env.SKAILE_FLEET_MANAGED_MOUNTS ?? "";
5278
5388
  if (raw.length === 0) return false;
@@ -5301,7 +5411,21 @@ async function ensureFleetMounted(dir, log, options) {
5301
5411
  `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)}`
5302
5412
  );
5303
5413
  }
5414
+ async function probeFleetHealth(dir) {
5415
+ try {
5416
+ const st = await stat(dir);
5417
+ if (!st.isDirectory()) return "error";
5418
+ await readdir(dir);
5419
+ return "healthy";
5420
+ } catch {
5421
+ return "error";
5422
+ }
5423
+ }
5304
5424
  var NOT_READY_SENTINEL = ".skaile-workspace-not-ready";
5425
+ var TARGET_ALREADY_PRESENT_CODES = /* @__PURE__ */ new Set(["EEXIST", "ENOTCONN", "EBUSY", "ENOTDIR"]);
5426
+ function errnoCode(err) {
5427
+ return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
5428
+ }
5305
5429
  var SENTINEL_BODY = [
5306
5430
  "This workspace is backed by a remote mount that is not live yet.",
5307
5431
  "It is mounted read-only on purpose: writing here would create a phantom",
@@ -5309,6 +5433,9 @@ var SENTINEL_BODY = [
5309
5433
  "Wait for the mount to finish syncing (watch connector_status) before writing.",
5310
5434
  ""
5311
5435
  ].join("\n");
5436
+ var defaultMkdir = async (target) => {
5437
+ await mkdir(target, { recursive: true });
5438
+ };
5312
5439
  var defaultRunner = (cmd, args) => new Promise((resolve5, reject) => {
5313
5440
  const proc = spawn(cmd, args, { stdio: "ignore" });
5314
5441
  proc.once("error", reject);
@@ -5319,13 +5446,27 @@ var defaultRunner = (cmd, args) => new Promise((resolve5, reject) => {
5319
5446
  });
5320
5447
  var RoMountPlaceholderBarrier = class {
5321
5448
  run;
5449
+ mkdirFn;
5322
5450
  modes = /* @__PURE__ */ new Map();
5323
5451
  constructor(opts) {
5324
5452
  this.run = opts?.run ?? defaultRunner;
5453
+ this.mkdirFn = opts?.mkdirFn ?? defaultMkdir;
5325
5454
  }
5326
5455
  async establish(target, log) {
5327
5456
  if (this.modes.has(target)) return;
5328
- await mkdir(target, { recursive: true });
5457
+ try {
5458
+ await this.mkdirFn(target);
5459
+ } catch (err) {
5460
+ const code = errnoCode(err);
5461
+ if (!code || !TARGET_ALREADY_PRESENT_CODES.has(code)) {
5462
+ await this.chmodFallback(target, err, log);
5463
+ return;
5464
+ }
5465
+ log?.debug("barrier target already present (live/broken mount) \u2014 protecting in place", {
5466
+ target,
5467
+ code
5468
+ });
5469
+ }
5329
5470
  let tmpfsMounted = false;
5330
5471
  try {
5331
5472
  await this.run("mount", [
@@ -5427,6 +5568,12 @@ var defaultMountPlaceholderBarrier = new RoMountPlaceholderBarrier();
5427
5568
 
5428
5569
  // connectors/src/connector-manager.ts
5429
5570
  var KNOWN_PREFIXES = ["env:", "forge:", "op:", "kp:", "oauth:"];
5571
+ var DEFAULT_FLEET_HEALTH_POLL_MS = 5e3;
5572
+ function parseFleetHealthPollMs(raw = process.env.SKAILE_FLEET_HEALTH_POLL_MS) {
5573
+ if (raw === void 0) return DEFAULT_FLEET_HEALTH_POLL_MS;
5574
+ const n = Number(raw);
5575
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_FLEET_HEALTH_POLL_MS;
5576
+ }
5430
5577
  function hasKnownPrefix(ref) {
5431
5578
  return KNOWN_PREFIXES.some((p) => ref.startsWith(p));
5432
5579
  }
@@ -5527,6 +5674,7 @@ var ConnectorManager = class {
5527
5674
  this.tokenMediator = opts.tokenMediator;
5528
5675
  this.preMintedSecrets = opts.preMintedSecrets;
5529
5676
  this.placeholderBarrier = opts.placeholderBarrier ?? defaultMountPlaceholderBarrier;
5677
+ this.fleetHealthPollMs = opts.fleetHealthPollMs ?? parseFleetHealthPollMs();
5530
5678
  const secrets = opts.secrets;
5531
5679
  if (secrets && typeof secrets === "object" && "prepareRef" in secrets) {
5532
5680
  this.chain = secrets;
@@ -5555,6 +5703,10 @@ var ConnectorManager = class {
5555
5703
  disposeController = new AbortController();
5556
5704
  /** Tracks the background task driving deferred (rclone-backed) mounts. */
5557
5705
  deferredConnect;
5706
+ /** Periodic local fleet-health probe; runs only while a fleet gate is active. */
5707
+ fleetHealthTimer;
5708
+ /** Local fleet-health poll interval (ms). 0 disables the local probe. */
5709
+ fleetHealthPollMs;
5558
5710
  // ── Private helpers ──────────────────────────────────────────────────────────
5559
5711
  /**
5560
5712
  * Wrap the user-supplied `TokenMediator` so `reason: 'initial'` calls for an
@@ -5826,7 +5978,9 @@ var ConnectorManager = class {
5826
5978
  connector,
5827
5979
  handle,
5828
5980
  declaration: decl,
5829
- placeholderTarget: previous?.placeholderTarget
5981
+ placeholderTarget: previous?.placeholderTarget,
5982
+ fleetHealthGate: previous?.fleetHealthGate,
5983
+ fleetHealthTarget: previous?.fleetHealthTarget
5830
5984
  });
5831
5985
  this.opts.onStatusChange?.({
5832
5986
  type: "connector_status",
@@ -5862,16 +6016,25 @@ var ConnectorManager = class {
5862
6016
  * prompt before the background mount completes.
5863
6017
  *
5864
6018
  * Before exposing the target, a read-only barrier is established so the agent
5865
- * cannot write a phantom workspace into a not-yet-mounted remote. Fleet-managed
5866
- * mounts are skipped: the host already bound the live share at the target, so a
5867
- * barrier on top would shadow real content.
6019
+ * cannot write a phantom workspace into a not-yet-mounted remote.
6020
+ *
6021
+ * Fleet-managed mounts skip the *static* boot-time barrier (the host already
6022
+ * bound the live share at the target — a static barrier would shadow real
6023
+ * content) but instead get a *dynamic* health gate: while the host fleet rclone
6024
+ * is healthy the target stays writable exactly as before; when it wedges, the
6025
+ * gate establishes the same read-only barrier until the mount recovers.
5868
6026
  */
5869
6027
  async preRegisterDeferred(decl, connector) {
5870
6028
  const mountTarget = this.resolveMountTarget(decl);
5871
6029
  if (!existsSync(mountTarget)) mkdirSync(mountTarget, { recursive: true });
5872
6030
  const fleetManaged = isFleetManaged(decl.id);
5873
6031
  let placeholderTarget;
5874
- if (!fleetManaged) {
6032
+ let fleetHealthGate;
6033
+ let fleetHealthTarget;
6034
+ if (fleetManaged) {
6035
+ fleetHealthGate = new FleetHealthGate(mountTarget, this.placeholderBarrier, this.mgrLog);
6036
+ fleetHealthTarget = mountTarget;
6037
+ } else {
5875
6038
  try {
5876
6039
  await this.placeholderBarrier.establish(mountTarget, this.mgrLog);
5877
6040
  placeholderTarget = mountTarget;
@@ -5890,7 +6053,58 @@ var ConnectorManager = class {
5890
6053
  // returns cleanly instead of dereferencing null state.
5891
6054
  state: {}
5892
6055
  };
5893
- this.active.set(decl.id, { connector, handle, declaration: decl, placeholderTarget });
6056
+ this.active.set(decl.id, {
6057
+ connector,
6058
+ handle,
6059
+ declaration: decl,
6060
+ placeholderTarget,
6061
+ fleetHealthGate,
6062
+ fleetHealthTarget
6063
+ });
6064
+ if (fleetHealthGate) this.ensureFleetHealthPoll();
6065
+ }
6066
+ /**
6067
+ * Start the periodic local fleet-health probe if it is not already running and
6068
+ * the poll interval is enabled. Each tick `stat`+`readdir`s every fleet target
6069
+ * and feeds the verdict to its gate (hysteresis lives in the gate). The probe
6070
+ * only catches directly-detectable failures (EIO / dead bind); silent
6071
+ * degradation is covered by the platform signal via `reportFleetHealth`.
6072
+ */
6073
+ ensureFleetHealthPoll() {
6074
+ if (this.fleetHealthTimer || this.fleetHealthPollMs <= 0 || this.disposed) return;
6075
+ this.fleetHealthTimer = setInterval(() => {
6076
+ void this.runFleetHealthProbe();
6077
+ }, this.fleetHealthPollMs);
6078
+ this.fleetHealthTimer.unref?.();
6079
+ }
6080
+ async runFleetHealthProbe() {
6081
+ if (this.disposed) return;
6082
+ const gates = [...this.active.values()].filter(
6083
+ (e) => e.fleetHealthGate != null && e.fleetHealthTarget != null
6084
+ );
6085
+ await Promise.all(
6086
+ gates.map(async (e) => {
6087
+ if (e.fleetHealthGate.isGated) return;
6088
+ const verdict = await probeFleetHealth(e.fleetHealthTarget);
6089
+ await e.fleetHealthGate.observe(verdict).catch(() => {
6090
+ });
6091
+ })
6092
+ );
6093
+ }
6094
+ /**
6095
+ * Feed an externally-derived fleet-health verdict for a connector into its gate.
6096
+ *
6097
+ * This is the consume side of the platform fleet-health signal: the host fleet
6098
+ * health poll knows `core/stats.errors` and can detect a *silently-empty-but-
6099
+ * writable* degraded mount that the local probe cannot. The runner watches the
6100
+ * `<workspace>/.skaile/fleet-health/<connectorId>` flag and forwards its verdict
6101
+ * here. Unknown / non-fleet ids are ignored.
6102
+ */
6103
+ async reportFleetHealth(connectorId, verdict) {
6104
+ const entry = this.active.get(connectorId);
6105
+ if (!entry?.fleetHealthGate) return;
6106
+ await entry.fleetHealthGate.observe(verdict).catch(() => {
6107
+ });
5894
6108
  }
5895
6109
  /** Drive all deferred mounts concurrently; resolves when every one settles. */
5896
6110
  async connectDeferred(deferred) {
@@ -5937,6 +6151,10 @@ var ConnectorManager = class {
5937
6151
  this.mgrLog.info("disconnectAll start", { count: this.active.size });
5938
6152
  this.disposed = true;
5939
6153
  this.disposeController.abort();
6154
+ if (this.fleetHealthTimer) {
6155
+ clearInterval(this.fleetHealthTimer);
6156
+ this.fleetHealthTimer = void 0;
6157
+ }
5940
6158
  if (this.deferredConnect) {
5941
6159
  try {
5942
6160
  await this.deferredConnect;
@@ -5954,6 +6172,8 @@ var ConnectorManager = class {
5954
6172
  await this.placeholderBarrier.remove(entry.placeholderTarget, this.mgrLog).catch(() => {
5955
6173
  });
5956
6174
  }
6175
+ if (entry.fleetHealthGate) await entry.fleetHealthGate.dispose().catch(() => {
6176
+ });
5957
6177
  this.opts.onStatusChange?.({
5958
6178
  type: "connector_status",
5959
6179
  connectorId: id,
@@ -6044,6 +6264,8 @@ var ConnectorManager = class {
6044
6264
  await this.placeholderBarrier.remove(entry.placeholderTarget, this.mgrLog).catch(() => {
6045
6265
  });
6046
6266
  }
6267
+ if (entry.fleetHealthGate) await entry.fleetHealthGate.dispose().catch(() => {
6268
+ });
6047
6269
  this.active.delete(id);
6048
6270
  this.opts.onStatusChange?.({
6049
6271
  type: "connector_status",
@@ -8164,6 +8386,6 @@ function createConnector() {
8164
8386
  return new FlowAdapter();
8165
8387
  }
8166
8388
 
8167
- export { AbstractConnector, BUILTIN_CONNECTOR_CATALOG, ConnectorFieldMissingError, ConnectorManager, ConnectorStartupError, DEFAULT_REACTIVE_COOLDOWN_MS, DeployConnector, DevServerConnector, 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, registerBuiltinConnectors, removeMountBlock, renderCredentialHelperScript, tryGetConnector, worktreeRegistry, writeHelperScript, writeMountBlock };
8168
- //# sourceMappingURL=chunk-362QBS57.js.map
8169
- //# sourceMappingURL=chunk-362QBS57.js.map
8389
+ 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 };
8390
+ //# sourceMappingURL=chunk-NAMD4AIK.js.map
8391
+ //# sourceMappingURL=chunk-NAMD4AIK.js.map