@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.
- package/CHANGELOG.md +109 -0
- package/dist/{asset-feeds-OEGJ6NOF.js → asset-feeds-NIAABO6U.js} +9 -9
- package/dist/{asset-feeds-OEGJ6NOF.js.map → asset-feeds-NIAABO6U.js.map} +1 -1
- package/dist/asset-manager/index.js +6 -6
- package/dist/asset-manager/installer.js +5 -5
- package/dist/asset-manager/src/index.d.ts +128 -0
- package/dist/asset-manager/src/index.d.ts.map +1 -1
- package/dist/bridge/drivers/claude-sdk.js +2 -2
- package/dist/bridge/drivers/codex.js +1 -1
- package/dist/bridge/drivers/continue.js +2 -2
- package/dist/bridge/drivers/echo.js +1 -1
- package/dist/bridge/drivers/gemini.js +3 -3
- package/dist/bridge/drivers/goose.js +2 -2
- package/dist/bridge/drivers/omp.js +1 -1
- package/dist/bridge/drivers/opencode.js +2 -2
- package/dist/bridge/drivers/qwen.js +3 -3
- package/dist/bridge/index.js +3 -3
- package/dist/{chunk-7ZWLZ5H3.js → chunk-35TLBMHX.js} +4 -4
- package/dist/{chunk-7ZWLZ5H3.js.map → chunk-35TLBMHX.js.map} +1 -1
- package/dist/{chunk-6VUP6XHK.js → chunk-52XHXSBN.js} +3 -3
- package/dist/{chunk-6VUP6XHK.js.map → chunk-52XHXSBN.js.map} +1 -1
- package/dist/{chunk-NXC6IEL4.js → chunk-5AGARSQT.js} +267 -261
- package/dist/chunk-5AGARSQT.js.map +1 -0
- package/dist/{chunk-QLLFCQU5.js → chunk-B4IOSXRZ.js} +485 -1502
- package/dist/chunk-B4IOSXRZ.js.map +1 -0
- package/dist/{chunk-VE2UIJ7Y.js → chunk-BCJL2UNH.js} +4 -44
- package/dist/chunk-BCJL2UNH.js.map +1 -0
- package/dist/{chunk-W53D5Q52.js → chunk-BM37QD6U.js} +3 -3
- package/dist/{chunk-W53D5Q52.js.map → chunk-BM37QD6U.js.map} +1 -1
- package/dist/{chunk-WD2NRI3T.js → chunk-DMHVL5MC.js} +5 -5
- package/dist/{chunk-WD2NRI3T.js.map → chunk-DMHVL5MC.js.map} +1 -1
- package/dist/{chunk-V4BGVOJC.js → chunk-EE4NL25D.js} +28 -19
- package/dist/chunk-EE4NL25D.js.map +1 -0
- package/dist/{chunk-VGT2R662.js → chunk-G2X3THJV.js} +5 -5
- package/dist/{chunk-VGT2R662.js.map → chunk-G2X3THJV.js.map} +1 -1
- package/dist/{chunk-3IHBGDCG.js → chunk-INUQVXUE.js} +2 -2
- package/dist/{chunk-3IHBGDCG.js.map → chunk-INUQVXUE.js.map} +1 -1
- package/dist/{chunk-UTHEIPEY.js → chunk-KVZ6XKVN.js} +56 -14
- package/dist/chunk-KVZ6XKVN.js.map +1 -0
- package/dist/{chunk-TCNYSHPM.js → chunk-L3SLC3S4.js} +253 -14
- package/dist/chunk-L3SLC3S4.js.map +1 -0
- package/dist/{chunk-3KPNDTMR.js → chunk-MG6UIS7G.js} +3 -3
- package/dist/{chunk-3KPNDTMR.js.map → chunk-MG6UIS7G.js.map} +1 -1
- package/dist/{chunk-LKOMH3QC.js → chunk-OIGYDVCM.js} +3 -3
- package/dist/{chunk-LKOMH3QC.js.map → chunk-OIGYDVCM.js.map} +1 -1
- package/dist/{chunk-N5AE5SOC.js → chunk-QVE3ILCY.js} +2 -2
- package/dist/{chunk-N5AE5SOC.js.map → chunk-QVE3ILCY.js.map} +1 -1
- package/dist/{chunk-GYPA2Q7F.js → chunk-SHOKXRL4.js} +21 -4
- package/dist/chunk-SHOKXRL4.js.map +1 -0
- package/dist/{chunk-6AD3NS7R.js → chunk-U5J77IIZ.js} +4 -4
- package/dist/{chunk-6AD3NS7R.js.map → chunk-U5J77IIZ.js.map} +1 -1
- package/dist/{chunk-OYMSCV7T.js → chunk-UY5Q2NZH.js} +3 -3
- package/dist/{chunk-OYMSCV7T.js.map → chunk-UY5Q2NZH.js.map} +1 -1
- package/dist/{chunk-2D5YE3FL.js → chunk-VYIK5IE5.js} +85 -6
- package/dist/chunk-VYIK5IE5.js.map +1 -0
- package/dist/{chunk-EOINYN5T.js → chunk-YI5OZ7QO.js} +3 -3
- package/dist/{chunk-EOINYN5T.js.map → chunk-YI5OZ7QO.js.map} +1 -1
- package/dist/{chunk-BXC5547R.js → chunk-Z4EJOBEL.js} +7 -7
- package/dist/{chunk-BXC5547R.js.map → chunk-Z4EJOBEL.js.map} +1 -1
- package/dist/{chunk-2ZK7M2SG.js → chunk-ZUE2PIJ5.js} +3 -3
- package/dist/{chunk-2ZK7M2SG.js.map → chunk-ZUE2PIJ5.js.map} +1 -1
- package/dist/cli/index.js +189 -56
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/src/commands/catalog.d.ts +18 -0
- package/dist/cli/src/commands/catalog.d.ts.map +1 -1
- package/dist/cli/src/commands/manage.d.ts.map +1 -1
- package/dist/cli/src/commands/tree.d.ts.map +1 -1
- package/dist/cli/src/commands/why.d.ts.map +1 -1
- package/dist/cli/src/helpers.d.ts +25 -0
- package/dist/cli/src/helpers.d.ts.map +1 -1
- package/dist/connectors/config.js +5 -5
- package/dist/connectors/index.js +6 -8
- package/dist/connectors/src/config.d.ts +1 -74
- package/dist/connectors/src/config.d.ts.map +1 -1
- package/dist/connectors/src/connector-manager.d.ts +13 -137
- package/dist/connectors/src/connector-manager.d.ts.map +1 -1
- package/dist/connectors/src/connector-registry.d.ts.map +1 -1
- package/dist/connectors/src/connector-types.d.ts +3 -25
- package/dist/connectors/src/connector-types.d.ts.map +1 -1
- package/dist/connectors/src/fleet-utils.d.ts +2 -30
- package/dist/connectors/src/fleet-utils.d.ts.map +1 -1
- package/dist/connectors/src/fs-utils.d.ts +1 -2
- package/dist/connectors/src/fs-utils.d.ts.map +1 -1
- package/dist/connectors/src/index.d.ts +2 -12
- package/dist/connectors/src/index.d.ts.map +1 -1
- package/dist/connectors/src/watcher.d.ts +1 -5
- package/dist/connectors/src/watcher.d.ts.map +1 -1
- package/dist/core/index.js +4 -4
- package/dist/core/runtime-assets.js +2 -2
- package/dist/core/src/index.d.ts +1 -1
- package/dist/core/src/index.d.ts.map +1 -1
- package/dist/core/src/repo-manager.d.ts +19 -1
- package/dist/core/src/repo-manager.d.ts.map +1 -1
- package/dist/core/src/walker.d.ts +7 -0
- package/dist/core/src/walker.d.ts.map +1 -1
- package/dist/core/workspace-config.js +1 -1
- package/dist/deploy/index.js +4 -4
- package/dist/discovery/index.js +1 -1
- package/dist/discovery/src/discover.d.ts.map +1 -1
- package/dist/discovery/src/index.d.ts +1 -1
- package/dist/discovery/src/index.d.ts.map +1 -1
- package/dist/discovery/src/reference-lint.d.ts +30 -0
- package/dist/discovery/src/reference-lint.d.ts.map +1 -1
- package/dist/discovery/src/tree-entries.d.ts.map +1 -1
- package/dist/{ensure-sources-GJOXWVWO.js → ensure-sources-MJYHZ77Y.js} +14 -7
- package/dist/ensure-sources-MJYHZ77Y.js.map +1 -0
- package/dist/factory-assets/connectors/box/CONNECTOR.md +33 -0
- package/dist/factory-assets/connectors/box/driver.d.ts +27 -0
- package/dist/factory-assets/connectors/box/driver.d.ts.map +1 -0
- package/dist/{connectors/rclone-config.d.ts → factory-assets/connectors/box.d.ts} +1 -1
- package/dist/factory-assets/connectors/box.js +20 -0
- package/dist/factory-assets/connectors/box.js.map +1 -0
- package/dist/factory-assets/connectors/deploy.js +6 -8
- package/dist/factory-assets/connectors/devserver.js +6 -8
- package/dist/factory-assets/connectors/flow/adapter.js +6 -8
- package/dist/factory-assets/connectors/flow/engine.js +4 -4
- package/dist/factory-assets/connectors/flow/run-flow.js +7 -9
- package/dist/factory-assets/connectors/flow.js +6 -8
- package/dist/factory-assets/connectors/git.js +6 -8
- package/dist/factory-assets/connectors/gmail.js +6 -8
- package/dist/factory-assets/connectors/googledrive/CONNECTOR.md +14 -128
- package/dist/factory-assets/connectors/googledrive/driver.d.ts +6 -13
- package/dist/factory-assets/connectors/googledrive/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/googledrive.js +6 -8
- package/dist/factory-assets/connectors/local.js +6 -8
- package/dist/factory-assets/connectors/mattermost.js +6 -8
- package/dist/factory-assets/connectors/memory.js +6 -8
- package/dist/factory-assets/connectors/minio.js +6 -8
- package/dist/factory-assets/connectors/postgres.js +6 -8
- package/dist/factory-assets/connectors/s3.js +6 -8
- package/dist/factory-assets/connectors/sharepoint/CONNECTOR.md +13 -159
- package/dist/factory-assets/connectors/sharepoint/driver.d.ts +6 -12
- package/dist/factory-assets/connectors/sharepoint/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/sharepoint.js +6 -8
- package/dist/factory-assets/connectors/sqlite.js +6 -8
- package/dist/factory-assets/connectors/static-server.js +6 -8
- package/dist/factory-assets/connectors/tunnel.js +6 -8
- package/dist/factory-assets/connectors/webdav/CONNECTOR.md +13 -118
- package/dist/factory-assets/connectors/webdav/driver.d.ts +6 -15
- package/dist/factory-assets/connectors/webdav/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/webdav.js +6 -8
- package/dist/factory-assets/connectors/xstate-store.js +6 -8
- package/dist/factory-assets/connectors/xstate.js +6 -8
- package/dist/factory-assets/skaile.manifest.yaml +4 -1
- package/dist/{flows-AGPIE6JM.js → flows-4Z74KY2H.js} +4 -4
- package/dist/{flows-AGPIE6JM.js.map → flows-4Z74KY2H.js.map} +1 -1
- package/dist/helpers-7KOVCMHW.js +22 -0
- package/dist/{helpers-F4NZ66VD.js.map → helpers-7KOVCMHW.js.map} +1 -1
- package/dist/library/index.js +7 -7
- package/dist/library/install.js +4 -4
- package/dist/library/src/library.d.ts +6 -0
- package/dist/library/src/library.d.ts.map +1 -1
- package/dist/library/src/local/db.d.ts.map +1 -1
- package/dist/library/src/local/library.d.ts +9 -0
- package/dist/library/src/local/library.d.ts.map +1 -1
- package/dist/library/src/local/local-catalog-source.d.ts +9 -0
- package/dist/library/src/local/local-catalog-source.d.ts.map +1 -1
- package/dist/library/src/local/schema.d.ts +19 -0
- package/dist/library/src/local/schema.d.ts.map +1 -1
- package/dist/open-library-PGTL332T.js +29 -0
- package/dist/{open-library-ERKRGH4B.js.map → open-library-PGTL332T.js.map} +1 -1
- package/dist/{paths-D6RUQ522.js → paths-S7QNXYMB.js} +7 -7
- package/dist/{paths-D6RUQ522.js.map → paths-S7QNXYMB.js.map} +1 -1
- package/dist/{plugin-store-SGNJTBYS.js → plugin-store-3DAVGKHN.js} +6 -6
- package/dist/{plugin-store-SGNJTBYS.js.map → plugin-store-3DAVGKHN.js.map} +1 -1
- package/dist/runner/index.js +11 -13
- package/dist/runner/src/serve.d.ts.map +1 -1
- package/dist/sdk/asset-manager.js +6 -6
- package/dist/sdk/bridge.js +3 -3
- package/dist/sdk/core.js +4 -4
- package/dist/sdk/flow.js +4 -4
- package/dist/sdk/index.js +11 -13
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/runner.js +11 -13
- package/dist/{setup-IV2JFW3T.js → setup-WBGDT2WY.js} +9 -9
- package/dist/{setup-IV2JFW3T.js.map → setup-WBGDT2WY.js.map} +1 -1
- package/dist/{source-FGOMQMZT.js → source-FJXRQ2NU.js} +18 -12
- package/dist/{source-FGOMQMZT.js.map → source-FJXRQ2NU.js.map} +1 -1
- package/dist/{store-client-GJT2XQ5Z.js → store-client-YGDN4L2G.js} +7 -7
- package/dist/{store-client-GJT2XQ5Z.js.map → store-client-YGDN4L2G.js.map} +1 -1
- package/dist/tui/index.js +11 -13
- package/dist/tui/index.js.map +1 -1
- package/dist/workspace-plugin/adapters/mcp.js +2 -2
- package/dist/workspace-plugin/adapters/omp.js +3 -3
- package/dist/workspace-plugin/index.js +1 -1
- package/package.json +7 -13
- package/dist/chunk-2D5YE3FL.js.map +0 -1
- package/dist/chunk-GYPA2Q7F.js.map +0 -1
- package/dist/chunk-NXC6IEL4.js.map +0 -1
- package/dist/chunk-OVRSNIKJ.js +0 -72
- package/dist/chunk-OVRSNIKJ.js.map +0 -1
- package/dist/chunk-QLLFCQU5.js.map +0 -1
- package/dist/chunk-RAIRWFAN.js +0 -843
- package/dist/chunk-RAIRWFAN.js.map +0 -1
- package/dist/chunk-TCNYSHPM.js.map +0 -1
- package/dist/chunk-UTHEIPEY.js.map +0 -1
- package/dist/chunk-V4BGVOJC.js.map +0 -1
- package/dist/chunk-VE2UIJ7Y.js.map +0 -1
- package/dist/connectors/rclone-config.js +0 -4
- package/dist/connectors/rclone-config.js.map +0 -1
- package/dist/connectors/rclone.d.ts +0 -2
- package/dist/connectors/rclone.js +0 -5
- package/dist/connectors/rclone.js.map +0 -1
- package/dist/connectors/src/fleet-health-gate.d.ts +0 -105
- package/dist/connectors/src/fleet-health-gate.d.ts.map +0 -1
- package/dist/connectors/src/mount-placeholder.d.ts +0 -131
- package/dist/connectors/src/mount-placeholder.d.ts.map +0 -1
- package/dist/connectors/src/rclone-config/googledrive.d.ts +0 -59
- package/dist/connectors/src/rclone-config/googledrive.d.ts.map +0 -1
- package/dist/connectors/src/rclone-config/index.d.ts +0 -9
- package/dist/connectors/src/rclone-config/index.d.ts.map +0 -1
- package/dist/connectors/src/rclone-config/onedrive.d.ts +0 -48
- package/dist/connectors/src/rclone-config/onedrive.d.ts.map +0 -1
- package/dist/connectors/src/rclone-config/sanitize.d.ts +0 -9
- package/dist/connectors/src/rclone-config/sanitize.d.ts.map +0 -1
- package/dist/connectors/src/rclone-config/webdav.d.ts +0 -50
- package/dist/connectors/src/rclone-config/webdav.d.ts.map +0 -1
- package/dist/connectors/src/rclone-process-manager.d.ts +0 -316
- package/dist/connectors/src/rclone-process-manager.d.ts.map +0 -1
- package/dist/connectors/src/rclone-token-refresh.d.ts +0 -84
- package/dist/connectors/src/rclone-token-refresh.d.ts.map +0 -1
- package/dist/ensure-sources-GJOXWVWO.js.map +0 -1
- package/dist/helpers-F4NZ66VD.js +0 -4
- package/dist/open-library-ERKRGH4B.js +0 -23
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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
|
|
9
|
+
import { join, dirname, resolve, sep, relative } from 'path';
|
|
12
10
|
import * as zNS from 'zod';
|
|
13
|
-
import {
|
|
11
|
+
import { execSync } from 'child_process';
|
|
14
12
|
import { createServer } from 'http';
|
|
15
13
|
import mime from 'mime';
|
|
16
|
-
import {
|
|
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
|
|
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
|
|
2154
|
+
function createConnector6() {
|
|
2109
2155
|
return new GmailConnector();
|
|
2110
2156
|
}
|
|
2111
2157
|
|
|
2112
2158
|
// factory-assets/connectors/googledrive/driver.ts
|
|
2113
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
2139
|
-
|
|
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
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
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
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2195
|
+
function createConnector7() {
|
|
2274
2196
|
return new GoogleDriveConnector();
|
|
2275
2197
|
}
|
|
2276
|
-
function
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
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
|
|
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,
|
|
2385
|
-
write: (handle,
|
|
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,
|
|
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/${
|
|
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,
|
|
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/${
|
|
2473
|
-
this.emitChange?.({ path
|
|
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,
|
|
2732
|
-
const res = await fetch(baseUrl +
|
|
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:
|
|
2736
|
-
throw new Error(`Mattermost GET ${
|
|
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,
|
|
2741
|
-
const res = await fetch(baseUrl +
|
|
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 ${
|
|
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,
|
|
2750
|
-
const res = await fetch(baseUrl +
|
|
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 ${
|
|
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
|
|
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,
|
|
2771
|
-
write: (handle,
|
|
2772
|
-
delete: (handle,
|
|
2773
|
-
list: (handle,
|
|
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,
|
|
2793
|
-
const value = this.store(handle).get(
|
|
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,
|
|
2798
|
-
const isNew = !this.store(handle).has(
|
|
2699
|
+
async _write(handle, path, content) {
|
|
2700
|
+
const isNew = !this.store(handle).has(path);
|
|
2799
2701
|
this.store(handle).set(
|
|
2800
|
-
|
|
2702
|
+
path,
|
|
2801
2703
|
typeof content.data === "string" ? content.data : content.data.toString("utf-8")
|
|
2802
2704
|
);
|
|
2803
|
-
this.emitChange?.({ path
|
|
2705
|
+
this.emitChange?.({ path, action: isNew ? "create" : "edit", source: "operation" });
|
|
2804
2706
|
}
|
|
2805
|
-
async _delete(handle,
|
|
2806
|
-
const deleted = this.store(handle).delete(
|
|
2807
|
-
if (deleted) this.emitChange?.({ path
|
|
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,
|
|
2811
|
-
const prefix =
|
|
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
|
|
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,
|
|
2925
|
-
read: (handle,
|
|
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,
|
|
3005
|
+
async _list(handle, path, recursive = false) {
|
|
3104
3006
|
const { client, bucket } = handle.state;
|
|
3105
3007
|
const entries = [];
|
|
3106
|
-
const stream = client.listObjects(bucket,
|
|
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,
|
|
3023
|
+
async _read(handle, path) {
|
|
3122
3024
|
const { client, bucket } = handle.state;
|
|
3123
3025
|
const chunks = [];
|
|
3124
|
-
const stream = await client.getObject(bucket,
|
|
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
|
|
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,
|
|
3183
|
-
list: (handle,
|
|
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,
|
|
3272
|
-
const parts =
|
|
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,
|
|
3292
|
-
if (!
|
|
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(
|
|
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: `${
|
|
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
|
|
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
|
|
3486
|
+
function createConnector13(sdkOverride) {
|
|
3585
3487
|
return new S3Connector(sdkOverride);
|
|
3586
3488
|
}
|
|
3587
3489
|
|
|
3588
3490
|
// factory-assets/connectors/sharepoint/driver.ts
|
|
3589
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
3608
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3527
|
+
function createConnector14() {
|
|
3762
3528
|
return new SharePointConnector();
|
|
3763
3529
|
}
|
|
3764
|
-
function
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
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,
|
|
3810
|
-
list: (handle,
|
|
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,
|
|
3895
|
-
const parts =
|
|
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,
|
|
3912
|
-
if (!
|
|
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(
|
|
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: `${
|
|
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
|
|
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
|
|
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
|
|
4278
|
+
function createConnector17() {
|
|
4539
4279
|
return new TunnelConnector();
|
|
4540
4280
|
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4575
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4664
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4315
|
+
function createConnector18() {
|
|
4718
4316
|
return new WebDAVConnector();
|
|
4719
4317
|
}
|
|
4720
|
-
function
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
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,
|
|
4758
|
-
list: (handle,
|
|
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,
|
|
4437
|
+
async _read(handle, path) {
|
|
4861
4438
|
const snapshot = this.s(handle).actor.getSnapshot();
|
|
4862
|
-
if (
|
|
4439
|
+
if (path === "" || path === "state") {
|
|
4863
4440
|
return { data: JSON.stringify(snapshot.value), contentType: "application/json" };
|
|
4864
4441
|
}
|
|
4865
|
-
if (
|
|
4442
|
+
if (path === "context") {
|
|
4866
4443
|
return { data: JSON.stringify(snapshot.context, null, 2), contentType: "application/json" };
|
|
4867
4444
|
}
|
|
4868
|
-
const value = snapshot.context?.[
|
|
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
|
|
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,
|
|
5063
|
-
write: (handle,
|
|
5064
|
-
delete: (handle,
|
|
5065
|
-
list: (handle,
|
|
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,
|
|
4696
|
+
async _read(handle, path) {
|
|
5120
4697
|
const ctx = this.context(handle);
|
|
5121
|
-
if (
|
|
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[
|
|
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,
|
|
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:
|
|
5138
|
-
this._recordHistory(handle, "set", { key:
|
|
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,
|
|
4717
|
+
async _delete(handle, path) {
|
|
5141
4718
|
const ctx = this.context(handle);
|
|
5142
|
-
if (!(
|
|
5143
|
-
this.storeState(handle).store.send({ type: "delete", key:
|
|
5144
|
-
this._recordHistory(handle, "delete", { key:
|
|
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
|
|
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", () =>
|
|
5378
|
-
reg("local", () =>
|
|
5379
|
-
reg("s3", () =>
|
|
5380
|
-
reg("webdav", () =>
|
|
5381
|
-
reg("sharepoint", () =>
|
|
5382
|
-
reg("googledrive", () =>
|
|
5383
|
-
reg("
|
|
5384
|
-
reg("
|
|
5385
|
-
reg("
|
|
5386
|
-
reg("
|
|
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", () =>
|
|
5389
|
-
reg("xstate-store", () =>
|
|
4967
|
+
reg("xstate", () => createConnector19());
|
|
4968
|
+
reg("xstate-store", () => createConnector20());
|
|
5390
4969
|
reg("devserver", () => createConnector3());
|
|
5391
|
-
reg("static-server", () =>
|
|
5392
|
-
reg("tunnel", () =>
|
|
4970
|
+
reg("static-server", () => createConnector16());
|
|
4971
|
+
reg("tunnel", () => createConnector17());
|
|
5393
4972
|
reg("deploy", () => createConnector2());
|
|
5394
|
-
reg("gmail", () =>
|
|
5395
|
-
reg("mattermost", () =>
|
|
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 [
|
|
6132
|
-
if (owner === id) this.mountTargets.delete(
|
|
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 -
|
|
6210
|
-
*
|
|
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
|
|
6219
|
-
|
|
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.
|
|
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
|
-
*
|
|
6347
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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,
|
|
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,
|
|
5747
|
+
() => tools.read(entry.handle, path)
|
|
6839
5748
|
);
|
|
6840
5749
|
}
|
|
6841
|
-
async write(id,
|
|
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,
|
|
5766
|
+
() => tools.write(entry.handle, path, content)
|
|
6858
5767
|
);
|
|
6859
5768
|
}
|
|
6860
|
-
async delete(id,
|
|
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,
|
|
5785
|
+
() => tools.delete(entry.handle, path)
|
|
6877
5786
|
);
|
|
6878
5787
|
}
|
|
6879
|
-
async list(id,
|
|
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,
|
|
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
|
|
5942
|
+
handler: async ({ connector_id, path }) => {
|
|
7034
5943
|
try {
|
|
7035
|
-
const content = await manager.read(connector_id,
|
|
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
|
|
5966
|
+
path,
|
|
7058
5967
|
content
|
|
7059
5968
|
}) => {
|
|
7060
5969
|
try {
|
|
7061
|
-
await manager.write(connector_id,
|
|
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
|
|
5990
|
+
path,
|
|
7082
5991
|
recursive
|
|
7083
5992
|
}) => {
|
|
7084
5993
|
try {
|
|
7085
|
-
const entries = await manager.list(connector_id,
|
|
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
|
|
6030
|
+
handler: async ({ connector_id, path }) => {
|
|
7122
6031
|
try {
|
|
7123
|
-
const ok = await manager.delete(connector_id,
|
|
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(
|
|
6387
|
+
function readFileOrEmpty(path) {
|
|
7440
6388
|
try {
|
|
7441
|
-
return readFileSync(
|
|
6389
|
+
return readFileSync(path, "utf-8");
|
|
7442
6390
|
} catch {
|
|
7443
6391
|
return "";
|
|
7444
6392
|
}
|
|
7445
6393
|
}
|
|
7446
|
-
function atomicWrite(
|
|
7447
|
-
const dir = dirname(
|
|
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 = `${
|
|
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,
|
|
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(
|
|
7526
|
-
return readFileOrEmpty(
|
|
6473
|
+
function readCredentialLines(path) {
|
|
6474
|
+
return readFileOrEmpty(path).split("\n").filter((l) => l.length > 0);
|
|
7527
6475
|
}
|
|
7528
|
-
function writeCredentialLines(
|
|
6476
|
+
function writeCredentialLines(path, lines) {
|
|
7529
6477
|
const content = lines.length > 0 ? `${lines.join("\n")}
|
|
7530
6478
|
` : "";
|
|
7531
|
-
atomicWrite(
|
|
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
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
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
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
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
|
-
|
|
6702
|
+
return matchers;
|
|
6703
|
+
} catch {
|
|
6704
|
+
return [];
|
|
7745
6705
|
}
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
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
|
-
|
|
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(
|
|
7767
|
-
let owners = this.entries.get(
|
|
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(
|
|
6753
|
+
this.entries.set(path, owners);
|
|
7771
6754
|
}
|
|
7772
6755
|
owners.add(owner);
|
|
7773
6756
|
}
|
|
7774
|
-
deregister(
|
|
7775
|
-
const owners = this.entries.get(
|
|
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(
|
|
6761
|
+
if (owners.size === 0) this.entries.delete(path);
|
|
7779
6762
|
}
|
|
7780
|
-
isInUse(
|
|
7781
|
-
return (this.entries.get(
|
|
6763
|
+
isInUse(path) {
|
|
6764
|
+
return (this.entries.get(path)?.size ?? 0) > 0;
|
|
7782
6765
|
}
|
|
7783
|
-
owners(
|
|
7784
|
-
return [...this.entries.get(
|
|
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,
|
|
7914
|
-
list: (handle,
|
|
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,
|
|
6941
|
+
async read(handle, path) {
|
|
7959
6942
|
const s = this.s(handle);
|
|
7960
|
-
if (
|
|
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 (
|
|
7967
|
-
const nodeId =
|
|
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,
|
|
8785
|
-
//# sourceMappingURL=chunk-
|
|
8786
|
-
//# sourceMappingURL=chunk-
|
|
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
|