@wavexzore/sandbox 0.1.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/Dockerfile +14 -0
- package/LICENSE +661 -0
- package/NOTICE +3 -0
- package/README.md +153 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/sandbox/cli/install.d.ts +5 -0
- package/dist/sandbox/cli/install.js +335 -0
- package/dist/sandbox/cli/local-store.d.ts +87 -0
- package/dist/sandbox/cli/local-store.js +604 -0
- package/dist/sandbox/cli/opencode-config.d.ts +25 -0
- package/dist/sandbox/cli/opencode-config.js +240 -0
- package/dist/sandbox/cli/path.d.ts +64 -0
- package/dist/sandbox/cli/path.js +127 -0
- package/dist/sandbox/cli/types.d.ts +145 -0
- package/dist/sandbox/cli/types.js +6 -0
- package/dist/sandbox/cli/wavexzore-sandbox.d.ts +65 -0
- package/dist/sandbox/cli/wavexzore-sandbox.js +577 -0
- package/dist/sandbox/core/cli-helper.d.ts +19 -0
- package/dist/sandbox/core/cli-helper.js +64 -0
- package/dist/sandbox/core/docker-archive-utils.d.ts +3 -0
- package/dist/sandbox/core/docker-archive-utils.js +50 -0
- package/dist/sandbox/core/docker-sandbox.d.ts +51 -0
- package/dist/sandbox/core/docker-sandbox.js +675 -0
- package/dist/sandbox/core/edit/filediff.d.ts +16 -0
- package/dist/sandbox/core/edit/filediff.js +21 -0
- package/dist/sandbox/core/edit/index.d.ts +5 -0
- package/dist/sandbox/core/edit/index.js +5 -0
- package/dist/sandbox/core/edit/line-endings.d.ts +4 -0
- package/dist/sandbox/core/edit/line-endings.js +10 -0
- package/dist/sandbox/core/edit/lock.d.ts +1 -0
- package/dist/sandbox/core/edit/lock.js +18 -0
- package/dist/sandbox/core/edit/replace.d.ts +10 -0
- package/dist/sandbox/core/edit/replace.js +14 -0
- package/dist/sandbox/core/edit/replacers.d.ts +15 -0
- package/dist/sandbox/core/edit/replacers.js +241 -0
- package/dist/sandbox/core/logger.d.ts +15 -0
- package/dist/sandbox/core/logger.js +59 -0
- package/dist/sandbox/core/lsp/client.d.ts +63 -0
- package/dist/sandbox/core/lsp/client.js +533 -0
- package/dist/sandbox/core/lsp/config.d.ts +13 -0
- package/dist/sandbox/core/lsp/config.js +36 -0
- package/dist/sandbox/core/lsp/diagnostics.d.ts +12 -0
- package/dist/sandbox/core/lsp/diagnostics.js +65 -0
- package/dist/sandbox/core/lsp/index.d.ts +4 -0
- package/dist/sandbox/core/lsp/index.js +4 -0
- package/dist/sandbox/core/lsp/language.d.ts +24 -0
- package/dist/sandbox/core/lsp/language.js +249 -0
- package/dist/sandbox/core/lsp/manager.d.ts +77 -0
- package/dist/sandbox/core/lsp/manager.js +237 -0
- package/dist/sandbox/core/lsp/tooling.d.ts +14 -0
- package/dist/sandbox/core/lsp/tooling.js +78 -0
- package/dist/sandbox/core/patch-parser.d.ts +23 -0
- package/dist/sandbox/core/patch-parser.js +248 -0
- package/dist/sandbox/core/path-map.d.ts +9 -0
- package/dist/sandbox/core/path-map.js +73 -0
- package/dist/sandbox/core/project-data-storage.d.ts +42 -0
- package/dist/sandbox/core/project-data-storage.js +167 -0
- package/dist/sandbox/core/read/binary.d.ts +4 -0
- package/dist/sandbox/core/read/binary.js +80 -0
- package/dist/sandbox/core/read/format.d.ts +38 -0
- package/dist/sandbox/core/read/format.js +85 -0
- package/dist/sandbox/core/read/index.d.ts +3 -0
- package/dist/sandbox/core/read/index.js +3 -0
- package/dist/sandbox/core/read/permissions.d.ts +7 -0
- package/dist/sandbox/core/read/permissions.js +13 -0
- package/dist/sandbox/core/session-manager.d.ts +29 -0
- package/dist/sandbox/core/session-manager.js +338 -0
- package/dist/sandbox/core/shell/config.d.ts +7 -0
- package/dist/sandbox/core/shell/config.js +82 -0
- package/dist/sandbox/core/shell/output.d.ts +35 -0
- package/dist/sandbox/core/shell/output.js +80 -0
- package/dist/sandbox/core/shell/parser.d.ts +7 -0
- package/dist/sandbox/core/shell/parser.js +122 -0
- package/dist/sandbox/core/shell/permissions.d.ts +13 -0
- package/dist/sandbox/core/shell/permissions.js +33 -0
- package/dist/sandbox/core/shell/workdir.d.ts +4 -0
- package/dist/sandbox/core/shell/workdir.js +19 -0
- package/dist/sandbox/core/stream-utils.d.ts +23 -0
- package/dist/sandbox/core/stream-utils.js +97 -0
- package/dist/sandbox/core/toast.d.ts +47 -0
- package/dist/sandbox/core/toast.js +73 -0
- package/dist/sandbox/core/types.d.ts +159 -0
- package/dist/sandbox/core/types.js +11 -0
- package/dist/sandbox/core/write/bom.d.ts +8 -0
- package/dist/sandbox/core/write/bom.js +15 -0
- package/dist/sandbox/core/write/config.d.ts +14 -0
- package/dist/sandbox/core/write/config.js +188 -0
- package/dist/sandbox/core/write/diagnostics.d.ts +19 -0
- package/dist/sandbox/core/write/diagnostics.js +120 -0
- package/dist/sandbox/core/write/diff.d.ts +7 -0
- package/dist/sandbox/core/write/diff.js +21 -0
- package/dist/sandbox/core/write/formatter.d.ts +16 -0
- package/dist/sandbox/core/write/formatter.js +51 -0
- package/dist/sandbox/core/write/index.d.ts +6 -0
- package/dist/sandbox/core/write/index.js +5 -0
- package/dist/sandbox/core/write/permissions.d.ts +13 -0
- package/dist/sandbox/core/write/permissions.js +19 -0
- package/dist/sandbox/core/write/pipeline.d.ts +48 -0
- package/dist/sandbox/core/write/pipeline.js +229 -0
- package/dist/sandbox/core/write/read-tracker.d.ts +13 -0
- package/dist/sandbox/core/write/read-tracker.js +30 -0
- package/dist/sandbox/git/host-git-manager.d.ts +40 -0
- package/dist/sandbox/git/host-git-manager.js +278 -0
- package/dist/sandbox/git/index.d.ts +5 -0
- package/dist/sandbox/git/index.js +5 -0
- package/dist/sandbox/git/sandbox-git-manager.d.ts +14 -0
- package/dist/sandbox/git/sandbox-git-manager.js +54 -0
- package/dist/sandbox/git/session-git-manager.d.ts +18 -0
- package/dist/sandbox/git/session-git-manager.js +85 -0
- package/dist/sandbox/index.d.ts +205 -0
- package/dist/sandbox/index.js +70 -0
- package/dist/sandbox/plugins/custom-tools.d.ts +203 -0
- package/dist/sandbox/plugins/custom-tools.js +15 -0
- package/dist/sandbox/plugins/session-events.d.ts +10 -0
- package/dist/sandbox/plugins/session-events.js +56 -0
- package/dist/sandbox/plugins/system-transform.d.ts +10 -0
- package/dist/sandbox/plugins/system-transform.js +23 -0
- package/dist/sandbox/tools/bash-output.d.ts +17 -0
- package/dist/sandbox/tools/bash-output.js +35 -0
- package/dist/sandbox/tools/bash-status.d.ts +13 -0
- package/dist/sandbox/tools/bash-status.js +29 -0
- package/dist/sandbox/tools/bash-stop.d.ts +13 -0
- package/dist/sandbox/tools/bash-stop.js +28 -0
- package/dist/sandbox/tools/bash.d.ts +26 -0
- package/dist/sandbox/tools/bash.js +120 -0
- package/dist/sandbox/tools/edit.d.ts +20 -0
- package/dist/sandbox/tools/edit.js +87 -0
- package/dist/sandbox/tools/get-preview-url.d.ts +17 -0
- package/dist/sandbox/tools/get-preview-url.js +16 -0
- package/dist/sandbox/tools/glob.d.ts +17 -0
- package/dist/sandbox/tools/glob.js +23 -0
- package/dist/sandbox/tools/grep.d.ts +17 -0
- package/dist/sandbox/tools/grep.js +23 -0
- package/dist/sandbox/tools/ls.d.ts +17 -0
- package/dist/sandbox/tools/ls.js +21 -0
- package/dist/sandbox/tools/lsp.d.ts +41 -0
- package/dist/sandbox/tools/lsp.js +198 -0
- package/dist/sandbox/tools/multiedit.d.ts +24 -0
- package/dist/sandbox/tools/multiedit.js +83 -0
- package/dist/sandbox/tools/patch.d.ts +14 -0
- package/dist/sandbox/tools/patch.js +260 -0
- package/dist/sandbox/tools/read.d.ts +22 -0
- package/dist/sandbox/tools/read.js +105 -0
- package/dist/sandbox/tools/write.d.ts +16 -0
- package/dist/sandbox/tools/write.js +27 -0
- package/dist/sandbox/tools.d.ts +200 -0
- package/dist/sandbox/tools.js +43 -0
- package/package.json +55 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
export const DEFAULT_SHELL_TIMEOUT_MS = 120_000;
|
|
5
|
+
export const DEFAULT_SHELL_MAX_OUTPUT_BYTES = 200_000;
|
|
6
|
+
export const DEFAULT_SHELL_MAX_OUTPUT_LINES = 2_000;
|
|
7
|
+
export const DEFAULT_SHELL_METADATA_BYTES = 30_000;
|
|
8
|
+
export function previewOutput(output, maxBytes = DEFAULT_SHELL_METADATA_BYTES) {
|
|
9
|
+
if (Buffer.byteLength(output, 'utf8') <= maxBytes)
|
|
10
|
+
return output;
|
|
11
|
+
return tailBytes(output, maxBytes);
|
|
12
|
+
}
|
|
13
|
+
export function tailBytes(value, maxBytes) {
|
|
14
|
+
const buffer = Buffer.from(value, 'utf8');
|
|
15
|
+
if (buffer.length <= maxBytes)
|
|
16
|
+
return value;
|
|
17
|
+
return buffer
|
|
18
|
+
.subarray(buffer.length - maxBytes)
|
|
19
|
+
.toString('utf8')
|
|
20
|
+
.replace(/^\uFFFD+/, '');
|
|
21
|
+
}
|
|
22
|
+
export function truncateShellOutput(value, limits) {
|
|
23
|
+
const bytes = Buffer.byteLength(value, 'utf8');
|
|
24
|
+
const lines = value === '' ? 0 : value.split(/\r?\n/).length;
|
|
25
|
+
let output = value;
|
|
26
|
+
let truncated = false;
|
|
27
|
+
if (lines > limits.maxLines) {
|
|
28
|
+
output = output.split(/\r?\n/).slice(-limits.maxLines).join('\n');
|
|
29
|
+
truncated = true;
|
|
30
|
+
}
|
|
31
|
+
if (Buffer.byteLength(output, 'utf8') > limits.maxBytes) {
|
|
32
|
+
output = tailBytes(output, limits.maxBytes);
|
|
33
|
+
truncated = true;
|
|
34
|
+
}
|
|
35
|
+
return { output, truncated, bytes, lines };
|
|
36
|
+
}
|
|
37
|
+
export function formatShellOutput(input) {
|
|
38
|
+
const parts = [];
|
|
39
|
+
if (input.stdout)
|
|
40
|
+
parts.push(input.stdout.replace(/\s+$/, ''));
|
|
41
|
+
if (input.stderr)
|
|
42
|
+
parts.push(input.stderr.replace(/\s+$/, ''));
|
|
43
|
+
let output = parts.filter(Boolean).join('\n');
|
|
44
|
+
if (!output)
|
|
45
|
+
output = '(no output)';
|
|
46
|
+
const metadata = [
|
|
47
|
+
`exitCode: ${input.exitCode === null ? 'null' : input.exitCode}`,
|
|
48
|
+
`durationMs: ${input.durationMs}`,
|
|
49
|
+
];
|
|
50
|
+
if (typeof input.stdoutBytes === 'number')
|
|
51
|
+
metadata.push(`stdoutBytes: ${input.stdoutBytes}`);
|
|
52
|
+
if (typeof input.stderrBytes === 'number')
|
|
53
|
+
metadata.push(`stderrBytes: ${input.stderrBytes}`);
|
|
54
|
+
if (typeof input.stdoutLines === 'number')
|
|
55
|
+
metadata.push(`stdoutLines: ${input.stdoutLines}`);
|
|
56
|
+
if (typeof input.stderrLines === 'number')
|
|
57
|
+
metadata.push(`stderrLines: ${input.stderrLines}`);
|
|
58
|
+
if (input.timedOut) {
|
|
59
|
+
metadata.push('timedOut: true', 'shell tool terminated the command after exceeding the timeout. If this command is expected to take longer and is not waiting for interactive input, retry with a larger timeout value in milliseconds.');
|
|
60
|
+
}
|
|
61
|
+
if (input.aborted)
|
|
62
|
+
metadata.push('aborted: true');
|
|
63
|
+
if (input.truncated)
|
|
64
|
+
metadata.push('truncated: true');
|
|
65
|
+
if (input.outputPath)
|
|
66
|
+
metadata.push(`fullOutputPath: ${input.outputPath}`);
|
|
67
|
+
return `${output}\n\n<shell_metadata>\n${metadata.join('\n')}\n</shell_metadata>`;
|
|
68
|
+
}
|
|
69
|
+
export function writeHostShellOutput(input) {
|
|
70
|
+
const xdgData = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share');
|
|
71
|
+
const dir = join(xdgData, 'opencode', 'storage', 'sandbox', 'shell-output', sanitizePathPart(input.sessionId));
|
|
72
|
+
mkdirSync(dir, { recursive: true });
|
|
73
|
+
const file = join(dir, `${sanitizePathPart(input.cmdId)}.log`);
|
|
74
|
+
const content = input.stderr ? `${input.stdout}\n\n[stderr]\n${input.stderr}` : input.stdout;
|
|
75
|
+
writeFileSync(file, content);
|
|
76
|
+
return file;
|
|
77
|
+
}
|
|
78
|
+
function sanitizePathPart(value) {
|
|
79
|
+
return value.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 120) || 'unknown';
|
|
80
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type ParsedShellCommand = {
|
|
2
|
+
text: string;
|
|
3
|
+
tokens: string[];
|
|
4
|
+
};
|
|
5
|
+
export declare function parseShellCommands(command: string, limit?: number): ParsedShellCommand[];
|
|
6
|
+
export declare function collectCommandPatterns(command: string): string[];
|
|
7
|
+
export declare function collectAlwaysPatterns(command: string): string[];
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export function parseShellCommands(command, limit = 8) {
|
|
2
|
+
const trimmed = command.trim();
|
|
3
|
+
if (!trimmed)
|
|
4
|
+
return [];
|
|
5
|
+
const split = splitCommandSegments(trimmed, limit);
|
|
6
|
+
const segments = split.fallback ? [trimmed] : split.segments;
|
|
7
|
+
return segments
|
|
8
|
+
.map((text) => ({ text, tokens: tokenizeShellCommand(text) }))
|
|
9
|
+
.filter((item) => item.text.length > 0)
|
|
10
|
+
.slice(0, limit);
|
|
11
|
+
}
|
|
12
|
+
export function collectCommandPatterns(command) {
|
|
13
|
+
const commands = parseShellCommands(command);
|
|
14
|
+
return commands.length ? commands.map((item) => item.text) : [command.trim()].filter(Boolean);
|
|
15
|
+
}
|
|
16
|
+
export function collectAlwaysPatterns(command) {
|
|
17
|
+
return parseShellCommands(command)
|
|
18
|
+
.map((item) => item.tokens.slice(0, 2).join(' ').trim())
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.map((prefix) => `${prefix} *`);
|
|
21
|
+
}
|
|
22
|
+
function splitCommandSegments(command, limit) {
|
|
23
|
+
const segments = [];
|
|
24
|
+
let current = '';
|
|
25
|
+
let quote;
|
|
26
|
+
let escaped = false;
|
|
27
|
+
for (let i = 0; i < command.length; i++) {
|
|
28
|
+
const char = command[i];
|
|
29
|
+
const next = command[i + 1];
|
|
30
|
+
if (escaped) {
|
|
31
|
+
current += char;
|
|
32
|
+
escaped = false;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === '\\') {
|
|
36
|
+
current += char;
|
|
37
|
+
escaped = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (quote) {
|
|
41
|
+
current += char;
|
|
42
|
+
if (char === quote)
|
|
43
|
+
quote = undefined;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (char === '"' || char === "'") {
|
|
47
|
+
current += char;
|
|
48
|
+
quote = char;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (char === ';' || char === '\n') {
|
|
52
|
+
pushSegment();
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if ((char === '&' && next === '&') || (char === '|' && next === '|')) {
|
|
56
|
+
pushSegment();
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (char === '|') {
|
|
61
|
+
pushSegment();
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
current += char;
|
|
65
|
+
}
|
|
66
|
+
if (quote || escaped)
|
|
67
|
+
return { segments: [], fallback: true };
|
|
68
|
+
pushSegment();
|
|
69
|
+
return { segments: segments.slice(0, limit), fallback: false };
|
|
70
|
+
function pushSegment() {
|
|
71
|
+
const segment = current.trim();
|
|
72
|
+
current = '';
|
|
73
|
+
if (segment)
|
|
74
|
+
segments.push(segment);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function tokenizeShellCommand(command) {
|
|
78
|
+
const tokens = [];
|
|
79
|
+
let current = '';
|
|
80
|
+
let quote;
|
|
81
|
+
let escaped = false;
|
|
82
|
+
for (let i = 0; i < command.length; i++) {
|
|
83
|
+
const char = command[i];
|
|
84
|
+
if (escaped) {
|
|
85
|
+
current += char;
|
|
86
|
+
escaped = false;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (char === '\\') {
|
|
90
|
+
escaped = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (quote) {
|
|
94
|
+
if (char === quote)
|
|
95
|
+
quote = undefined;
|
|
96
|
+
else
|
|
97
|
+
current += char;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (char === '"' || char === "'") {
|
|
101
|
+
quote = char;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (/\s/.test(char)) {
|
|
105
|
+
pushToken();
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (char === '>' || char === '<') {
|
|
109
|
+
pushToken();
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
current += char;
|
|
113
|
+
}
|
|
114
|
+
pushToken();
|
|
115
|
+
return tokens;
|
|
116
|
+
function pushToken() {
|
|
117
|
+
if (!current)
|
|
118
|
+
return;
|
|
119
|
+
tokens.push(current);
|
|
120
|
+
current = '';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ToolContext } from '@opencode-ai/plugin/tool';
|
|
2
|
+
export declare function commandDescription(command: string, description?: string): string;
|
|
3
|
+
export declare function collectSimpleBashPatterns(command: string): string[];
|
|
4
|
+
export declare function collectSimpleBashAlwaysPatterns(command: string): string[];
|
|
5
|
+
export declare function requestBashPermission(input: {
|
|
6
|
+
ctx: ToolContext;
|
|
7
|
+
command: string;
|
|
8
|
+
description: string;
|
|
9
|
+
workdir: string;
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
background?: boolean;
|
|
12
|
+
sandboxId?: string;
|
|
13
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { collectAlwaysPatterns, collectCommandPatterns } from './parser.js';
|
|
2
|
+
export function commandDescription(command, description) {
|
|
3
|
+
const trimmed = description?.trim();
|
|
4
|
+
if (trimmed)
|
|
5
|
+
return trimmed;
|
|
6
|
+
const firstLine = command.trim().split(/\r?\n/, 1)[0] ?? command;
|
|
7
|
+
return firstLine.length > 80 ? `${firstLine.slice(0, 77)}...` : firstLine;
|
|
8
|
+
}
|
|
9
|
+
export function collectSimpleBashPatterns(command) {
|
|
10
|
+
return collectCommandPatterns(command);
|
|
11
|
+
}
|
|
12
|
+
export function collectSimpleBashAlwaysPatterns(command) {
|
|
13
|
+
return collectAlwaysPatterns(command);
|
|
14
|
+
}
|
|
15
|
+
export async function requestBashPermission(input) {
|
|
16
|
+
if (typeof input.ctx.ask !== 'function')
|
|
17
|
+
return;
|
|
18
|
+
const patterns = collectSimpleBashPatterns(input.command);
|
|
19
|
+
const always = collectSimpleBashAlwaysPatterns(input.command);
|
|
20
|
+
await input.ctx.ask({
|
|
21
|
+
permission: 'bash',
|
|
22
|
+
patterns,
|
|
23
|
+
always: always.length ? always : patterns,
|
|
24
|
+
metadata: {
|
|
25
|
+
command: input.command,
|
|
26
|
+
description: input.description,
|
|
27
|
+
workdir: input.workdir,
|
|
28
|
+
timeout: input.timeoutMs,
|
|
29
|
+
background: Boolean(input.background),
|
|
30
|
+
sandbox: input.sandboxId,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import posix from 'path/posix';
|
|
3
|
+
function isInsideOrEqualPosix(target, root) {
|
|
4
|
+
const relative = posix.relative(root, target);
|
|
5
|
+
return relative === '' || (!relative.startsWith('..') && !posix.isAbsolute(relative));
|
|
6
|
+
}
|
|
7
|
+
export function resolveShellWorkdir(input, paths, opts) {
|
|
8
|
+
if (!input || input.trim() === '')
|
|
9
|
+
return paths.repoPath;
|
|
10
|
+
const containerPath = paths.toContainer(input);
|
|
11
|
+
const normalized = posix.normalize(containerPath);
|
|
12
|
+
if (!opts?.allowOutsideProject && !isInsideOrEqualPosix(normalized, paths.repoPath)) {
|
|
13
|
+
throw new Error(`workdir must be inside the mapped project (${paths.repoPath}). Received: ${input}. Use a path under the host worktree (${paths.worktree}) or ${paths.repoPath}.`);
|
|
14
|
+
}
|
|
15
|
+
if (!opts?.allowOutsideProject && path.isAbsolute(input) && !paths.isMapped(input)) {
|
|
16
|
+
throw new Error(`workdir is outside the mapped project: ${input}`);
|
|
17
|
+
}
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type Dockerode from 'dockerode';
|
|
2
|
+
export type DemuxedOutput = {
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function demuxStream(stream: NodeJS.ReadableStream): Promise<DemuxedOutput>;
|
|
7
|
+
export declare function execInContainer(container: Dockerode.Container, command: string[], workDir?: string, opts?: {
|
|
8
|
+
tty?: boolean;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
exitCode: number;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function parseLsOutput(output: string): Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
isDirectory: boolean;
|
|
17
|
+
size: number;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function parseGrepOutput(output: string): Array<{
|
|
20
|
+
file: string;
|
|
21
|
+
line: number;
|
|
22
|
+
content: string;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const HEADER_SIZE = 8;
|
|
2
|
+
const STREAM_STDOUT = 1;
|
|
3
|
+
const STREAM_STDERR = 2;
|
|
4
|
+
export function demuxStream(stream) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
let stdout = '';
|
|
7
|
+
let stderr = '';
|
|
8
|
+
let buffer = Buffer.alloc(0);
|
|
9
|
+
stream.on('data', (chunk) => {
|
|
10
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
11
|
+
while (buffer.length >= HEADER_SIZE) {
|
|
12
|
+
const header = buffer.subarray(0, HEADER_SIZE);
|
|
13
|
+
const streamType = header[0];
|
|
14
|
+
const payloadSize = header.readUInt32BE(4);
|
|
15
|
+
if (buffer.length < HEADER_SIZE + payloadSize)
|
|
16
|
+
break;
|
|
17
|
+
const payload = buffer.subarray(HEADER_SIZE, HEADER_SIZE + payloadSize);
|
|
18
|
+
const content = payload.toString('utf-8');
|
|
19
|
+
if (streamType === STREAM_STDOUT) {
|
|
20
|
+
stdout += content;
|
|
21
|
+
}
|
|
22
|
+
else if (streamType === STREAM_STDERR) {
|
|
23
|
+
stderr += content;
|
|
24
|
+
}
|
|
25
|
+
buffer = buffer.subarray(HEADER_SIZE + payloadSize);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
stream.on('end', () => resolve({ stdout, stderr }));
|
|
29
|
+
stream.on('error', (err) => reject(err));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export async function execInContainer(container, command, workDir, opts) {
|
|
33
|
+
const tty = opts?.tty ?? true;
|
|
34
|
+
const exec = await container.exec({
|
|
35
|
+
Cmd: command,
|
|
36
|
+
WorkingDir: workDir,
|
|
37
|
+
AttachStdout: true,
|
|
38
|
+
AttachStderr: true,
|
|
39
|
+
Tty: tty,
|
|
40
|
+
});
|
|
41
|
+
const stream = await exec.start({ Tty: tty });
|
|
42
|
+
const output = tty ? { stdout: await readStreamFully(stream), stderr: '' } : await demuxStream(stream);
|
|
43
|
+
const inspectResult = await exec.inspect();
|
|
44
|
+
const exitCode = inspectResult.ExitCode ?? 1;
|
|
45
|
+
if (tty) {
|
|
46
|
+
return {
|
|
47
|
+
exitCode,
|
|
48
|
+
stdout: exitCode === 0 ? output.stdout : '',
|
|
49
|
+
stderr: exitCode !== 0 ? output.stdout : '',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
exitCode,
|
|
54
|
+
stdout: output.stdout,
|
|
55
|
+
stderr: output.stderr,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function readStreamFully(stream) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const chunks = [];
|
|
61
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
62
|
+
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').replace(/\r\n/g, '\n')));
|
|
63
|
+
stream.on('error', (err) => reject(err));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function parseLsOutput(output) {
|
|
67
|
+
const lines = output.trim().split('\n').filter(Boolean);
|
|
68
|
+
return lines.map((line) => {
|
|
69
|
+
const parts = line.split(/\s+/);
|
|
70
|
+
const perms = parts[0] || '';
|
|
71
|
+
const size = parseInt(parts[4] || '0', 10);
|
|
72
|
+
const name = parts.slice(8).join(' ');
|
|
73
|
+
return {
|
|
74
|
+
name,
|
|
75
|
+
isDirectory: perms.startsWith('d'),
|
|
76
|
+
size: isNaN(size) ? 0 : size,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function parseGrepOutput(output) {
|
|
81
|
+
const lines = output.trim().split('\n').filter(Boolean);
|
|
82
|
+
return lines.map((line) => {
|
|
83
|
+
const firstColon = line.indexOf(':');
|
|
84
|
+
const secondColon = line.indexOf(':', firstColon + 1);
|
|
85
|
+
if (firstColon === -1 || secondColon === -1) {
|
|
86
|
+
return { file: line, line: 0, content: '' };
|
|
87
|
+
}
|
|
88
|
+
const file = line.substring(0, firstColon);
|
|
89
|
+
const lineNum = parseInt(line.substring(firstColon + 1, secondColon), 10);
|
|
90
|
+
const content = line.substring(secondColon + 1);
|
|
91
|
+
return {
|
|
92
|
+
file,
|
|
93
|
+
line: isNaN(lineNum) ? 0 : lineNum,
|
|
94
|
+
content,
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Daytona Platforms Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Global toast notification singleton
|
|
7
|
+
* Queues toasts to prevent showing multiple at the same time
|
|
8
|
+
*/
|
|
9
|
+
type ToastVariant = 'success' | 'error' | 'warning' | 'info';
|
|
10
|
+
interface ToastOptions {
|
|
11
|
+
title: string;
|
|
12
|
+
message: string;
|
|
13
|
+
variant?: ToastVariant;
|
|
14
|
+
}
|
|
15
|
+
interface TuiShowToast {
|
|
16
|
+
showToast: (options: {
|
|
17
|
+
body: {
|
|
18
|
+
title: string;
|
|
19
|
+
message: string;
|
|
20
|
+
variant: ToastVariant;
|
|
21
|
+
};
|
|
22
|
+
}) => void;
|
|
23
|
+
}
|
|
24
|
+
declare class ToastManager {
|
|
25
|
+
private tui;
|
|
26
|
+
private queue;
|
|
27
|
+
private isShowing;
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the toast manager with the TUI instance
|
|
30
|
+
*/
|
|
31
|
+
initialize(tui: TuiShowToast | null | undefined): void;
|
|
32
|
+
/**
|
|
33
|
+
* Show a toast notification
|
|
34
|
+
* If a toast is currently showing, this will be queued
|
|
35
|
+
*/
|
|
36
|
+
show(options: ToastOptions): void;
|
|
37
|
+
/**
|
|
38
|
+
* Process the toast queue, showing one toast at a time
|
|
39
|
+
*/
|
|
40
|
+
private processQueue;
|
|
41
|
+
/**
|
|
42
|
+
* Clear all pending toasts
|
|
43
|
+
*/
|
|
44
|
+
clear(): void;
|
|
45
|
+
}
|
|
46
|
+
export declare const toast: ToastManager;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Daytona Platforms Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
class ToastManager {
|
|
6
|
+
tui = null;
|
|
7
|
+
queue = [];
|
|
8
|
+
isShowing = false;
|
|
9
|
+
/**
|
|
10
|
+
* Initialize the toast manager with the TUI instance
|
|
11
|
+
*/
|
|
12
|
+
initialize(tui) {
|
|
13
|
+
this.tui = tui || null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Show a toast notification
|
|
17
|
+
* If a toast is currently showing, this will be queued
|
|
18
|
+
*/
|
|
19
|
+
show(options) {
|
|
20
|
+
const toast = {
|
|
21
|
+
variant: 'info',
|
|
22
|
+
...options,
|
|
23
|
+
};
|
|
24
|
+
this.queue.push(toast);
|
|
25
|
+
this.processQueue();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Process the toast queue, showing one toast at a time
|
|
29
|
+
*/
|
|
30
|
+
processQueue() {
|
|
31
|
+
if (this.isShowing || this.queue.length === 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!this.tui) {
|
|
35
|
+
// If TUI is not available, clear the queue
|
|
36
|
+
this.queue = [];
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.isShowing = true;
|
|
40
|
+
const toast = this.queue.shift();
|
|
41
|
+
if (!toast) {
|
|
42
|
+
this.isShowing = false;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
this.tui.showToast({
|
|
47
|
+
body: {
|
|
48
|
+
title: toast.title,
|
|
49
|
+
message: toast.message,
|
|
50
|
+
variant: toast.variant || 'info',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// If showing fails, continue with next toast
|
|
56
|
+
console.error('Failed to show toast:', err);
|
|
57
|
+
}
|
|
58
|
+
// Wait a bit before showing the next toast to avoid overlap
|
|
59
|
+
// Most toasts are visible for 2-3 seconds, so we wait 2.5 seconds
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
this.isShowing = false;
|
|
62
|
+
this.processQueue();
|
|
63
|
+
}, 2500);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Clear all pending toasts
|
|
67
|
+
*/
|
|
68
|
+
clear() {
|
|
69
|
+
this.queue = [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Export singleton instance
|
|
73
|
+
export const toast = new ToastManager();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Daytona Platforms Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Type definitions and constants for the Docker sandbox OpenCode plugin
|
|
7
|
+
*/
|
|
8
|
+
export type EventSessionDeleted = {
|
|
9
|
+
type: 'session.deleted';
|
|
10
|
+
properties: {
|
|
11
|
+
info: {
|
|
12
|
+
id: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export type EventSessionIdle = {
|
|
17
|
+
type: 'session.idle';
|
|
18
|
+
properties: {
|
|
19
|
+
sessionID: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type ExperimentalChatSystemTransformInput = {
|
|
23
|
+
sessionID?: string;
|
|
24
|
+
model: any;
|
|
25
|
+
};
|
|
26
|
+
export type ExperimentalChatSystemTransformOutput = {
|
|
27
|
+
system: string[];
|
|
28
|
+
};
|
|
29
|
+
export declare const EVENT_TYPE_SESSION_DELETED = "session.deleted";
|
|
30
|
+
export declare const EVENT_TYPE_SESSION_IDLE = "session.idle";
|
|
31
|
+
export type LogLevel = 'INFO' | 'ERROR' | 'WARN';
|
|
32
|
+
export type SandboxInfo = {
|
|
33
|
+
id: string;
|
|
34
|
+
};
|
|
35
|
+
export type SessionInfo = {
|
|
36
|
+
sandboxId: string;
|
|
37
|
+
branchNumber?: number;
|
|
38
|
+
created: number;
|
|
39
|
+
lastAccessed: number;
|
|
40
|
+
};
|
|
41
|
+
export type ProjectSessionData = {
|
|
42
|
+
projectId: string;
|
|
43
|
+
worktree: string;
|
|
44
|
+
sessions: Record<string, SessionInfo>;
|
|
45
|
+
};
|
|
46
|
+
export type SessionSandboxMap = Map<string, Sandbox | SandboxInfo>;
|
|
47
|
+
export type SandboxCommandOptions = {
|
|
48
|
+
cwd?: string;
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
signal?: AbortSignal;
|
|
51
|
+
sessionId?: string;
|
|
52
|
+
maxOutputBytes?: number;
|
|
53
|
+
maxOutputLines?: number;
|
|
54
|
+
tty?: boolean;
|
|
55
|
+
onUpdate?: (update: SandboxCommandUpdate) => void;
|
|
56
|
+
};
|
|
57
|
+
export type SandboxCommandUpdate = SandboxBackgroundOutput & {
|
|
58
|
+
durationMs: number;
|
|
59
|
+
};
|
|
60
|
+
export type SandboxProcessResult = {
|
|
61
|
+
exitCode: number | null;
|
|
62
|
+
result: string;
|
|
63
|
+
stdout?: string;
|
|
64
|
+
stderr?: string;
|
|
65
|
+
durationMs?: number;
|
|
66
|
+
timedOut?: boolean;
|
|
67
|
+
aborted?: boolean;
|
|
68
|
+
truncated?: boolean;
|
|
69
|
+
outputPath?: string;
|
|
70
|
+
stdoutBytes?: number;
|
|
71
|
+
stderrBytes?: number;
|
|
72
|
+
stdoutLines?: number;
|
|
73
|
+
stderrLines?: number;
|
|
74
|
+
cmdId?: string;
|
|
75
|
+
};
|
|
76
|
+
export type SandboxBackgroundCommandOptions = {
|
|
77
|
+
cwd?: string;
|
|
78
|
+
sessionId?: string;
|
|
79
|
+
};
|
|
80
|
+
export type SandboxBackgroundStartResult = {
|
|
81
|
+
cmdId: string;
|
|
82
|
+
runDir: string;
|
|
83
|
+
startedAt: number;
|
|
84
|
+
};
|
|
85
|
+
export type SandboxBackgroundStatus = {
|
|
86
|
+
cmdId: string;
|
|
87
|
+
status: 'running' | 'exited' | 'failed' | 'stopped' | 'unknown';
|
|
88
|
+
exitCode: number | null;
|
|
89
|
+
pid?: number;
|
|
90
|
+
runDir?: string;
|
|
91
|
+
};
|
|
92
|
+
export type SandboxBackgroundOutput = {
|
|
93
|
+
cmdId: string;
|
|
94
|
+
output: string;
|
|
95
|
+
stdout: string;
|
|
96
|
+
stderr: string;
|
|
97
|
+
stdoutBytes: number;
|
|
98
|
+
stderrBytes: number;
|
|
99
|
+
stdoutLines?: number;
|
|
100
|
+
stderrLines?: number;
|
|
101
|
+
truncated: boolean;
|
|
102
|
+
runDir?: string;
|
|
103
|
+
};
|
|
104
|
+
export type SandboxSessionResult = {
|
|
105
|
+
cmdId: string;
|
|
106
|
+
};
|
|
107
|
+
export type SandboxFileInfo = {
|
|
108
|
+
name: string;
|
|
109
|
+
isDirectory: boolean;
|
|
110
|
+
size: number;
|
|
111
|
+
};
|
|
112
|
+
export type SandboxFileSearchResult = {
|
|
113
|
+
files: string[];
|
|
114
|
+
};
|
|
115
|
+
export type SandboxMatch = {
|
|
116
|
+
file: string;
|
|
117
|
+
line: number;
|
|
118
|
+
content: string;
|
|
119
|
+
};
|
|
120
|
+
export type SandboxPreviewLink = {
|
|
121
|
+
url: string;
|
|
122
|
+
};
|
|
123
|
+
export interface Sandbox {
|
|
124
|
+
readonly id: string;
|
|
125
|
+
readonly state: string;
|
|
126
|
+
readonly process: {
|
|
127
|
+
executeCommand(command: string, cwd?: string | SandboxCommandOptions): Promise<SandboxProcessResult>;
|
|
128
|
+
startBackgroundCommand(command: string, opts?: SandboxBackgroundCommandOptions): Promise<SandboxBackgroundStartResult>;
|
|
129
|
+
getBackgroundCommand(cmdId: string): Promise<SandboxBackgroundStatus>;
|
|
130
|
+
readBackgroundCommand(cmdId: string, opts?: {
|
|
131
|
+
maxOutputBytes?: number;
|
|
132
|
+
maxOutputLines?: number;
|
|
133
|
+
}): Promise<SandboxBackgroundOutput>;
|
|
134
|
+
stopBackgroundCommand(cmdId: string): Promise<SandboxBackgroundStatus>;
|
|
135
|
+
createSession(sessionId: string): Promise<void>;
|
|
136
|
+
getSession(sessionId: string): Promise<void>;
|
|
137
|
+
executeSessionCommand(sessionId: string, opts: {
|
|
138
|
+
command: string;
|
|
139
|
+
runAsync?: boolean;
|
|
140
|
+
cwd?: string;
|
|
141
|
+
}): Promise<SandboxSessionResult>;
|
|
142
|
+
};
|
|
143
|
+
readonly fs: {
|
|
144
|
+
downloadFile(path: string): Promise<Buffer>;
|
|
145
|
+
uploadFile(content: Buffer, path: string): Promise<void>;
|
|
146
|
+
createFolder(path: string, mode?: string): Promise<void>;
|
|
147
|
+
listFiles(path: string): Promise<SandboxFileInfo[]>;
|
|
148
|
+
searchFiles(path: string, pattern: string): Promise<SandboxFileSearchResult>;
|
|
149
|
+
findFiles(path: string, pattern: string): Promise<SandboxMatch[]>;
|
|
150
|
+
};
|
|
151
|
+
getWorkDir(): Promise<string>;
|
|
152
|
+
getPreviewLink(port: number): Promise<SandboxPreviewLink>;
|
|
153
|
+
start(): Promise<void>;
|
|
154
|
+
refreshData(): Promise<void>;
|
|
155
|
+
delete(): Promise<void>;
|
|
156
|
+
}
|
|
157
|
+
export declare const LOG_LEVEL_INFO: LogLevel;
|
|
158
|
+
export declare const LOG_LEVEL_ERROR: LogLevel;
|
|
159
|
+
export declare const LOG_LEVEL_WARN: LogLevel;
|