@solarisdk/core 0.4.3 → 0.4.5
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/dist/desktop.d.ts +5 -0
- package/dist/desktop.js +18 -1
- package/dist/handle.d.ts +24 -4
- package/dist/handle.js +117 -22
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
package/dist/desktop.d.ts
CHANGED
|
@@ -41,6 +41,11 @@ export declare class Desktop extends SessionHandle {
|
|
|
41
41
|
doubleClick: (x: number, y: number, opts?: ClickOptions) => Promise<void>;
|
|
42
42
|
down: (x: number, y: number, button?: MouseButton) => Promise<void>;
|
|
43
43
|
up: (x: number, y: number, button?: MouseButton) => Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Scroll the wheel with the pointer at (x, y). `direction` defaults to "down", `amount` (wheel clicks, 1-50)
|
|
46
|
+
* to 3. The guest takes the wheel delta in the `y` field and does not move the pointer, so the pointer is moved
|
|
47
|
+
* first and (x, y) is never passed as the delta (it used to be: scroll(x, 500) fired 500 clicks, 20-30 s).
|
|
48
|
+
*/
|
|
44
49
|
scroll: (x: number, y: number, opts?: ScrollOptions) => Promise<void>;
|
|
45
50
|
drag: (from: {
|
|
46
51
|
x: number;
|
package/dist/desktop.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SessionHandle } from "./handle.js";
|
|
2
|
+
import { SolariError } from "./errors.js";
|
|
2
3
|
import { base64ToBytes } from "./transport.js";
|
|
3
4
|
function buttonToCode(button) {
|
|
4
5
|
if (button === undefined)
|
|
@@ -79,7 +80,23 @@ export class Desktop extends SessionHandle {
|
|
|
79
80
|
}).then(() => undefined),
|
|
80
81
|
down: (x, y, button = "left") => this.mouseCall(x, y, "down", button),
|
|
81
82
|
up: (x, y, button = "left") => this.mouseCall(x, y, "up", button),
|
|
82
|
-
scroll: (x, y, opts = {}) =>
|
|
83
|
+
scroll: async (x, y, opts = {}) => {
|
|
84
|
+
const amount = opts.amount ?? 3;
|
|
85
|
+
if (!Number.isInteger(amount) || amount < 1 || amount > 50) {
|
|
86
|
+
throw new SolariError("scroll amount must be a whole number of wheel clicks from 1 to 50");
|
|
87
|
+
}
|
|
88
|
+
const direction = opts.direction ?? "down";
|
|
89
|
+
if (direction !== "up" && direction !== "down") {
|
|
90
|
+
throw new SolariError('scroll direction must be "up" or "down"');
|
|
91
|
+
}
|
|
92
|
+
await this.mouseCall(x, y, "move", undefined, opts.humanize);
|
|
93
|
+
await this.guiCall("input.mouse", {
|
|
94
|
+
x,
|
|
95
|
+
y: direction === "up" ? amount : -amount,
|
|
96
|
+
action: "scroll",
|
|
97
|
+
button: buttonToCode(opts.button),
|
|
98
|
+
});
|
|
99
|
+
},
|
|
83
100
|
drag: (from, to, button = "left") => this.guiCall("input.drag", { from, to, button: buttonToCode(button) }).then(() => undefined),
|
|
84
101
|
};
|
|
85
102
|
mouseCall(x, y, action, button, humanize) {
|
package/dist/handle.d.ts
CHANGED
|
@@ -56,6 +56,16 @@ export interface SessionConfig extends ControlChannelConfig {
|
|
|
56
56
|
}
|
|
57
57
|
/** The longest `timeoutMs` the gateway accepts for a command (600 000 ms), and the default for `commands.run`. */
|
|
58
58
|
export declare const DEFAULT_COMMAND_TIMEOUT_MS = 600000;
|
|
59
|
+
/**
|
|
60
|
+
* The session `id` is no longer running (killed or paused, through any handle or by id), so the exit reminder must not
|
|
61
|
+
* name it. Tracking is by id, not by handle: two handles to one session, or a `client.kill(id)` with no handle at all,
|
|
62
|
+
* all clear the same entry.
|
|
63
|
+
*/
|
|
64
|
+
export declare function markSessionReleased(id: string): void;
|
|
65
|
+
/** The lines the exit reminder prints: one per distinct session id, however many handles track it. */
|
|
66
|
+
export declare function exitReminderMessages(): string[];
|
|
67
|
+
/** Ids the exit reminder would name right now (for tests). */
|
|
68
|
+
export declare function unreleasedSessionIds(): string[];
|
|
59
69
|
/** Cap on PTY output held before the first onData() subscriber (bytes, total). */
|
|
60
70
|
export declare const PTY_EARLY_BUFFER_MAX: number;
|
|
61
71
|
export interface SessionHandle {
|
|
@@ -121,6 +131,8 @@ export declare class SessionHandle {
|
|
|
121
131
|
connect: () => Promise<void>;
|
|
122
132
|
};
|
|
123
133
|
private startCommand;
|
|
134
|
+
/** SIGTERM + close stdin now, SIGKILL after CANCEL_KILL_GRACE_MS. Best effort: a dead channel or a finished command is fine. */
|
|
135
|
+
private cancelCommand;
|
|
124
136
|
private runCommand;
|
|
125
137
|
private runCommandInner;
|
|
126
138
|
readonly pty: {
|
|
@@ -136,6 +148,7 @@ export declare class SessionHandle {
|
|
|
136
148
|
/** Create a fresh stateful kernel context (`code.context.create`). */
|
|
137
149
|
createCodeContext(language?: CodeLanguageArg): Promise<string>;
|
|
138
150
|
readonly files: {
|
|
151
|
+
/** Read a whole file. Chunked under the hood (4 MiB per frame), so a big file never travels as one frame. */
|
|
139
152
|
read: (path: string) => Promise<Uint8Array>;
|
|
140
153
|
readText: (path: string) => Promise<string>;
|
|
141
154
|
write: (path: string, data: Uint8Array | string, mode?: number) => Promise<void>;
|
|
@@ -151,7 +164,7 @@ export declare class SessionHandle {
|
|
|
151
164
|
* Returns an unwatch function.
|
|
152
165
|
*/
|
|
153
166
|
watch: (path: string, cb: (ev: FsWatchEvent) => void, recursive?: boolean) => Promise<() => Promise<void>>;
|
|
154
|
-
/** Upload local bytes to an in-guest path (
|
|
167
|
+
/** Upload local bytes to an in-guest path: sent in 4 MiB chunks (offset/truncate), so a big file never travels as one frame. */
|
|
155
168
|
upload: (path: string, data: Uint8Array | string) => Promise<void>;
|
|
156
169
|
/** Download an in-guest file's bytes. */
|
|
157
170
|
download: (path: string) => Promise<Uint8Array>;
|
|
@@ -172,10 +185,11 @@ export declare class SessionHandle {
|
|
|
172
185
|
}>;
|
|
173
186
|
readonly volumes: {
|
|
174
187
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
188
|
+
* Not available at runtime. A volume is mounted when the sandbox is created (or resumed): the guest cannot fetch the
|
|
189
|
+
* storage credentials itself, and the gateway has no runtime-mount route, so the old pass-through to the guest
|
|
190
|
+
* `volume.mount` RPC always failed with "bucket is required". Pass `volumes: [{ volumeId, path }]` at create.
|
|
177
191
|
*/
|
|
178
|
-
mount: (
|
|
192
|
+
mount: (_volId: string, _path: string) => Promise<void>;
|
|
179
193
|
};
|
|
180
194
|
readonly git: {
|
|
181
195
|
/** Clone `url` into `opts.path` (or git's default dir) under `opts.cwd`. */
|
|
@@ -289,4 +303,10 @@ export declare class SessionHandle {
|
|
|
289
303
|
}
|
|
290
304
|
/** Local helper type for `createCodeContext` to avoid a hard import cycle. */
|
|
291
305
|
type CodeLanguageArg = "python" | "javascript" | "typescript" | "bash" | "r";
|
|
306
|
+
/** How long a cancelled command gets between SIGTERM and SIGKILL. */
|
|
307
|
+
export declare const CANCEL_KILL_GRACE_MS = 2000;
|
|
308
|
+
/** Bytes per fs.upload / fs.download frame. */
|
|
309
|
+
export declare const FILE_CHUNK_BYTES: number;
|
|
310
|
+
/** Largest whole-file payload one single-frame call (files.write) accepts; above it use files.upload / files.download / a signed URL. */
|
|
311
|
+
export declare const MAX_SINGLE_CALL_BYTES: number;
|
|
292
312
|
export {};
|
package/dist/handle.js
CHANGED
|
@@ -6,6 +6,18 @@ export const DEFAULT_COMMAND_TIMEOUT_MS = 600_000;
|
|
|
6
6
|
const unreleased = new Map();
|
|
7
7
|
const MAX_TRACKED_UNRELEASED = 1000;
|
|
8
8
|
let exitHookInstalled = false;
|
|
9
|
+
export function markSessionReleased(id) {
|
|
10
|
+
for (const [token, tracked] of unreleased)
|
|
11
|
+
if (tracked === id)
|
|
12
|
+
unreleased.delete(token);
|
|
13
|
+
}
|
|
14
|
+
export function exitReminderMessages() {
|
|
15
|
+
return [...new Set(unreleased.values())].map((id) => `[solari] this process is exiting but session ${shortId(id)} is still running and will bill until its ` +
|
|
16
|
+
`idle timeout. Call kill() to release it (or declare it with \`await using\`).`);
|
|
17
|
+
}
|
|
18
|
+
export function unreleasedSessionIds() {
|
|
19
|
+
return [...unreleased.values()];
|
|
20
|
+
}
|
|
9
21
|
function trackUnreleased(token, id) {
|
|
10
22
|
unreleased.set(token, id);
|
|
11
23
|
if (unreleased.size > MAX_TRACKED_UNRELEASED)
|
|
@@ -14,10 +26,8 @@ function trackUnreleased(token, id) {
|
|
|
14
26
|
return;
|
|
15
27
|
exitHookInstalled = true;
|
|
16
28
|
process.on("exit", () => {
|
|
17
|
-
for (const
|
|
18
|
-
console.warn(
|
|
19
|
-
`idle timeout. Call kill() to release it (or declare it with \`await using\`).`);
|
|
20
|
-
}
|
|
29
|
+
for (const line of exitReminderMessages())
|
|
30
|
+
console.warn(line);
|
|
21
31
|
unreleased.clear();
|
|
22
32
|
});
|
|
23
33
|
}
|
|
@@ -70,7 +80,8 @@ export class SessionHandle {
|
|
|
70
80
|
return this.channel.reconnect();
|
|
71
81
|
}
|
|
72
82
|
close(opts = {}) {
|
|
73
|
-
|
|
83
|
+
if (!opts.quiet)
|
|
84
|
+
unreleased.delete(this.releaseToken);
|
|
74
85
|
if (!this.killed && !opts.quiet && typeof console !== "undefined") {
|
|
75
86
|
console.warn(`[solari] close() on session ${shortId(this.id)} closed the local channel only — ` +
|
|
76
87
|
`the remote machine is still running and will bill until its idle ` +
|
|
@@ -116,6 +127,22 @@ export class SessionHandle {
|
|
|
116
127
|
exitResolve?.(-1);
|
|
117
128
|
}, opts.timeoutMs);
|
|
118
129
|
}
|
|
130
|
+
if (opts.signal) {
|
|
131
|
+
const signal = opts.signal;
|
|
132
|
+
const onAbort = () => {
|
|
133
|
+
clearTimeout(deadline);
|
|
134
|
+
void this.cancelCommand(cmdId);
|
|
135
|
+
this.channel.offFrame("cmd.data", cmdId);
|
|
136
|
+
this.channel.offFrame("cmd.exit", cmdId);
|
|
137
|
+
this.channel.offStreamClose(cmdId);
|
|
138
|
+
exitReject?.(abortReason(signal));
|
|
139
|
+
};
|
|
140
|
+
if (signal.aborted)
|
|
141
|
+
onAbort();
|
|
142
|
+
else
|
|
143
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
144
|
+
void exitPromise.then(() => signal.removeEventListener("abort", onAbort), () => signal.removeEventListener("abort", onAbort));
|
|
145
|
+
}
|
|
119
146
|
if (acc) {
|
|
120
147
|
dataCbs.push((c) => {
|
|
121
148
|
if (c.stream === "stdout")
|
|
@@ -180,6 +207,13 @@ export class SessionHandle {
|
|
|
180
207
|
.then(() => undefined),
|
|
181
208
|
};
|
|
182
209
|
}
|
|
210
|
+
async cancelCommand(cmdId) {
|
|
211
|
+
await this.channel.call("cmd.kill", { cmdId, signal: 15 }).catch(() => undefined);
|
|
212
|
+
await this.channel.call("cmd.stdin", { cmdId, base64: "", eof: true }).catch(() => undefined);
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
this.channel.call("cmd.kill", { cmdId, signal: 9 }).catch(() => undefined);
|
|
215
|
+
}, CANCEL_KILL_GRACE_MS).unref?.();
|
|
216
|
+
}
|
|
183
217
|
async runCommand(cmd, opts) {
|
|
184
218
|
try {
|
|
185
219
|
return await this.runCommandInner(cmd, opts);
|
|
@@ -199,6 +233,7 @@ export class SessionHandle {
|
|
|
199
233
|
!opts.onStderr &&
|
|
200
234
|
!opts.env &&
|
|
201
235
|
!opts.user &&
|
|
236
|
+
!opts.signal &&
|
|
202
237
|
timeoutMs >= MIN_REST_TIMEOUT_MS &&
|
|
203
238
|
timeoutMs <= DEFAULT_COMMAND_TIMEOUT_MS;
|
|
204
239
|
const viaRest = async () => {
|
|
@@ -309,11 +344,34 @@ export class SessionHandle {
|
|
|
309
344
|
};
|
|
310
345
|
}
|
|
311
346
|
async runCode(code, opts = {}) {
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
347
|
+
const params = { code, language: opts.language, contextId: opts.contextId };
|
|
348
|
+
const running = this.channel.call("code.run", params);
|
|
349
|
+
let r;
|
|
350
|
+
const signal = opts.signal;
|
|
351
|
+
if (signal) {
|
|
352
|
+
let onAbort;
|
|
353
|
+
const aborted = new Promise((_, reject) => {
|
|
354
|
+
onAbort = () => {
|
|
355
|
+
this.channel.call("code.interrupt", { language: opts.language, contextId: opts.contextId }).catch(() => undefined);
|
|
356
|
+
reject(abortReason(signal));
|
|
357
|
+
};
|
|
358
|
+
if (signal.aborted)
|
|
359
|
+
onAbort();
|
|
360
|
+
else
|
|
361
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
362
|
+
});
|
|
363
|
+
running.catch(() => undefined);
|
|
364
|
+
try {
|
|
365
|
+
r = await Promise.race([running, aborted]);
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
if (onAbort)
|
|
369
|
+
signal.removeEventListener("abort", onAbort);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
r = await running;
|
|
374
|
+
}
|
|
317
375
|
const results = Array.isArray(r.results) ? r.results : [];
|
|
318
376
|
for (const item of results) {
|
|
319
377
|
if (item.type === "stdout" && item.text)
|
|
@@ -332,16 +390,29 @@ export class SessionHandle {
|
|
|
332
390
|
}
|
|
333
391
|
files = {
|
|
334
392
|
read: async (path) => {
|
|
335
|
-
|
|
336
|
-
|
|
393
|
+
try {
|
|
394
|
+
return await downloadAll((method, params) => this.channel.call(method, { ...params, length: FILE_CHUNK_BYTES }), path);
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
if (!isUnknownMethod(err))
|
|
398
|
+
throw err;
|
|
399
|
+
const r = await this.channel.call("fs.read", { path });
|
|
400
|
+
return base64ToBytes(r.base64);
|
|
401
|
+
}
|
|
337
402
|
},
|
|
338
403
|
readText: async (path) => {
|
|
339
404
|
const bytes = await this.files.read(path);
|
|
340
405
|
return new TextDecoder().decode(bytes);
|
|
341
406
|
},
|
|
342
|
-
write: (path, data, mode) =>
|
|
343
|
-
|
|
344
|
-
.
|
|
407
|
+
write: (path, data, mode) => {
|
|
408
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
409
|
+
if (bytes.length > MAX_SINGLE_CALL_BYTES) {
|
|
410
|
+
return Promise.reject(singleCallTooLarge(bytes.length));
|
|
411
|
+
}
|
|
412
|
+
return this.channel
|
|
413
|
+
.call("fs.write", { path, base64: toB64(bytes), mode })
|
|
414
|
+
.then(() => undefined);
|
|
415
|
+
},
|
|
345
416
|
list: async (path) => {
|
|
346
417
|
const r = await this.channel.call("fs.list", {
|
|
347
418
|
path,
|
|
@@ -376,10 +447,16 @@ export class SessionHandle {
|
|
|
376
447
|
await this.channel.call("fs.unwatch", { watchId });
|
|
377
448
|
};
|
|
378
449
|
},
|
|
379
|
-
upload: (path, data) =>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
450
|
+
upload: async (path, data) => {
|
|
451
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
452
|
+
let offset = 0;
|
|
453
|
+
do {
|
|
454
|
+
const slice = bytes.subarray(offset, offset + FILE_CHUNK_BYTES);
|
|
455
|
+
await this.channel.call("fs.upload", { path, base64: toB64(slice), offset, truncate: offset === 0 });
|
|
456
|
+
offset += slice.length;
|
|
457
|
+
} while (offset < bytes.length);
|
|
458
|
+
},
|
|
459
|
+
download: (path) => downloadAll((method, params) => this.channel.call(method, { ...params, length: FILE_CHUNK_BYTES }), path),
|
|
383
460
|
};
|
|
384
461
|
env(vars) {
|
|
385
462
|
return this.channel
|
|
@@ -399,9 +476,7 @@ export class SessionHandle {
|
|
|
399
476
|
return this.hooks.uploadUrl(this.id, path);
|
|
400
477
|
}
|
|
401
478
|
volumes = {
|
|
402
|
-
mount: (
|
|
403
|
-
.call("volume.mount", { volumeId: volId, path })
|
|
404
|
-
.then(() => undefined),
|
|
479
|
+
mount: (_volId, _path) => Promise.reject(new SolariError("volumes.mount() is not supported on a running sandbox: pass volumes: [{ volumeId, path }] when you create it")),
|
|
405
480
|
};
|
|
406
481
|
git = {
|
|
407
482
|
clone: (url, opts = {}) => this.gitClone(url, opts),
|
|
@@ -624,6 +699,7 @@ export class SessionHandle {
|
|
|
624
699
|
throw new Error("pause requires a client-created handle");
|
|
625
700
|
}
|
|
626
701
|
await this.hooks.pause(this.id);
|
|
702
|
+
markSessionReleased(this.id);
|
|
627
703
|
this.close({ quiet: true });
|
|
628
704
|
}
|
|
629
705
|
async resume() {
|
|
@@ -631,6 +707,7 @@ export class SessionHandle {
|
|
|
631
707
|
throw new Error("resume requires a client-created handle");
|
|
632
708
|
}
|
|
633
709
|
const r = await this.hooks.resume(this.id);
|
|
710
|
+
trackUnreleased(this.releaseToken, this.id);
|
|
634
711
|
this.channel.setControlUrl(r.controlUrl);
|
|
635
712
|
await this.channel.reconnect();
|
|
636
713
|
}
|
|
@@ -673,6 +750,7 @@ export class SessionHandle {
|
|
|
673
750
|
if (this.hooks.kill)
|
|
674
751
|
await this.hooks.kill(this.id);
|
|
675
752
|
this.killed = true;
|
|
753
|
+
markSessionReleased(this.id);
|
|
676
754
|
this.close();
|
|
677
755
|
}
|
|
678
756
|
}
|
|
@@ -716,6 +794,23 @@ function isRouteUnavailable(err) {
|
|
|
716
794
|
return (err instanceof GatewayError &&
|
|
717
795
|
(err.status === 404 || err.status === 405 || err.status === 501));
|
|
718
796
|
}
|
|
797
|
+
function isUnknownMethod(err) {
|
|
798
|
+
return /unknown method|method not found|not implemented/i.test(err instanceof Error ? err.message : String(err));
|
|
799
|
+
}
|
|
800
|
+
export const CANCEL_KILL_GRACE_MS = 2000;
|
|
801
|
+
function abortReason(signal) {
|
|
802
|
+
const r = signal.reason;
|
|
803
|
+
if (r instanceof Error)
|
|
804
|
+
return r;
|
|
805
|
+
const e = new Error("The command was cancelled");
|
|
806
|
+
e.name = "AbortError";
|
|
807
|
+
return e;
|
|
808
|
+
}
|
|
809
|
+
export const FILE_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
810
|
+
export const MAX_SINGLE_CALL_BYTES = 32 * 1024 * 1024;
|
|
811
|
+
function singleCallTooLarge(n) {
|
|
812
|
+
return new SolariError(`file too large for a single call (${n} bytes, max ${MAX_SINGLE_CALL_BYTES}): use files.upload()/files.download() or uploadUrl()`);
|
|
813
|
+
}
|
|
719
814
|
function toB64(data) {
|
|
720
815
|
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
721
816
|
return bytesToBase64(bytes);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DEFAULT_COMMAND_TIMEOUT_MS, SessionHandle } from "./handle.js";
|
|
1
|
+
export { CANCEL_KILL_GRACE_MS, DEFAULT_COMMAND_TIMEOUT_MS, FILE_CHUNK_BYTES, MAX_SINGLE_CALL_BYTES, SessionHandle, markSessionReleased, unreleasedSessionIds, exitReminderMessages } from "./handle.js";
|
|
2
2
|
export { shortId } from "./ids.js";
|
|
3
3
|
export { formatIdlePolicy } from "./idle-policy.js";
|
|
4
4
|
export type { SessionConfig, SessionHooks } from "./handle.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DEFAULT_COMMAND_TIMEOUT_MS, SessionHandle } from "./handle.js";
|
|
1
|
+
export { CANCEL_KILL_GRACE_MS, DEFAULT_COMMAND_TIMEOUT_MS, FILE_CHUNK_BYTES, MAX_SINGLE_CALL_BYTES, SessionHandle, markSessionReleased, unreleasedSessionIds, exitReminderMessages } from "./handle.js";
|
|
2
2
|
export { shortId } from "./ids.js";
|
|
3
3
|
export { formatIdlePolicy } from "./idle-policy.js";
|
|
4
4
|
export { Desktop } from "./desktop.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -632,6 +632,10 @@ export interface MouseResult {
|
|
|
632
632
|
export interface ScrollOptions {
|
|
633
633
|
button?: MouseButton;
|
|
634
634
|
humanize?: boolean;
|
|
635
|
+
/** Which way the wheel turns. Default "down". */
|
|
636
|
+
direction?: "up" | "down";
|
|
637
|
+
/** Wheel clicks, a whole number from 1 to 50. Default 3. */
|
|
638
|
+
amount?: number;
|
|
635
639
|
}
|
|
636
640
|
export interface ClickOptions {
|
|
637
641
|
button?: MouseButton;
|
|
@@ -763,6 +767,12 @@ export interface CommandOptions {
|
|
|
763
767
|
background?: boolean;
|
|
764
768
|
onStdout?: (data: string) => void;
|
|
765
769
|
onStderr?: (data: string) => void;
|
|
770
|
+
/**
|
|
771
|
+
* Cancel the command. On abort the process in the sandbox gets SIGTERM and its stdin is closed, then SIGKILL after
|
|
772
|
+
* 2 s if it is still running; the call rejects with the signal's reason. Giving a signal runs the command over the live
|
|
773
|
+
* connection (the one-shot REST route has nothing to kill).
|
|
774
|
+
*/
|
|
775
|
+
signal?: AbortSignal;
|
|
766
776
|
}
|
|
767
777
|
/** Terminal result of `commands.run`. */
|
|
768
778
|
export interface CommandResult {
|
|
@@ -831,6 +841,8 @@ export interface RunCodeOptions {
|
|
|
831
841
|
contextId?: string;
|
|
832
842
|
onStdout?: (data: string) => void;
|
|
833
843
|
onStderr?: (data: string) => void;
|
|
844
|
+
/** Cancel the run: the cell is interrupted in the kernel (KeyboardInterrupt) and the call rejects with the signal's reason. */
|
|
845
|
+
signal?: AbortSignal;
|
|
834
846
|
}
|
|
835
847
|
/** Kind of a structured chart extracted from a matplotlib figure. */
|
|
836
848
|
export type ChartType = "line" | "scatter" | "bar" | "pie" | "box_and_whisker" | "composite" | "unknown";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solarisdk/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Shared runtime for the Solari SDK \u2014 transport, session handles, images, templates, and types used by @solarisdk/desktop and @solarisdk/sandbox.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|