@exagone313/dsh-podman 0.2.0-rc.3 → 0.2.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/README.md +14 -2
- package/README.zh.md +14 -2
- package/cordis.patch.yml +58 -0
- package/dist/approval-reasons.d.ts +3 -4
- package/dist/approval-reasons.js +40 -49
- package/dist/approval-reasons.test.js +43 -15
- package/dist/approval.js +27 -10
- package/dist/approval.test.js +165 -34
- package/dist/card-route.d.ts +3 -2
- package/dist/card-route.js +154 -42
- package/dist/card-route.test.js +85 -43
- package/dist/card-test-support.js +39 -66
- package/dist/client/ContainerCard.d.ts +1 -1
- package/dist/client/ContainerCard.js +92 -50
- package/dist/client/card-protocol.d.ts +6 -3
- package/dist/client/container-card-caches.js +11 -6
- package/dist/client/container-card-controller.d.ts +16 -18
- package/dist/client/container-card-controller.js +92 -40
- package/dist/client/container-card-create-modal.d.ts +2 -2
- package/dist/client/container-card-create-modal.js +13 -5
- package/dist/client/container-card-default-env.d.ts +10 -0
- package/dist/client/container-card-default-env.js +69 -0
- package/dist/client/container-card-directory.d.ts +2 -2
- package/dist/client/container-card-directory.js +12 -4
- package/dist/client/container-card-editors.d.ts +3 -3
- package/dist/client/container-card-editors.js +104 -32
- package/dist/client/container-card-images.d.ts +4 -4
- package/dist/client/container-card-images.js +19 -6
- package/dist/client/container-card-paths.d.ts +3 -3
- package/dist/client/container-card-paths.js +18 -11
- package/dist/client/container-card-row.d.ts +10 -6
- package/dist/client/container-card-row.js +82 -13
- package/dist/client/container-card-secrets.d.ts +3 -3
- package/dist/client/container-card-secrets.js +8 -6
- package/dist/client/container-card-shared.d.ts +4 -17
- package/dist/client/container-card-shared.js +9 -25
- package/dist/client/container-card-styles.d.ts +2 -8
- package/dist/client/container-card-styles.js +14 -52
- package/dist/client/container-card-volumes.d.ts +2 -2
- package/dist/client/container-card-volumes.js +9 -5
- package/dist/client/container-card-workspace.d.ts +8 -6
- package/dist/client/container-card-workspace.js +9 -3
- package/dist/client/directory-picker.js +2 -1
- package/dist/client/index.js +13141 -2036
- package/dist/client/locales.d.ts +6 -1
- package/dist/client/locales.js +106 -38
- package/dist/client/podman-terminal-guide.d.ts +11 -0
- package/dist/client/podman-terminal-guide.js +208 -0
- package/dist/client/podman-terminal-title.d.ts +7 -0
- package/dist/client/podman-terminal-title.js +30 -0
- package/dist/client/podman-terminal.d.ts +26 -0
- package/dist/client/podman-terminal.js +412 -0
- package/dist/client/read-only-approval.d.ts +2 -2
- package/dist/client/read-only-approval.js +13 -3
- package/dist/client/slot-contract.d.ts +0 -11
- package/dist/client/terminal-preference.d.ts +26 -0
- package/dist/client/terminal-preference.js +67 -0
- package/dist/client/terminal-protocol.d.ts +85 -0
- package/dist/client/terminal-protocol.js +14 -0
- package/dist/client/terminal-tab.d.ts +60 -0
- package/dist/client/terminal-tab.js +35 -0
- package/dist/client/terminal-targets.d.ts +33 -0
- package/dist/client/terminal-targets.js +65 -0
- package/dist/client/terminal-titles.d.ts +23 -0
- package/dist/client/terminal-titles.js +52 -0
- package/dist/client/terminal-transport.d.ts +63 -0
- package/dist/client/terminal-transport.js +207 -0
- package/dist/client/tool-views.js +215 -49
- package/dist/container-env.d.ts +11 -0
- package/dist/container-env.js +96 -0
- package/dist/container-env.test.d.ts +1 -0
- package/dist/container-env.test.js +93 -0
- package/dist/containers.test.js +196 -18
- package/dist/daemons.test.js +18 -4
- package/dist/dsh-version.d.ts +1 -0
- package/dist/dsh-version.js +46 -0
- package/dist/env-rows.d.ts +14 -0
- package/dist/env-rows.js +52 -0
- package/dist/env-rows.test.d.ts +1 -0
- package/dist/env-rows.test.js +43 -0
- package/dist/fs-provider.d.ts +1 -0
- package/dist/fs-provider.js +26 -12
- package/dist/fs-provider.test.js +51 -10
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +2 -2
- package/dist/grpc/proto/dshctl/v1/control.proto +11 -1
- package/dist/grpc/proto/dshguest/v1/guest.proto +5 -0
- package/dist/grpc/runtime-client.d.ts +3 -1
- package/dist/grpc/runtime-client.js +90 -17
- package/dist/guest-rpc.d.ts +15 -19
- package/dist/guest-rpc.js +289 -70
- package/dist/guest-rpc.test.d.ts +1 -0
- package/dist/guest-rpc.test.js +241 -0
- package/dist/guest-terminal.d.ts +30 -0
- package/dist/guest-terminal.js +196 -0
- package/dist/images.test.js +10 -3
- package/dist/index.d.ts +11 -16
- package/dist/index.js +42 -36
- package/dist/locales.test.d.ts +1 -0
- package/dist/locales.test.js +34 -0
- package/dist/misc.test.js +38 -10
- package/dist/mount-enums.js +2 -1
- package/dist/mount-input.d.ts +2 -0
- package/dist/mount-input.js +38 -22
- package/dist/mounts.test.js +81 -11
- package/dist/naming.test.d.ts +1 -0
- package/dist/naming.test.js +28 -0
- package/dist/output-reader.js +36 -4
- package/dist/package-deps.test.d.ts +1 -0
- package/dist/package-deps.test.js +49 -0
- package/dist/paths.test.js +4 -2
- package/dist/plugin-meta.test.d.ts +1 -0
- package/dist/plugin-meta.test.js +29 -0
- package/dist/project-path.js +33 -7
- package/dist/project-path.test.js +14 -0
- package/dist/prompts.d.ts +0 -4
- package/dist/prompts.js +15 -84
- package/dist/read-only-shell.js +3 -1
- package/dist/read-only-shell.test.js +80 -25
- package/dist/runtime-client.test.d.ts +1 -0
- package/dist/runtime-client.test.js +53 -0
- package/dist/secrets.test.js +21 -6
- package/dist/settings-commands.test.js +61 -14
- package/dist/settings-create.test.js +32 -10
- package/dist/settings-default-env.test.d.ts +1 -0
- package/dist/settings-default-env.test.js +175 -0
- package/dist/settings-mounts.test.js +46 -7
- package/dist/settings-schema.d.ts +6 -11
- package/dist/settings-schema.js +4 -8
- package/dist/settings-schema.test.d.ts +1 -0
- package/dist/settings-schema.test.js +35 -0
- package/dist/settings-workspaces.test.js +38 -63
- package/dist/spill-store.d.ts +1 -0
- package/dist/spill-store.js +17 -4
- package/dist/spill-store.test.js +31 -8
- package/dist/subprocess.d.ts +4 -0
- package/dist/subprocess.js +148 -162
- package/dist/subprocess.test.js +118 -8
- package/dist/terminal-preference.test.d.ts +1 -0
- package/dist/terminal-preference.test.js +62 -0
- package/dist/terminal-route.d.ts +10 -0
- package/dist/terminal-route.js +270 -0
- package/dist/terminal-route.test.d.ts +1 -0
- package/dist/terminal-route.test.js +265 -0
- package/dist/terminal-sessions.d.ts +69 -0
- package/dist/terminal-sessions.js +252 -0
- package/dist/terminal-shells.d.ts +22 -0
- package/dist/terminal-shells.js +99 -0
- package/dist/terminal-tab.test.d.ts +1 -0
- package/dist/terminal-tab.test.js +58 -0
- package/dist/terminal-targets.test.d.ts +1 -0
- package/dist/terminal-targets.test.js +43 -0
- package/dist/terminal-titles.test.d.ts +1 -0
- package/dist/terminal-titles.test.js +36 -0
- package/dist/test-support.d.ts +35 -5
- package/dist/test-support.js +91 -29
- package/dist/tool-defs.js +19 -4
- package/dist/tool-handlers.js +80 -28
- package/dist/tool-params.js +19 -5
- package/dist/version-compat.d.ts +4 -0
- package/dist/version-compat.js +29 -0
- package/dist/version-compat.test.d.ts +1 -0
- package/dist/version-compat.test.js +27 -0
- package/dist/volumes.test.js +1 -1
- package/dist/workspace-binding.d.ts +19 -10
- package/dist/workspace-binding.js +252 -78
- package/dist/workspace-binding.test.js +202 -27
- package/docs/architecture.md +71 -20
- package/docs/architecture.zh.md +62 -12
- package/docs/configuration.md +61 -20
- package/docs/configuration.zh.md +53 -19
- package/docs/development-prompt.md +72 -0
- package/docs/development-prompt.zh.md +69 -0
- package/docs/development.md +184 -29
- package/docs/development.zh.md +165 -26
- package/docs/install-dsh-and-dsh-podman.md +24 -21
- package/docs/install-dsh-and-dsh-podman.zh.md +20 -21
- package/docs/uninstall.md +83 -0
- package/docs/uninstall.zh.md +79 -0
- package/docs/update.md +120 -0
- package/docs/update.zh.md +114 -0
- package/docs/usage.md +108 -61
- package/docs/usage.zh.md +76 -38
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +58 -25
- package/quadlet/dsh-podman-orchestrator.container +46 -0
- package/quadlet/dsh.container +35 -0
- package/LICENSE.pkg +0 -17103
- package/dist/preferences.d.ts +0 -6
- package/dist/preferences.js +0 -27
package/dist/fs-provider.test.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { editGuest, editProvider, fakeGuest, providerFor, stubResolver } from "./test-support.js";
|
|
6
|
+
import { editGuest, editProvider, fakeGuest, providerFor, stubResolver, } from "./test-support.js";
|
|
7
7
|
import { createFilesystemProvider } from "./index.js";
|
|
8
8
|
test("filesystem provider resolves absolute safe paths", async () => {
|
|
9
9
|
const provider = createFilesystemProvider(stubResolver);
|
|
@@ -71,7 +71,9 @@ test("filesystem provider lstat maps entry types without following links", async
|
|
|
71
71
|
token: "t",
|
|
72
72
|
}),
|
|
73
73
|
});
|
|
74
|
-
const info = await provider.lstat("/projects/team/link", {
|
|
74
|
+
const info = await provider.lstat("/projects/team/link", {
|
|
75
|
+
cwd: "/projects/team",
|
|
76
|
+
});
|
|
75
77
|
assert.equal(info.type, "symlink");
|
|
76
78
|
assert.equal(info.size, 3);
|
|
77
79
|
assert.deepEqual(requests, [{ path: "/projects/team/link", noFollow: true }]);
|
|
@@ -100,7 +102,8 @@ test("filesystem provider streamText decodes UTF-8 and rejects binary", async ()
|
|
|
100
102
|
for await (const _chunk of await binary.streamText(binaryTarget)) {
|
|
101
103
|
// Drain the stream so the binary sample is inspected.
|
|
102
104
|
}
|
|
103
|
-
}, (error) => error instanceof Error &&
|
|
105
|
+
}, (error) => error instanceof Error &&
|
|
106
|
+
error.code === "FS_NOT_TEXT");
|
|
104
107
|
});
|
|
105
108
|
test("filesystem provider readBytes returns content and caps at maxBytes", async () => {
|
|
106
109
|
const fake = fakeGuest(Buffer.from("hello"));
|
|
@@ -109,7 +112,8 @@ test("filesystem provider readBytes returns content and caps at maxBytes", async
|
|
|
109
112
|
const bytes = await provider.readBytes(target, undefined, 10);
|
|
110
113
|
assert.equal(Buffer.from(bytes).toString(), "hello");
|
|
111
114
|
assert.equal(fake.requests[0].length, 11);
|
|
112
|
-
await assert.rejects(() => provider.readBytes(target, undefined, 3), (error) => error instanceof Error &&
|
|
115
|
+
await assert.rejects(() => provider.readBytes(target, undefined, 3), (error) => error instanceof Error &&
|
|
116
|
+
error.code === "FS_TOO_LARGE");
|
|
113
117
|
});
|
|
114
118
|
test("filesystem provider readByteRange returns the window and empty for zero length", async () => {
|
|
115
119
|
const fake = fakeGuest(Buffer.from("abcdef"));
|
|
@@ -152,7 +156,10 @@ test("filesystem provider writeText reports the harness error codes", async () =
|
|
|
152
156
|
const existing = editProvider(editGuest({}, Buffer.from("hello")));
|
|
153
157
|
const target = await existing.resolve("/a", { cwd: "/x" });
|
|
154
158
|
await assert.rejects(() => existing.writeText(target, "x", { kind: "createIfAbsent" }), (error) => error.code === "FS_NOT_OBSERVED");
|
|
155
|
-
await assert.rejects(() => existing.writeText(target, "x", {
|
|
159
|
+
await assert.rejects(() => existing.writeText(target, "x", {
|
|
160
|
+
kind: "replaceIfVersion",
|
|
161
|
+
version: "other",
|
|
162
|
+
}), (error) => error.code === "FS_STALE_VERSION");
|
|
156
163
|
const dir = editProvider(editGuest({ isDir: true }));
|
|
157
164
|
const dirTarget = await dir.resolve("/a", { cwd: "/x" });
|
|
158
165
|
await assert.rejects(() => dir.writeText(dirTarget, "x"), (error) => error.code === "FS_NOT_REGULAR_FILE");
|
|
@@ -215,7 +222,9 @@ test("filesystem provider listDir returns resolved child targets", async () => {
|
|
|
215
222
|
token: "t",
|
|
216
223
|
}),
|
|
217
224
|
});
|
|
218
|
-
const target = await provider.resolve("/projects/team", {
|
|
225
|
+
const target = await provider.resolve("/projects/team", {
|
|
226
|
+
cwd: "/projects/team",
|
|
227
|
+
});
|
|
219
228
|
const entries = await provider.listDir(target);
|
|
220
229
|
assert.deepEqual(requests, [{ path: "/projects/team" }]);
|
|
221
230
|
assert.deepEqual(entries.map((entry) => ({
|
|
@@ -224,9 +233,24 @@ test("filesystem provider listDir returns resolved child targets", async () => {
|
|
|
224
233
|
path: entry.target.displayPath,
|
|
225
234
|
binding: entry.target.binding.token,
|
|
226
235
|
})), [
|
|
227
|
-
{
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
{
|
|
237
|
+
name: "src",
|
|
238
|
+
type: "directory",
|
|
239
|
+
path: "/projects/team/src",
|
|
240
|
+
binding: "t",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "a.txt",
|
|
244
|
+
type: "file",
|
|
245
|
+
path: "/projects/team/a.txt",
|
|
246
|
+
binding: "t",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "link",
|
|
250
|
+
type: "other",
|
|
251
|
+
path: "/projects/team/link",
|
|
252
|
+
binding: "t",
|
|
253
|
+
},
|
|
230
254
|
]);
|
|
231
255
|
assert.equal(entries[1].size, 3);
|
|
232
256
|
assert.equal(entries[0].size, undefined);
|
|
@@ -247,7 +271,10 @@ test("filesystem provider listDir falls back to the directory bit", async () =>
|
|
|
247
271
|
});
|
|
248
272
|
const target = await provider.resolve("/w", { cwd: "/w" });
|
|
249
273
|
const entries = await provider.listDir(target);
|
|
250
|
-
assert.deepEqual(entries.map((entry) => entry.type), [
|
|
274
|
+
assert.deepEqual(entries.map((entry) => entry.type), [
|
|
275
|
+
"directory",
|
|
276
|
+
"file",
|
|
277
|
+
]);
|
|
251
278
|
});
|
|
252
279
|
test("filesystem provider writeText normalizes the diff basis", async () => {
|
|
253
280
|
const binding = editGuest({}, Buffer.from("a\r\nb\r\n"));
|
|
@@ -258,3 +285,17 @@ test("filesystem provider writeText normalizes the diff basis", async () => {
|
|
|
258
285
|
assert.equal(outcome.after, "c\nd\n");
|
|
259
286
|
assert.equal(Buffer.concat(binding.writes).toString(), "c\r\nd\r\n");
|
|
260
287
|
});
|
|
288
|
+
test("filesystem provider reports watching as unsupported", async () => {
|
|
289
|
+
const provider = createFilesystemProvider(stubResolver);
|
|
290
|
+
const target = await provider.resolve("/projects/team/app", { cwd: "/x" });
|
|
291
|
+
let notified = false;
|
|
292
|
+
await assert.rejects(() => provider.watch(target, () => {
|
|
293
|
+
notified = true;
|
|
294
|
+
}, new AbortController().signal), (error) => error.code === "FS_IO_ERROR" &&
|
|
295
|
+
error.message ===
|
|
296
|
+
"Filesystem watching is not supported by this provider.");
|
|
297
|
+
assert.equal(notified, false, "changed must not fire when watching is unsupported");
|
|
298
|
+
const aborted = new AbortController();
|
|
299
|
+
aborted.abort();
|
|
300
|
+
assert.throws(() => provider.watch(target, () => { }, aborted.signal), (error) => error.code === "FS_ABORTED");
|
|
301
|
+
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.2.0
|
|
2
|
-
export declare const GIT_COMMIT = "
|
|
1
|
+
export declare const VERSION = "0.2.0";
|
|
2
|
+
export declare const GIT_COMMIT = "276d50e";
|
|
@@ -7,9 +7,11 @@ package dshctl.v1;
|
|
|
7
7
|
option go_package = "github.com/Exagone313/dsh-podman/internal/genproto/dshctl/v1;dshctlv1";
|
|
8
8
|
|
|
9
9
|
service OrchestratorControl {
|
|
10
|
+
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);
|
|
10
11
|
rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse);
|
|
11
12
|
rpc CreateWorkspace(CreateWorkspaceRequest) returns (Workspace);
|
|
12
13
|
rpc DescribeWorkspace(DescribeWorkspaceRequest) returns (Workspace);
|
|
14
|
+
rpc EnsureContainer(EnsureContainerRequest) returns (Container);
|
|
13
15
|
rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse);
|
|
14
16
|
rpc RemoveWorkspace(RemoveWorkspaceRequest) returns (RemoveWorkspaceResponse);
|
|
15
17
|
rpc ListImages(ListImagesRequest) returns (ListImagesResponse);
|
|
@@ -41,6 +43,8 @@ service OrchestratorControl {
|
|
|
41
43
|
rpc RemoveContainerSecret(RemoveContainerSecretRequest) returns (Container);
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
message GetVersionRequest {}
|
|
47
|
+
message GetVersionResponse { string version = 1; string commit = 2; }
|
|
44
48
|
message ListProjectsRequest {}
|
|
45
49
|
message ListProjectsResponse { repeated Project projects = 1; }
|
|
46
50
|
message Project { string name = 1; string host_path = 2; }
|
|
@@ -79,6 +83,11 @@ message ListImagesResponse { repeated Image images = 1; }
|
|
|
79
83
|
message Image { string image_id = 1; string parent = 2; repeated string packages = 3; string image_tag = 4; string built_at = 5; bool is_base = 6; string status = 7; string primitive = 8; string package_manager = 9; bool base_public = 10; }
|
|
80
84
|
message RebuildImageRequest { string image_id = 1; }
|
|
81
85
|
message RebuildAllImagesRequest {}
|
|
86
|
+
// The images one RebuildAllImages call rebuilt or could not provide. `rebuilt`
|
|
87
|
+
// names the stored custom images that were rebuilt, in dependency order; a base
|
|
88
|
+
// image ensured beforehand is not listed. `skipped` names every image the call
|
|
89
|
+
// could not make available: base short names first, then the custom images
|
|
90
|
+
// whose rebuild failed and the images that depend on them.
|
|
82
91
|
message RebuildAllImagesResponse { repeated string rebuilt = 1; repeated string skipped = 2; }
|
|
83
92
|
message RemoveImageRequest { string image_id = 1; }
|
|
84
93
|
message RemoveImageResponse {}
|
|
@@ -109,7 +118,8 @@ message Container {
|
|
|
109
118
|
repeated string paths = 12;
|
|
110
119
|
}
|
|
111
120
|
message StartContainerRequest { string workspace_slug = 1; string container = 2; string image_id = 3; repeated ProjectMount mounts = 4; map<string, string> env = 5; map<string, string> secret_env = 6; repeated string paths = 7; }
|
|
112
|
-
message
|
|
121
|
+
message EnsureContainerRequest { string workspace_slug = 1; string container = 2; }
|
|
122
|
+
message RecreateContainerRequest { string workspace_slug = 1; string image_id = 2; string container = 3; repeated ProjectMount mounts = 4; map<string, string> env = 5; repeated string paths = 6; map<string, string> secret_env = 7; }
|
|
113
123
|
message RemoveContainerRequest { string workspace_slug = 1; string container = 2; }
|
|
114
124
|
message RemoveContainerResponse {}
|
|
115
125
|
message AddContainerMountRequest { string workspace_slug = 1; string container = 2; string project = 3; reserved 4; string destination = 5; MountMode mode = 6; MountKind kind = 7; string volume = 8; string secret = 9; }
|
|
@@ -9,6 +9,9 @@ option go_package = "github.com/Exagone313/dsh-podman/internal/genproto/dshguest
|
|
|
9
9
|
import "google/protobuf/wrappers.proto";
|
|
10
10
|
|
|
11
11
|
service WorkspaceGuestAgent {
|
|
12
|
+
// Ping reports that the agent is reachable and accepts the caller's
|
|
13
|
+
// credential. Callers use it as a readiness probe before issuing work.
|
|
14
|
+
rpc Ping(PingRequest) returns (PingResponse);
|
|
12
15
|
rpc Exec(stream ExecInput) returns (stream ExecOutput);
|
|
13
16
|
rpc Signal(SignalRequest) returns (SignalResponse);
|
|
14
17
|
rpc ReadFile(ReadFileRequest) returns (stream ReadFileChunk);
|
|
@@ -27,6 +30,8 @@ service WorkspaceGuestAgent {
|
|
|
27
30
|
rpc DaemonLogs(DaemonLogsRequest) returns (DaemonLogsResponse);
|
|
28
31
|
rpc Terminal(stream TerminalInput) returns (stream TerminalOutput);
|
|
29
32
|
}
|
|
33
|
+
message PingRequest {}
|
|
34
|
+
message PingResponse { string version = 1; string commit = 2; }
|
|
30
35
|
message ExecInput { oneof payload { ExecStart start = 1; bytes stdin_chunk = 2; } }
|
|
31
36
|
message ExecStart { repeated string argv = 1; string cwd = 2; map<string, string> env = 3; repeated string unset_env = 4; SpillTarget spill_stdout = 5; SpillTarget spill_stderr = 6; google.protobuf.Int32Value uid = 7; google.protobuf.Int32Value gid = 8; repeated uint32 groups = 9; bool stdin_pipe = 10; }
|
|
32
37
|
message ExecOutput { oneof payload { bytes stdout_chunk = 1; bytes stderr_chunk = 2; ExecExit exit = 3; } string process_id = 4; }
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import grpc from "@grpc/grpc-js";
|
|
2
2
|
export declare function controlClient(socket: string): grpc.Client;
|
|
3
3
|
export declare function guestClient(socket: string): grpc.Client;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function closeClients(): void;
|
|
5
|
+
export declare function abortError(label: string): Error;
|
|
6
|
+
export declare function unary<T>(client: grpc.Client, method: string, request: unknown, metadata?: grpc.Metadata, signal?: AbortSignal): Promise<T>;
|
|
5
7
|
export { grpc };
|
|
@@ -7,27 +7,55 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { dirname, resolve } from "node:path";
|
|
8
8
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "proto");
|
|
9
9
|
const definitions = new Map();
|
|
10
|
+
// Live channels, keyed like the constructors: one client per service and
|
|
11
|
+
// socket for the plugin's lifetime. A channel holds a unix-socket connection
|
|
12
|
+
// and worker threads, so constructing one per call leaks both; the resolver
|
|
13
|
+
// disposes this cache on shutdown.
|
|
14
|
+
const clients = new Map();
|
|
15
|
+
// Keepalive lets a half-open channel fail fast instead of leaving a call
|
|
16
|
+
// pending forever, which is what backs the "hung orchestrator" symptom. Calls
|
|
17
|
+
// themselves carry no deadline: several control operations (image pull/build,
|
|
18
|
+
// cache clean) are legitimately long, so cancellation is driven by the turn's
|
|
19
|
+
// AbortSignal instead.
|
|
20
|
+
//
|
|
21
|
+
// Both servers permit exactly this policy (internal/grpcopts.KeepalivePolicy);
|
|
22
|
+
// grpc-go's default (5 minutes, no pings without streams) answers these pings
|
|
23
|
+
// with GOAWAY "too many pings", which grpc-js logs as "rejected by server
|
|
24
|
+
// because of excess pings". Keep the interval in sync with that policy.
|
|
25
|
+
const CHANNEL_OPTIONS = {
|
|
26
|
+
"grpc.keepalive_time_ms": 30_000,
|
|
27
|
+
"grpc.keepalive_timeout_ms": 10_000,
|
|
28
|
+
"grpc.keepalive_permit_without_calls": 1,
|
|
29
|
+
};
|
|
10
30
|
function unixTarget(socket) {
|
|
11
31
|
return socket.startsWith("unix:") ? socket : `unix:${socket}`;
|
|
12
32
|
}
|
|
33
|
+
function constructorFor(service, proto) {
|
|
34
|
+
const cached = definitions.get(service);
|
|
35
|
+
if (cached !== undefined)
|
|
36
|
+
return cached;
|
|
37
|
+
const packageDefinition = loader.loadSync(resolve(root, proto), {
|
|
38
|
+
longs: String,
|
|
39
|
+
enums: String,
|
|
40
|
+
defaults: true,
|
|
41
|
+
});
|
|
42
|
+
const loaded = grpc.loadPackageDefinition(packageDefinition);
|
|
43
|
+
const namespace = service.startsWith("dshctl")
|
|
44
|
+
? loaded.dshctl
|
|
45
|
+
: loaded.dshguest;
|
|
46
|
+
const version = namespace.v1;
|
|
47
|
+
const Constructor = version[service.split(".").at(-1)];
|
|
48
|
+
definitions.set(service, Constructor);
|
|
49
|
+
return Constructor;
|
|
50
|
+
}
|
|
13
51
|
function client(service, proto, socket) {
|
|
14
52
|
const key = `${service}:${socket}`;
|
|
15
|
-
let
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
enums: String,
|
|
20
|
-
defaults: true,
|
|
21
|
-
});
|
|
22
|
-
const loaded = grpc.loadPackageDefinition(packageDefinition);
|
|
23
|
-
const namespace = service.startsWith("dshctl")
|
|
24
|
-
? loaded.dshctl
|
|
25
|
-
: loaded.dshguest;
|
|
26
|
-
const version = namespace.v1;
|
|
27
|
-
Constructor = version[service.split(".").at(-1)];
|
|
28
|
-
definitions.set(key, Constructor);
|
|
53
|
+
let existing = clients.get(key);
|
|
54
|
+
if (existing === undefined) {
|
|
55
|
+
existing = new (constructorFor(service, proto))(unixTarget(socket), grpc.credentials.createInsecure(), CHANNEL_OPTIONS);
|
|
56
|
+
clients.set(key, existing);
|
|
29
57
|
}
|
|
30
|
-
return
|
|
58
|
+
return existing;
|
|
31
59
|
}
|
|
32
60
|
export function controlClient(socket) {
|
|
33
61
|
return client("dshctl.v1.OrchestratorControl", "dshctl/v1/control.proto", socket);
|
|
@@ -35,9 +63,54 @@ export function controlClient(socket) {
|
|
|
35
63
|
export function guestClient(socket) {
|
|
36
64
|
return client("dshguest.v1.WorkspaceGuestAgent", "dshguest/v1/guest.proto", socket);
|
|
37
65
|
}
|
|
38
|
-
|
|
66
|
+
// closeClients closes and forgets every cached channel. Called when the plugin
|
|
67
|
+
// is disposed; a later call would create a fresh client.
|
|
68
|
+
export function closeClients() {
|
|
69
|
+
for (const client of clients.values()) {
|
|
70
|
+
client.close();
|
|
71
|
+
}
|
|
72
|
+
clients.clear();
|
|
73
|
+
}
|
|
74
|
+
// abortError is the stable shape an aborted call rejects with, so callers can
|
|
75
|
+
// tell a cancellation apart from a transport failure without parsing text.
|
|
76
|
+
export function abortError(label) {
|
|
77
|
+
const error = new Error(`${label} aborted`);
|
|
78
|
+
error.code = "ABORTED";
|
|
79
|
+
return error;
|
|
80
|
+
}
|
|
81
|
+
// onAbortCancel cancels a pending call when the signal aborts, and returns the
|
|
82
|
+
// detach function that removes the listener once the call settles.
|
|
83
|
+
function onAbortCancel(call, signal) {
|
|
84
|
+
if (signal === undefined)
|
|
85
|
+
return () => { };
|
|
86
|
+
const onAbort = () => {
|
|
87
|
+
try {
|
|
88
|
+
call.cancel();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// A finished call cannot be cancelled again; nothing to do.
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
95
|
+
return () => signal.removeEventListener("abort", onAbort);
|
|
96
|
+
}
|
|
97
|
+
export function unary(client, method, request, metadata, signal) {
|
|
39
98
|
return new Promise((resolvePromise, reject) => {
|
|
40
|
-
|
|
99
|
+
if (signal?.aborted) {
|
|
100
|
+
reject(abortError(method));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
let detach = () => { };
|
|
104
|
+
const call = client[method](request, metadata ?? new grpc.Metadata(), (error, value) => {
|
|
105
|
+
detach();
|
|
106
|
+
if (error) {
|
|
107
|
+
reject(signal?.aborted ? abortError(method) : error);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
resolvePromise(value);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
detach = onAbortCancel(call, signal);
|
|
41
114
|
});
|
|
42
115
|
}
|
|
43
116
|
export { grpc };
|
package/dist/guest-rpc.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkspaceResolver } from "./workspace-binding.js";
|
|
1
|
+
import { type WorkspaceBinding, WorkspaceResolver } from "./workspace-binding.js";
|
|
2
2
|
export declare function resolveGuestPath(path: string, sessionCwd: unknown): string;
|
|
3
3
|
export declare function resolveGuestCwd(cwd: unknown, sessionCwd: unknown): string | undefined;
|
|
4
4
|
export declare function guestCwd(requested: unknown, sessionCwd: unknown, binding: {
|
|
@@ -11,6 +11,7 @@ export declare function detectLineEndings(raw: string): "CRLF" | "LF";
|
|
|
11
11
|
export declare function restoreLineEndings(content: string, endings: "CRLF" | "LF"): string;
|
|
12
12
|
export declare function fsError(code: string, message: string): Error;
|
|
13
13
|
export declare function throwIfAborted(signal: AbortSignal | undefined, verb: string): void;
|
|
14
|
+
export declare function withGuestAuth<T>(binding: WorkspaceBinding, run: (guest: any, token: string) => Promise<T>): Promise<T>;
|
|
14
15
|
export declare function guestChunks(target: any, range: {
|
|
15
16
|
offset?: number;
|
|
16
17
|
length?: number;
|
|
@@ -19,42 +20,37 @@ export declare function guestTextChunks(target: any, signal: AbortSignal | undef
|
|
|
19
20
|
export declare function readGuestText(target: any, signal: AbortSignal | undefined): Promise<string>;
|
|
20
21
|
export declare function readDiffBasis(target: any, signal: AbortSignal | undefined): Promise<string | null>;
|
|
21
22
|
export declare function guestVersion(result: any): string;
|
|
22
|
-
export declare function writeGuestFile(binding: {
|
|
23
|
-
guest: any;
|
|
24
|
-
token: string;
|
|
25
|
-
}, path: string, content: string, opts: {
|
|
23
|
+
export declare function writeGuestFile(binding: WorkspaceBinding, path: string, content: string, opts: {
|
|
26
24
|
create: boolean;
|
|
27
25
|
truncate: boolean;
|
|
28
26
|
}, signal?: AbortSignal): Promise<number>;
|
|
29
|
-
export declare function readGuestFile(binding:
|
|
30
|
-
guest: any;
|
|
31
|
-
token: string;
|
|
32
|
-
}, path: string): Promise<string>;
|
|
27
|
+
export declare function readGuestFile(binding: WorkspaceBinding, path: string): Promise<string>;
|
|
33
28
|
export interface ExecIdentity {
|
|
34
29
|
uid?: number;
|
|
35
30
|
gid?: number;
|
|
36
31
|
groups?: number[];
|
|
37
32
|
}
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
export interface RunExecOptions {
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
maxBytes?: number;
|
|
36
|
+
spillBytes?: number;
|
|
37
|
+
killGraceMs?: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function runExec(binding: WorkspaceBinding, argv: readonly string[], cwd?: string, env?: Record<string, string>, timeoutMs?: number, identity?: ExecIdentity, options?: RunExecOptions): Promise<{
|
|
42
40
|
exitCode: number;
|
|
43
41
|
signal: string | null;
|
|
44
42
|
stdout: string;
|
|
45
43
|
stderr: string;
|
|
44
|
+
stdoutSpillPath?: string;
|
|
45
|
+
stderrSpillPath?: string;
|
|
46
46
|
}>;
|
|
47
47
|
export declare function sliceLines(content: string, offset: unknown, limit: unknown): string;
|
|
48
|
+
export declare function streamLines(stream: AsyncIterable<string>, offset: unknown, limit: unknown): Promise<string>;
|
|
48
49
|
export declare function outputLines(text: string): string[];
|
|
49
50
|
export declare function bytesText(value: unknown): string;
|
|
50
51
|
export declare function currentCwd(exec: any): unknown;
|
|
51
52
|
export declare function sessionWorkspaceSlug(resolver: WorkspaceResolver, cwd: unknown): Promise<string>;
|
|
52
|
-
export declare function resolveToolBinding(resolver: WorkspaceResolver, cwd: unknown, container: string): Promise<
|
|
53
|
-
guest: any;
|
|
54
|
-
token: string;
|
|
55
|
-
socket: string;
|
|
56
|
-
defaultCwd?: string;
|
|
57
|
-
}>;
|
|
53
|
+
export declare function resolveToolBinding(resolver: WorkspaceResolver, cwd: unknown, container: string, signal?: AbortSignal): Promise<WorkspaceBinding>;
|
|
58
54
|
export declare function guestStat(target: any, signal?: AbortSignal): Promise<any>;
|
|
59
55
|
export declare function guestStatResponse(target: any, signal?: AbortSignal): Promise<any>;
|
|
60
56
|
export declare function unaryGuest(target: any, method: string, request: unknown, signal?: AbortSignal): Promise<unknown>;
|