@declaw/sdk 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/index.cjs +240 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -33
- package/dist/index.d.ts +107 -33
- package/dist/index.js +239 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,46 @@ All notable changes to the Declaw TypeScript / JavaScript SDK are documented in
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.3]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `sandbox.pty.connect(pid, { onData })` — reattach a fresh data callback
|
|
13
|
+
to an already-running PTY session by pid. Multiple clients can
|
|
14
|
+
subscribe concurrently; each receives output from the moment it
|
|
15
|
+
connects (no scrollback replay).
|
|
16
|
+
- Server-side PTY session TTL. The `timeout` option on
|
|
17
|
+
`sandbox.pty.create()` is now enforced inside the sandbox — the PTY
|
|
18
|
+
is terminated when the deadline elapses, even if no client is
|
|
19
|
+
attached. `timeout: 0` keeps it alive indefinitely.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `PtyHandle.wait()` now resolves with a `PtyResult` object (`{ exitCode }`)
|
|
24
|
+
instead of a bare number. This matches the shape of other lifecycle
|
|
25
|
+
APIs and leaves room for future fields (e.g. signal). Existing
|
|
26
|
+
numeric access becomes `(await handle.wait()).exitCode`.
|
|
27
|
+
|
|
28
|
+
## [1.0.2]
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Real interactive PTY support. `sandbox.pty.create()` now returns a
|
|
33
|
+
`PtyHandle` with both an `onData` callback and an `async *stream()`
|
|
34
|
+
iterator that deliver raw terminal bytes as they arrive from the
|
|
35
|
+
sandbox (ANSI escapes included).
|
|
36
|
+
- `PtyHandle.sendInput`, `PtyHandle.resize`, `PtyHandle.kill`,
|
|
37
|
+
`PtyHandle.disconnect`, `PtyHandle.wait()` for full lifecycle control.
|
|
38
|
+
- `ApiClient.streamGet()` for SSE consumers.
|
|
39
|
+
- `PtyHandle` exported from the package root.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- `Pty.create()` accepts `onData` in `PtyCreateOpts` and returns the new
|
|
44
|
+
`PtyHandle` type. Existing fields are unchanged; callers that only
|
|
45
|
+
used the returned handle for `sendInput`/`resize`/`kill` continue to
|
|
46
|
+
work without edits.
|
|
47
|
+
|
|
8
48
|
## [1.0.0]
|
|
9
49
|
|
|
10
50
|
First stable release. The public API described below is covered by
|
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
NotFoundError: () => NotFoundError,
|
|
43
43
|
PIIType: () => PIIType,
|
|
44
44
|
Pty: () => Pty,
|
|
45
|
+
PtyHandle: () => PtyHandle,
|
|
45
46
|
RedactionAction: () => RedactionAction,
|
|
46
47
|
Sandbox: () => Sandbox,
|
|
47
48
|
SandboxError: () => SandboxError,
|
|
@@ -263,6 +264,13 @@ var ApiClient = class {
|
|
|
263
264
|
async stream(path, opts) {
|
|
264
265
|
return this.requestWithRetry("POST", path, opts, true);
|
|
265
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Send a GET request and return the raw Response for SSE streaming.
|
|
269
|
+
* Does NOT parse the response body. Used by PTY stream consumers.
|
|
270
|
+
*/
|
|
271
|
+
async streamGet(path, opts) {
|
|
272
|
+
return this.requestWithRetry("GET", path, opts, true);
|
|
273
|
+
}
|
|
266
274
|
/**
|
|
267
275
|
* Abort all in-flight requests and release resources.
|
|
268
276
|
*/
|
|
@@ -542,18 +550,12 @@ function parseInjectionDefenseConfig(data) {
|
|
|
542
550
|
// src/security/audit.ts
|
|
543
551
|
function createAuditConfig(opts) {
|
|
544
552
|
return {
|
|
545
|
-
enabled: opts?.enabled ??
|
|
546
|
-
logRequestBody: opts?.logRequestBody ?? true,
|
|
547
|
-
logResponseBody: opts?.logResponseBody ?? false,
|
|
548
|
-
retentionHours: opts?.retentionHours ?? 24
|
|
553
|
+
enabled: opts?.enabled ?? true
|
|
549
554
|
};
|
|
550
555
|
}
|
|
551
556
|
function parseAuditConfig(data) {
|
|
552
557
|
return {
|
|
553
|
-
enabled: data.enabled ??
|
|
554
|
-
logRequestBody: data.log_request_body ?? data.logRequestBody ?? true,
|
|
555
|
-
logResponseBody: data.log_response_body ?? data.logResponseBody ?? false,
|
|
556
|
-
retentionHours: data.retention_hours ?? data.retentionHours ?? 24
|
|
558
|
+
enabled: data.enabled ?? true
|
|
557
559
|
};
|
|
558
560
|
}
|
|
559
561
|
function parseAuditEntry(data) {
|
|
@@ -850,7 +852,7 @@ function createSecurityPolicy(opts) {
|
|
|
850
852
|
injectionDefense: opts?.injectionDefense ?? false,
|
|
851
853
|
transformations: opts?.transformations ?? [],
|
|
852
854
|
network: opts?.network,
|
|
853
|
-
audit: opts?.audit ??
|
|
855
|
+
audit: opts?.audit ?? true,
|
|
854
856
|
envSecurity: opts?.envSecurity ?? createEnvSecurityConfig(),
|
|
855
857
|
toxicity: opts?.toxicity,
|
|
856
858
|
codeSecurity: opts?.codeSecurity,
|
|
@@ -865,7 +867,7 @@ function parseSecurityPolicy(data) {
|
|
|
865
867
|
injectionDefense: typeof injDef === "boolean" ? injDef : injDef ? parseInjectionDefenseConfig(injDef) : false,
|
|
866
868
|
transformations: Array.isArray(data.transformations) ? data.transformations.map((t) => parseTransformationRule(t)) : [],
|
|
867
869
|
network: data.network ? parseNetworkPolicy(data.network) : void 0,
|
|
868
|
-
audit: typeof auditData === "boolean" ? auditData : auditData ? parseAuditConfig(auditData) :
|
|
870
|
+
audit: typeof auditData === "boolean" ? auditData : auditData ? parseAuditConfig(auditData) : true,
|
|
869
871
|
envSecurity: data.env_security ?? data.envSecurity ? parseEnvSecurityConfig(data.env_security ?? data.envSecurity) : createEnvSecurityConfig(),
|
|
870
872
|
toxicity: data.toxicity ? parseToxicityConfig(data.toxicity) : void 0,
|
|
871
873
|
codeSecurity: data.code_security ?? data.codeSecurity ? parseCodeSecurityConfig(data.code_security ?? data.codeSecurity) : void 0,
|
|
@@ -894,10 +896,7 @@ function securityPolicyToJSON(policy) {
|
|
|
894
896
|
}
|
|
895
897
|
const auditConfig = typeof policy.audit === "boolean" ? createAuditConfig({ enabled: policy.audit }) : policy.audit;
|
|
896
898
|
const audit = {
|
|
897
|
-
enabled: auditConfig.enabled
|
|
898
|
-
log_request_body: auditConfig.logRequestBody,
|
|
899
|
-
log_response_body: auditConfig.logResponseBody,
|
|
900
|
-
retention_hours: auditConfig.retentionHours
|
|
899
|
+
enabled: auditConfig.enabled
|
|
901
900
|
};
|
|
902
901
|
const envSec = {
|
|
903
902
|
mask_patterns: policy.envSecurity.maskPatterns,
|
|
@@ -1364,6 +1363,11 @@ var Filesystem = class {
|
|
|
1364
1363
|
/**
|
|
1365
1364
|
* Write data to a file.
|
|
1366
1365
|
*
|
|
1366
|
+
* When `data` is a `Uint8Array`, the SDK streams the raw bytes to the
|
|
1367
|
+
* binary-safe `PUT /files/raw` endpoint (500 MiB cap). When it's a string,
|
|
1368
|
+
* the SDK uses the JSON `POST /files` endpoint. Callers do not need to
|
|
1369
|
+
* pick the transport.
|
|
1370
|
+
*
|
|
1367
1371
|
* @param path - Absolute path to the file.
|
|
1368
1372
|
* @param data - String or Uint8Array content to write.
|
|
1369
1373
|
* @param opts - Optional user and request timeout.
|
|
@@ -1371,11 +1375,22 @@ var Filesystem = class {
|
|
|
1371
1375
|
*/
|
|
1372
1376
|
async write(path, data, opts) {
|
|
1373
1377
|
const user = opts?.user ?? DEFAULT_USER;
|
|
1374
|
-
|
|
1378
|
+
if (data instanceof Uint8Array) {
|
|
1379
|
+
const result2 = await this.client.put(
|
|
1380
|
+
`/sandboxes/${this.sandboxId}/files/raw`,
|
|
1381
|
+
{
|
|
1382
|
+
params: { path, username: user },
|
|
1383
|
+
body: data,
|
|
1384
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
1385
|
+
timeout: opts?.requestTimeout
|
|
1386
|
+
}
|
|
1387
|
+
);
|
|
1388
|
+
return parseWriteInfo(result2);
|
|
1389
|
+
}
|
|
1375
1390
|
const result = await this.client.post(
|
|
1376
1391
|
`/sandboxes/${this.sandboxId}/files`,
|
|
1377
1392
|
{
|
|
1378
|
-
json: { path, data
|
|
1393
|
+
json: { path, data, username: user },
|
|
1379
1394
|
timeout: opts?.requestTimeout
|
|
1380
1395
|
}
|
|
1381
1396
|
);
|
|
@@ -1384,24 +1399,58 @@ var Filesystem = class {
|
|
|
1384
1399
|
/**
|
|
1385
1400
|
* Write multiple files in a single batch request.
|
|
1386
1401
|
*
|
|
1402
|
+
* The batch endpoint is JSON-only and cannot carry binary. Entries are
|
|
1403
|
+
* partitioned: string entries go through `POST /files/batch` in one call,
|
|
1404
|
+
* `Uint8Array` entries are streamed individually to `PUT /files/raw`.
|
|
1405
|
+
* Results are merged back in input order.
|
|
1406
|
+
*
|
|
1387
1407
|
* @param files - Array of files to write.
|
|
1388
1408
|
* @param opts - Optional user and request timeout.
|
|
1389
|
-
* @returns Array of write info for each file.
|
|
1409
|
+
* @returns Array of write info for each file, in input order.
|
|
1390
1410
|
*/
|
|
1391
1411
|
async writeFiles(files, opts) {
|
|
1392
1412
|
const user = opts?.user ?? DEFAULT_USER;
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
{
|
|
1400
|
-
|
|
1401
|
-
timeout: opts?.requestTimeout
|
|
1413
|
+
const results = new Array(files.length);
|
|
1414
|
+
const strIndices = [];
|
|
1415
|
+
const bytesIndices = [];
|
|
1416
|
+
for (let i = 0; i < files.length; i++) {
|
|
1417
|
+
if (files[i].data instanceof Uint8Array) {
|
|
1418
|
+
bytesIndices.push(i);
|
|
1419
|
+
} else {
|
|
1420
|
+
strIndices.push(i);
|
|
1402
1421
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1422
|
+
}
|
|
1423
|
+
if (strIndices.length > 0) {
|
|
1424
|
+
const batchFiles = strIndices.map((i) => ({
|
|
1425
|
+
path: files[i].path,
|
|
1426
|
+
data: files[i].data
|
|
1427
|
+
}));
|
|
1428
|
+
const result = await this.client.post(
|
|
1429
|
+
`/sandboxes/${this.sandboxId}/files/batch`,
|
|
1430
|
+
{
|
|
1431
|
+
json: { files: batchFiles, username: user },
|
|
1432
|
+
timeout: opts?.requestTimeout
|
|
1433
|
+
}
|
|
1434
|
+
);
|
|
1435
|
+
const parsed = (result ?? []).map(parseWriteInfo);
|
|
1436
|
+
for (let k = 0; k < strIndices.length; k++) {
|
|
1437
|
+
results[strIndices[k]] = parsed[k];
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
for (const i of bytesIndices) {
|
|
1441
|
+
const entry = files[i];
|
|
1442
|
+
const result = await this.client.put(
|
|
1443
|
+
`/sandboxes/${this.sandboxId}/files/raw`,
|
|
1444
|
+
{
|
|
1445
|
+
params: { path: entry.path, username: user },
|
|
1446
|
+
body: entry.data,
|
|
1447
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
1448
|
+
timeout: opts?.requestTimeout
|
|
1449
|
+
}
|
|
1450
|
+
);
|
|
1451
|
+
results[i] = parseWriteInfo(result);
|
|
1452
|
+
}
|
|
1453
|
+
return results.filter((r) => r !== void 0);
|
|
1405
1454
|
}
|
|
1406
1455
|
/**
|
|
1407
1456
|
* List entries in a directory.
|
|
@@ -1540,52 +1589,188 @@ var Filesystem = class {
|
|
|
1540
1589
|
};
|
|
1541
1590
|
|
|
1542
1591
|
// src/sandbox/pty/pty.ts
|
|
1543
|
-
var
|
|
1592
|
+
var PtyHandle = class {
|
|
1593
|
+
pid;
|
|
1544
1594
|
sandboxId;
|
|
1545
1595
|
client;
|
|
1546
|
-
|
|
1596
|
+
exitPromise;
|
|
1597
|
+
resolveExit;
|
|
1598
|
+
aborter = new AbortController();
|
|
1599
|
+
constructor(pid, sandboxId, client, onData) {
|
|
1600
|
+
this.pid = pid;
|
|
1547
1601
|
this.sandboxId = sandboxId;
|
|
1548
1602
|
this.client = client;
|
|
1603
|
+
this.exitPromise = new Promise((resolve) => {
|
|
1604
|
+
this.resolveExit = resolve;
|
|
1605
|
+
});
|
|
1606
|
+
if (onData) {
|
|
1607
|
+
void this.consumeStream(onData);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
/** Forward keystrokes to the PTY. */
|
|
1611
|
+
async sendInput(data, requestTimeout) {
|
|
1612
|
+
const strData = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
1613
|
+
await this.client.post(
|
|
1614
|
+
`/sandboxes/${this.sandboxId}/pty/${this.pid}/stdin`,
|
|
1615
|
+
{ json: { data: strData }, timeout: requestTimeout }
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
/** Update the terminal size (TIOCSWINSZ inside the VM). */
|
|
1619
|
+
async resize(size, requestTimeout) {
|
|
1620
|
+
await this.client.patch(
|
|
1621
|
+
`/sandboxes/${this.sandboxId}/pty/${this.pid}`,
|
|
1622
|
+
{
|
|
1623
|
+
json: { size: { cols: size.cols, rows: size.rows } },
|
|
1624
|
+
timeout: requestTimeout
|
|
1625
|
+
}
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
/** SIGKILL the remote process and close any open streams. */
|
|
1629
|
+
async kill(requestTimeout) {
|
|
1630
|
+
this.aborter.abort();
|
|
1631
|
+
const data = await this.client.delete(
|
|
1632
|
+
`/sandboxes/${this.sandboxId}/pty/${this.pid}`,
|
|
1633
|
+
{ timeout: requestTimeout }
|
|
1634
|
+
);
|
|
1635
|
+
return data.killed === true;
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Stop consuming output without killing the process. The PTY keeps
|
|
1639
|
+
* running server-side and a fresh `stream()` call reattaches.
|
|
1640
|
+
*/
|
|
1641
|
+
disconnect() {
|
|
1642
|
+
this.aborter.abort();
|
|
1643
|
+
}
|
|
1644
|
+
/** Resolves with the remote exit result when the PTY process exits. */
|
|
1645
|
+
wait() {
|
|
1646
|
+
return this.exitPromise;
|
|
1549
1647
|
}
|
|
1550
1648
|
/**
|
|
1551
|
-
*
|
|
1649
|
+
* Async iterator over raw output chunks. Use when you want to drive
|
|
1650
|
+
* the stream yourself:
|
|
1651
|
+
*
|
|
1652
|
+
* for await (const chunk of handle.stream()) { ... }
|
|
1552
1653
|
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1654
|
+
* Don't mix this with `onData` on the same handle — they both try to
|
|
1655
|
+
* consume the same underlying SSE connection.
|
|
1555
1656
|
*/
|
|
1657
|
+
async *stream() {
|
|
1658
|
+
const response = await this.client.streamGet(
|
|
1659
|
+
`/sandboxes/${this.sandboxId}/pty/${this.pid}/stream`,
|
|
1660
|
+
{ timeout: void 0 }
|
|
1661
|
+
);
|
|
1662
|
+
if (!response.body) {
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
const reader = response.body.getReader();
|
|
1666
|
+
const decoder = new TextDecoder();
|
|
1667
|
+
let buffer = "";
|
|
1668
|
+
try {
|
|
1669
|
+
while (true) {
|
|
1670
|
+
if (this.aborter.signal.aborted) return;
|
|
1671
|
+
const { value, done } = await reader.read();
|
|
1672
|
+
if (done) break;
|
|
1673
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1674
|
+
let idx;
|
|
1675
|
+
while ((idx = buffer.indexOf("\n\n")) >= 0) {
|
|
1676
|
+
const frame = buffer.slice(0, idx);
|
|
1677
|
+
buffer = buffer.slice(idx + 2);
|
|
1678
|
+
const parsed = parseSSEFrame(frame);
|
|
1679
|
+
if (!parsed) continue;
|
|
1680
|
+
if (parsed.event === "exit") {
|
|
1681
|
+
this.resolveExit({ exitCode: parsed.exitCode ?? -1 });
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
if (parsed.bytes) {
|
|
1685
|
+
yield parsed.bytes;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
} finally {
|
|
1690
|
+
reader.releaseLock();
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
async consumeStream(onData) {
|
|
1694
|
+
try {
|
|
1695
|
+
for await (const chunk of this.stream()) {
|
|
1696
|
+
onData(chunk);
|
|
1697
|
+
}
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
if (!this.aborter.signal.aborted) {
|
|
1700
|
+
throw err;
|
|
1701
|
+
}
|
|
1702
|
+
} finally {
|
|
1703
|
+
this.resolveExit({ exitCode: -1 });
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
function parseSSEFrame(frame) {
|
|
1708
|
+
let event = "message";
|
|
1709
|
+
let data = "";
|
|
1710
|
+
for (const line of frame.split("\n")) {
|
|
1711
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
1712
|
+
else if (line.startsWith("data:")) data += line.slice(5).trim();
|
|
1713
|
+
}
|
|
1714
|
+
if (!event) return null;
|
|
1715
|
+
if (event === "exit") {
|
|
1716
|
+
try {
|
|
1717
|
+
const parsed = JSON.parse(data);
|
|
1718
|
+
return { event, exitCode: parsed.exit_code ?? -1 };
|
|
1719
|
+
} catch {
|
|
1720
|
+
return { event, exitCode: -1 };
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
if (event === "data") {
|
|
1724
|
+
try {
|
|
1725
|
+
const parsed = JSON.parse(data);
|
|
1726
|
+
if (typeof parsed.data === "string") {
|
|
1727
|
+
const bin = atob(parsed.data);
|
|
1728
|
+
const bytes = new Uint8Array(bin.length);
|
|
1729
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1730
|
+
return { event, bytes };
|
|
1731
|
+
}
|
|
1732
|
+
} catch {
|
|
1733
|
+
return null;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
var Pty = class {
|
|
1739
|
+
sandboxId;
|
|
1740
|
+
client;
|
|
1741
|
+
constructor(sandboxId, client) {
|
|
1742
|
+
this.sandboxId = sandboxId;
|
|
1743
|
+
this.client = client;
|
|
1744
|
+
}
|
|
1556
1745
|
async create(opts) {
|
|
1557
1746
|
const size = opts?.size ?? { cols: 80, rows: 24 };
|
|
1558
1747
|
const user = opts?.user ?? "user";
|
|
1559
1748
|
const body = {
|
|
1560
1749
|
size: { cols: size.cols, rows: size.rows },
|
|
1561
|
-
user
|
|
1750
|
+
user,
|
|
1751
|
+
timeout: opts?.timeout ?? 3600
|
|
1562
1752
|
};
|
|
1563
|
-
if (opts?.cwd !== void 0)
|
|
1564
|
-
|
|
1565
|
-
}
|
|
1566
|
-
if (opts?.envs !== void 0) {
|
|
1567
|
-
body.envs = opts.envs;
|
|
1568
|
-
}
|
|
1569
|
-
if (opts?.timeout !== void 0) {
|
|
1570
|
-
body.timeout = opts.timeout;
|
|
1571
|
-
}
|
|
1753
|
+
if (opts?.cwd !== void 0) body.cwd = opts.cwd;
|
|
1754
|
+
if (opts?.envs !== void 0) body.envs = opts.envs;
|
|
1572
1755
|
const data = await this.client.post(
|
|
1573
1756
|
`/sandboxes/${this.sandboxId}/pty`,
|
|
1574
|
-
{
|
|
1575
|
-
json: body,
|
|
1576
|
-
timeout: opts?.requestTimeout
|
|
1577
|
-
}
|
|
1757
|
+
{ json: body, timeout: opts?.requestTimeout }
|
|
1578
1758
|
);
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
return new CommandHandle(pid, this.sandboxId, this.client);
|
|
1759
|
+
const pid = data.pid;
|
|
1760
|
+
return new PtyHandle(pid, this.sandboxId, this.client, opts?.onData);
|
|
1582
1761
|
}
|
|
1583
1762
|
/**
|
|
1584
|
-
*
|
|
1763
|
+
* Reattach to an already-running PTY by its pid.
|
|
1585
1764
|
*
|
|
1586
|
-
*
|
|
1587
|
-
*
|
|
1765
|
+
* Returns a fresh `PtyHandle` that streams the live output of the
|
|
1766
|
+
* existing session. Multiple clients can subscribe to the same pid
|
|
1767
|
+
* concurrently — each receives output from the moment it connects
|
|
1768
|
+
* (no scrollback replay).
|
|
1588
1769
|
*/
|
|
1770
|
+
connect(pid, opts) {
|
|
1771
|
+
return new PtyHandle(pid, this.sandboxId, this.client, opts?.onData);
|
|
1772
|
+
}
|
|
1773
|
+
// --- Low-level API by pid (kept for callers that already hold one). ---
|
|
1589
1774
|
async kill(pid, requestTimeout) {
|
|
1590
1775
|
const data = await this.client.delete(
|
|
1591
1776
|
`/sandboxes/${this.sandboxId}/pty/${pid}`,
|
|
@@ -1593,27 +1778,13 @@ var Pty = class {
|
|
|
1593
1778
|
);
|
|
1594
1779
|
return data.killed === true;
|
|
1595
1780
|
}
|
|
1596
|
-
/**
|
|
1597
|
-
* Send input to a PTY session.
|
|
1598
|
-
*
|
|
1599
|
-
* Sends POST /sandboxes/:id/pty/:pid/stdin.
|
|
1600
|
-
* If data is a Uint8Array, it is decoded to a string using TextDecoder.
|
|
1601
|
-
*/
|
|
1602
1781
|
async sendStdin(pid, data, requestTimeout) {
|
|
1603
1782
|
const strData = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
1604
1783
|
await this.client.post(
|
|
1605
1784
|
`/sandboxes/${this.sandboxId}/pty/${pid}/stdin`,
|
|
1606
|
-
{
|
|
1607
|
-
json: { data: strData },
|
|
1608
|
-
timeout: requestTimeout
|
|
1609
|
-
}
|
|
1785
|
+
{ json: { data: strData }, timeout: requestTimeout }
|
|
1610
1786
|
);
|
|
1611
1787
|
}
|
|
1612
|
-
/**
|
|
1613
|
-
* Resize a PTY session.
|
|
1614
|
-
*
|
|
1615
|
-
* Sends PATCH /sandboxes/:id/pty/:pid.
|
|
1616
|
-
*/
|
|
1617
1788
|
async resize(pid, size, requestTimeout) {
|
|
1618
1789
|
await this.client.patch(
|
|
1619
1790
|
`/sandboxes/${this.sandboxId}/pty/${pid}`,
|
|
@@ -2398,6 +2569,7 @@ var Template = class {
|
|
|
2398
2569
|
NotFoundError,
|
|
2399
2570
|
PIIType,
|
|
2400
2571
|
Pty,
|
|
2572
|
+
PtyHandle,
|
|
2401
2573
|
RedactionAction,
|
|
2402
2574
|
Sandbox,
|
|
2403
2575
|
SandboxError,
|