@exagone313/dsh-podman 0.2.0-rc.4 → 0.3.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 +12 -0
- 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 +103 -35
- package/dist/card-route.test.js +60 -43
- package/dist/card-test-support.js +39 -66
- package/dist/client/ContainerCard.d.ts +1 -1
- package/dist/client/ContainerCard.js +76 -70
- package/dist/client/card-protocol.d.ts +3 -3
- package/dist/client/container-card-caches.js +11 -6
- package/dist/client/container-card-controller.d.ts +13 -18
- package/dist/client/container-card-controller.js +86 -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 +13109 -2117
- package/dist/client/locales.d.ts +6 -1
- package/dist/client/locales.js +88 -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.js +2 -1
- 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 +8 -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 +36 -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/volumes.test.js +1 -1
- package/dist/workspace-binding.d.ts +18 -9
- package/dist/workspace-binding.js +244 -77
- package/dist/workspace-binding.test.js +189 -25
- package/docs/architecture.md +62 -21
- package/docs/architecture.zh.md +57 -13
- 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 +141 -21
- package/docs/development.zh.md +125 -17
- package/docs/install-dsh-and-dsh-podman.md +21 -8
- package/docs/install-dsh-and-dsh-podman.zh.md +18 -8
- 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 +59 -26
- 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
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { EventEmitter } from "node:events";
|
|
7
|
+
import { runExec, sessionWorkspaceSlug, sliceLines, streamLines, withGuestAuth, } from "./guest-rpc.js";
|
|
8
|
+
import { grpc } from "./grpc/runtime-client.js";
|
|
9
|
+
// pingOnce is a stand-in for a real guest call: it invokes the guest client and
|
|
10
|
+
// settles from its callback.
|
|
11
|
+
function pingOnce(guest, _token) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
guest.ping({}, undefined, (error, value) => error ? reject(error) : resolve(value));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function rejectingGuest() {
|
|
17
|
+
return {
|
|
18
|
+
guest: {
|
|
19
|
+
ping: (_request, _metadata, callback) => callback(Object.assign(new Error("invalid agent token"), {
|
|
20
|
+
code: grpc.status.UNAUTHENTICATED,
|
|
21
|
+
})),
|
|
22
|
+
},
|
|
23
|
+
token: "old",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
test("withGuestAuth retries once with a refreshed binding", async () => {
|
|
27
|
+
const seen = [];
|
|
28
|
+
const fresh = {
|
|
29
|
+
guest: {
|
|
30
|
+
ping: (_request, _metadata, callback) => callback(null, { version: "v" }),
|
|
31
|
+
},
|
|
32
|
+
token: "new",
|
|
33
|
+
};
|
|
34
|
+
const binding = { ...rejectingGuest(), refresh: async () => fresh };
|
|
35
|
+
const result = await withGuestAuth(binding, (guest, token) => {
|
|
36
|
+
seen.push(token);
|
|
37
|
+
return pingOnce(guest, token);
|
|
38
|
+
});
|
|
39
|
+
assert.deepEqual(seen, ["old", "new"]);
|
|
40
|
+
assert.deepEqual(result, { version: "v" });
|
|
41
|
+
});
|
|
42
|
+
test("withGuestAuth surfaces a second rejection", async () => {
|
|
43
|
+
const rejecting = rejectingGuest();
|
|
44
|
+
const binding = { ...rejecting, refresh: async () => rejecting };
|
|
45
|
+
await assert.rejects(() => withGuestAuth(binding, pingOnce), /invalid agent token/);
|
|
46
|
+
});
|
|
47
|
+
test("withGuestAuth does not refresh on a non-auth error", async () => {
|
|
48
|
+
let refreshed = false;
|
|
49
|
+
const binding = {
|
|
50
|
+
guest: {
|
|
51
|
+
ping: (_request, _metadata, callback) => callback(Object.assign(new Error("boom"), { code: grpc.status.INTERNAL })),
|
|
52
|
+
},
|
|
53
|
+
token: "t",
|
|
54
|
+
refresh: async () => {
|
|
55
|
+
refreshed = true;
|
|
56
|
+
return binding;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
await assert.rejects(() => withGuestAuth(binding, pingOnce), /boom/);
|
|
60
|
+
assert.equal(refreshed, false);
|
|
61
|
+
});
|
|
62
|
+
test("withGuestAuth surfaces the error when the binding cannot refresh", async () => {
|
|
63
|
+
const binding = rejectingGuest();
|
|
64
|
+
await assert.rejects(() => withGuestAuth(binding, pingOnce), /invalid agent token/);
|
|
65
|
+
});
|
|
66
|
+
// FakeExecStream is the guest Exec bidi stream a real channel would give us.
|
|
67
|
+
class FakeExecStream extends EventEmitter {
|
|
68
|
+
written = [];
|
|
69
|
+
ended = false;
|
|
70
|
+
cancelled = false;
|
|
71
|
+
write(message) {
|
|
72
|
+
this.written.push(message);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
end() {
|
|
76
|
+
this.ended = true;
|
|
77
|
+
}
|
|
78
|
+
cancel() {
|
|
79
|
+
this.cancelled = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// execGuest returns a guest double driving one FakeExecStream. `signal` and
|
|
83
|
+
// `delete` are the calls runExec makes for cancellation and spill cleanup.
|
|
84
|
+
function execGuest(stream) {
|
|
85
|
+
const signals = [];
|
|
86
|
+
return {
|
|
87
|
+
stream,
|
|
88
|
+
signals,
|
|
89
|
+
guest: {
|
|
90
|
+
exec: () => stream,
|
|
91
|
+
signal: (request, _metadata, callback) => {
|
|
92
|
+
signals.push(request.signal);
|
|
93
|
+
callback(null, {});
|
|
94
|
+
},
|
|
95
|
+
delete: (_request, _metadata, callback) => callback(null, {}),
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const EXIT_OK = {
|
|
100
|
+
exitCode: 0,
|
|
101
|
+
signaled: false,
|
|
102
|
+
stdoutSpillValid: true,
|
|
103
|
+
stderrSpillValid: true,
|
|
104
|
+
};
|
|
105
|
+
test("runExec resolves on exit and asks the guest for a spill copy", async () => {
|
|
106
|
+
const harness = execGuest(new FakeExecStream());
|
|
107
|
+
const binding = { guest: harness.guest, token: "t" };
|
|
108
|
+
const promise = runExec(binding, ["echo", "hi"]);
|
|
109
|
+
harness.stream.emit("data", { processId: "1" });
|
|
110
|
+
harness.stream.emit("data", { stdoutChunk: Buffer.from("hi\n") });
|
|
111
|
+
harness.stream.emit("data", { exit: EXIT_OK });
|
|
112
|
+
const result = await promise;
|
|
113
|
+
assert.equal(result.exitCode, 0);
|
|
114
|
+
assert.equal(result.stdout, "hi\n");
|
|
115
|
+
const start = harness.stream.written[0].start;
|
|
116
|
+
assert.match(start.spillStdout.path, /^\/tmp\/dsh-podman\/[0-9a-f-]+\.stdout$/);
|
|
117
|
+
assert.equal(start.spillStderr.path.endsWith(".stderr"), true);
|
|
118
|
+
// The output fit in memory, so the spill file is deleted again.
|
|
119
|
+
assert.equal(result.stdoutSpillPath, undefined);
|
|
120
|
+
});
|
|
121
|
+
test("runExec rejects when the stream ends without an exit message", async () => {
|
|
122
|
+
const harness = execGuest(new FakeExecStream());
|
|
123
|
+
const binding = { guest: harness.guest, token: "t" };
|
|
124
|
+
const promise = runExec(binding, ["hang"]);
|
|
125
|
+
harness.stream.emit("end");
|
|
126
|
+
await assert.rejects(promise, /ended before the process exited/);
|
|
127
|
+
});
|
|
128
|
+
test("runExec aborts with the turn signal, then escalates to SIGKILL", async () => {
|
|
129
|
+
const harness = execGuest(new FakeExecStream());
|
|
130
|
+
const binding = { guest: harness.guest, token: "t" };
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
const promise = runExec(binding, ["sleep", "99"], undefined, undefined, undefined, undefined, {
|
|
133
|
+
signal: controller.signal,
|
|
134
|
+
killGraceMs: 60,
|
|
135
|
+
});
|
|
136
|
+
harness.stream.emit("data", { processId: "7" });
|
|
137
|
+
controller.abort();
|
|
138
|
+
await assert.rejects(promise, /aborted/);
|
|
139
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
140
|
+
assert.deepEqual(harness.signals, ["SIGTERM"]);
|
|
141
|
+
// The stream stays open for the grace period, so a command that ignores
|
|
142
|
+
// SIGTERM can still be killed by its process group.
|
|
143
|
+
assert.equal(harness.stream.cancelled, false);
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
145
|
+
assert.deepEqual(harness.signals, ["SIGTERM", "SIGKILL"]);
|
|
146
|
+
assert.equal(harness.stream.cancelled, true);
|
|
147
|
+
});
|
|
148
|
+
test("runExec leaves a command that exits within the grace period alone", async () => {
|
|
149
|
+
const harness = execGuest(new FakeExecStream());
|
|
150
|
+
const binding = { guest: harness.guest, token: "t" };
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const promise = runExec(binding, ["sleep", "99"], undefined, undefined, undefined, undefined, {
|
|
153
|
+
signal: controller.signal,
|
|
154
|
+
killGraceMs: 30,
|
|
155
|
+
});
|
|
156
|
+
harness.stream.emit("data", { processId: "7" });
|
|
157
|
+
controller.abort();
|
|
158
|
+
await assert.rejects(promise, /aborted/);
|
|
159
|
+
harness.stream.emit("data", { exit: EXIT_OK });
|
|
160
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
161
|
+
assert.deepEqual(harness.signals, ["SIGTERM"]);
|
|
162
|
+
assert.equal(harness.stream.cancelled, false);
|
|
163
|
+
});
|
|
164
|
+
test("runExec cancels an abort that arrives before the process id", async () => {
|
|
165
|
+
const harness = execGuest(new FakeExecStream());
|
|
166
|
+
const binding = { guest: harness.guest, token: "t" };
|
|
167
|
+
const controller = new AbortController();
|
|
168
|
+
const promise = runExec(binding, ["sleep", "99"], undefined, undefined, undefined, undefined, {
|
|
169
|
+
signal: controller.signal,
|
|
170
|
+
});
|
|
171
|
+
controller.abort();
|
|
172
|
+
await assert.rejects(promise, /aborted/);
|
|
173
|
+
assert.equal(harness.stream.cancelled, true);
|
|
174
|
+
assert.deepEqual(harness.signals, []);
|
|
175
|
+
});
|
|
176
|
+
test("runExec rejects a pre-aborted call without starting it", async () => {
|
|
177
|
+
const harness = execGuest(new FakeExecStream());
|
|
178
|
+
const binding = { guest: harness.guest, token: "t" };
|
|
179
|
+
const controller = new AbortController();
|
|
180
|
+
controller.abort();
|
|
181
|
+
await assert.rejects(() => runExec(binding, ["x"], undefined, undefined, undefined, undefined, {
|
|
182
|
+
signal: controller.signal,
|
|
183
|
+
}), /aborted/);
|
|
184
|
+
assert.equal(harness.stream.written.length, 0);
|
|
185
|
+
});
|
|
186
|
+
test("runExec keeps a bounded tail and points at the spill on truncation", async () => {
|
|
187
|
+
const harness = execGuest(new FakeExecStream());
|
|
188
|
+
const binding = { guest: harness.guest, token: "t" };
|
|
189
|
+
const promise = runExec(binding, ["big"], undefined, undefined, undefined, undefined, {
|
|
190
|
+
maxBytes: 4,
|
|
191
|
+
spillBytes: 1024,
|
|
192
|
+
});
|
|
193
|
+
harness.stream.emit("data", { stdoutChunk: Buffer.from("0123456789") });
|
|
194
|
+
harness.stream.emit("data", { exit: EXIT_OK });
|
|
195
|
+
const result = await promise;
|
|
196
|
+
assert.equal(result.stdout, "6789", "only the bounded tail is retained");
|
|
197
|
+
assert.match(result.stdoutSpillPath ?? "", /^\/tmp\/dsh-podman\/[0-9a-f-]+\.stdout$/);
|
|
198
|
+
});
|
|
199
|
+
test("sessionWorkspaceSlug fails instead of fabricating a default workspace", async () => {
|
|
200
|
+
const resolver = { registry: { resolveByPath: async () => undefined } };
|
|
201
|
+
await assert.rejects(() => sessionWorkspaceSlug(resolver, "/projects/gone"), /cannot resolve a DH workspace/);
|
|
202
|
+
});
|
|
203
|
+
// chunks yields one character at a time, the worst case for a streaming line
|
|
204
|
+
// reader because every boundary can fall inside a line.
|
|
205
|
+
async function* characters(content) {
|
|
206
|
+
for (const character of content)
|
|
207
|
+
yield character;
|
|
208
|
+
}
|
|
209
|
+
test("streamLines matches sliceLines for every window shape", async () => {
|
|
210
|
+
const cases = [
|
|
211
|
+
["", 1, 5],
|
|
212
|
+
["a", 1, 5],
|
|
213
|
+
["a\n", 1, 5],
|
|
214
|
+
["a\nb\nc", 1, 2],
|
|
215
|
+
["a\nb\nc\n", 1, 2],
|
|
216
|
+
["a\nb\nc\n", 2, 2],
|
|
217
|
+
["a\nb\nc\n", 2, 10],
|
|
218
|
+
["a\nb", 2, 2],
|
|
219
|
+
["a\nb", 5, 2],
|
|
220
|
+
["x\ny\nz\n", 3, 10],
|
|
221
|
+
["1\n2\n3\n4\n5", 2, 3],
|
|
222
|
+
["\n\n\n", 2, 2],
|
|
223
|
+
];
|
|
224
|
+
for (const [content, offset, limit] of cases) {
|
|
225
|
+
const expected = sliceLines(content, offset, limit);
|
|
226
|
+
const streamed = await streamLines(characters(content), offset, limit);
|
|
227
|
+
assert.equal(streamed, expected, `content=${JSON.stringify(content)} offset=${offset} limit=${limit}`);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
test("streamLines stops consuming once the window is filled", async () => {
|
|
231
|
+
let consumed = 0;
|
|
232
|
+
async function* source() {
|
|
233
|
+
for (let index = 1; index <= 100; index++) {
|
|
234
|
+
consumed++;
|
|
235
|
+
yield `${index}\n`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const out = await streamLines(source(), 1, 2);
|
|
239
|
+
assert.equal(out, "1\n2");
|
|
240
|
+
assert.equal(consumed, 2);
|
|
241
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type WorkspaceBinding } from "./workspace-binding.js";
|
|
2
|
+
export interface GuestTerminalOptions {
|
|
3
|
+
argv: readonly string[];
|
|
4
|
+
cwd: string;
|
|
5
|
+
env?: Readonly<Record<string, string>>;
|
|
6
|
+
cols: number;
|
|
7
|
+
rows: number;
|
|
8
|
+
terminalType?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GuestTerminalExit {
|
|
11
|
+
exitCode: number | null;
|
|
12
|
+
signal: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface GuestTerminal {
|
|
15
|
+
readonly pid: number;
|
|
16
|
+
readonly started: Promise<number>;
|
|
17
|
+
readonly done: Promise<GuestTerminalExit>;
|
|
18
|
+
exited(): boolean;
|
|
19
|
+
activityRevision(): number;
|
|
20
|
+
onOutput(listener: (chunk: Buffer) => void): () => void;
|
|
21
|
+
write(data: Buffer | string): void;
|
|
22
|
+
resize(cols: number, rows: number): void;
|
|
23
|
+
inspectForeground(): Promise<{
|
|
24
|
+
processGroupId: number;
|
|
25
|
+
inputWaiting: boolean;
|
|
26
|
+
} | undefined>;
|
|
27
|
+
signalForeground(signal: string): Promise<number>;
|
|
28
|
+
terminate(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export declare function openGuestTerminal(binding: WorkspaceBinding, options: GuestTerminalOptions): GuestTerminal;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
// One interactive guest-terminal stream. The harness's subprocess provider and
|
|
5
|
+
// the browser terminal routes both build on this: the guest owns the pty and
|
|
6
|
+
// reports started/stdout/exit plus foreground queries, so the terminal protocol
|
|
7
|
+
// is spoken in exactly one place.
|
|
8
|
+
import { remoteArgv } from "./guest-rpc.js";
|
|
9
|
+
import { metadata } from "./workspace-binding.js";
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
export function openGuestTerminal(binding, options) {
|
|
12
|
+
const call = binding.guest.terminal(metadata(binding.token));
|
|
13
|
+
const listeners = new Set();
|
|
14
|
+
const pending = new Map();
|
|
15
|
+
let pid = -1;
|
|
16
|
+
let exited = false;
|
|
17
|
+
let terminated = false;
|
|
18
|
+
let activity = 0;
|
|
19
|
+
let resolveStarted;
|
|
20
|
+
let rejectStarted;
|
|
21
|
+
const started = new Promise((resolveStartedPromise, rejectStartedPromise) => {
|
|
22
|
+
resolveStarted = resolveStartedPromise;
|
|
23
|
+
rejectStarted = rejectStartedPromise;
|
|
24
|
+
});
|
|
25
|
+
// A caller that never awaits startup must not crash on an unhandled rejection.
|
|
26
|
+
started.catch(() => { });
|
|
27
|
+
let resolveExit;
|
|
28
|
+
let rejectExit;
|
|
29
|
+
const done = new Promise((resolveExitPromise, rejectExitPromise) => {
|
|
30
|
+
resolveExit = resolveExitPromise;
|
|
31
|
+
rejectExit = rejectExitPromise;
|
|
32
|
+
});
|
|
33
|
+
done.catch(() => { });
|
|
34
|
+
const failPending = (error) => {
|
|
35
|
+
for (const waiter of pending.values())
|
|
36
|
+
waiter.reject(error);
|
|
37
|
+
pending.clear();
|
|
38
|
+
};
|
|
39
|
+
// Settle the stream exactly once, however it ends: an exit message, a stream
|
|
40
|
+
// error, or a stream that ends without one. Every path also releases pending
|
|
41
|
+
// foreground queries so a caller cannot hang on them.
|
|
42
|
+
const settle = (error, outcome) => {
|
|
43
|
+
if (exited)
|
|
44
|
+
return;
|
|
45
|
+
exited = true;
|
|
46
|
+
if (error !== undefined) {
|
|
47
|
+
rejectStarted(error);
|
|
48
|
+
rejectExit(error);
|
|
49
|
+
failPending(error);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (outcome !== undefined)
|
|
53
|
+
resolveExit(outcome);
|
|
54
|
+
failPending(new Error("terminal has exited"));
|
|
55
|
+
};
|
|
56
|
+
call.on("data", (message) => {
|
|
57
|
+
if (message.started) {
|
|
58
|
+
pid = Number(message.started.pid);
|
|
59
|
+
resolveStarted(pid);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (message.stdoutChunk) {
|
|
63
|
+
activity++;
|
|
64
|
+
const chunk = Buffer.from(message.stdoutChunk);
|
|
65
|
+
for (const listener of listeners)
|
|
66
|
+
listener(chunk);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (message.exit) {
|
|
70
|
+
settle(undefined, {
|
|
71
|
+
exitCode: message.exit.exitCode,
|
|
72
|
+
signal: message.exit.signaled ? message.exit.signal : null,
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (message.foreground) {
|
|
77
|
+
activity++;
|
|
78
|
+
const waiter = pending.get(message.foreground.requestId);
|
|
79
|
+
if (waiter !== undefined) {
|
|
80
|
+
pending.delete(message.foreground.requestId);
|
|
81
|
+
waiter.resolve(message.foreground);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (message.signalled) {
|
|
86
|
+
const waiter = pending.get(message.signalled.requestId);
|
|
87
|
+
if (waiter !== undefined) {
|
|
88
|
+
pending.delete(message.signalled.requestId);
|
|
89
|
+
waiter.resolve(message.signalled);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
call.on("error", (error) => {
|
|
94
|
+
settle(error instanceof Error ? error : new Error(String(error)));
|
|
95
|
+
});
|
|
96
|
+
call.on("end", () => {
|
|
97
|
+
settle(new Error("terminal stream ended before the process exited"));
|
|
98
|
+
});
|
|
99
|
+
const env = { ...(options.env ?? {}) };
|
|
100
|
+
if (options.terminalType !== undefined &&
|
|
101
|
+
options.terminalType !== "" &&
|
|
102
|
+
env.TERM === undefined) {
|
|
103
|
+
env.TERM = options.terminalType;
|
|
104
|
+
}
|
|
105
|
+
call.write({
|
|
106
|
+
start: {
|
|
107
|
+
argv: remoteArgv(options.argv),
|
|
108
|
+
cwd: options.cwd,
|
|
109
|
+
env,
|
|
110
|
+
rows: options.rows,
|
|
111
|
+
cols: options.cols,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const request = (message) => {
|
|
115
|
+
const requestId = randomUUID();
|
|
116
|
+
return new Promise((resolveWaiter, rejectWaiter) => {
|
|
117
|
+
pending.set(requestId, { resolve: resolveWaiter, reject: rejectWaiter });
|
|
118
|
+
try {
|
|
119
|
+
call.write(message(requestId));
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
pending.delete(requestId);
|
|
123
|
+
rejectWaiter(error);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
get pid() {
|
|
129
|
+
return pid;
|
|
130
|
+
},
|
|
131
|
+
started,
|
|
132
|
+
done,
|
|
133
|
+
exited: () => exited,
|
|
134
|
+
activityRevision: () => activity,
|
|
135
|
+
onOutput: (listener) => {
|
|
136
|
+
listeners.add(listener);
|
|
137
|
+
return () => {
|
|
138
|
+
listeners.delete(listener);
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
write: (data) => {
|
|
142
|
+
if (exited)
|
|
143
|
+
throw new Error("terminal has exited");
|
|
144
|
+
activity++;
|
|
145
|
+
call.write({ stdinChunk: Buffer.from(data) });
|
|
146
|
+
},
|
|
147
|
+
resize: (cols, rows) => {
|
|
148
|
+
if (exited)
|
|
149
|
+
throw new Error("terminal has exited");
|
|
150
|
+
activity++;
|
|
151
|
+
call.write({ resize: { rows, cols } });
|
|
152
|
+
},
|
|
153
|
+
inspectForeground: async () => {
|
|
154
|
+
if (exited)
|
|
155
|
+
return undefined;
|
|
156
|
+
const response = await request((requestId) => ({
|
|
157
|
+
inspectRequestId: requestId,
|
|
158
|
+
}));
|
|
159
|
+
return response.found
|
|
160
|
+
? {
|
|
161
|
+
processGroupId: response.processGroupId,
|
|
162
|
+
inputWaiting: response.inputWaiting,
|
|
163
|
+
}
|
|
164
|
+
: undefined;
|
|
165
|
+
},
|
|
166
|
+
signalForeground: async (signal) => {
|
|
167
|
+
if (exited)
|
|
168
|
+
throw new Error("terminal has exited");
|
|
169
|
+
const response = await request((requestId) => ({
|
|
170
|
+
signal: { signal, requestId },
|
|
171
|
+
}));
|
|
172
|
+
if (!response.found) {
|
|
173
|
+
throw new Error(`no foreground process group to signal ${signal}`);
|
|
174
|
+
}
|
|
175
|
+
return response.processGroupId;
|
|
176
|
+
},
|
|
177
|
+
terminate: async () => {
|
|
178
|
+
if (terminated)
|
|
179
|
+
return;
|
|
180
|
+
terminated = true;
|
|
181
|
+
try {
|
|
182
|
+
call.write({ close: true });
|
|
183
|
+
call.end();
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// A finished call cannot be closed again; nothing to do.
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
await done;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Termination is idempotent and never rejects.
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
package/dist/images.test.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { EXPECTED_TOOLS } from "./test-support.js";
|
|
7
|
-
import {
|
|
7
|
+
import { imageRemoveParameters, toolHandlers, TOOLS } from "./index.js";
|
|
8
8
|
test("tool set covers the image and container surface", () => {
|
|
9
9
|
assert.deepEqual(TOOLS.map((tool) => tool.name).sort(), [...EXPECTED_TOOLS].sort());
|
|
10
10
|
});
|
|
@@ -12,7 +12,11 @@ test("image_build requires imageId, parent and packages", () => {
|
|
|
12
12
|
const tool = TOOLS.find((entry) => entry.name === "image_build");
|
|
13
13
|
assert.ok(tool, "image_build registered");
|
|
14
14
|
assert.equal(tool.approval, true, "image_build must require approval");
|
|
15
|
-
assert.deepEqual(tool.parameters.required, [
|
|
15
|
+
assert.deepEqual(tool.parameters.required, [
|
|
16
|
+
"imageId",
|
|
17
|
+
"parent",
|
|
18
|
+
"packages",
|
|
19
|
+
]);
|
|
16
20
|
assert.equal(tool.parameters.properties.imageId.description, "Image short name.");
|
|
17
21
|
assert.ok(tool.parameters.properties.parent.description.includes("Short name of the parent image"), "parent must be documented as a short name");
|
|
18
22
|
});
|
|
@@ -72,7 +76,10 @@ test("image_list returns image objects", async () => {
|
|
|
72
76
|
});
|
|
73
77
|
test("image_rebuild_all returns rebuilt and skipped arrays", async () => {
|
|
74
78
|
const resolver = {
|
|
75
|
-
control: async () => ({
|
|
79
|
+
control: async () => ({
|
|
80
|
+
rebuilt: ["archlinux", "dev"],
|
|
81
|
+
skipped: ["broken"],
|
|
82
|
+
}),
|
|
76
83
|
};
|
|
77
84
|
const out = await toolHandlers.image_rebuild_all(resolver, {}, {});
|
|
78
85
|
assert.deepEqual(out, { rebuilt: ["archlinux", "dev"], skipped: ["broken"] });
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
|
+
import { type Config } from "./settings-schema.js";
|
|
1
2
|
export declare const name = "podman";
|
|
2
3
|
export declare const inject: string[];
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
defaultImage?: string;
|
|
6
|
-
projectsRoot?: string;
|
|
7
|
-
controlToken?: string;
|
|
8
|
-
imagePrefix?: string;
|
|
9
|
-
}
|
|
10
|
-
export declare function apply(ctx: any, config?: PluginConfig): void;
|
|
11
|
-
export { PODMAN_OPS_APPROVAL_TOOLS, PODMAN_OPS_PRESET, READ_ONLY_TOOLS, SANDBOX_ESCALATION_REASON_PREFIX, approvalDecision, isSandboxEscalation, mountDestinationsReason, preExecutePolicy, summarizeArgs, } from "./approval.js";
|
|
4
|
+
export declare function apply(ctx: any, config: Config): void;
|
|
5
|
+
export { approvalDecision, isSandboxEscalation, mountDestinationsReason, PODMAN_OPS_APPROVAL_TOOLS, PODMAN_OPS_PRESET, preExecutePolicy, READ_ONLY_TOOLS, SANDBOX_ESCALATION_REASON_PREFIX, summarizeArgs, } from "./approval.js";
|
|
12
6
|
export type { SessionFacts } from "./approval.js";
|
|
13
|
-
export { READ_ONLY_GATED_TOOLS, REMOUNT_TOOL_NAME,
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
7
|
+
export { createReadOnlyShellGate, READ_ONLY_GATED_TOOLS, REMOUNT_TOOL_NAME, } from "./read-only-shell.js";
|
|
8
|
+
export { createFilesystemProvider, FilesystemProvider } from "./fs-provider.js";
|
|
9
|
+
export { globCwd, remoteArgv, resolveGuestCwd, resolveGuestPath, } from "./guest-rpc.js";
|
|
16
10
|
export { inferMountKind, projectMountDestinationReason, projectMountMirror, } from "./mount-input.js";
|
|
17
|
-
export { HARNESS_SOURCE_SECTION,
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
11
|
+
export { HARNESS_SOURCE_SECTION, podmanRuntimeSection, withoutHarnessSourceSection, } from "./prompts.js";
|
|
12
|
+
export { Config } from "./settings-schema.js";
|
|
13
|
+
export { publicContainer, publicDaemon, publicImage, publicMount, } from "./public.js";
|
|
14
|
+
export { createSubprocessProvider, OutputReader, outputReader, SubprocessProvider, } from "./subprocess.js";
|
|
20
15
|
export { toolHandlers } from "./tool-handlers.js";
|
|
21
|
-
export {
|
|
16
|
+
export { containerBashParameters, containerEditParameters, containerExecParameters, containerGlobParameters, containerGrepParameters, containerListParameters, containerMountAddParameters, containerMountListParameters, containerMountRemoveParameters, containerReadParameters, containerRecreateParameters, containerRemoveParameters, containerSecretAddParameters, containerSecretRemoveParameters, containerStartParameters, containerWriteParameters, daemonListParameters, daemonLogsParameters, daemonRestartParameters, daemonStartParameters, daemonStopParameters, defineTool, envParam, imageBuildParameters, imageGetParameters, imageListParameters, imageRebuildAllParameters, imageRebuildParameters, imageRemoveParameters, secretCreateParameters, secretListParameters, secretRemoveParameters, ToolDefinition, toolOutput, TOOLS, volumeCreateParameters, volumeListParameters, volumeRemoveParameters, } from "./tool-schemas.js";
|
|
22
17
|
export { toolCallView, toolResultView } from "./tool-views.js";
|
package/dist/index.js
CHANGED
|
@@ -1,32 +1,27 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
|
-
import { isSandboxEscalation, preExecutePolicy } from "./approval.js";
|
|
4
|
+
import { isSandboxEscalation, preExecutePolicy, } from "./approval.js";
|
|
5
5
|
import { resolveReasonLocale } from "./approval-reasons.js";
|
|
6
6
|
import { createFilesystemProvider } from "./fs-provider.js";
|
|
7
7
|
import { createSpillStore } from "./spill-store.js";
|
|
8
|
-
import {
|
|
8
|
+
import { podmanRuntimeSection, withoutHarnessSourceSection, } from "./prompts.js";
|
|
9
9
|
import { createSubprocessProvider } from "./subprocess.js";
|
|
10
|
-
import { createReadOnlyShellGate, REMOUNT_TOOL_NAME } from "./read-only-shell.js";
|
|
10
|
+
import { createReadOnlyShellGate, REMOUNT_TOOL_NAME, } from "./read-only-shell.js";
|
|
11
11
|
import { toolHandlers } from "./tool-handlers.js";
|
|
12
|
-
import {
|
|
12
|
+
import { defineTool, TOOL_DESCRIPTIONS, toolOutput, TOOLS, } from "./tool-schemas.js";
|
|
13
13
|
import { toolCallView, toolResultView } from "./tool-views.js";
|
|
14
|
-
import { installContainerPreferences } from "./preferences.js";
|
|
15
14
|
import { registerCardRoute } from "./card-route.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
15
|
+
import { registerTerminalRoutes } from "./terminal-route.js";
|
|
16
|
+
import { TerminalSessions } from "./terminal-sessions.js";
|
|
17
|
+
import { normalizeToolError, WorkspaceResolver } from "./workspace-binding.js";
|
|
20
18
|
export const name = "podman";
|
|
21
19
|
export const inject = ["tools", "workspaceRegistry"];
|
|
22
|
-
export function apply(ctx, config
|
|
20
|
+
export function apply(ctx, config) {
|
|
23
21
|
// Approval text follows the session language: the browser client records its
|
|
24
|
-
// active locale in the plugin
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
ctx.inject(["settings"], (settingsCtx) => {
|
|
28
|
-
readLocale = () => resolveReasonLocale(settingsCtx.settings);
|
|
29
|
-
});
|
|
22
|
+
// active locale in the plugin's own volatile `uiLocale` preference, so the
|
|
23
|
+
// value is always read live and an unset one renders in English.
|
|
24
|
+
const readLocale = () => resolveReasonLocale(config.uiLocale.get());
|
|
30
25
|
// The session's permission knobs live in the harness's own services: the
|
|
31
26
|
// sandbox policy resolves the effective mode (approved override, last logged
|
|
32
27
|
// mode, then the deployment default), the approval service reports the logged
|
|
@@ -76,19 +71,20 @@ export function apply(ctx, config = {}) {
|
|
|
76
71
|
// listener is prepended so it runs before the bridge that forwards approvals
|
|
77
72
|
// to the browser, and it delegates everything else.
|
|
78
73
|
ctx.on("approval/request", (request, next) => isSandboxEscalation(request) ? Promise.resolve("allowed-once") : next(), { prepend: true });
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}, ctx.workspaceRegistry);
|
|
74
|
+
const resolver = new WorkspaceResolver(
|
|
75
|
+
// A reader, not a snapshot: every preference is a volatile config field an
|
|
76
|
+
// edit commits in place, so the resolver observes changes without a reload.
|
|
77
|
+
() => ({
|
|
78
|
+
// Both roots are env-only, so the plugin and the orchestrator always
|
|
79
|
+
// resolve the same paths.
|
|
80
|
+
socketsRoot: process.env.DSH_PODMAN_SOCKETS_ROOT ?? "/run/dsh-podman",
|
|
81
|
+
defaultImage: config.defaultImage.get() || "archlinux",
|
|
82
|
+
projectsRoot: process.env.DSH_PODMAN_PROJECTS_ROOT ?? "/projects",
|
|
83
|
+
controlToken: process.env.DSH_PODMAN_ORCHESTRATOR_TOKEN ?? "",
|
|
84
|
+
containerEnv: config.containerEnv.get(),
|
|
85
|
+
}), ctx.workspaceRegistry);
|
|
91
86
|
ctx.provide("workspaceResolver", resolver);
|
|
87
|
+
ctx.effect(() => () => resolver.dispose(), "podman: resolver cleanup");
|
|
92
88
|
const subprocess = createSubprocessProvider(resolver);
|
|
93
89
|
ctx.provide("subprocess", subprocess);
|
|
94
90
|
ctx.effect(() => () => subprocess.dispose(), "podman: subprocess cleanup");
|
|
@@ -102,8 +98,17 @@ export function apply(ctx, config = {}) {
|
|
|
102
98
|
promptCtx.on("system-prompt/assemble", async (_assembly, _context, next) => withoutHarnessSourceSection(await next()), { global: true, prepend: true });
|
|
103
99
|
});
|
|
104
100
|
registerTools(ctx, resolver);
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
// The plugin ships its own configuration card, so the host must not
|
|
102
|
+
// auto-generate a settings page for the preferences namespace.
|
|
103
|
+
ctx.inject(["settings"], (child) => {
|
|
104
|
+
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber), "podman: settings presentation");
|
|
105
|
+
});
|
|
106
|
+
registerCardRoute(ctx, resolver, ctx.workspaceRegistry, readLocale);
|
|
107
|
+
// The Podman terminal tab talks to guest ptys through its own routes; the
|
|
108
|
+
// registry keeps shells alive while the browser is away.
|
|
109
|
+
const terminalSessions = new TerminalSessions({ resolver });
|
|
110
|
+
ctx.effect(() => () => terminalSessions.dispose(), "podman: terminal sessions cleanup");
|
|
111
|
+
registerTerminalRoutes(ctx, resolver, terminalSessions, ctx.workspaceRegistry);
|
|
107
112
|
}
|
|
108
113
|
function registerTools(ctx, resolver) {
|
|
109
114
|
for (const tool of TOOLS) {
|
|
@@ -127,14 +132,15 @@ function registerTools(ctx, resolver) {
|
|
|
127
132
|
}));
|
|
128
133
|
}
|
|
129
134
|
}
|
|
130
|
-
export {
|
|
131
|
-
export { READ_ONLY_GATED_TOOLS, REMOUNT_TOOL_NAME,
|
|
135
|
+
export { approvalDecision, isSandboxEscalation, mountDestinationsReason, PODMAN_OPS_APPROVAL_TOOLS, PODMAN_OPS_PRESET, preExecutePolicy, READ_ONLY_TOOLS, SANDBOX_ESCALATION_REASON_PREFIX, summarizeArgs, } from "./approval.js";
|
|
136
|
+
export { createReadOnlyShellGate, READ_ONLY_GATED_TOOLS, REMOUNT_TOOL_NAME, } from "./read-only-shell.js";
|
|
132
137
|
export { createFilesystemProvider } from "./fs-provider.js";
|
|
133
|
-
export {
|
|
138
|
+
export { globCwd, remoteArgv, resolveGuestCwd, resolveGuestPath, } from "./guest-rpc.js";
|
|
134
139
|
export { inferMountKind, projectMountDestinationReason, projectMountMirror, } from "./mount-input.js";
|
|
135
|
-
export { HARNESS_SOURCE_SECTION,
|
|
136
|
-
export {
|
|
140
|
+
export { HARNESS_SOURCE_SECTION, podmanRuntimeSection, withoutHarnessSourceSection, } from "./prompts.js";
|
|
141
|
+
export { Config } from "./settings-schema.js";
|
|
142
|
+
export { publicContainer, publicDaemon, publicImage, publicMount, } from "./public.js";
|
|
137
143
|
export { createSubprocessProvider, outputReader, } from "./subprocess.js";
|
|
138
144
|
export { toolHandlers } from "./tool-handlers.js";
|
|
139
|
-
export {
|
|
145
|
+
export { containerBashParameters, containerEditParameters, containerExecParameters, containerGlobParameters, containerGrepParameters, containerListParameters, containerMountAddParameters, containerMountListParameters, containerMountRemoveParameters, containerReadParameters, containerRecreateParameters, containerRemoveParameters, containerSecretAddParameters, containerSecretRemoveParameters, containerStartParameters, containerWriteParameters, daemonListParameters, daemonLogsParameters, daemonRestartParameters, daemonStartParameters, daemonStopParameters, defineTool, envParam, imageBuildParameters, imageGetParameters, imageListParameters, imageRebuildAllParameters, imageRebuildParameters, imageRemoveParameters, secretCreateParameters, secretListParameters, secretRemoveParameters, toolOutput, TOOLS, volumeCreateParameters, volumeListParameters, volumeRemoveParameters, } from "./tool-schemas.js";
|
|
140
146
|
export { toolCallView, toolResultView } from "./tool-views.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|