@ai-ecoverse/slicc-agent 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +51 -1
  2. package/dist/agent-worker.js +6 -0
  3. package/dist/agent-worker.js.map +7 -0
  4. package/dist/agent.js +2 -1
  5. package/dist/agent.js.map +2 -2
  6. package/dist/client.js +4 -1
  7. package/dist/client.js.map +2 -2
  8. package/dist/credentials.js +100 -0
  9. package/dist/credentials.js.map +7 -0
  10. package/dist/host.js +14 -2
  11. package/dist/host.js.map +2 -2
  12. package/dist/index.js +31 -1
  13. package/dist/index.js.map +2 -2
  14. package/dist/kernel/client.js +1 -0
  15. package/dist/kernel/client.js.map +7 -0
  16. package/dist/kernel/env.js +204 -0
  17. package/dist/kernel/env.js.map +7 -0
  18. package/dist/kernel/exec.js +115 -0
  19. package/dist/kernel/exec.js.map +7 -0
  20. package/dist/kernel/files.js +153 -0
  21. package/dist/kernel/files.js.map +7 -0
  22. package/dist/kernel/paths.js +31 -0
  23. package/dist/kernel/paths.js.map +7 -0
  24. package/dist/kernel/watch.js +71 -0
  25. package/dist/kernel/watch.js.map +7 -0
  26. package/dist/main.js +46 -0
  27. package/dist/main.js.map +7 -0
  28. package/dist/net.js +39 -0
  29. package/dist/net.js.map +7 -0
  30. package/dist/owner.js +32 -9
  31. package/dist/owner.js.map +2 -2
  32. package/dist/prompt.js +18 -0
  33. package/dist/prompt.js.map +7 -0
  34. package/dist/settings.js +77 -0
  35. package/dist/settings.js.map +7 -0
  36. package/dist/spectrum/index.js +10 -2
  37. package/dist/spectrum/index.js.map +3 -3
  38. package/dist/spectrum/settings-port.js +73 -0
  39. package/dist/spectrum/settings-port.js.map +7 -0
  40. package/dist/types/agent-worker.d.ts +1 -0
  41. package/dist/types/agent.d.ts +1 -0
  42. package/dist/types/client.d.ts +2 -0
  43. package/dist/types/credentials.d.ts +12 -0
  44. package/dist/types/host.d.ts +2 -0
  45. package/dist/types/index.d.ts +8 -1
  46. package/dist/types/kernel/client.d.ts +39 -0
  47. package/dist/types/kernel/env.d.ts +57 -0
  48. package/dist/types/kernel/exec.d.ts +5 -0
  49. package/dist/types/kernel/files.d.ts +34 -0
  50. package/dist/types/kernel/paths.d.ts +4 -0
  51. package/dist/types/kernel/watch.d.ts +10 -0
  52. package/dist/types/main.d.ts +23 -0
  53. package/dist/types/net.d.ts +19 -0
  54. package/dist/types/owner.d.ts +6 -2
  55. package/dist/types/prompt.d.ts +1 -0
  56. package/dist/types/settings.d.ts +37 -0
  57. package/dist/types/spectrum/index.d.ts +6 -1
  58. package/dist/types/spectrum/settings-port.d.ts +17 -0
  59. package/dist/types/worker.d.ts +1 -0
  60. package/dist/worker.js +9 -0
  61. package/dist/worker.js.map +2 -2
  62. package/package.json +10 -2
@@ -0,0 +1,204 @@
1
+ import {
2
+ err,
3
+ FileError,
4
+ ok
5
+ } from "@earendil-works/pi-durable/env";
6
+ import { execute } from "./exec.js";
7
+ import { attempt, info, KernelDirReader, SnapshotLines, SnapshotReader } from "./files.js";
8
+ import { dirname, normalize, resolve } from "./paths.js";
9
+ import { pollWatch } from "./watch.js";
10
+ class SliccKernelEnv {
11
+ id;
12
+ cwd;
13
+ #client;
14
+ #watchers = /* @__PURE__ */ new Set();
15
+ #poll;
16
+ constructor(client, options) {
17
+ this.#client = client;
18
+ this.cwd = options.cwd;
19
+ this.id = options.id ?? "slicc-kernel";
20
+ this.#poll = options.watch ?? {};
21
+ }
22
+ #path(path) {
23
+ return resolve(this.cwd, path);
24
+ }
25
+ get #fs() {
26
+ return this.#client.fs;
27
+ }
28
+ async absolutePath(path) {
29
+ return ok(this.#path(path));
30
+ }
31
+ async joinPath(parts) {
32
+ return ok(normalize(parts.join("/")));
33
+ }
34
+ readTextFile(path, context) {
35
+ const resolved = this.#path(path);
36
+ return attempt(context, resolved, () => this.#fs.readText(resolved));
37
+ }
38
+ readBinaryFile(path, context) {
39
+ const resolved = this.#path(path);
40
+ return attempt(context, resolved, () => this.#fs.readFile(resolved));
41
+ }
42
+ async openTextLineReader(path, context) {
43
+ const text = await this.readTextFile(path, context);
44
+ return text.ok ? ok(new SnapshotLines(text.value)) : text;
45
+ }
46
+ async readTextLines(path, options, context) {
47
+ if (options?.maxLines !== void 0 && options.maxLines <= 0) return ok([]);
48
+ const text = await this.readTextFile(path, context);
49
+ if (!text.ok) return text;
50
+ const reader = new SnapshotLines(text.value);
51
+ const lines = [];
52
+ for (let line = await reader.readLine(); line.ok && line.value; line = await reader.readLine()) {
53
+ if (options?.maxLines !== void 0 && lines.length >= options.maxLines) break;
54
+ lines.push(line.value.text);
55
+ }
56
+ return ok(lines);
57
+ }
58
+ openBinaryReader(path, options, context) {
59
+ const resolved = this.#path(path);
60
+ return attempt(context, resolved, async () => {
61
+ if (options?.noFollow && (await this.#fs.lstat(resolved)).isSymbolicLink) {
62
+ throw new FileError("invalid", "Refusing to follow a symbolic link", resolved);
63
+ }
64
+ const stat = await this.#fs.stat(resolved);
65
+ if (stat.isDirectory) throw new FileError("is_directory", "Is a directory", resolved);
66
+ if (!stat.isFile) throw new FileError("invalid", "Not a regular file", resolved);
67
+ return new SnapshotReader(await this.#fs.readFile(resolved), info(resolved, stat));
68
+ });
69
+ }
70
+ writeFile(path, content, context) {
71
+ const resolved = this.#path(path);
72
+ return attempt(context, resolved, async () => {
73
+ await this.#fs.mkdir(dirname(resolved));
74
+ await this.#fs.writeFile(resolved, content);
75
+ });
76
+ }
77
+ appendFile(path, content, context) {
78
+ const resolved = this.#path(path);
79
+ return attempt(context, resolved, async () => {
80
+ const before = await this.#fs.exists(resolved) ? await this.#fs.readFile(resolved) : new Uint8Array();
81
+ const added = typeof content === "string" ? new TextEncoder().encode(content) : content;
82
+ const all = new Uint8Array(before.length + added.length);
83
+ all.set(before);
84
+ all.set(added, before.length);
85
+ await this.#fs.mkdir(dirname(resolved));
86
+ await this.#fs.writeFile(resolved, all);
87
+ });
88
+ }
89
+ truncateFile(path, size, context) {
90
+ const resolved = this.#path(path);
91
+ if (!Number.isSafeInteger(size) || size < 0) {
92
+ return Promise.resolve(
93
+ err(new FileError("invalid", "File size must be a non-negative integer", resolved))
94
+ );
95
+ }
96
+ return attempt(context, resolved, async () => {
97
+ const before = await this.#fs.readFile(resolved);
98
+ const after = new Uint8Array(size);
99
+ after.set(before.subarray(0, size));
100
+ await this.#fs.writeFile(resolved, after);
101
+ });
102
+ }
103
+ flushFile(path, context) {
104
+ const resolved = this.#path(path);
105
+ return attempt(context, resolved, async () => {
106
+ if ((await this.#fs.stat(resolved)).isDirectory)
107
+ throw Object.assign(new Error("Is a directory"), { code: "EISDIR" });
108
+ });
109
+ }
110
+ renameFile(sourcePath, destinationPath, context) {
111
+ const source = this.#path(sourcePath);
112
+ return attempt(context, source, () => this.#fs.rename(source, this.#path(destinationPath)));
113
+ }
114
+ fileInfo(path, context) {
115
+ const resolved = this.#path(path);
116
+ return attempt(context, resolved, async () => info(resolved, await this.#fs.lstat(resolved)));
117
+ }
118
+ async listDir(path, context) {
119
+ const reader = await this.openDirReader(path, context);
120
+ if (!reader.ok) return reader;
121
+ return ok(await reader.value.rest());
122
+ }
123
+ openDirReader(path, context) {
124
+ const resolved = this.#path(path);
125
+ return attempt(context, resolved, async () => {
126
+ if (!(await this.#fs.stat(resolved)).isDirectory) {
127
+ throw Object.assign(new Error("Not a directory"), { code: "ENOTDIR" });
128
+ }
129
+ return new KernelDirReader(this.#fs, resolved, await this.#fs.readdir(resolved));
130
+ });
131
+ }
132
+ watch(targets, onChange, context) {
133
+ return attempt(context, this.cwd, async () => {
134
+ const watcher = await pollWatch(this.#fs, this.cwd, targets, onChange, this.#poll);
135
+ this.#watchers.add(watcher);
136
+ return {
137
+ mode: watcher.mode,
138
+ close: async (closing) => {
139
+ this.#watchers.delete(watcher);
140
+ await watcher.close(closing);
141
+ }
142
+ };
143
+ });
144
+ }
145
+ canonicalPath(path, context) {
146
+ const resolved = this.#path(path);
147
+ return attempt(context, resolved, () => this.#fs.realpath(resolved));
148
+ }
149
+ async exists(path, context) {
150
+ const found = await this.fileInfo(path, context);
151
+ if (found.ok) return ok(true);
152
+ return found.error.code === "not_found" ? ok(false) : found;
153
+ }
154
+ createDir(path, _options, context) {
155
+ const resolved = this.#path(path);
156
+ return attempt(context, resolved, () => this.#fs.mkdir(resolved));
157
+ }
158
+ remove(path, options, context) {
159
+ const resolved = this.#path(path);
160
+ return attempt(context, resolved, async () => {
161
+ if (!options?.recursive) {
162
+ const stat = await this.#fs.lstat(resolved).catch((error) => {
163
+ if (options?.force) return void 0;
164
+ throw error;
165
+ });
166
+ if (stat?.isDirectory) throw Object.assign(new Error("Is a directory"), { code: "EISDIR" });
167
+ }
168
+ await this.#fs.rm(resolved, { force: options?.force ?? false });
169
+ });
170
+ }
171
+ async createTempDir(prefix, context) {
172
+ const path = `/tmp/${prefix ?? "tmp-"}${crypto.randomUUID()}`;
173
+ return attempt(context, path, async () => {
174
+ await this.#fs.mkdir(path);
175
+ return path;
176
+ });
177
+ }
178
+ async createTempFile(options, context) {
179
+ const dir = await this.createTempDir("tmp-", context);
180
+ if (!dir.ok) return dir;
181
+ const path = `${dir.value}/${options?.prefix ?? ""}${crypto.randomUUID()}${options?.suffix ?? ""}`;
182
+ return attempt(context, path, async () => {
183
+ await this.#fs.writeFile(path, "");
184
+ return path;
185
+ });
186
+ }
187
+ exec(command, options, context) {
188
+ return execute(this.#client, this.cwd, command, options, context);
189
+ }
190
+ async cleanup(context) {
191
+ for (const watcher of [...this.#watchers]) await watcher.close(context);
192
+ this.#watchers.clear();
193
+ }
194
+ }
195
+ const HOME = "/home";
196
+ function kernelEnvironment(client, options = {}) {
197
+ return (target) => new SliccKernelEnv(client, { ...options, cwd: target.cwd ?? HOME });
198
+ }
199
+ export {
200
+ HOME,
201
+ SliccKernelEnv,
202
+ kernelEnvironment
203
+ };
204
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/env.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type BinaryReader,\n type DirReader,\n type ExecutionEnv,\n type ExecutionError,\n err,\n FileError,\n type FileInfo,\n type FileWatcher,\n ok,\n type Result,\n type ShellExecOptions,\n type ShellExecResult,\n type TextLineReader,\n type WatchChange,\n type WatchTarget,\n} from '@earendil-works/pi-durable/env';\nimport type { KernelClient } from './client.ts';\nimport { execute } from './exec.ts';\nimport { attempt, info, KernelDirReader, SnapshotLines, SnapshotReader } from './files.ts';\nimport { dirname, normalize, resolve } from './paths.ts';\nimport { type PollOptions, pollWatch } from './watch.ts';\n\nexport interface KernelEnvOptions {\n cwd: string;\n id?: string;\n watch?: PollOptions;\n}\n\ntype FileResult<T> = Promise<Result<T, FileError>>;\n\nexport class SliccKernelEnv implements ExecutionEnv {\n readonly id: string;\n cwd: string;\n readonly #client: KernelClient;\n readonly #watchers = new Set<FileWatcher>();\n readonly #poll: PollOptions;\n\n constructor(client: KernelClient, options: KernelEnvOptions) {\n this.#client = client;\n this.cwd = options.cwd;\n this.id = options.id ?? 'slicc-kernel';\n this.#poll = options.watch ?? {};\n }\n\n #path(path: string): string {\n return resolve(this.cwd, path);\n }\n\n get #fs() {\n return this.#client.fs;\n }\n\n async absolutePath(path: string): FileResult<string> {\n return ok(this.#path(path));\n }\n\n async joinPath(parts: string[]): FileResult<string> {\n return ok(normalize(parts.join('/')));\n }\n\n readTextFile(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readText(resolved));\n }\n\n readBinaryFile(path: string, context: Context): FileResult<Uint8Array> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.readFile(resolved));\n }\n\n async openTextLineReader(path: string, context: Context): FileResult<TextLineReader> {\n const text = await this.readTextFile(path, context);\n return text.ok ? ok(new SnapshotLines(text.value)) : text;\n }\n\n async readTextLines(\n path: string,\n options: { maxLines?: number } | undefined,\n context: Context\n ): FileResult<string[]> {\n if (options?.maxLines !== undefined && options.maxLines <= 0) return ok([]);\n const text = await this.readTextFile(path, context);\n if (!text.ok) return text;\n const reader = new SnapshotLines(text.value);\n const lines: string[] = [];\n for (\n let line = await reader.readLine();\n line.ok && line.value;\n line = await reader.readLine()\n ) {\n if (options?.maxLines !== undefined && lines.length >= options.maxLines) break;\n lines.push(line.value.text);\n }\n return ok(lines);\n }\n\n openBinaryReader(\n path: string,\n options: { noFollow?: boolean } | undefined,\n context: Context\n ): FileResult<BinaryReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (options?.noFollow && (await this.#fs.lstat(resolved)).isSymbolicLink) {\n throw new FileError('invalid', 'Refusing to follow a symbolic link', resolved);\n }\n const stat = await this.#fs.stat(resolved);\n if (stat.isDirectory) throw new FileError('is_directory', 'Is a directory', resolved);\n if (!stat.isFile) throw new FileError('invalid', 'Not a regular file', resolved);\n return new SnapshotReader(await this.#fs.readFile(resolved), info(resolved, stat));\n });\n }\n\n writeFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, content);\n });\n }\n\n appendFile(path: string, content: string | Uint8Array, context: Context): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n const before = (await this.#fs.exists(resolved))\n ? await this.#fs.readFile(resolved)\n : new Uint8Array();\n const added = typeof content === 'string' ? new TextEncoder().encode(content) : content;\n const all = new Uint8Array(before.length + added.length);\n all.set(before);\n all.set(added, before.length);\n await this.#fs.mkdir(dirname(resolved));\n await this.#fs.writeFile(resolved, all);\n });\n }\n\n truncateFile(path: string, size: number, context: Context): FileResult<void> {\n const resolved = this.#path(path);\n if (!Number.isSafeInteger(size) || size < 0) {\n return Promise.resolve(\n err(new FileError('invalid', 'File size must be a non-negative integer', resolved))\n );\n }\n return attempt(context, resolved, async () => {\n const before = await this.#fs.readFile(resolved);\n const after = new Uint8Array(size);\n after.set(before.subarray(0, size));\n await this.#fs.writeFile(resolved, after);\n });\n }\n\n flushFile(path: string, context: Context): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if ((await this.#fs.stat(resolved)).isDirectory)\n throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n });\n }\n\n renameFile(sourcePath: string, destinationPath: string, context: Context): FileResult<void> {\n const source = this.#path(sourcePath);\n return attempt(context, source, () => this.#fs.rename(source, this.#path(destinationPath)));\n }\n\n fileInfo(path: string, context: Context): FileResult<FileInfo> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => info(resolved, await this.#fs.lstat(resolved)));\n }\n\n async listDir(path: string, context: Context): FileResult<FileInfo[]> {\n const reader = await this.openDirReader(path, context);\n if (!reader.ok) return reader;\n return ok(await (reader.value as KernelDirReader).rest());\n }\n\n openDirReader(path: string, context: Context): FileResult<DirReader> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (!(await this.#fs.stat(resolved)).isDirectory) {\n throw Object.assign(new Error('Not a directory'), { code: 'ENOTDIR' });\n }\n return new KernelDirReader(this.#fs, resolved, await this.#fs.readdir(resolved));\n });\n }\n\n watch(\n targets: readonly WatchTarget[],\n onChange: (change: WatchChange) => void,\n context: Context\n ): FileResult<FileWatcher> {\n return attempt(context, this.cwd, async () => {\n const watcher = await pollWatch(this.#fs, this.cwd, targets, onChange, this.#poll);\n this.#watchers.add(watcher);\n return {\n mode: watcher.mode,\n close: async (closing: Context) => {\n this.#watchers.delete(watcher);\n await watcher.close(closing);\n },\n };\n });\n }\n\n canonicalPath(path: string, context: Context): FileResult<string> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.realpath(resolved));\n }\n\n async exists(path: string, context: Context): FileResult<boolean> {\n const found = await this.fileInfo(path, context);\n if (found.ok) return ok(true);\n return found.error.code === 'not_found' ? ok(false) : found;\n }\n\n createDir(\n path: string,\n _options: { recursive?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, () => this.#fs.mkdir(resolved));\n }\n\n remove(\n path: string,\n options: { recursive?: boolean; force?: boolean } | undefined,\n context: Context\n ): FileResult<void> {\n const resolved = this.#path(path);\n return attempt(context, resolved, async () => {\n if (!options?.recursive) {\n const stat = await this.#fs.lstat(resolved).catch((error: unknown) => {\n if (options?.force) return undefined;\n throw error;\n });\n if (stat?.isDirectory) throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });\n }\n await this.#fs.rm(resolved, { force: options?.force ?? false });\n });\n }\n\n async createTempDir(prefix: string | undefined, context: Context): FileResult<string> {\n const path = `/tmp/${prefix ?? 'tmp-'}${crypto.randomUUID()}`;\n return attempt(context, path, async () => {\n await this.#fs.mkdir(path);\n return path;\n });\n }\n\n async createTempFile(\n options: { prefix?: string; suffix?: string } | undefined,\n context: Context\n ): FileResult<string> {\n const dir = await this.createTempDir('tmp-', context);\n if (!dir.ok) return dir;\n const path = `${dir.value}/${options?.prefix ?? ''}${crypto.randomUUID()}${options?.suffix ?? ''}`;\n return attempt(context, path, async () => {\n await this.#fs.writeFile(path, '');\n return path;\n });\n }\n\n exec(\n command: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n ): Promise<Result<ShellExecResult, ExecutionError>> {\n return execute(this.#client, this.cwd, command, options, context);\n }\n\n async cleanup(context: Context): Promise<void> {\n for (const watcher of [...this.#watchers]) await watcher.close(context);\n this.#watchers.clear();\n }\n}\n\nexport const HOME = '/home';\n\nexport function kernelEnvironment(\n client: KernelClient,\n options: Omit<KernelEnvOptions, 'cwd'> = {}\n): (target: { cwd?: string }) => SliccKernelEnv {\n return (target) => new SliccKernelEnv(client, { ...options, cwd: target.cwd ?? HOME });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,115 @@
1
+ import {
2
+ ExecutionError,
3
+ err,
4
+ ok,
5
+ StreamDecoder
6
+ } from "@earendil-works/pi-durable/env";
7
+ import { resolve } from "./paths.js";
8
+ const SHELL = ["bash", "-c"];
9
+ class Spill {
10
+ #chunks = [];
11
+ #limit;
12
+ #bytes = 0;
13
+ #lines = 0;
14
+ constructor(limit) {
15
+ this.#limit = limit;
16
+ }
17
+ add(chunk) {
18
+ if (!this.#limit) return;
19
+ this.#chunks.push(chunk);
20
+ this.#bytes += chunk.length;
21
+ for (const byte of chunk) if (byte === 10) this.#lines++;
22
+ }
23
+ get over() {
24
+ const limit = this.#limit;
25
+ return limit !== void 0 && (this.#bytes > limit.afterBytes || this.#lines > limit.afterLines);
26
+ }
27
+ async write(client) {
28
+ if (!this.over) return void 0;
29
+ const path = `/tmp/slicc-agent-output-${crypto.randomUUID()}.log`;
30
+ const all = new Uint8Array(this.#bytes);
31
+ let offset = 0;
32
+ for (const chunk of this.#chunks) {
33
+ all.set(chunk, offset);
34
+ offset += chunk.length;
35
+ }
36
+ await client.fs.mkdir("/tmp");
37
+ await client.fs.writeFile(path, all);
38
+ return path;
39
+ }
40
+ }
41
+ function sink(stream, spill, options, context) {
42
+ const decoder = new StreamDecoder();
43
+ return {
44
+ push(bytes) {
45
+ spill.add(bytes);
46
+ const text = decoder.decode(bytes);
47
+ if (text) options?.onOutput?.(text, context, { stream });
48
+ },
49
+ end() {
50
+ const text = decoder.decode();
51
+ if (text) options?.onOutput?.(text, context, { stream });
52
+ }
53
+ };
54
+ }
55
+ function guard(process, options, context) {
56
+ let ending;
57
+ const stop = (why) => {
58
+ ending ??= why;
59
+ process.signal("SIGKILL");
60
+ };
61
+ const timer = options?.timeout === void 0 ? void 0 : setTimeout(() => stop("timeout"), options.timeout * 1e3);
62
+ const onAbort = () => stop("aborted");
63
+ context.abortSignal?.addEventListener("abort", onAbort, { once: true });
64
+ if (context.abortSignal?.aborted) onAbort();
65
+ return {
66
+ get ending() {
67
+ return ending;
68
+ },
69
+ release() {
70
+ clearTimeout(timer);
71
+ context.abortSignal?.removeEventListener("abort", onAbort);
72
+ }
73
+ };
74
+ }
75
+ async function execute(client, cwd, command, options, context) {
76
+ const argv = typeof command === "string" ? [...SHELL, command] : [...command];
77
+ if (argv.length === 0) return err(new ExecutionError("spawn_error", "No program to run"));
78
+ if (context.abortSignal?.aborted)
79
+ return err(new ExecutionError("aborted", "The command was aborted"));
80
+ const spill = new Spill(options?.spill);
81
+ const out = sink("stdout", spill, options, context);
82
+ const errors = sink("stderr", spill, options, context);
83
+ let process;
84
+ try {
85
+ process = await client.spawn(argv, {
86
+ cwd: resolve(cwd, options?.cwd ?? "."),
87
+ ...options?.env ? { env: options.env } : {},
88
+ onStdout: (bytes) => out.push(bytes),
89
+ onStderr: (bytes) => errors.push(bytes)
90
+ });
91
+ } catch (error) {
92
+ const message = error instanceof Error ? error.message : String(error);
93
+ return err(new ExecutionError("spawn_error", message));
94
+ }
95
+ const watch = guard(process, options, context);
96
+ const exitCode = await process.exited.finally(() => watch.release());
97
+ out.end();
98
+ errors.end();
99
+ const spillPath = await spill.write(client);
100
+ const ending = watch.ending;
101
+ if (ending) {
102
+ const error = new ExecutionError(
103
+ ending,
104
+ `The command was ${ending === "timeout" ? "timed out" : "aborted"}`
105
+ );
106
+ if (spillPath) error.spillPath = spillPath;
107
+ return err(error);
108
+ }
109
+ return ok(spillPath ? { exitCode, spillPath } : { exitCode });
110
+ }
111
+ export {
112
+ SHELL,
113
+ execute
114
+ };
115
+ //# sourceMappingURL=exec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/exec.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n ExecutionError,\n err,\n ok,\n type Result,\n type ShellExecOptions,\n type ShellExecResult,\n StreamDecoder,\n} from '@earendil-works/pi-durable/env';\nimport type { KernelClient, KernelProcess } from './client.ts';\nimport { resolve } from './paths.ts';\n\nexport const SHELL = ['bash', '-c'] as const;\n\nclass Spill {\n readonly #chunks: Uint8Array[] = [];\n readonly #limit: { afterBytes: number; afterLines: number } | undefined;\n #bytes = 0;\n #lines = 0;\n\n constructor(limit: ShellExecOptions['spill']) {\n this.#limit = limit;\n }\n\n add(chunk: Uint8Array): void {\n if (!this.#limit) return;\n this.#chunks.push(chunk);\n this.#bytes += chunk.length;\n for (const byte of chunk) if (byte === 10) this.#lines++;\n }\n\n get over(): boolean {\n const limit = this.#limit;\n return (\n limit !== undefined && (this.#bytes > limit.afterBytes || this.#lines > limit.afterLines)\n );\n }\n\n async write(client: KernelClient): Promise<string | undefined> {\n if (!this.over) return undefined;\n const path = `/tmp/slicc-agent-output-${crypto.randomUUID()}.log`;\n const all = new Uint8Array(this.#bytes);\n let offset = 0;\n for (const chunk of this.#chunks) {\n all.set(chunk, offset);\n offset += chunk.length;\n }\n await client.fs.mkdir('/tmp');\n await client.fs.writeFile(path, all);\n return path;\n }\n}\n\nfunction sink(\n stream: 'stdout' | 'stderr',\n spill: Spill,\n options: ShellExecOptions | undefined,\n context: Context\n) {\n const decoder = new StreamDecoder();\n return {\n push(bytes: Uint8Array) {\n spill.add(bytes);\n const text = decoder.decode(bytes);\n if (text) options?.onOutput?.(text, context, { stream });\n },\n end() {\n const text = decoder.decode();\n if (text) options?.onOutput?.(text, context, { stream });\n },\n };\n}\n\ntype Ending = 'timeout' | 'aborted' | undefined;\n\nfunction guard(process: KernelProcess, options: ShellExecOptions | undefined, context: Context) {\n let ending: Ending;\n const stop = (why: Ending) => {\n ending ??= why;\n process.signal('SIGKILL');\n };\n const timer =\n options?.timeout === undefined\n ? undefined\n : setTimeout(() => stop('timeout'), options.timeout * 1000);\n const onAbort = () => stop('aborted');\n context.abortSignal?.addEventListener('abort', onAbort, { once: true });\n if (context.abortSignal?.aborted) onAbort();\n return {\n get ending() {\n return ending;\n },\n release() {\n clearTimeout(timer);\n context.abortSignal?.removeEventListener('abort', onAbort);\n },\n };\n}\n\nexport async function execute(\n client: KernelClient,\n cwd: string,\n command: string | readonly string[],\n options: ShellExecOptions | undefined,\n context: Context\n): Promise<Result<ShellExecResult, ExecutionError>> {\n const argv = typeof command === 'string' ? [...SHELL, command] : [...command];\n if (argv.length === 0) return err(new ExecutionError('spawn_error', 'No program to run'));\n if (context.abortSignal?.aborted)\n return err(new ExecutionError('aborted', 'The command was aborted'));\n const spill = new Spill(options?.spill);\n const out = sink('stdout', spill, options, context);\n const errors = sink('stderr', spill, options, context);\n let process: KernelProcess;\n try {\n process = await client.spawn(argv, {\n cwd: resolve(cwd, options?.cwd ?? '.'),\n ...(options?.env ? { env: options.env } : {}),\n onStdout: (bytes) => out.push(bytes),\n onStderr: (bytes) => errors.push(bytes),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return err(new ExecutionError('spawn_error', message));\n }\n const watch = guard(process, options, context);\n const exitCode = await process.exited.finally(() => watch.release());\n out.end();\n errors.end();\n const spillPath = await spill.write(client);\n const ending = watch.ending;\n if (ending) {\n const error = new ExecutionError(\n ending,\n `The command was ${ending === 'timeout' ? 'timed out' : 'aborted'}`\n );\n if (spillPath) error.spillPath = spillPath;\n return err(error);\n }\n return ok(spillPath ? { exitCode, spillPath } : { exitCode });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,153 @@
1
+ import {
2
+ err,
3
+ FileError,
4
+ LineScanner,
5
+ ok
6
+ } from "@earendil-works/pi-durable/env";
7
+ import { basename } from "./paths.js";
8
+ const codes = {
9
+ ENOENT: "not_found",
10
+ EACCES: "permission_denied",
11
+ EPERM: "permission_denied",
12
+ ENOTDIR: "not_directory",
13
+ EISDIR: "is_directory",
14
+ EINVAL: "invalid",
15
+ ELOOP: "invalid",
16
+ ENOSYS: "not_supported",
17
+ ENOTSUP: "not_supported"
18
+ };
19
+ function fileError(error, path) {
20
+ if (error instanceof FileError) return error;
21
+ const code = error?.code;
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ const cause = error instanceof Error ? error : void 0;
24
+ return new FileError(code && codes[code] || "unknown", message, path, cause);
25
+ }
26
+ function aborted(context, path) {
27
+ if (!context.abortSignal?.aborted) return void 0;
28
+ return err(new FileError("aborted", "The operation was aborted", path));
29
+ }
30
+ async function attempt(context, path, run) {
31
+ const before = aborted(context, path);
32
+ if (before) return before;
33
+ try {
34
+ const value = await run();
35
+ return aborted(context, path) ?? ok(value);
36
+ } catch (error) {
37
+ return err(fileError(error, path));
38
+ }
39
+ }
40
+ function info(path, stat) {
41
+ const kind = stat.isSymbolicLink ? "symlink" : stat.isDirectory ? "directory" : "file";
42
+ return {
43
+ name: basename(path),
44
+ path,
45
+ kind,
46
+ size: stat.size,
47
+ mtimeMs: new Date(stat.mtime).getTime()
48
+ };
49
+ }
50
+ function closed(what, path) {
51
+ return err(new FileError("invalid", `${what} is closed`, path));
52
+ }
53
+ class SnapshotReader {
54
+ #bytes;
55
+ #info;
56
+ #closed = false;
57
+ constructor(bytes, fileInfo) {
58
+ this.#bytes = bytes;
59
+ this.#info = fileInfo;
60
+ }
61
+ async info() {
62
+ return this.#closed ? closed("Binary reader", this.#info.path) : ok(this.#info);
63
+ }
64
+ async read(offset, length, context) {
65
+ const path = this.#info.path;
66
+ const stop = aborted(context, path);
67
+ if (stop) return stop;
68
+ if (this.#closed) return closed("Binary reader", path);
69
+ if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 0) {
70
+ return err(new FileError("invalid", "Offset and length must be non-negative integers", path));
71
+ }
72
+ return ok(this.#bytes.slice(offset, offset + length));
73
+ }
74
+ async scanLines(options, context) {
75
+ const path = this.#info.path;
76
+ const stop = aborted(context, path);
77
+ if (stop) return stop;
78
+ if (this.#closed) return closed("Binary reader", path);
79
+ let scanner;
80
+ try {
81
+ scanner = new LineScanner(options.startLine, options.endLine);
82
+ } catch {
83
+ return err(new FileError("invalid", "Invalid line range", path));
84
+ }
85
+ scanner.push(this.#bytes);
86
+ return ok(scanner.finish());
87
+ }
88
+ async close() {
89
+ this.#closed = true;
90
+ }
91
+ }
92
+ class SnapshotLines {
93
+ #lines;
94
+ #next = 0;
95
+ constructor(text) {
96
+ const parts = text.split("\n");
97
+ const last = parts.pop();
98
+ this.#lines = parts.map((part) => ({ text: part, terminated: true }));
99
+ if (last !== "") this.#lines.push({ text: last, terminated: false });
100
+ }
101
+ async readLine() {
102
+ return ok(this.#lines[this.#next++]);
103
+ }
104
+ async close() {
105
+ this.#next = this.#lines.length;
106
+ }
107
+ }
108
+ class KernelDirReader {
109
+ #fs;
110
+ #path;
111
+ #names;
112
+ #closed = false;
113
+ constructor(fs, path, names) {
114
+ this.#fs = fs;
115
+ this.#path = path;
116
+ this.#names = names;
117
+ }
118
+ async next(maxEntries, context) {
119
+ if (this.#closed) return closed("Directory reader", this.#path);
120
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
121
+ return err(new FileError("invalid", "maxEntries must be a positive integer", this.#path));
122
+ }
123
+ const stop = aborted(context, this.#path);
124
+ if (stop) return stop;
125
+ const entries = await this.#take(maxEntries);
126
+ return ok({ entries, done: this.#names.length === 0 });
127
+ }
128
+ rest() {
129
+ return this.#take(Number.MAX_SAFE_INTEGER);
130
+ }
131
+ async #take(max) {
132
+ const entries = [];
133
+ while (entries.length < max && this.#names.length > 0) {
134
+ const path = `${this.#path === "/" ? "" : this.#path}/${this.#names.shift()}`;
135
+ const stat = await this.#fs.lstat(path).catch(() => void 0);
136
+ if (stat) entries.push(info(path, stat));
137
+ }
138
+ return entries;
139
+ }
140
+ async close() {
141
+ this.#closed = true;
142
+ }
143
+ }
144
+ export {
145
+ KernelDirReader,
146
+ SnapshotLines,
147
+ SnapshotReader,
148
+ aborted,
149
+ attempt,
150
+ fileError,
151
+ info
152
+ };
153
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/files.ts"],
4
+ "sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport {\n type BinaryReader,\n type DirReader,\n err,\n FileError,\n type FileErrorCode,\n type FileInfo,\n type LineScan,\n LineScanner,\n ok,\n type Result,\n type TextLine,\n type TextLineReader,\n} from '@earendil-works/pi-durable/env';\nimport type { KernelFs, KernelStat } from './client.ts';\nimport { basename } from './paths.ts';\n\nconst codes: Record<string, FileErrorCode> = {\n ENOENT: 'not_found',\n EACCES: 'permission_denied',\n EPERM: 'permission_denied',\n ENOTDIR: 'not_directory',\n EISDIR: 'is_directory',\n EINVAL: 'invalid',\n ELOOP: 'invalid',\n ENOSYS: 'not_supported',\n ENOTSUP: 'not_supported',\n};\n\nexport function fileError(error: unknown, path?: string): FileError {\n if (error instanceof FileError) return error;\n const code = (error as { code?: string } | null)?.code;\n const message = error instanceof Error ? error.message : String(error);\n const cause = error instanceof Error ? error : undefined;\n return new FileError((code && codes[code]) || 'unknown', message, path, cause);\n}\n\nexport function aborted(context: Context, path?: string): Result<never, FileError> | undefined {\n if (!context.abortSignal?.aborted) return undefined;\n return err(new FileError('aborted', 'The operation was aborted', path));\n}\n\nexport async function attempt<T>(\n context: Context,\n path: string,\n run: () => Promise<T>\n): Promise<Result<T, FileError>> {\n const before = aborted(context, path);\n if (before) return before;\n try {\n const value = await run();\n return aborted(context, path) ?? ok(value);\n } catch (error) {\n return err(fileError(error, path));\n }\n}\n\nexport function info(path: string, stat: KernelStat): FileInfo {\n const kind = stat.isSymbolicLink ? 'symlink' : stat.isDirectory ? 'directory' : 'file';\n return {\n name: basename(path),\n path,\n kind,\n size: stat.size,\n mtimeMs: new Date(stat.mtime).getTime(),\n };\n}\n\nfunction closed<T>(what: string, path: string): Result<T, FileError> {\n return err(new FileError('invalid', `${what} is closed`, path));\n}\n\nexport class SnapshotReader implements BinaryReader {\n readonly #bytes: Uint8Array;\n readonly #info: FileInfo;\n #closed = false;\n\n constructor(bytes: Uint8Array, fileInfo: FileInfo) {\n this.#bytes = bytes;\n this.#info = fileInfo;\n }\n\n async info(): Promise<Result<FileInfo, FileError>> {\n return this.#closed ? closed('Binary reader', this.#info.path) : ok(this.#info);\n }\n\n async read(\n offset: number,\n length: number,\n context: Context\n ): Promise<Result<Uint8Array, FileError>> {\n const path = this.#info.path;\n const stop = aborted(context, path);\n if (stop) return stop;\n if (this.#closed) return closed('Binary reader', path);\n if (\n !Number.isSafeInteger(offset) ||\n offset < 0 ||\n !Number.isSafeInteger(length) ||\n length < 0\n ) {\n return err(new FileError('invalid', 'Offset and length must be non-negative integers', path));\n }\n return ok(this.#bytes.slice(offset, offset + length));\n }\n\n async scanLines(\n options: { startLine: number; endLine?: number },\n context: Context\n ): Promise<Result<LineScan, FileError>> {\n const path = this.#info.path;\n const stop = aborted(context, path);\n if (stop) return stop;\n if (this.#closed) return closed('Binary reader', path);\n let scanner: LineScanner;\n try {\n scanner = new LineScanner(options.startLine, options.endLine);\n } catch {\n return err(new FileError('invalid', 'Invalid line range', path));\n }\n scanner.push(this.#bytes);\n return ok(scanner.finish());\n }\n\n async close(): Promise<void> {\n this.#closed = true;\n }\n}\n\nexport class SnapshotLines implements TextLineReader {\n readonly #lines: TextLine[];\n #next = 0;\n\n constructor(text: string) {\n const parts = text.split('\\n');\n const last = parts.pop() as string;\n this.#lines = parts.map((part) => ({ text: part, terminated: true }));\n if (last !== '') this.#lines.push({ text: last, terminated: false });\n }\n\n async readLine(): Promise<Result<TextLine | undefined, FileError>> {\n return ok(this.#lines[this.#next++]);\n }\n\n async close(): Promise<void> {\n this.#next = this.#lines.length;\n }\n}\n\nexport class KernelDirReader implements DirReader {\n readonly #fs: KernelFs;\n readonly #path: string;\n readonly #names: string[];\n #closed = false;\n\n constructor(fs: KernelFs, path: string, names: string[]) {\n this.#fs = fs;\n this.#path = path;\n this.#names = names;\n }\n\n async next(\n maxEntries: number,\n context: Context\n ): Promise<Result<{ entries: FileInfo[]; done: boolean }, FileError>> {\n if (this.#closed) return closed('Directory reader', this.#path);\n if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {\n return err(new FileError('invalid', 'maxEntries must be a positive integer', this.#path));\n }\n const stop = aborted(context, this.#path);\n if (stop) return stop;\n const entries = await this.#take(maxEntries);\n return ok({ entries, done: this.#names.length === 0 });\n }\n\n rest(): Promise<FileInfo[]> {\n return this.#take(Number.MAX_SAFE_INTEGER);\n }\n\n async #take(max: number): Promise<FileInfo[]> {\n const entries: FileInfo[] = [];\n while (entries.length < max && this.#names.length > 0) {\n const path = `${this.#path === '/' ? '' : this.#path}/${this.#names.shift()}`;\n const stat = await this.#fs.lstat(path).catch(() => undefined);\n if (stat) entries.push(info(path, stat));\n }\n return entries;\n }\n\n async close(): Promise<void> {\n this.#closed = true;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,31 @@
1
+ function normalize(path) {
2
+ const absolute = path.startsWith("/");
3
+ const parts = [];
4
+ for (const part of path.split("/")) {
5
+ if (part === "" || part === ".") continue;
6
+ if (part === "..") {
7
+ if (parts.length > 0 && parts.at(-1) !== "..") parts.pop();
8
+ else if (!absolute) parts.push(part);
9
+ } else parts.push(part);
10
+ }
11
+ const joined = parts.join("/");
12
+ if (absolute) return `/${joined}`;
13
+ return joined || ".";
14
+ }
15
+ function resolve(cwd, path) {
16
+ return normalize(path.startsWith("/") ? path : `${cwd}/${path}`);
17
+ }
18
+ function dirname(path) {
19
+ const index = path.lastIndexOf("/");
20
+ return index <= 0 ? "/" : path.slice(0, index);
21
+ }
22
+ function basename(path) {
23
+ return path.slice(path.lastIndexOf("/") + 1);
24
+ }
25
+ export {
26
+ basename,
27
+ dirname,
28
+ normalize,
29
+ resolve
30
+ };
31
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/kernel/paths.ts"],
4
+ "sourcesContent": ["export function normalize(path: string): string {\n const absolute = path.startsWith('/');\n const parts: string[] = [];\n for (const part of path.split('/')) {\n if (part === '' || part === '.') continue;\n if (part === '..') {\n if (parts.length > 0 && parts.at(-1) !== '..') parts.pop();\n else if (!absolute) parts.push(part);\n } else parts.push(part);\n }\n const joined = parts.join('/');\n if (absolute) return `/${joined}`;\n return joined || '.';\n}\n\nexport function resolve(cwd: string, path: string): string {\n return normalize(path.startsWith('/') ? path : `${cwd}/${path}`);\n}\n\nexport function dirname(path: string): string {\n const index = path.lastIndexOf('/');\n return index <= 0 ? '/' : path.slice(0, index);\n}\n\nexport function basename(path: string): string {\n return path.slice(path.lastIndexOf('/') + 1);\n}\n"],
5
+ "mappings": "AAAO,SAAS,UAAU,MAAsB;AAC9C,QAAM,WAAW,KAAK,WAAW,GAAG;AACpC,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,KAAK,MAAM,GAAG,GAAG;AAClC,QAAI,SAAS,MAAM,SAAS,IAAK;AACjC,QAAI,SAAS,MAAM;AACjB,UAAI,MAAM,SAAS,KAAK,MAAM,GAAG,EAAE,MAAM,KAAM,OAAM,IAAI;AAAA,eAChD,CAAC,SAAU,OAAM,KAAK,IAAI;AAAA,IACrC,MAAO,OAAM,KAAK,IAAI;AAAA,EACxB;AACA,QAAM,SAAS,MAAM,KAAK,GAAG;AAC7B,MAAI,SAAU,QAAO,IAAI,MAAM;AAC/B,SAAO,UAAU;AACnB;AAEO,SAAS,QAAQ,KAAa,MAAsB;AACzD,SAAO,UAAU,KAAK,WAAW,GAAG,IAAI,OAAO,GAAG,GAAG,IAAI,IAAI,EAAE;AACjE;AAEO,SAAS,QAAQ,MAAsB;AAC5C,QAAM,QAAQ,KAAK,YAAY,GAAG;AAClC,SAAO,SAAS,IAAI,MAAM,KAAK,MAAM,GAAG,KAAK;AAC/C;AAEO,SAAS,SAAS,MAAsB;AAC7C,SAAO,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC;AAC7C;",
6
+ "names": []
7
+ }