@shardflux/sdk 0.14.0 → 0.15.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/CHANGELOG.md +45 -3
- package/README.md +127 -6
- package/dist/cell.d.ts +25 -0
- package/dist/cell.js +69 -0
- package/dist/client.d.ts +29 -4
- package/dist/client.js +57 -5
- package/dist/computer.d.ts +143 -0
- package/dist/computer.js +146 -0
- package/dist/generated/app-api.d.ts +452 -68
- package/dist/generated/cell-api.d.ts +332 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +4 -1
- package/dist/templates.d.ts +12 -0
- package/dist/templates.js +9 -0
- package/dist/tools.d.ts +16 -3
- package/dist/tools.js +111 -22
- package/dist/workspace-ref.d.ts +83 -0
- package/dist/workspace-ref.js +149 -0
- package/dist/workspace.d.ts +23 -1
- package/dist/workspace.js +36 -0
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -82,6 +82,8 @@ export function validateArgs(schema, value, path = '$') {
|
|
|
82
82
|
issues.push(`${path} must be one of ${schema.enum.join(', ')}`);
|
|
83
83
|
return issues;
|
|
84
84
|
}
|
|
85
|
+
// Without a token yet (no grantedTools) and no opts.tools, every tool but computer: computer is offered only when a
|
|
86
|
+
// token grants it (the workspace's computer use is on) or opts.tools names it.
|
|
85
87
|
const ALL = ['exec', 'files', 'pty', 'process', 'git', 'browser'];
|
|
86
88
|
/** The tool permissions whose tools work on a file-first workspace: files and executions. */
|
|
87
89
|
const FILE_FIRST_TOOLS = ['exec', 'files'];
|
|
@@ -97,6 +99,45 @@ const RESOURCE_HINTS = new Set(['auto', 'light', 'heavy']);
|
|
|
97
99
|
/** Tools served from a sleeping workspace's disk without waking it: the runner sends no hint. */
|
|
98
100
|
const DISK_READS = new Set(['read_file', 'list_files', 'search_files']);
|
|
99
101
|
const obj = (properties, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
|
|
102
|
+
/** One computer toolset action's parameters (the computer tool's and each computer_batch item's). */
|
|
103
|
+
const COMPUTER_ACTION = {
|
|
104
|
+
action: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
enum: ['screenshot', 'zoom', 'left_click', 'right_click', 'middle_click', 'double_click', 'triple_click', 'left_click_drag', 'mouse_move',
|
|
107
|
+
'left_mouse_down', 'left_mouse_up', 'cursor_position', 'scroll', 'type', 'key', 'hold_key', 'wait'],
|
|
108
|
+
},
|
|
109
|
+
coordinate: { type: 'array', items: { type: 'integer', minimum: 0 }, minItems: 2, maxItems: 2, description: '[x, y]. Required for mouse_move and as the end of left_click_drag; optional for clicks and scroll (default: at the pointer).' },
|
|
110
|
+
start_coordinate: { type: 'array', items: { type: 'integer', minimum: 0 }, minItems: 2, maxItems: 2, description: 'left_click_drag: where the drag starts.' },
|
|
111
|
+
region: { type: 'array', items: { type: 'integer', minimum: 0 }, minItems: 4, maxItems: 4, description: 'zoom: [x0, y0, x1, y1], shown enlarged.' },
|
|
112
|
+
text: { type: 'string', maxLength: 20_000, description: 'type: the text. key, hold_key: keys such as "Return", "ctrl+s", "alt+Tab" (space-separated for a sequence). Clicks, drag, scroll: modifiers held, e.g. "shift".' },
|
|
113
|
+
scroll_direction: { type: 'string', enum: ['up', 'down', 'left', 'right'] },
|
|
114
|
+
scroll_amount: { type: 'integer', minimum: 1, maximum: 100, description: 'scroll: wheel clicks.' },
|
|
115
|
+
duration: { type: 'number', minimum: 0, maximum: 300, description: 'Seconds: wait, hold_key.' },
|
|
116
|
+
repeat: { type: 'integer', minimum: 1, maximum: 100, description: 'key: presses (default 1).' },
|
|
117
|
+
};
|
|
118
|
+
/** The screenshot after input actions (0.15.0+). */
|
|
119
|
+
const COMPUTER_LOOK = {
|
|
120
|
+
screenshot: { type: 'boolean', description: 'Take a screenshot after the input actions (default true); false when you do not need to look.' },
|
|
121
|
+
settle_ms: { type: 'integer', minimum: 0, maximum: 10_000, description: 'Wait this long before that screenshot when the screen may change (default 250).' },
|
|
122
|
+
format: { type: 'string', enum: ['png', 'jpeg'], description: 'Image format (default png; jpeg is smaller).' },
|
|
123
|
+
quality: { type: 'integer', minimum: 1, maximum: 100, description: 'JPEG quality (default 80).' },
|
|
124
|
+
};
|
|
125
|
+
const COMPUTER_ACTION_KEYS = ['coordinate', 'start_coordinate', 'region', 'text', 'scroll_direction', 'scroll_amount', 'duration', 'repeat'];
|
|
126
|
+
function computerAction(a) {
|
|
127
|
+
const action = { action: a.action };
|
|
128
|
+
for (const k of COMPUTER_ACTION_KEYS)
|
|
129
|
+
if (a[k] !== undefined && a[k] !== null)
|
|
130
|
+
action[k] = a[k];
|
|
131
|
+
return action;
|
|
132
|
+
}
|
|
133
|
+
function computerImage(a) {
|
|
134
|
+
return {
|
|
135
|
+
...(typeof a.settle_ms === 'number' ? { settle_ms: a.settle_ms } : {}),
|
|
136
|
+
...(a.format === 'png' || a.format === 'jpeg' ? { format: a.format } : {}),
|
|
137
|
+
...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const imageView = (img) => ({ mime_type: img.format === 'jpeg' ? 'image/jpeg' : 'image/png', width: img.width, height: img.height, data_base64: img.data });
|
|
100
141
|
const path = (description = 'Absolute path inside the workspace, e.g. /home/user/project/main.py') => ({ type: 'string', minLength: 1, maxLength: 4096, description });
|
|
101
142
|
const signal = { type: 'string', description: 'Signal name such as SIGTERM, SIGINT or SIGKILL.', minLength: 2, maxLength: 12 };
|
|
102
143
|
function clip(text, max) {
|
|
@@ -245,7 +286,7 @@ async function waitForExit(c, s, waitMs, signal) {
|
|
|
245
286
|
}
|
|
246
287
|
/** Builds the tool list for a workspace. Synchronous: tokens are fetched on first use. */
|
|
247
288
|
export function workspaceTools(workspace, opts = {}) {
|
|
248
|
-
const cell = () => workspace.cell({
|
|
289
|
+
const cell = async () => workspace.cell({
|
|
249
290
|
...(opts.agentLabel !== undefined ? { agentLabel: opts.agentLabel } : {}),
|
|
250
291
|
...(opts.wake !== undefined ? { wake: opts.wake } : {}),
|
|
251
292
|
...(opts.transitionTimeoutMs !== undefined ? { transitionTimeoutMs: opts.transitionTimeoutMs } : {}),
|
|
@@ -297,7 +338,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
297
338
|
run: async (a, o) => {
|
|
298
339
|
const executionId = newExecutionId();
|
|
299
340
|
opts.onExecution?.(executionId);
|
|
300
|
-
const r = await cell().executions.run(['bash', '-lc', String(a.command)], {
|
|
341
|
+
const r = await (await cell()).executions.run(['bash', '-lc', String(a.command)], {
|
|
301
342
|
executionId,
|
|
302
343
|
...(typeof a.cwd === 'string' ? { cwd: a.cwd } : opts.defaultCwd ? { cwd: opts.defaultCwd } : {}),
|
|
303
344
|
timeoutMs: typeof a.timeout_ms === 'number' ? a.timeout_ms : 600_000,
|
|
@@ -343,7 +384,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
343
384
|
if (a.background === true) {
|
|
344
385
|
// A session of its own: the start answers at once, and exec_read / exec_cancel find it by session_id.
|
|
345
386
|
// The command runs until it exits or its timeout_ms, if one is set. (Never a burst: refused above.)
|
|
346
|
-
const s = await cell().exec.start({
|
|
387
|
+
const s = await (await cell()).exec.start({
|
|
347
388
|
argv: ['bash', '-lc', String(a.command)],
|
|
348
389
|
...(cwd !== undefined ? { cwd } : {}),
|
|
349
390
|
...(typeof a.timeout_ms === 'number' ? { timeout_ms: a.timeout_ms } : {}),
|
|
@@ -359,7 +400,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
359
400
|
}
|
|
360
401
|
let r;
|
|
361
402
|
try {
|
|
362
|
-
r = await cell().exec.run(['bash', '-lc', String(a.command)], {
|
|
403
|
+
r = await (await cell()).exec.run(['bash', '-lc', String(a.command)], {
|
|
363
404
|
...(cwd !== undefined ? { cwd } : {}),
|
|
364
405
|
timeoutMs: typeof a.timeout_ms === 'number' ? a.timeout_ms : 600_000,
|
|
365
406
|
...(typeof a.stdin === 'string' ? { stdin: a.stdin } : {}),
|
|
@@ -411,7 +452,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
411
452
|
description: 'Read a command started with exec background: true: its state, exit code and output. Without offsets it returns the end of each stream; pass next_stdout_offset and next_stderr_offset back to read only new output. wait_ms waits up to that long for the command to exit and returns as soon as it does.',
|
|
412
453
|
parameters: obj({ session_id: sessionId, stdout_offset: { type: 'integer', minimum: 0 }, stderr_offset: { type: 'integer', minimum: 0 }, wait_ms: { type: 'integer', minimum: 0, maximum: 60_000 } }, ['session_id']),
|
|
413
454
|
run: async (a, o) => {
|
|
414
|
-
const c = cell();
|
|
455
|
+
const c = await cell();
|
|
415
456
|
const id = String(a.session_id);
|
|
416
457
|
let s = await c.exec.get(id);
|
|
417
458
|
if (typeof a.wait_ms === 'number' && a.wait_ms > 0 && running(s))
|
|
@@ -461,7 +502,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
461
502
|
// The workspace waits 5 s without a grace and at most 60 s.
|
|
462
503
|
parameters: obj({ session_id: sessionId, grace_ms: { type: 'integer', minimum: 1, maximum: 60_000 } }, ['session_id']),
|
|
463
504
|
run: async (a) => {
|
|
464
|
-
const s = await cell().exec.cancel(String(a.session_id), typeof a.grace_ms === 'number' ? a.grace_ms : undefined);
|
|
505
|
+
const s = await (await cell()).exec.cancel(String(a.session_id), typeof a.grace_ms === 'number' ? a.grace_ms : undefined);
|
|
465
506
|
return { session_id: s.session_id, state: s.state, exit_code: running(s) ? null : (s.exit_code ?? null), canceled: s.canceled ?? false };
|
|
466
507
|
},
|
|
467
508
|
},
|
|
@@ -472,7 +513,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
472
513
|
description: 'Read a text file from the workspace (UTF-8). Use offset/length for large files.',
|
|
473
514
|
parameters: obj({ path: path(), offset: { type: 'integer', minimum: 0 }, length: { type: 'integer', minimum: 1, maximum: 10_485_760 } }, ['path']),
|
|
474
515
|
run: async (a) => {
|
|
475
|
-
const bytes = await cell().files.read(String(a.path), {
|
|
516
|
+
const bytes = await (await cell()).files.read(String(a.path), {
|
|
476
517
|
...(typeof a.offset === 'number' ? { offset: a.offset } : {}),
|
|
477
518
|
length: typeof a.length === 'number' ? Math.min(a.length, max) : max + 1,
|
|
478
519
|
});
|
|
@@ -491,7 +532,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
491
532
|
create_parents: { type: 'boolean', description: 'Create missing parent directories (default true).' },
|
|
492
533
|
}, ['path', 'content']),
|
|
493
534
|
run: async (a) => {
|
|
494
|
-
const r = await cell().files.write(String(a.path), String(a.content), { append: a.append === true, createParents: a.create_parents !== false });
|
|
535
|
+
const r = await (await cell()).files.write(String(a.path), String(a.content), { append: a.append === true, createParents: a.create_parents !== false });
|
|
495
536
|
return { path: r.path, bytes_written: r.bytes_written, sha256: r.sha256, durable: r.durable };
|
|
496
537
|
},
|
|
497
538
|
},
|
|
@@ -501,7 +542,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
501
542
|
description: 'List a directory in the workspace.',
|
|
502
543
|
parameters: obj({ path: path('Absolute directory path.'), limit: { type: 'integer', minimum: 1, maximum: 10_000 } }, ['path']),
|
|
503
544
|
run: async (a) => {
|
|
504
|
-
const r = await cell().files.list(String(a.path), typeof a.limit === 'number' ? { limit: a.limit } : { limit: 500 });
|
|
545
|
+
const r = await (await cell()).files.list(String(a.path), typeof a.limit === 'number' ? { limit: a.limit } : { limit: 500 });
|
|
505
546
|
return { entries: r.entries.map((e) => ({ name: e.name, path: e.path, type: e.type, size: e.size, modified_at: e.modified_at })), truncated: r.truncated };
|
|
506
547
|
},
|
|
507
548
|
},
|
|
@@ -520,7 +561,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
520
561
|
context_lines: { type: 'integer', minimum: 0, maximum: 5, description: 'Lines of context to return before and after each match.' },
|
|
521
562
|
}, ['path', 'pattern']),
|
|
522
563
|
run: async (a, o) => {
|
|
523
|
-
const r = await cell().files.search(String(a.path), String(a.pattern), {
|
|
564
|
+
const r = await (await cell()).files.search(String(a.path), String(a.pattern), {
|
|
524
565
|
...(typeof a.regex === 'boolean' ? { regex: a.regex } : {}),
|
|
525
566
|
...(typeof a.case_insensitive === 'boolean' ? { caseInsensitive: a.case_insensitive } : {}),
|
|
526
567
|
...(Array.isArray(a.include) ? { include: a.include } : {}),
|
|
@@ -566,7 +607,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
566
607
|
},
|
|
567
608
|
}, ['path', 'edits']),
|
|
568
609
|
run: async (a, o) => {
|
|
569
|
-
const c = cell();
|
|
610
|
+
const c = await cell();
|
|
570
611
|
const file = String(a.path);
|
|
571
612
|
// Without a revision from the model, pin the edit to the content current now, so a concurrent change between
|
|
572
613
|
// this read and the patch is refused (revision_mismatch) instead of edited blindly.
|
|
@@ -582,7 +623,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
582
623
|
description: 'List processes running in the workspace.',
|
|
583
624
|
parameters: obj({}),
|
|
584
625
|
run: async () => {
|
|
585
|
-
const r = await cell().processes.list();
|
|
626
|
+
const r = await (await cell()).processes.list();
|
|
586
627
|
return { processes: r.data.map((p) => ({ pid: p.pid, ppid: p.ppid, comm: p.comm, cmdline: p.cmdline, state: p.state, rss_bytes: p.rss_bytes })) };
|
|
587
628
|
},
|
|
588
629
|
},
|
|
@@ -592,7 +633,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
592
633
|
description: 'Send a signal to a process in the workspace (e.g. stop a server).',
|
|
593
634
|
parameters: obj({ pid: { type: 'integer', minimum: 2 }, signal }, ['pid', 'signal']),
|
|
594
635
|
run: async (a) => {
|
|
595
|
-
await cell().processes.signal(Number(a.pid), String(a.signal));
|
|
636
|
+
await (await cell()).processes.signal(Number(a.pid), String(a.signal));
|
|
596
637
|
return { signalled: true };
|
|
597
638
|
},
|
|
598
639
|
},
|
|
@@ -602,7 +643,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
602
643
|
description: 'Open an interactive terminal (PTY) in the workspace; returns a session_id for terminal_send/terminal_read.',
|
|
603
644
|
parameters: obj({ command: { type: 'string', maxLength: 10_000, description: 'Program to run (default: login shell).' }, rows: { type: 'integer', minimum: 1, maximum: 1000 }, cols: { type: 'integer', minimum: 1, maximum: 1000 } }),
|
|
604
645
|
run: async (a) => {
|
|
605
|
-
const s = await cell().pty.open({
|
|
646
|
+
const s = await (await cell()).pty.open({
|
|
606
647
|
...(typeof a.command === 'string' ? { argv: ['bash', '-lc', a.command] } : {}),
|
|
607
648
|
...(typeof a.rows === 'number' ? { rows: a.rows } : {}),
|
|
608
649
|
...(typeof a.cols === 'number' ? { cols: a.cols } : {}),
|
|
@@ -616,7 +657,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
616
657
|
description: 'Type input into a terminal session (include "\\n" to press Enter).',
|
|
617
658
|
parameters: obj({ session_id: { type: 'string', minLength: 1, maxLength: 64 }, input: { type: 'string', maxLength: 1_000_000 } }, ['session_id', 'input']),
|
|
618
659
|
run: async (a) => {
|
|
619
|
-
const s = await cell().pty.input(String(a.session_id), String(a.input));
|
|
660
|
+
const s = await (await cell()).pty.input(String(a.session_id), String(a.input));
|
|
620
661
|
return { state: s.state, next_offset: s.output_size };
|
|
621
662
|
},
|
|
622
663
|
},
|
|
@@ -626,7 +667,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
626
667
|
description: 'Read terminal output from an offset (returns next_offset to continue). Waits briefly for new output.',
|
|
627
668
|
parameters: obj({ session_id: { type: 'string', minLength: 1, maxLength: 64 }, offset: { type: 'integer', minimum: 0 }, wait_ms: { type: 'integer', minimum: 0, maximum: 60_000 } }, ['session_id']),
|
|
628
669
|
run: async (a) => {
|
|
629
|
-
const r = await cell().pty.read(String(a.session_id), {
|
|
670
|
+
const r = await (await cell()).pty.read(String(a.session_id), {
|
|
630
671
|
offset: typeof a.offset === 'number' ? a.offset : 0,
|
|
631
672
|
timeoutMs: typeof a.wait_ms === 'number' ? Math.max(100, a.wait_ms) : 3_000,
|
|
632
673
|
maxBytes: max,
|
|
@@ -640,7 +681,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
640
681
|
description: 'Close a terminal session.',
|
|
641
682
|
parameters: obj({ session_id: { type: 'string', minLength: 1, maxLength: 64 } }, ['session_id']),
|
|
642
683
|
run: async (a) => {
|
|
643
|
-
const s = await cell().pty.close(String(a.session_id));
|
|
684
|
+
const s = await (await cell()).pty.close(String(a.session_id));
|
|
644
685
|
return { state: s.state };
|
|
645
686
|
},
|
|
646
687
|
},
|
|
@@ -650,7 +691,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
650
691
|
description: 'Clone a git repository (HTTPS) into the workspace.',
|
|
651
692
|
parameters: obj({ url: { type: 'string', minLength: 9, maxLength: 2048, description: 'https:// remote URL.' }, path: path('Destination directory.'), branch: { type: 'string', maxLength: 255 }, depth: { type: 'integer', minimum: 1 } }, ['url', 'path']),
|
|
652
693
|
run: async (a) => {
|
|
653
|
-
const r = await cell().git.clone({
|
|
694
|
+
const r = await (await cell()).git.clone({
|
|
654
695
|
url: String(a.url),
|
|
655
696
|
path: String(a.path),
|
|
656
697
|
...(typeof a.branch === 'string' ? { branch: a.branch } : {}),
|
|
@@ -664,7 +705,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
664
705
|
permission: 'git',
|
|
665
706
|
description: 'Show the git status of a repository in the workspace.',
|
|
666
707
|
parameters: obj({ path: path('Repository directory.') }, ['path']),
|
|
667
|
-
run: async (a) => cell().git.status(String(a.path)),
|
|
708
|
+
run: async (a) => (await cell()).git.status(String(a.path)),
|
|
668
709
|
},
|
|
669
710
|
{
|
|
670
711
|
name: 'git_commit',
|
|
@@ -672,17 +713,65 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
672
713
|
description: 'Stage all changes and commit in a repository in the workspace.',
|
|
673
714
|
parameters: obj({ path: path('Repository directory.'), message: { type: 'string', minLength: 1, maxLength: 65_536 } }, ['path', 'message']),
|
|
674
715
|
run: async (a) => {
|
|
675
|
-
const r = await cell().git.commit({ path: String(a.path), message: String(a.message), all: true });
|
|
716
|
+
const r = await (await cell()).git.commit({ path: String(a.path), message: String(a.message), all: true });
|
|
676
717
|
return { exit_code: r.exit_code, commit: r.commit ?? null, stdout: clip(r.stdout, max).text, stderr: clip(r.stderr, max).text };
|
|
677
718
|
},
|
|
678
719
|
},
|
|
720
|
+
{
|
|
721
|
+
name: 'computer',
|
|
722
|
+
permission: 'computer',
|
|
723
|
+
description: 'Use the workspace desktop: its screen, mouse and keyboard. One action per call (computer_batch runs several); input actions answer with a screenshot taken after them, screenshot and zoom answer with the image, cursor_position with the pointer. Coordinates are screen pixels from the top-left of the latest screenshot. Start with action "screenshot". Programs started with exec in the background (e.g. "chromium https://example.com &") open on this desktop.',
|
|
724
|
+
parameters: obj({ ...COMPUTER_ACTION, ...COMPUTER_LOOK }, ['action']),
|
|
725
|
+
run: async (a, o) => {
|
|
726
|
+
const look = a.action === 'screenshot' || a.action === 'zoom' || a.action === 'cursor_position';
|
|
727
|
+
const r = await (await cell()).computer.act({ actions: [computerAction(a)], screenshot: !look && a.screenshot !== false, ...computerImage(a) }, o.signal);
|
|
728
|
+
const res = r.results[0];
|
|
729
|
+
const img = res?.image ?? r.screenshot;
|
|
730
|
+
return {
|
|
731
|
+
ok: res?.ok ?? false,
|
|
732
|
+
...(res?.output !== undefined ? { output: res.output } : {}),
|
|
733
|
+
...(res?.error ? { error: res.error.message } : {}),
|
|
734
|
+
cursor: r.cursor,
|
|
735
|
+
display: r.display,
|
|
736
|
+
...(img ? { screenshot: imageView(img) } : {}),
|
|
737
|
+
};
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
name: 'computer_batch',
|
|
742
|
+
permission: 'computer',
|
|
743
|
+
description: 'Run several actions of the computer tool on the workspace desktop in one call, in order (e.g. click a field, type, press Return). The first failure stops the batch and the rest come back skipped. Answers with each action\'s result (zoom with its image, cursor_position with the pointer) and one screenshot after the last action that ran (screenshot: false skips it).',
|
|
744
|
+
parameters: obj({
|
|
745
|
+
actions: { type: 'array', minItems: 1, maxItems: 50, items: obj(COMPUTER_ACTION, ['action']), description: 'Actions with the computer tool\'s parameters, run in order.' },
|
|
746
|
+
...COMPUTER_LOOK,
|
|
747
|
+
}, ['actions']),
|
|
748
|
+
run: async (a, o) => {
|
|
749
|
+
const actions = a.actions.map(computerAction);
|
|
750
|
+
const r = await (await cell()).computer.act({ actions, screenshot: a.screenshot !== false, ...computerImage(a) }, o.signal);
|
|
751
|
+
return {
|
|
752
|
+
ok: r.results.every((x) => x.ok),
|
|
753
|
+
results: r.results.map((x) => ({
|
|
754
|
+
action: x.action,
|
|
755
|
+
ok: x.ok,
|
|
756
|
+
...(x.skipped ? { skipped: true } : {}),
|
|
757
|
+
...(x.output !== undefined ? { output: x.output } : {}),
|
|
758
|
+
...(x.error ? { error: x.error.message } : {}),
|
|
759
|
+
took_ms: x.took_ms,
|
|
760
|
+
...(x.image ? { image: imageView(x.image) } : {}),
|
|
761
|
+
})),
|
|
762
|
+
cursor: r.cursor,
|
|
763
|
+
display: r.display,
|
|
764
|
+
...(r.screenshot ? { screenshot: imageView(r.screenshot) } : {}),
|
|
765
|
+
};
|
|
766
|
+
},
|
|
767
|
+
},
|
|
679
768
|
{
|
|
680
769
|
name: 'browser_screenshot',
|
|
681
770
|
permission: 'browser',
|
|
682
771
|
description: 'Open a URL in the workspace’s headless browser and return a PNG screenshot (base64).',
|
|
683
772
|
parameters: obj({ url: { type: 'string', minLength: 8, maxLength: 8192 }, width: { type: 'integer', minimum: 100, maximum: 3840 }, height: { type: 'integer', minimum: 100, maximum: 2160 } }, ['url']),
|
|
684
773
|
run: async (a) => {
|
|
685
|
-
const png = await cell().browser.screenshot({
|
|
774
|
+
const png = await (await cell()).browser.screenshot({
|
|
686
775
|
url: String(a.url),
|
|
687
776
|
...(typeof a.width === 'number' ? { width: a.width } : {}),
|
|
688
777
|
...(typeof a.height === 'number' ? { height: a.height } : {}),
|
|
@@ -696,7 +785,7 @@ export function workspaceTools(workspace, opts = {}) {
|
|
|
696
785
|
description: 'Open a URL in the workspace’s headless browser and return the rendered page as text or HTML.',
|
|
697
786
|
parameters: obj({ url: { type: 'string', minLength: 8, maxLength: 8192 }, format: { type: 'string', enum: ['text', 'html'] } }, ['url']),
|
|
698
787
|
run: async (a) => {
|
|
699
|
-
const r = await cell().browser.content({ url: String(a.url), format: a.format === 'html' ? 'html' : 'text' });
|
|
788
|
+
const r = await (await cell()).browser.content({ url: String(a.url), format: a.format === 'html' ? 'html' : 'text' });
|
|
700
789
|
const c = clip(r.content, max);
|
|
701
790
|
return { url: r.url, format: r.format, content: c.text, truncated: r.truncated || c.truncated };
|
|
702
791
|
},
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspaces by key (0.15.0+, contracts §46). `cloud.workspace(key, { template })` names a workspace without a request.
|
|
3
|
+
* Its first call opens the key (the workspace is created on first use and resumed afterwards, one held request that
|
|
4
|
+
* returns it with a tool token), and later calls go straight to the workspace, which wakes on use.
|
|
5
|
+
*/
|
|
6
|
+
import type { CellClient, CellClientOptions, RunOptions, RunResult } from './cell.js';
|
|
7
|
+
import { CAPTURE_BARRIER } from './cell.js';
|
|
8
|
+
import type { OpenParams, WorkspacesApi } from './client.js';
|
|
9
|
+
import type { WorkspaceMode } from './errors.js';
|
|
10
|
+
import type { ExecutionGetOptions, ExecutionResult, ExecutionRunOptions } from './executions.js';
|
|
11
|
+
import type { ToolName } from './tokens.js';
|
|
12
|
+
import type { ToolTarget, WorkspaceTool, WorkspaceToolsOptions } from './tools.js';
|
|
13
|
+
import type { HintOptions, HintResult, Workspace } from './workspace.js';
|
|
14
|
+
/**
|
|
15
|
+
* `cloud.workspace(key, params)`: the open's parameters without the key. `template` is required, unless `create: false`:
|
|
16
|
+
* then the first call finds the key's live workspace (a lookup, no VM start) and fails with ShardfluxApiError 404
|
|
17
|
+
* `not_found` when there is none. Default: the first call opens the key, creating the workspace.
|
|
18
|
+
*/
|
|
19
|
+
export type WorkspaceRefParams = (Omit<OpenParams, 'key'> & {
|
|
20
|
+
create?: true;
|
|
21
|
+
}) | (Omit<OpenParams, 'key' | 'template'> & {
|
|
22
|
+
template?: string;
|
|
23
|
+
create: false;
|
|
24
|
+
});
|
|
25
|
+
type Files = CellClient['files'];
|
|
26
|
+
type FileMethod = 'read' | 'readText' | 'readWithInfo' | 'write' | 'list' | 'stat' | 'remove' | 'mkdir' | 'move' | 'search' | 'patch';
|
|
27
|
+
/** The workspace's files, as on `cell().files`; each call opens the key first if needed. */
|
|
28
|
+
export type WorkspaceRefFiles = Pick<Files, FileMethod>;
|
|
29
|
+
/**
|
|
30
|
+
* A workspace named by its key. Nothing is requested until the first call: `exec()`, `files`, a tool from `tools()`,
|
|
31
|
+
* `hint()` or `open()`. Concurrent first calls share one open; a failed open is not kept, so the next call opens again.
|
|
32
|
+
* A workspace deleted under the ref (409 `workspace_deleted`, `isWorkspaceGone`) fails the call that meets it and is
|
|
33
|
+
* forgotten: the next call opens the key again (a new workspace, as `open()` would). `open()` returns the full
|
|
34
|
+
* `Workspace` (suspend, fork, ports, computer, tool-call capture).
|
|
35
|
+
*/
|
|
36
|
+
export declare class WorkspaceRef implements ToolTarget {
|
|
37
|
+
#private;
|
|
38
|
+
readonly key: string;
|
|
39
|
+
/** Internal: use `cloud.workspace(key, params)` or the module-level `workspace(key, params)`. */
|
|
40
|
+
constructor(api: WorkspacesApi, key: string, params: WorkspaceRefParams, grants: () => Promise<ToolName[] | null>);
|
|
41
|
+
/**
|
|
42
|
+
* Whether the open that produced the current workspace created it (contracts §46.2): undefined until the ref has
|
|
43
|
+
* opened (or when the API does not report it), false for `create: false`.
|
|
44
|
+
*/
|
|
45
|
+
get created(): boolean | undefined;
|
|
46
|
+
/** The opened workspace's mode, else the params' (`processful` when neither says). */
|
|
47
|
+
get mode(): WorkspaceMode;
|
|
48
|
+
/** Tools granted by the opened workspace's last token; null before the open. */
|
|
49
|
+
get grantedTools(): ToolName[] | null;
|
|
50
|
+
/** The workspace: opened (or, with `create: false`, found) on the first call and kept. */
|
|
51
|
+
open(): Promise<Workspace>;
|
|
52
|
+
/**
|
|
53
|
+
* Runs a command and collects its output (`cell().exec.run()`). A string runs through `bash -lc`, so shell syntax
|
|
54
|
+
* works; an argv array runs without a shell. A file-first workspace runs commands with `executions.run()`.
|
|
55
|
+
*/
|
|
56
|
+
exec(command: string | readonly string[], opts?: RunOptions): Promise<RunResult>;
|
|
57
|
+
/** The workspace's files, as on `cell().files`. */
|
|
58
|
+
readonly files: WorkspaceRefFiles;
|
|
59
|
+
/** A file-first workspace's executions (`run`, `get`), as on `Workspace.executions`. */
|
|
60
|
+
readonly executions: {
|
|
61
|
+
run: (argv: string[], opts?: ExecutionRunOptions) => Promise<ExecutionResult>;
|
|
62
|
+
get: (executionId: string, opts?: ExecutionGetOptions) => Promise<ExecutionResult>;
|
|
63
|
+
};
|
|
64
|
+
/** The workspace's cell client (`Workspace.cell()`), opening the key first if needed. */
|
|
65
|
+
cell(opts?: {
|
|
66
|
+
agentLabel?: string;
|
|
67
|
+
tools?: ToolName[];
|
|
68
|
+
} & CellClientOptions): Promise<CellClient>;
|
|
69
|
+
/**
|
|
70
|
+
* Says a tool call is coming. Before the first open it starts the open in the background (`wake` resolves when the
|
|
71
|
+
* workspace runs; nothing has to await it), unless `wake: null`; afterwards it is `Workspace.hint()`.
|
|
72
|
+
*/
|
|
73
|
+
hint(opts?: HintOptions): Promise<HintResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Workspace tools for this key (`workspaceTools`), built before any VM exists: the definitions come from the API
|
|
76
|
+
* key's tool permissions (`GET /v1/me`, read once per client), and `computer` only with `computerUse: true` in the
|
|
77
|
+
* params or on an opened workspace whose computer use is on. The first tool call opens the key.
|
|
78
|
+
*/
|
|
79
|
+
tools(opts?: WorkspaceToolsOptions): Promise<WorkspaceTool[]>;
|
|
80
|
+
/** Internal: tool-call capture's read-your-writes barrier, once the workspace is open. */
|
|
81
|
+
[CAPTURE_BARRIER](): Promise<void> | undefined;
|
|
82
|
+
}
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { CAPTURE_BARRIER } from "./cell.js";
|
|
2
|
+
import { ShardfluxApiError, isWorkspaceGone } from "./errors.js";
|
|
3
|
+
import { workspaceTools } from "./tools.js";
|
|
4
|
+
const FILE_METHODS = ['read', 'readText', 'readWithInfo', 'write', 'list', 'stat', 'remove', 'mkdir', 'move', 'search', 'patch'];
|
|
5
|
+
/**
|
|
6
|
+
* A workspace named by its key. Nothing is requested until the first call: `exec()`, `files`, a tool from `tools()`,
|
|
7
|
+
* `hint()` or `open()`. Concurrent first calls share one open; a failed open is not kept, so the next call opens again.
|
|
8
|
+
* A workspace deleted under the ref (409 `workspace_deleted`, `isWorkspaceGone`) fails the call that meets it and is
|
|
9
|
+
* forgotten: the next call opens the key again (a new workspace, as `open()` would). `open()` returns the full
|
|
10
|
+
* `Workspace` (suspend, fork, ports, computer, tool-call capture).
|
|
11
|
+
*/
|
|
12
|
+
export class WorkspaceRef {
|
|
13
|
+
key;
|
|
14
|
+
#api;
|
|
15
|
+
#params;
|
|
16
|
+
#grants;
|
|
17
|
+
#workspace = null;
|
|
18
|
+
#opening = null;
|
|
19
|
+
/** Internal: use `cloud.workspace(key, params)` or the module-level `workspace(key, params)`. */
|
|
20
|
+
constructor(api, key, params, grants) {
|
|
21
|
+
if (typeof key !== 'string' || key.length === 0)
|
|
22
|
+
throw new TypeError('workspace(key, params): key must be a non-empty string');
|
|
23
|
+
if (params.create !== false && (typeof params.template !== 'string' || params.template.length === 0)) {
|
|
24
|
+
throw new TypeError(`workspace("${key}", params): params.template is required (a template slug, e.g. "default")`);
|
|
25
|
+
}
|
|
26
|
+
this.key = key;
|
|
27
|
+
this.#api = api;
|
|
28
|
+
this.#params = params;
|
|
29
|
+
this.#grants = grants;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether the open that produced the current workspace created it (contracts §46.2): undefined until the ref has
|
|
33
|
+
* opened (or when the API does not report it), false for `create: false`.
|
|
34
|
+
*/
|
|
35
|
+
get created() {
|
|
36
|
+
if (!this.#workspace)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (this.#params.create === false)
|
|
39
|
+
return false;
|
|
40
|
+
return this.#workspace.created ?? undefined;
|
|
41
|
+
}
|
|
42
|
+
/** The opened workspace's mode, else the params' (`processful` when neither says). */
|
|
43
|
+
get mode() {
|
|
44
|
+
return this.#workspace?.mode ?? this.#params.mode ?? 'processful';
|
|
45
|
+
}
|
|
46
|
+
/** Tools granted by the opened workspace's last token; null before the open. */
|
|
47
|
+
get grantedTools() {
|
|
48
|
+
return this.#workspace?.grantedTools ?? null;
|
|
49
|
+
}
|
|
50
|
+
/** The workspace: opened (or, with `create: false`, found) on the first call and kept. */
|
|
51
|
+
open() {
|
|
52
|
+
if (this.#workspace)
|
|
53
|
+
return Promise.resolve(this.#workspace);
|
|
54
|
+
this.#opening ??= this.#open().then((ws) => {
|
|
55
|
+
this.#workspace = ws;
|
|
56
|
+
this.#opening = null;
|
|
57
|
+
return ws;
|
|
58
|
+
}, (err) => {
|
|
59
|
+
this.#opening = null;
|
|
60
|
+
throw err;
|
|
61
|
+
});
|
|
62
|
+
return this.#opening;
|
|
63
|
+
}
|
|
64
|
+
async #open() {
|
|
65
|
+
const params = this.#params;
|
|
66
|
+
// open() sends only the open's own parameters (`create` is the ref's).
|
|
67
|
+
if (params.create !== false)
|
|
68
|
+
return this.#api.open({ ...params, key: this.key });
|
|
69
|
+
const found = await this.#api.findByKey(this.key, {
|
|
70
|
+
includeDeleted: false,
|
|
71
|
+
...(params.projectId !== undefined ? { projectId: params.projectId } : {}),
|
|
72
|
+
...(params.agentLabel !== undefined ? { agentLabel: params.agentLabel } : {}),
|
|
73
|
+
...(params.tools !== undefined ? { tools: params.tools } : {}),
|
|
74
|
+
});
|
|
75
|
+
if (found)
|
|
76
|
+
return found;
|
|
77
|
+
throw new ShardfluxApiError(404, { error: { code: 'not_found', message: `No workspace with key "${this.key}" in this project (create: false).`, request_id: '', retryable: false, details: { key: this.key } } }, 'api');
|
|
78
|
+
}
|
|
79
|
+
/** Runs `fn` on the workspace; a workspace found deleted is forgotten so the next call opens the key again. */
|
|
80
|
+
async #use(fn) {
|
|
81
|
+
const ws = await this.open();
|
|
82
|
+
try {
|
|
83
|
+
return await fn(ws);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (isWorkspaceGone(err) && this.#workspace === ws)
|
|
87
|
+
this.#workspace = null;
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Runs a command and collects its output (`cell().exec.run()`). A string runs through `bash -lc`, so shell syntax
|
|
93
|
+
* works; an argv array runs without a shell. A file-first workspace runs commands with `executions.run()`.
|
|
94
|
+
*/
|
|
95
|
+
exec(command, opts = {}) {
|
|
96
|
+
const argv = typeof command === 'string' ? ['bash', '-lc', command] : [...command];
|
|
97
|
+
return this.#use((ws) => ws.cell().exec.run(argv, opts));
|
|
98
|
+
}
|
|
99
|
+
/** The workspace's files, as on `cell().files`. */
|
|
100
|
+
files = Object.fromEntries(FILE_METHODS.map((name) => [
|
|
101
|
+
name,
|
|
102
|
+
(...args) => this.#use((ws) => ws.cell().files[name](...args)),
|
|
103
|
+
]));
|
|
104
|
+
/** A file-first workspace's executions (`run`, `get`), as on `Workspace.executions`. */
|
|
105
|
+
executions = {
|
|
106
|
+
run: (argv, opts = {}) => this.#use((ws) => ws.executions.run(argv, opts)),
|
|
107
|
+
get: (executionId, opts = {}) => this.#use((ws) => ws.executions.get(executionId, opts)),
|
|
108
|
+
};
|
|
109
|
+
/** The workspace's cell client (`Workspace.cell()`), opening the key first if needed. */
|
|
110
|
+
async cell(opts = {}) {
|
|
111
|
+
return (await this.open()).cell(opts);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Says a tool call is coming. Before the first open it starts the open in the background (`wake` resolves when the
|
|
115
|
+
* workspace runs; nothing has to await it), unless `wake: null`; afterwards it is `Workspace.hint()`.
|
|
116
|
+
*/
|
|
117
|
+
async hint(opts = {}) {
|
|
118
|
+
if (this.#workspace)
|
|
119
|
+
return this.#workspace.hint(opts);
|
|
120
|
+
if (opts.wake === null)
|
|
121
|
+
return { residency: null, wake: null };
|
|
122
|
+
const wake = this.open().then(() => true);
|
|
123
|
+
wake.catch(() => undefined); // a failed open is left to the next call, which opens again
|
|
124
|
+
return { residency: null, wake };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Workspace tools for this key (`workspaceTools`), built before any VM exists: the definitions come from the API
|
|
128
|
+
* key's tool permissions (`GET /v1/me`, read once per client), and `computer` only with `computerUse: true` in the
|
|
129
|
+
* params or on an opened workspace whose computer use is on. The first tool call opens the key.
|
|
130
|
+
*/
|
|
131
|
+
async tools(opts = {}) {
|
|
132
|
+
// create: false finds the workspace now (a lookup, no VM start), so the tools match its mode.
|
|
133
|
+
if (this.#params.create === false)
|
|
134
|
+
await this.open();
|
|
135
|
+
let tools = opts.tools ?? this.grantedTools ?? undefined;
|
|
136
|
+
if (tools === undefined) {
|
|
137
|
+
const granted = await this.#grants();
|
|
138
|
+
if (granted) {
|
|
139
|
+
const computer = this.#workspace ? this.#workspace.computerUse.enabled : this.#params.computerUse === true;
|
|
140
|
+
tools = granted.filter((t) => t !== 'computer' || computer);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return workspaceTools(this, { ...opts, ...(tools !== undefined ? { tools } : {}) });
|
|
144
|
+
}
|
|
145
|
+
/** Internal: tool-call capture's read-your-writes barrier, once the workspace is open. */
|
|
146
|
+
[CAPTURE_BARRIER]() {
|
|
147
|
+
return this.#workspace?.[CAPTURE_BARRIER]();
|
|
148
|
+
}
|
|
149
|
+
}
|
package/dist/workspace.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* A workspace handle: the latest view from the application API plus managed
|
|
3
3
|
* tool tokens and cell clients (one per agent label / tool set).
|
|
4
4
|
*/
|
|
5
|
-
import type { AllocationMode, ClientContext, IdlePolicy, DiskLayout, ForkTarget, Operation, ResizeParams, ResizeResult, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResult, WaitOptions, WorkspaceLifetime, WorkspaceMemory, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
|
|
5
|
+
import type { AllocationMode, ComputerUse, ClientContext, IdlePolicy, DiskLayout, ForkTarget, Operation, ResizeParams, ResizeResult, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResult, WaitOptions, WorkspaceLifetime, WorkspaceMemory, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
|
|
6
6
|
import { CAPTURE_BARRIER, CellClient } from './cell.js';
|
|
7
7
|
import { ToolCallCapture } from './capture.js';
|
|
8
8
|
import type { ToolCallCaptureOptions } from './capture.js';
|
|
@@ -10,6 +10,7 @@ import type { WorkspaceMode } from './errors.js';
|
|
|
10
10
|
import type { FinishedOperation, ForkOptions, LifecycleOptions, ResumeOptions, SuspendOptions, WaitedForkOptions, WaitedLifecycleOptions, WaitedResumeOptions, WaitedSuspendOptions } from './lifecycle.js';
|
|
11
11
|
import { Trace } from './progress.js';
|
|
12
12
|
import type { LifecycleTiming, ProgressListener } from './progress.js';
|
|
13
|
+
import { WorkspaceComputer } from './computer.js';
|
|
13
14
|
import { WorkspacePorts } from './ports.js';
|
|
14
15
|
import { WorkspaceSecrets } from './secrets.js';
|
|
15
16
|
import type { CellClientOptions, Residency, WorkspaceChangesPage, WorkspaceChangesParams } from './cell.js';
|
|
@@ -57,11 +58,18 @@ export interface HintResult {
|
|
|
57
58
|
}
|
|
58
59
|
export declare class Workspace {
|
|
59
60
|
#private;
|
|
61
|
+
/**
|
|
62
|
+
* Whether the open() that returned this handle created the workspace (0.15.0+, contracts §46.2): true for a new key
|
|
63
|
+
* (or a key whose session ended), false when it reconnected to or resumed an existing workspace. Null for handles
|
|
64
|
+
* from get(), list() and findByKey(), and from an API that does not report it.
|
|
65
|
+
*/
|
|
66
|
+
readonly created: boolean | null;
|
|
60
67
|
constructor(ctx: ClientContext, view: WorkspaceView, opts?: {
|
|
61
68
|
agentLabel?: string | undefined;
|
|
62
69
|
tools?: ToolName[] | undefined;
|
|
63
70
|
token?: ToolToken | null;
|
|
64
71
|
trace?: Trace;
|
|
72
|
+
created?: boolean | null;
|
|
65
73
|
});
|
|
66
74
|
/**
|
|
67
75
|
* Where the time went in the last lifecycle call made through this handle: open(), wake() (also when a tool call
|
|
@@ -160,6 +168,20 @@ export declare class Workspace {
|
|
|
160
168
|
* const link = await workspace.ports.link(3000); // open link.url in a browser
|
|
161
169
|
*/
|
|
162
170
|
get ports(): WorkspacePorts;
|
|
171
|
+
/**
|
|
172
|
+
* The workspace desktop (0.15.0+, contracts §45): `act(actions)`, `screenshot()`, `stream()` (a private link to watch
|
|
173
|
+
* it), `status()`, `start()`, `stop()`. Needs computer use on (`setComputerUse(true)`, or the template's switch); the
|
|
174
|
+
* platform starts the desktop on the first call that needs it.
|
|
175
|
+
*/
|
|
176
|
+
get computer(): WorkspaceComputer;
|
|
177
|
+
/** Computer use (0.15.0+): `enabled` is what tool tokens carry; `workspace` null follows the `template`'s switch. */
|
|
178
|
+
get computerUse(): ComputerUse;
|
|
179
|
+
/**
|
|
180
|
+
* Switches computer use on or off for this workspace (null follows the template). Takes effect on the next tool token:
|
|
181
|
+
* this handle's cached tokens are dropped. 409 `computer_use_unavailable` when its template version cannot run a
|
|
182
|
+
* desktop.
|
|
183
|
+
*/
|
|
184
|
+
setComputerUse(enabled: boolean | null): Promise<this>;
|
|
163
185
|
/** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
|
|
164
186
|
inputs(): Promise<Record<string, string>>;
|
|
165
187
|
get labels(): Record<string, string>;
|
package/dist/workspace.js
CHANGED
|
@@ -4,6 +4,7 @@ import { NotSupportedForModeError, OperationFailedError, ShardfluxApiError } fro
|
|
|
4
4
|
import { SERVER_WAIT_MAX_S, defaultSleep, randomId } from "./http.js";
|
|
5
5
|
import { AFTER_WAIT, HELD_RESUME, TRACE } from "./lifecycle.js";
|
|
6
6
|
import { Trace, combineListeners, traced } from "./progress.js";
|
|
7
|
+
import { WorkspaceComputer } from "./computer.js";
|
|
7
8
|
import { WorkspacePorts } from "./ports.js";
|
|
8
9
|
import { WorkspaceSecrets } from "./secrets.js";
|
|
9
10
|
import { ToolTokenManager } from "./tokens.js";
|
|
@@ -21,8 +22,15 @@ export class Workspace {
|
|
|
21
22
|
#lastTiming;
|
|
22
23
|
/** The newest tree revision seen (file-first): the view's, or any cell response's since. */
|
|
23
24
|
#treeRevision;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the open() that returned this handle created the workspace (0.15.0+, contracts §46.2): true for a new key
|
|
27
|
+
* (or a key whose session ended), false when it reconnected to or resumed an existing workspace. Null for handles
|
|
28
|
+
* from get(), list() and findByKey(), and from an API that does not report it.
|
|
29
|
+
*/
|
|
30
|
+
created;
|
|
24
31
|
constructor(ctx, view, opts = {}) {
|
|
25
32
|
this.#ctx = ctx;
|
|
33
|
+
this.created = opts.created ?? null;
|
|
26
34
|
this.#view = view;
|
|
27
35
|
this.#treeRevision = view.mode === 'file_first' && typeof view.tree_revision === 'number' ? view.tree_revision : null;
|
|
28
36
|
this.#defaults = { agentLabel: opts.agentLabel, tools: opts.tools };
|
|
@@ -212,6 +220,34 @@ export class Workspace {
|
|
|
212
220
|
get ports() {
|
|
213
221
|
return new WorkspacePorts(this.#ctx, this.id);
|
|
214
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* The workspace desktop (0.15.0+, contracts §45): `act(actions)`, `screenshot()`, `stream()` (a private link to watch
|
|
225
|
+
* it), `status()`, `start()`, `stop()`. Needs computer use on (`setComputerUse(true)`, or the template's switch); the
|
|
226
|
+
* platform starts the desktop on the first call that needs it.
|
|
227
|
+
*/
|
|
228
|
+
get computer() {
|
|
229
|
+
return new WorkspaceComputer({
|
|
230
|
+
cell: () => this.cell(),
|
|
231
|
+
ports: () => this.ports,
|
|
232
|
+
setEnabled: (enabled) => this.setComputerUse(enabled).then(() => this.computerUse),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/** Computer use (0.15.0+): `enabled` is what tool tokens carry; `workspace` null follows the `template`'s switch. */
|
|
236
|
+
get computerUse() {
|
|
237
|
+
return this.#view.computer_use;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Switches computer use on or off for this workspace (null follows the template). Takes effect on the next tool token:
|
|
241
|
+
* this handle's cached tokens are dropped. 409 `computer_use_unavailable` when its template version cannot run a
|
|
242
|
+
* desktop.
|
|
243
|
+
*/
|
|
244
|
+
async setComputerUse(enabled) {
|
|
245
|
+
const cu = await this.#ctx.workspaces.setComputerUse(this.id, enabled);
|
|
246
|
+
this.#view = { ...this.#view, computer_use: cu };
|
|
247
|
+
for (const m of this.#managers.values())
|
|
248
|
+
m.invalidate();
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
215
251
|
/** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
|
|
216
252
|
inputs() {
|
|
217
253
|
return this.#ctx.workspaces.inputs(this.id);
|