@tickernelz/paperclip-pro-plugin-daytona 2026.925.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +49 -0
  2. package/dist/duplex-command-stream.d.ts +97 -0
  3. package/dist/duplex-command-stream.d.ts.map +1 -0
  4. package/dist/duplex-command-stream.js +205 -0
  5. package/dist/duplex-command-stream.js.map +1 -0
  6. package/dist/duplex-command-stream.live.test.d.ts +2 -0
  7. package/dist/duplex-command-stream.live.test.d.ts.map +1 -0
  8. package/dist/duplex-command-stream.live.test.js +324 -0
  9. package/dist/duplex-command-stream.live.test.js.map +1 -0
  10. package/dist/duplex-command-stream.test.d.ts +2 -0
  11. package/dist/duplex-command-stream.test.d.ts.map +1 -0
  12. package/dist/duplex-command-stream.test.js +519 -0
  13. package/dist/duplex-command-stream.test.js.map +1 -0
  14. package/dist/file-sync.d.ts +77 -0
  15. package/dist/file-sync.d.ts.map +1 -0
  16. package/dist/file-sync.js +1055 -0
  17. package/dist/file-sync.js.map +1 -0
  18. package/dist/file-sync.test.d.ts +2 -0
  19. package/dist/file-sync.test.d.ts.map +1 -0
  20. package/dist/file-sync.test.js +974 -0
  21. package/dist/file-sync.test.js.map +1 -0
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +3 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/login-pty.d.ts +162 -0
  27. package/dist/login-pty.d.ts.map +1 -0
  28. package/dist/login-pty.js +258 -0
  29. package/dist/login-pty.js.map +1 -0
  30. package/dist/login-pty.test.d.ts +2 -0
  31. package/dist/login-pty.test.d.ts.map +1 -0
  32. package/dist/login-pty.test.js +319 -0
  33. package/dist/login-pty.test.js.map +1 -0
  34. package/dist/manifest.d.ts +4 -0
  35. package/dist/manifest.d.ts.map +1 -0
  36. package/dist/manifest.js +179 -0
  37. package/dist/manifest.js.map +1 -0
  38. package/dist/plugin.d.ts +49 -0
  39. package/dist/plugin.d.ts.map +1 -0
  40. package/dist/plugin.js +2563 -0
  41. package/dist/plugin.js.map +1 -0
  42. package/dist/plugin.test.d.ts +2 -0
  43. package/dist/plugin.test.d.ts.map +1 -0
  44. package/dist/plugin.test.js +4701 -0
  45. package/dist/plugin.test.js.map +1 -0
  46. package/dist/pty-chunked-input.d.ts +48 -0
  47. package/dist/pty-chunked-input.d.ts.map +1 -0
  48. package/dist/pty-chunked-input.js +74 -0
  49. package/dist/pty-chunked-input.js.map +1 -0
  50. package/dist/pty-chunked-input.test.d.ts +2 -0
  51. package/dist/pty-chunked-input.test.d.ts.map +1 -0
  52. package/dist/pty-chunked-input.test.js +115 -0
  53. package/dist/pty-chunked-input.test.js.map +1 -0
  54. package/dist/worker.d.ts +3 -0
  55. package/dist/worker.d.ts.map +1 -0
  56. package/dist/worker.js +5 -0
  57. package/dist/worker.js.map +1 -0
  58. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # `@tickernelz/paperclip-pro-plugin-daytona`
2
+
3
+ Published Daytona sandbox provider plugin for Paperclip.
4
+
5
+ This package lives in the Paperclip monorepo, but it is intentionally excluded from the root `pnpm` workspace and shaped to publish and install like a standalone npm package. That lets operators install it from the Plugins page by package name without introducing root lockfile churn for Daytona's SDK dependencies.
6
+
7
+ ## Install
8
+
9
+ From a Paperclip instance, install:
10
+
11
+ ```text
12
+ @tickernelz/paperclip-pro-plugin-daytona
13
+ ```
14
+
15
+ The host plugin installer runs `npm install` into the managed plugin directory, so transitive dependencies such as `@daytonaio/sdk` are pulled in during installation.
16
+
17
+ ## Configuration
18
+
19
+ Configure Daytona from `Instance Settings -> Environments`, not from the plugin's plugin page.
20
+
21
+ - Put the Daytona API key on the sandbox environment itself.
22
+ - When you save an environment, Paperclip stores pasted API keys as company secrets.
23
+ - `DAYTONA_API_KEY` remains an optional host-level fallback when an environment omits the key.
24
+ - Optional `apiUrl` and `target` settings map directly to the Daytona SDK/client configuration. If `apiUrl` is omitted, the Daytona SDK uses its default endpoint.
25
+
26
+ Notes:
27
+
28
+ - The current published Daytona SDK package is `@daytonaio/sdk`.
29
+ - The driver supports both `snapshot`-based and `image`-based sandbox creation. If both are set, validation rejects the config as ambiguous.
30
+ - Reusable leases map to Daytona stop/start semantics. Non-reusable leases are deleted on release. A provider-resolved `target` does not change the identity of an existing sandbox. Release closes the same scoped lease that a later sentinel-verified resume reopens.
31
+ - A sandbox record can survive the loss of its underlying container. Resume treats it as expired only when a fresh provider read confirms the exact missing-container error for that sandbox and marks it unrecoverable. Unknown errors and failed confirmation reads preserve the lease. The host still requires a verified native-runner backup before replacement.
32
+
33
+ ## Local development
34
+
35
+ ```bash
36
+ cd packages/plugins/sandbox-providers/daytona
37
+ pnpm install --ignore-workspace --no-lockfile
38
+ pnpm build
39
+ pnpm test
40
+ pnpm typecheck
41
+ ```
42
+
43
+ These commands assume the repo root has already been installed once so the local `@tickernelz/paperclip-pro-plugin-sdk` workspace package is available to the compiler during development.
44
+
45
+ ## Package layout
46
+
47
+ - `src/manifest.ts` declares the sandbox-provider driver metadata
48
+ - `src/plugin.ts` implements the environment lifecycle hooks
49
+ - `paperclipPlugin.manifest` and `paperclipPlugin.worker` point the host at the built plugin entrypoints in `dist/`
@@ -0,0 +1,97 @@
1
+ import type { DaytonaPtyProcess } from "./login-pty.js";
2
+ export type { DaytonaPtyCreateOptions, DaytonaPtyHandle, DaytonaPtyProcess, } from "./login-pty.js";
3
+ /**
4
+ * A live duplex channel session for one command. The session allocates a real
5
+ * pseudo-terminal in raw mode, streams the raw output, accepts host input, and
6
+ * stops the child. The shape matches the worker duplex channel session, so the
7
+ * worker forwards the data and the exit with no adapter.
8
+ */
9
+ export interface DuplexChannelSession {
10
+ /** Registers the one data listener. The session streams each raw byte chunk in order. */
11
+ onData(listener: (chunk: Uint8Array) => void): void;
12
+ /** Writes raw input bytes to the pseudo-terminal. */
13
+ write(data: Uint8Array): void;
14
+ /**
15
+ * Resolves when the command ends or the transport closes. A numeric `exitCode`
16
+ * is a real process exit. `transportClosed` is true when the pseudo-terminal
17
+ * socket closed with no exit data, so the caller can tell a real process exit
18
+ * from a reason-less transport close.
19
+ */
20
+ wait(): Promise<{
21
+ exitCode: number | null;
22
+ transportClosed: boolean;
23
+ }>;
24
+ /** Stops the child process. Safe to call more than one time. */
25
+ kill(): void;
26
+ /** Releases the session resources. Safe to call more than one time. */
27
+ close(): Promise<void>;
28
+ }
29
+ /**
30
+ * Opens a {@link DuplexChannelSession} for `command`. The transport calls it one
31
+ * time. `command` is an argument vector: element 0 is the program and the rest
32
+ * are its arguments. The session quotes each element, so a shell metacharacter in
33
+ * an element cannot inject a shell command.
34
+ */
35
+ export type DuplexChannelSessionOpener = (command: readonly string[]) => Promise<DuplexChannelSession>;
36
+ /**
37
+ * The typed, closed reason for a duplex channel write failure. The seam reports
38
+ * only this constant, never the raw provider error text. It maps to the host
39
+ * telemetry `write_error` loss reason.
40
+ */
41
+ export declare const DAYTONA_DUPLEX_WRITE_ERROR_REASON: "write_error";
42
+ /** The typed reason a rejected host-to-sandbox write reports. */
43
+ export type DaytonaDuplexWriteErrorReason = typeof DAYTONA_DUPLEX_WRITE_ERROR_REASON;
44
+ /** The options for the Daytona duplex channel session. */
45
+ export interface DaytonaDuplexChannelOptions {
46
+ /** The working directory for the duplex channel PTY. Defaults to the sandbox default. */
47
+ cwd?: string;
48
+ /**
49
+ * The absolute sandbox path for the gateway diagnostics. The wrapper redirects
50
+ * the terminal's stderr to this path, so a diagnostic line never reaches the
51
+ * stdout frame stream. Defaults to a per-channel path under `/tmp`.
52
+ */
53
+ diagnosticsPath?: string;
54
+ /**
55
+ * The write-error seam. The session calls it one time when a host-to-sandbox
56
+ * `sendInput` rejects. The session then ends the channel at once. The seam
57
+ * carries only the typed {@link DaytonaDuplexWriteErrorReason}; the raw provider
58
+ * error never reaches it.
59
+ */
60
+ onWriteError?: (reason: DaytonaDuplexWriteErrorReason) => void;
61
+ }
62
+ /**
63
+ * Builds the launch wrapper input line for the duplex channel. `command` is an
64
+ * argument vector: element 0 is the program and the rest are its arguments.
65
+ *
66
+ * The wrapper does three steps in order:
67
+ * 1. `exec 2>'<diagnosticsPath>'` redirects the shell's stderr to the file. The
68
+ * later `exec` inherits it, so the gateway diagnostics land in the file and
69
+ * never on the stdout frame stream.
70
+ * 2. `stty raw -echo` sets the terminal to raw mode with echo off, so the
71
+ * terminal neither echoes host input as data nor translates newlines.
72
+ * 3. `exec '<program>' '<arg>'...` replaces the shell with the gateway, so the
73
+ * PTY runs the gateway directly and the gateway exit code becomes the PTY
74
+ * exit code.
75
+ *
76
+ * The wrapper quotes the diagnostics path and every command argument as a
77
+ * single-quoted shell word. So a shell metacharacter in a path or an argument
78
+ * stays literal text and cannot inject a shell command.
79
+ */
80
+ export declare function buildDuplexChannelLaunchWrapper(command: readonly string[], diagnosticsPath: string): string;
81
+ /**
82
+ * Opens a Daytona duplex channel PTY session for `command` and returns it as a
83
+ * {@link DuplexChannelSession}. The session allocates a real pseudo-terminal in
84
+ * raw mode, streams the raw output, accepts host input, and stops the child.
85
+ *
86
+ * The function forwards the terminal bytes unchanged. It buffers the output
87
+ * until the transport registers the listener, so no early chunk is lost.
88
+ */
89
+ export declare function openDaytonaDuplexChannelSession(process: DaytonaPtyProcess, command: readonly string[], options?: DaytonaDuplexChannelOptions): Promise<DuplexChannelSession>;
90
+ /**
91
+ * Creates a {@link DuplexChannelSessionOpener} bound to a Daytona `process`. Pass
92
+ * `sandbox.process` from the Daytona SDK. The opener runs the gateway command on a
93
+ * raw pseudo-terminal, streams the output, accepts host input, and stops the child
94
+ * for a terminal state.
95
+ */
96
+ export declare function createDaytonaDuplexChannelSessionOpener(process: DaytonaPtyProcess, options?: DaytonaDuplexChannelOptions): DuplexChannelSessionOpener;
97
+ //# sourceMappingURL=duplex-command-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplex-command-stream.d.ts","sourceRoot":"","sources":["../src/duplex-command-stream.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAIxB;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,yFAAyF;IACzF,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACpD,qDAAqD;IACrD,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B;;;;;OAKG;IACH,IAAI,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,eAAe,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACvE,gEAAgE;IAChE,IAAI,IAAI,IAAI,CAAC;IACb,uEAAuE;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACvC,OAAO,EAAE,SAAS,MAAM,EAAE,KACvB,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEnC;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,EAAG,aAAsB,CAAC;AAExE,iEAAiE;AACjE,MAAM,MAAM,6BAA6B,GAAG,OAAO,iCAAiC,CAAC;AAErF,0DAA0D;AAC1D,MAAM,WAAW,2BAA2B;IAC1C,yFAAyF;IACzF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,6BAA6B,KAAK,IAAI,CAAC;CAChE;AAwBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,eAAe,EAAE,MAAM,GACtB,MAAM,CASR;AAED;;;;;;;GAOG;AACH,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,oBAAoB,CAAC,CA2G/B;AAED;;;;;GAKG;AACH,wBAAgB,uCAAuC,CACrD,OAAO,EAAE,iBAAiB,EAC1B,OAAO,CAAC,EAAE,2BAA2B,GACpC,0BAA0B,CAG5B"}
@@ -0,0 +1,205 @@
1
+ // The Daytona duplex command stream for the sandbox callback bridge. The bridge
2
+ // needs one live bidirectional byte stream: the host writes request frames to the
3
+ // process, and the process writes response frames back, on one connection while
4
+ // the command runs. This module binds a Daytona pseudo-terminal (PTY) to a small
5
+ // session that the duplex transport consumes, so the gateway runs on a real
6
+ // terminal, streams its output, and receives host input.
7
+ //
8
+ // Primitive choice (the first acceptance criterion of the phase): the Daytona SDK
9
+ // proves two primitives. The ordinary session primitive (`createSession` plus
10
+ // `executeSessionCommand` plus `getSessionCommandLogs`) dispatches a command and
11
+ // tails its output, split into separate stdout and stderr streams. It exposes no
12
+ // call that writes bytes to a running command's stdin. The PTY primitive
13
+ // (`createPty`) exposes `sendInput` for host-to-process bytes and one `onData`
14
+ // callback for process-to-host bytes, on one live socket. Only the PTY carries a
15
+ // bidirectional stream on one connection, so this module uses the PTY. The
16
+ // characterization test in `duplex-command-stream.test.ts` records the evidence.
17
+ //
18
+ // Clean stream: a PTY echoes its input and translates newlines by default
19
+ // (`ICRNL` on input, `ONLCR` on output). The frames are newline-delimited JSON, so
20
+ // echo and newline translation corrupt the stream. The launch wrapper runs
21
+ // `stty raw -echo` before it starts the gateway, so the terminal becomes an 8-bit
22
+ // transparent path with no echo and no newline translation. The wrapper also
23
+ // redirects the gateway diagnostics to a file, so a diagnostic line never reaches
24
+ // the stdout frame stream. The wrapper starts the gateway with `exec`, so the PTY
25
+ // runs the gateway directly and the gateway exit code becomes the PTY exit code.
26
+ //
27
+ // Dependency boundary: this provider plugin ships standalone (the workspace
28
+ // excludes `packages/plugins/sandbox-providers/**`). So the module imports no
29
+ // workspace package. It reuses the narrow Daytona PTY surface that
30
+ // `login-pty.ts` declares (`DaytonaPtyProcess`, `DaytonaPtyHandle`,
31
+ // `DaytonaPtyCreateOptions`), so a caller passes `sandbox.process` with no
32
+ // adapter. The narrow surface keeps the module unit-testable with a fake PTY.
33
+ import { randomUUID } from "node:crypto";
34
+ import { sendPtyInputInChunks } from "./pty-chunked-input.js";
35
+ /**
36
+ * The typed, closed reason for a duplex channel write failure. The seam reports
37
+ * only this constant, never the raw provider error text. It maps to the host
38
+ * telemetry `write_error` loss reason.
39
+ */
40
+ export const DAYTONA_DUPLEX_WRITE_ERROR_REASON = "write_error";
41
+ // The terminal size for the duplex channel PTY. The channel carries bytes, not a
42
+ // visible UI, so a fixed standard size is enough. A fixed size keeps the launch
43
+ // deterministic.
44
+ const DUPLEX_CHANNEL_PTY_COLS = 120;
45
+ const DUPLEX_CHANNEL_PTY_ROWS = 30;
46
+ /**
47
+ * The input terminator that submits the launch wrapper line. The terminal reads
48
+ * the carriage return as the Enter key, so the shell runs the wrapper line.
49
+ */
50
+ const PTY_COMMAND_TERMINATOR = "\r";
51
+ /**
52
+ * Quotes one value for a POSIX shell. The result is one single-quoted word, so
53
+ * the shell reads it as literal text and never expands or splits it. The function
54
+ * closes the quote, adds an escaped single quote, and reopens the quote for each
55
+ * single quote in the value.
56
+ */
57
+ function shellQuote(value) {
58
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
59
+ }
60
+ /**
61
+ * Builds the launch wrapper input line for the duplex channel. `command` is an
62
+ * argument vector: element 0 is the program and the rest are its arguments.
63
+ *
64
+ * The wrapper does three steps in order:
65
+ * 1. `exec 2>'<diagnosticsPath>'` redirects the shell's stderr to the file. The
66
+ * later `exec` inherits it, so the gateway diagnostics land in the file and
67
+ * never on the stdout frame stream.
68
+ * 2. `stty raw -echo` sets the terminal to raw mode with echo off, so the
69
+ * terminal neither echoes host input as data nor translates newlines.
70
+ * 3. `exec '<program>' '<arg>'...` replaces the shell with the gateway, so the
71
+ * PTY runs the gateway directly and the gateway exit code becomes the PTY
72
+ * exit code.
73
+ *
74
+ * The wrapper quotes the diagnostics path and every command argument as a
75
+ * single-quoted shell word. So a shell metacharacter in a path or an argument
76
+ * stays literal text and cannot inject a shell command.
77
+ */
78
+ export function buildDuplexChannelLaunchWrapper(command, diagnosticsPath) {
79
+ if (command.length === 0) {
80
+ throw new Error("The duplex channel launch command needs at least one argument.");
81
+ }
82
+ const quotedCommand = command.map(shellQuote).join(" ");
83
+ return (`exec 2>${shellQuote(diagnosticsPath)}; stty raw -echo; ` +
84
+ `exec ${quotedCommand}${PTY_COMMAND_TERMINATOR}`);
85
+ }
86
+ /**
87
+ * Opens a Daytona duplex channel PTY session for `command` and returns it as a
88
+ * {@link DuplexChannelSession}. The session allocates a real pseudo-terminal in
89
+ * raw mode, streams the raw output, accepts host input, and stops the child.
90
+ *
91
+ * The function forwards the terminal bytes unchanged. It buffers the output
92
+ * until the transport registers the listener, so no early chunk is lost.
93
+ */
94
+ export async function openDaytonaDuplexChannelSession(process, command, options) {
95
+ let listener = null;
96
+ let buffered = Buffer.alloc(0);
97
+ const diagnosticsPath = options?.diagnosticsPath ?? `/tmp/paperclip-duplex-${randomUUID()}.log`;
98
+ const handle = await process.createPty({
99
+ id: `paperclip-duplex-${randomUUID()}`,
100
+ ...(options?.cwd ? { cwd: options.cwd } : {}),
101
+ cols: DUPLEX_CHANNEL_PTY_COLS,
102
+ rows: DUPLEX_CHANNEL_PTY_ROWS,
103
+ onData: (data) => {
104
+ // Forward the raw bytes unchanged; the frame codec owns the
105
+ // newline-delimited JSON parsing and any multi-byte character
106
+ // reassembly on the read side.
107
+ if (data.byteLength === 0)
108
+ return;
109
+ if (listener)
110
+ listener(data);
111
+ else
112
+ buffered = buffered.length === 0 ? Buffer.from(data) : Buffer.concat([buffered, data]);
113
+ },
114
+ });
115
+ await handle.waitForConnection();
116
+ // Start the gateway through the raw-mode wrapper. Raw mode with echo off stops
117
+ // the terminal from echoing host input back as data and from translating the
118
+ // frame newlines. The diagnostics redirect keeps the stdout frame stream clean.
119
+ await handle.sendInput(buildDuplexChannelLaunchWrapper(command, diagnosticsPath));
120
+ // Report a host-to-sandbox write failure one time and end the channel at once.
121
+ // The seam carries only the typed reason; the raw provider error never leaves
122
+ // this scope. The channel end propagates the loss up through the exit.
123
+ //
124
+ // `channelTerminated` turns true on the first rejected chunk, before the early
125
+ // return, so a later queued write reads it and sends no chunk. Without the flag
126
+ // a queued write runs after the terminal kill and calls `sendInput` on the
127
+ // closed transport.
128
+ let channelTerminated = false;
129
+ let writeErrorReported = false;
130
+ const endOnWriteError = () => {
131
+ channelTerminated = true;
132
+ if (writeErrorReported)
133
+ return;
134
+ writeErrorReported = true;
135
+ options?.onWriteError?.(DAYTONA_DUPLEX_WRITE_ERROR_REASON);
136
+ void handle.kill().catch(() => undefined);
137
+ };
138
+ // The tail of the write chain. The chunker awaits each chunk, so one write can
139
+ // suspend between its chunks. The chain runs each write after the previous
140
+ // write ends, so the chunks of two writes never interleave on the wire. The
141
+ // chain never rejects, because the chunker maps a rejected send to
142
+ // `endOnWriteError` and returns.
143
+ let writeChain = Promise.resolve();
144
+ return {
145
+ onData(next) {
146
+ listener = next;
147
+ if (buffered.length > 0) {
148
+ const pending = buffered;
149
+ buffered = Buffer.alloc(0);
150
+ next(pending);
151
+ }
152
+ },
153
+ write(data) {
154
+ // Send the input as byte-bounded chunks under the provider message cap. A
155
+ // whole payload in one message can cross the cap and take the channel down,
156
+ // so the chunker slices the payload and sends each chunk in order. The chain
157
+ // runs this write after the previous write ends, so two writes keep their
158
+ // order and never interleave their chunks. A write error must not throw into
159
+ // the transport, so the transport's stream stays the single result path. On
160
+ // a rejected chunk, the chunker ends the channel one time through
161
+ // `endOnWriteError` and sends no later chunk. The raw provider error never
162
+ // reaches a sink.
163
+ //
164
+ // A rejected chunk terminalizes the channel and kills the transport. So this
165
+ // write skips the send when `channelTerminated` is true, and a queued write
166
+ // never calls `sendInput` on the closed transport.
167
+ writeChain = writeChain.then(() => {
168
+ if (channelTerminated)
169
+ return;
170
+ return sendPtyInputInChunks((chunk) => handle.sendInput(chunk), data, endOnWriteError);
171
+ });
172
+ },
173
+ async wait() {
174
+ const result = await handle.wait();
175
+ if (typeof result.exitCode === "number") {
176
+ // A numeric exit code is a real process exit. The SDK parses it from the
177
+ // pseudo-terminal WebSocket close reason (for example `{"exitCode":0}`).
178
+ return { exitCode: result.exitCode, transportClosed: false };
179
+ }
180
+ // A non-numeric exit code marks a reason-less transport close: the socket
181
+ // closed with no exit data. It is a transport close, not a process exit.
182
+ return { exitCode: null, transportClosed: true };
183
+ },
184
+ kill() {
185
+ void handle.kill().catch(() => undefined);
186
+ },
187
+ async close() {
188
+ // Stop the child and release the socket. `kill` ends the gateway and `wait`
189
+ // resolves with the exit; `disconnect` releases the PTY socket. Both are
190
+ // safe to call more than one time, so close stays idempotent.
191
+ await handle.kill().catch(() => undefined);
192
+ await handle.disconnect().catch(() => undefined);
193
+ },
194
+ };
195
+ }
196
+ /**
197
+ * Creates a {@link DuplexChannelSessionOpener} bound to a Daytona `process`. Pass
198
+ * `sandbox.process` from the Daytona SDK. The opener runs the gateway command on a
199
+ * raw pseudo-terminal, streams the output, accepts host input, and stops the child
200
+ * for a terminal state.
201
+ */
202
+ export function createDaytonaDuplexChannelSessionOpener(process, options) {
203
+ return (command) => openDaytonaDuplexChannelSession(process, command, options);
204
+ }
205
+ //# sourceMappingURL=duplex-command-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplex-command-stream.js","sourceRoot":"","sources":["../src/duplex-command-stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=duplex-command-stream.live.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplex-command-stream.live.test.d.ts","sourceRoot":"","sources":["../src/duplex-command-stream.live.test.ts"],"names":[],"mappings":""}