@sigrun/sigrun 0.1.6 → 0.2.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 +104 -0
- package/README.md +352 -36
- package/dist/blocks/schema.js +9 -2
- package/dist/blocks/validate.js +57 -8
- package/dist/cli.js +26 -2
- package/dist/config.js +67 -1
- package/dist/extension.d.ts +189 -15
- package/dist/harness/ambient.js +39 -185
- package/dist/harness/blocks/entries.js +47 -0
- package/dist/harness/blocks/state.js +58 -0
- package/dist/harness/blocks/tools.js +274 -0
- package/dist/harness/boot.js +253 -20
- package/dist/harness/builtins.js +15 -11
- package/dist/harness/event-bus.js +105 -10
- package/dist/harness/identity/accounts.js +186 -0
- package/dist/harness/identity/groups.js +14 -0
- package/dist/harness/identity/index.js +13 -6
- package/dist/harness/identity/json-store.js +15 -4
- package/dist/harness/identity/setup-code-check.js +9 -0
- package/dist/harness/identity/tokens.js +28 -5
- package/dist/harness/identity/users.js +111 -12
- package/dist/harness/interactions.js +292 -136
- package/dist/harness/loader.js +271 -17
- package/dist/harness/packages.js +1 -1
- package/dist/harness/reserved-sections.js +21 -0
- package/dist/harness/seam-host.js +80 -6
- package/dist/harness/settings.js +63 -2
- package/dist/harness/stores.js +241 -4
- package/dist/harness/user-services.js +391 -0
- package/dist/harness/user-tools.js +187 -0
- package/dist/harness/workspace.js +61 -57
- package/dist/jobs/fire.js +10 -16
- package/dist/sandbox/availability.js +162 -0
- package/dist/sandbox/command-observer.js +59 -0
- package/dist/sandbox/grants.js +159 -0
- package/dist/sandbox/helper.js +29 -0
- package/dist/sandbox/isolation.js +29 -0
- package/dist/sandbox/launch.js +20 -0
- package/dist/sandbox/os-account.js +49 -0
- package/dist/sandbox/policy.js +253 -0
- package/dist/sandbox/pool.js +548 -0
- package/dist/sandbox/protocol.js +167 -0
- package/dist/sandbox/sandboxed-env.js +245 -0
- package/dist/sandbox/unavailable-env.js +49 -0
- package/dist/sandbox/worker.js +281 -0
- package/dist/serve.js +82 -7
- package/dist/server/auth/access-principal.js +16 -0
- package/dist/server/auth/pipeline.js +47 -33
- package/dist/server/blocks/answers.js +94 -0
- package/dist/server/blocks/chat-surfaces.js +393 -0
- package/dist/server/blocks/notes.js +216 -0
- package/dist/server/blocks/summaries.js +328 -0
- package/dist/server/commands.js +5 -0
- package/dist/server/content-parts-lazy.js +13 -0
- package/dist/server/conversation-handler.js +2 -5
- package/dist/server/conversation-ops.js +138 -37
- package/dist/server/helpers.js +18 -6
- package/dist/server/host-bridge.js +40 -20
- package/dist/server/images/cache.js +205 -0
- package/dist/server/images/convert.js +13 -0
- package/dist/server/images/fetch.js +302 -0
- package/dist/server/images/ranges.js +42 -0
- package/dist/server/images/rewrite.js +94 -0
- package/dist/server/images/sign.js +84 -0
- package/dist/server/images/sniff.js +62 -0
- package/dist/server/index.js +144 -4
- package/dist/server/login/flows.js +126 -0
- package/dist/server/login/http.js +31 -0
- package/dist/server/login/resolve.js +133 -0
- package/dist/server/login/setup-claim.js +55 -0
- package/dist/server/login/usernames.js +15 -0
- package/dist/server/ownership.js +13 -0
- package/dist/server/routes/admin.js +58 -0
- package/dist/server/routes/auth.js +88 -2
- package/dist/server/routes/conversations.js +3 -6
- package/dist/server/routes/events.js +28 -3
- package/dist/server/routes/extensions.js +37 -5
- package/dist/server/routes/images.js +267 -0
- package/dist/server/routes/interactions.js +57 -68
- package/dist/server/routes/jobs.js +2 -8
- package/dist/server/routes/login.js +223 -0
- package/dist/server/routes/me.js +90 -0
- package/dist/server/routes/meta.js +15 -0
- package/dist/server/routes/onboarding.js +94 -84
- package/dist/server/routes/settings-sections.js +190 -0
- package/dist/server/routes/settings.js +16 -4
- package/dist/server/routes/submissions.js +1 -1
- package/dist/server/routes/surfaces.js +175 -23
- package/dist/server/routes/users.js +116 -20
- package/dist/server/schema-fixture.js +213 -0
- package/dist/server/session-meta.js +3 -0
- package/dist/server/shutdown.js +11 -4
- package/dist/server/static.js +18 -0
- package/dist/server/surfaces/dedup.js +9 -2
- package/dist/server/surfaces/service.js +181 -13
- package/dist/server/surfaces/settings-surfaces.js +597 -0
- package/dist/server/turns/submit-turn.js +26 -0
- package/dist/server/views.js +16 -0
- package/dist/settings/core-sections.js +532 -0
- package/dist/settings/pending.js +181 -0
- package/dist/settings/registry.js +453 -0
- package/dist/skills/index.js +6 -0
- package/node_modules/@sigrun/shared/CHANGELOG.md +6 -0
- package/node_modules/@sigrun/shared/dist/access.d.ts +4 -2
- package/node_modules/@sigrun/shared/dist/access.js +11 -4
- package/node_modules/@sigrun/shared/dist/ambient.d.ts +17 -74
- package/node_modules/@sigrun/shared/dist/ambient.js +8 -6
- package/node_modules/@sigrun/shared/dist/blocks/text.js +16 -2
- package/node_modules/@sigrun/shared/dist/blocks/transport.d.ts +4 -0
- package/node_modules/@sigrun/shared/dist/blocks/types.d.ts +21 -4
- package/node_modules/@sigrun/shared/dist/interactions.d.ts +22 -48
- package/node_modules/@sigrun/shared/dist/interactions.js +9 -7
- package/node_modules/@sigrun/shared/dist/settings.d.ts +27 -2
- package/node_modules/@sigrun/shared/dist/themes.d.ts +2 -3
- package/node_modules/@sigrun/shared/dist/themes.js +2 -3
- package/node_modules/@sigrun/shared/dist/turns.d.ts +14 -0
- package/node_modules/@sigrun/shared/dist/turns.js +53 -3
- package/node_modules/@sigrun/shared/dist/views.d.ts +175 -12
- package/node_modules/@sigrun/shared/dist/views.js +16 -0
- package/node_modules/@sigrun/shared/package.json +1 -1
- package/package.json +11 -6
- package/webui/dist/assets/index-CxB969qp.js +55 -0
- package/webui/dist/assets/index-yqkL6BSS.css +1 -0
- package/webui/dist/index.html +2 -2
- package/webui/dist/assets/index-CiYInlLx.css +0 -1
- package/webui/dist/assets/index-JDfOQV9z.js +0 -52
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/** The newline-delimited JSON wire shared by the worker and its client. */
|
|
2
|
+
import { ExecutionError, FileError, } from "@earendil-works/pi-durable/env";
|
|
3
|
+
/** Encode binary without ambiguity; undefined void results become null. */
|
|
4
|
+
export function encodeValue(value) {
|
|
5
|
+
if (value === undefined || value === null)
|
|
6
|
+
return null;
|
|
7
|
+
if (value instanceof Uint8Array) {
|
|
8
|
+
return { $bytes: Buffer.from(value).toString("base64") };
|
|
9
|
+
}
|
|
10
|
+
if (typeof value === "string" || typeof value === "boolean")
|
|
11
|
+
return value;
|
|
12
|
+
if (typeof value === "number") {
|
|
13
|
+
if (!Number.isFinite(value))
|
|
14
|
+
throw new TypeError("non-finite number on sandbox wire");
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return value.map(encodeValue);
|
|
19
|
+
if (typeof value === "object") {
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [key, item] of Object.entries(value)) {
|
|
22
|
+
if (item !== undefined)
|
|
23
|
+
result[key] = encodeValue(item);
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
throw new TypeError(`unsupported sandbox wire value: ${typeof value}`);
|
|
28
|
+
}
|
|
29
|
+
/** Decode binary; handle sentinels remain plain objects for the client to own. */
|
|
30
|
+
export function decodeValue(value) {
|
|
31
|
+
if (value === null || typeof value !== "object")
|
|
32
|
+
return value;
|
|
33
|
+
if (Array.isArray(value))
|
|
34
|
+
return value.map(decodeValue);
|
|
35
|
+
if ("$bytes" in value) {
|
|
36
|
+
if (Object.keys(value).length !== 1 || typeof value.$bytes !== "string") {
|
|
37
|
+
throw new TypeError("invalid $bytes value on sandbox wire");
|
|
38
|
+
}
|
|
39
|
+
return new Uint8Array(Buffer.from(value.$bytes, "base64"));
|
|
40
|
+
}
|
|
41
|
+
if ("$handle" in value) {
|
|
42
|
+
if (Object.keys(value).length !== 1 ||
|
|
43
|
+
typeof value.$handle !== "number" ||
|
|
44
|
+
!Number.isSafeInteger(value.$handle) ||
|
|
45
|
+
value.$handle <= 0) {
|
|
46
|
+
throw new TypeError("invalid $handle value on sandbox wire");
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
const result = {};
|
|
51
|
+
for (const [key, item] of Object.entries(value))
|
|
52
|
+
result[key] = decodeValue(item);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
export function encodeMessage(message) {
|
|
56
|
+
return `${JSON.stringify(encodeValue(message))}\n`;
|
|
57
|
+
}
|
|
58
|
+
export function decodeMessage(line) {
|
|
59
|
+
const value = JSON.parse(line);
|
|
60
|
+
if (typeof value !== "object" || value === null || !("id" in value)) {
|
|
61
|
+
throw new TypeError("invalid sandbox message");
|
|
62
|
+
}
|
|
63
|
+
const id = value.id;
|
|
64
|
+
if (typeof id !== "number" || !Number.isSafeInteger(id)) {
|
|
65
|
+
throw new TypeError("invalid sandbox message id");
|
|
66
|
+
}
|
|
67
|
+
if ("event" in value) {
|
|
68
|
+
if (value.event === "output") {
|
|
69
|
+
// SAFETY: the id was checked above; stream and text are checked below.
|
|
70
|
+
const event = value;
|
|
71
|
+
if ((event.stream !== "stdout" && event.stream !== "stderr") ||
|
|
72
|
+
typeof event.text !== "string") {
|
|
73
|
+
throw new TypeError("invalid sandbox output event");
|
|
74
|
+
}
|
|
75
|
+
return event;
|
|
76
|
+
}
|
|
77
|
+
if (value.event === "command" && "pid" in value && typeof value.pid === "number" &&
|
|
78
|
+
"phase" in value && (value.phase === "start" || value.phase === "end")) {
|
|
79
|
+
// SAFETY: id, numeric pid and the two command phases were checked.
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
if (value.event === "watch" && "change" in value) {
|
|
83
|
+
// SAFETY: id, event and change presence were checked above.
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
throw new TypeError("invalid sandbox event");
|
|
87
|
+
}
|
|
88
|
+
if (!("ok" in value) || typeof value.ok !== "boolean") {
|
|
89
|
+
throw new TypeError("invalid sandbox reply");
|
|
90
|
+
}
|
|
91
|
+
if (value.ok) {
|
|
92
|
+
if (!("value" in value))
|
|
93
|
+
throw new TypeError("sandbox reply has no value");
|
|
94
|
+
// SAFETY: id, ok and value presence were checked above.
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
if (!("error" in value))
|
|
98
|
+
throw new TypeError("sandbox failure has no error");
|
|
99
|
+
const error = value.error;
|
|
100
|
+
if ((error.kind !== "file" && error.kind !== "exec") ||
|
|
101
|
+
typeof error.code !== "string" ||
|
|
102
|
+
typeof error.message !== "string" ||
|
|
103
|
+
(error.path !== undefined && typeof error.path !== "string")) {
|
|
104
|
+
throw new TypeError("invalid sandbox error");
|
|
105
|
+
}
|
|
106
|
+
// SAFETY: id, ok=false and every required error field were checked above.
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
export function wireError(error, fallbackKind = "file") {
|
|
110
|
+
if (error instanceof FileError) {
|
|
111
|
+
return {
|
|
112
|
+
kind: "file",
|
|
113
|
+
code: error.code,
|
|
114
|
+
message: error.message,
|
|
115
|
+
...(error.path === undefined ? {} : { path: error.path }),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (error instanceof ExecutionError) {
|
|
119
|
+
return { kind: "exec", code: error.code, message: error.message };
|
|
120
|
+
}
|
|
121
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
122
|
+
return { kind: fallbackKind, code: fallbackKind === "exec" ? "unknown" : "invalid", message };
|
|
123
|
+
}
|
|
124
|
+
export function errorReply(id, error, fallbackKind = "file") {
|
|
125
|
+
return { id, ok: false, error: wireError(error, fallbackKind) };
|
|
126
|
+
}
|
|
127
|
+
export function decodeError(error) {
|
|
128
|
+
return error.kind === "file"
|
|
129
|
+
? new FileError(error.code, error.message, error.path)
|
|
130
|
+
: new ExecutionError(error.code, error.message);
|
|
131
|
+
}
|
|
132
|
+
export function decodeWatchChange(value) {
|
|
133
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
134
|
+
throw new TypeError("invalid watcher change");
|
|
135
|
+
}
|
|
136
|
+
if ("error" in value) {
|
|
137
|
+
const candidate = value.error;
|
|
138
|
+
if (typeof candidate !== "object" ||
|
|
139
|
+
candidate === null ||
|
|
140
|
+
Array.isArray(candidate) ||
|
|
141
|
+
candidate instanceof Uint8Array ||
|
|
142
|
+
"$handle" in candidate) {
|
|
143
|
+
throw new TypeError("invalid watcher error");
|
|
144
|
+
}
|
|
145
|
+
const kind = candidate.kind;
|
|
146
|
+
const code = candidate.code;
|
|
147
|
+
const message = candidate.message;
|
|
148
|
+
const path = candidate.path;
|
|
149
|
+
if (kind !== "file" ||
|
|
150
|
+
typeof code !== "string" ||
|
|
151
|
+
typeof message !== "string" ||
|
|
152
|
+
(path !== undefined && typeof path !== "string")) {
|
|
153
|
+
throw new TypeError("invalid watcher error");
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
error: new FileError(code, message, path),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if ("overflow" in value && value.overflow === true)
|
|
160
|
+
return { overflow: true };
|
|
161
|
+
if ("paths" in value &&
|
|
162
|
+
Array.isArray(value.paths) &&
|
|
163
|
+
value.paths.every((path) => typeof path === "string")) {
|
|
164
|
+
return { paths: value.paths };
|
|
165
|
+
}
|
|
166
|
+
throw new TypeError("invalid watcher change");
|
|
167
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/** Forward one ExecutionEnv over a user's worker, including live handles. */
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { FileError, } from "@earendil-works/pi-durable/env";
|
|
4
|
+
import { decodeError, decodeValue, decodeWatchChange, } from "./protocol.js";
|
|
5
|
+
function wireArgs(values) {
|
|
6
|
+
// The protocol encoder is intentionally shared with the worker; casting
|
|
7
|
+
// here is safe because the return value is checked as it is encoded.
|
|
8
|
+
return values.map((value) => {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
return null;
|
|
11
|
+
if (value instanceof Uint8Array)
|
|
12
|
+
return { $bytes: Buffer.from(value).toString("base64") };
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return wireArgs(value);
|
|
15
|
+
if (typeof value === "object" && value !== null) {
|
|
16
|
+
const result = {};
|
|
17
|
+
for (const [key, item] of Object.entries(value)) {
|
|
18
|
+
if (item !== undefined && typeof item !== "function")
|
|
19
|
+
result[key] = wireArgs([item])[0];
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function decoded(value) {
|
|
27
|
+
return restoreResult(decodeValue(value));
|
|
28
|
+
}
|
|
29
|
+
function restoreResult(value) {
|
|
30
|
+
if (Array.isArray(value))
|
|
31
|
+
return value.map(restoreResult);
|
|
32
|
+
if (typeof value !== "object" || value === null || value instanceof Uint8Array || "$handle" in value)
|
|
33
|
+
return value;
|
|
34
|
+
if ("ok" in value && value.ok === false && "error" in value) {
|
|
35
|
+
const candidate = value.error;
|
|
36
|
+
if (typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) && !(candidate instanceof Uint8Array) && !("$handle" in candidate)) {
|
|
37
|
+
const kind = candidate.kind;
|
|
38
|
+
const code = candidate.code;
|
|
39
|
+
const message = candidate.message;
|
|
40
|
+
const path = candidate.path;
|
|
41
|
+
if ((kind === "file" || kind === "exec") && typeof code === "string" && typeof message === "string") {
|
|
42
|
+
const wire = { kind, code, message, ...(typeof path === "string" ? { path } : {}) };
|
|
43
|
+
// SAFETY: pi-durable errors are class instances at the API boundary;
|
|
44
|
+
// DecodedValue deliberately describes JSON-only structural data.
|
|
45
|
+
return { ok: false, error: decodeError(wire) };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const [key, item] of Object.entries(value))
|
|
51
|
+
result[key] = restoreResult(item);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
function handleId(value) {
|
|
55
|
+
if (typeof value !== "object" || value === null || !("$handle" in value)) {
|
|
56
|
+
throw new Error("sandbox worker did not return a handle");
|
|
57
|
+
}
|
|
58
|
+
const id = value.$handle;
|
|
59
|
+
if (typeof id !== "number")
|
|
60
|
+
throw new Error("sandbox worker returned an invalid handle");
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
class BaseRemoteHandle {
|
|
64
|
+
client;
|
|
65
|
+
id;
|
|
66
|
+
closed = false;
|
|
67
|
+
constructor(client, id) {
|
|
68
|
+
this.client = client;
|
|
69
|
+
this.id = id;
|
|
70
|
+
}
|
|
71
|
+
async close(context) {
|
|
72
|
+
if (this.closed)
|
|
73
|
+
return;
|
|
74
|
+
this.closed = true;
|
|
75
|
+
try {
|
|
76
|
+
if (this.client.hasHandle(this.id)) {
|
|
77
|
+
await this.client.call("handle.close", [this.id], undefined, context);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
this.client.releaseHandle(this.id);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async call(method, args, context) {
|
|
85
|
+
if (this.closed)
|
|
86
|
+
throw new FileError("invalid", "sandbox handle is closed");
|
|
87
|
+
const value = await this.client.call(`handle.${method}`, [this.id, ...args], undefined, context);
|
|
88
|
+
return decoded(value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
class RemoteTextLineReader extends BaseRemoteHandle {
|
|
92
|
+
readLine(context) {
|
|
93
|
+
return this.call("readLine", [], context);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
class RemoteBinaryReader extends BaseRemoteHandle {
|
|
97
|
+
info(context) {
|
|
98
|
+
return this.call("info", [], context);
|
|
99
|
+
}
|
|
100
|
+
read(offset, length, context) {
|
|
101
|
+
return this.call("read", [offset, length], context);
|
|
102
|
+
}
|
|
103
|
+
scanLines(options, context) {
|
|
104
|
+
return this.call("scanLines", [options], context);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
class RemoteDirReader extends BaseRemoteHandle {
|
|
108
|
+
next(maxEntries, context) {
|
|
109
|
+
return this.call("next", [maxEntries], context);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
class RemoteWatcher extends BaseRemoteHandle {
|
|
113
|
+
mode;
|
|
114
|
+
constructor(client, id, mode) {
|
|
115
|
+
super(client, id);
|
|
116
|
+
this.mode = mode;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Every required FileSystem and Shell member is explicitly forwarded. */
|
|
120
|
+
export class SandboxedExecutionEnv {
|
|
121
|
+
client;
|
|
122
|
+
id;
|
|
123
|
+
currentCwd;
|
|
124
|
+
listFilesForwarder;
|
|
125
|
+
constructor(client, cwd) {
|
|
126
|
+
this.client = client;
|
|
127
|
+
this.id = `sandbox:${client.user}`;
|
|
128
|
+
this.currentCwd = cwd;
|
|
129
|
+
}
|
|
130
|
+
get cwd() {
|
|
131
|
+
return this.currentCwd;
|
|
132
|
+
}
|
|
133
|
+
set cwd(value) {
|
|
134
|
+
this.currentCwd = isAbsolute(value) ? value : resolve(this.currentCwd, value);
|
|
135
|
+
}
|
|
136
|
+
get listFiles() {
|
|
137
|
+
return this.listFilesForwarder;
|
|
138
|
+
}
|
|
139
|
+
async call(op, args, context, onEvent) {
|
|
140
|
+
// Result is the ExecutionEnv error channel; only transport failures
|
|
141
|
+
// reject. Never turn an ordinary ENOENT into an RPC exception.
|
|
142
|
+
const value = await this.client.call("result", wireArgs([op, args, this.currentCwd]), onEvent, context);
|
|
143
|
+
// A capability reply is consumed by the client at worker start. Install
|
|
144
|
+
// the optional member only after the worker has actually advertised it.
|
|
145
|
+
const capabilities = await this.client.capabilities();
|
|
146
|
+
if (capabilities.listFiles)
|
|
147
|
+
this.listFilesForwarder ??= this.forwardListFiles.bind(this);
|
|
148
|
+
return decoded(value);
|
|
149
|
+
}
|
|
150
|
+
forwardListFiles(path, options, context) {
|
|
151
|
+
return this.call("listFiles", [path, options], context);
|
|
152
|
+
}
|
|
153
|
+
absolutePath(path, context) {
|
|
154
|
+
return this.call("absolutePath", [path], context);
|
|
155
|
+
}
|
|
156
|
+
joinPath(parts, context) {
|
|
157
|
+
return this.call("joinPath", [parts], context);
|
|
158
|
+
}
|
|
159
|
+
readTextFile(path, context) {
|
|
160
|
+
return this.call("readTextFile", [path], context);
|
|
161
|
+
}
|
|
162
|
+
async openTextLineReader(path, context) {
|
|
163
|
+
const result = await this.call("openTextLineReader", [path], context);
|
|
164
|
+
return result.ok ? { ok: true, value: new RemoteTextLineReader(this.client, handleId(result.value)) } : result;
|
|
165
|
+
}
|
|
166
|
+
readTextLines(path, options, context) {
|
|
167
|
+
return this.call("readTextLines", [path, options], context);
|
|
168
|
+
}
|
|
169
|
+
readBinaryFile(path, context) {
|
|
170
|
+
return this.call("readBinaryFile", [path], context);
|
|
171
|
+
}
|
|
172
|
+
async openBinaryReader(path, options, context) {
|
|
173
|
+
const result = await this.call("openBinaryReader", [path, options], context);
|
|
174
|
+
return result.ok ? { ok: true, value: new RemoteBinaryReader(this.client, handleId(result.value)) } : result;
|
|
175
|
+
}
|
|
176
|
+
writeFile(path, content, context) {
|
|
177
|
+
return this.call("writeFile", [path, content], context);
|
|
178
|
+
}
|
|
179
|
+
appendFile(path, content, context) {
|
|
180
|
+
return this.call("appendFile", [path, content], context);
|
|
181
|
+
}
|
|
182
|
+
truncateFile(path, size, context) {
|
|
183
|
+
return this.call("truncateFile", [path, size], context);
|
|
184
|
+
}
|
|
185
|
+
flushFile(path, context) {
|
|
186
|
+
return this.call("flushFile", [path], context);
|
|
187
|
+
}
|
|
188
|
+
renameFile(sourcePath, destinationPath, context) {
|
|
189
|
+
return this.call("renameFile", [sourcePath, destinationPath], context);
|
|
190
|
+
}
|
|
191
|
+
fileInfo(path, context) {
|
|
192
|
+
return this.call("fileInfo", [path], context);
|
|
193
|
+
}
|
|
194
|
+
listDir(path, context) {
|
|
195
|
+
return this.call("listDir", [path], context);
|
|
196
|
+
}
|
|
197
|
+
async openDirReader(path, context) {
|
|
198
|
+
const result = await this.call("openDirReader", [path], context);
|
|
199
|
+
return result.ok ? { ok: true, value: new RemoteDirReader(this.client, handleId(result.value)) } : result;
|
|
200
|
+
}
|
|
201
|
+
async watch(targets, onChange, context) {
|
|
202
|
+
const result = await this.call("watch", [targets], context, (event) => {
|
|
203
|
+
if (event.event === "watch")
|
|
204
|
+
onChange(decodeWatchChange(decodeValue(event.change)));
|
|
205
|
+
});
|
|
206
|
+
return result.ok
|
|
207
|
+
? { ok: true, value: new RemoteWatcher(this.client, handleId(result.value.handle), result.value.mode) }
|
|
208
|
+
: result;
|
|
209
|
+
}
|
|
210
|
+
canonicalPath(path, context) {
|
|
211
|
+
return this.call("canonicalPath", [path], context);
|
|
212
|
+
}
|
|
213
|
+
exists(path, context) {
|
|
214
|
+
return this.call("exists", [path], context);
|
|
215
|
+
}
|
|
216
|
+
createDir(path, options, context) {
|
|
217
|
+
return this.call("createDir", [path, options], context);
|
|
218
|
+
}
|
|
219
|
+
remove(path, options, context) {
|
|
220
|
+
return this.call("remove", [path, options], context);
|
|
221
|
+
}
|
|
222
|
+
createTempDir(prefix, context) {
|
|
223
|
+
return this.call("createTempDir", [prefix], context);
|
|
224
|
+
}
|
|
225
|
+
createTempFile(options, context) {
|
|
226
|
+
return this.call("createTempFile", [options], context);
|
|
227
|
+
}
|
|
228
|
+
async cleanup(_context) {
|
|
229
|
+
// NO-OP: this worker is shared by all of the user's conversations;
|
|
230
|
+
// cleanup here would kill another conversation's live commands.
|
|
231
|
+
}
|
|
232
|
+
exec(command, options, context) {
|
|
233
|
+
const wireOptions = { ...(options ?? {}) };
|
|
234
|
+
delete wireOptions.onOutput;
|
|
235
|
+
return this.call("exec", [command, wireOptions], context, (event) => {
|
|
236
|
+
if (event.event !== "output")
|
|
237
|
+
return;
|
|
238
|
+
const info = {
|
|
239
|
+
stream: event.stream,
|
|
240
|
+
...(event.skipped === undefined ? {} : { skipped: event.skipped }),
|
|
241
|
+
};
|
|
242
|
+
options?.onOutput?.(event.text, context, info);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** An ExecutionEnv that can never touch a filesystem or start a process. */
|
|
2
|
+
import { ExecutionError, FileError, } from "@earendil-works/pi-durable/env";
|
|
3
|
+
export class UnavailableExecutionEnv {
|
|
4
|
+
id = "unavailable";
|
|
5
|
+
cwd = ".";
|
|
6
|
+
message;
|
|
7
|
+
constructor(reason) {
|
|
8
|
+
this.message = `isolation_unavailable: ${reason}`;
|
|
9
|
+
}
|
|
10
|
+
file() {
|
|
11
|
+
return Promise.resolve({ ok: false, error: new FileError("permission_denied", this.message) });
|
|
12
|
+
}
|
|
13
|
+
shell() {
|
|
14
|
+
return Promise.resolve({ ok: false, error: new ExecutionError("spawn_error", this.message) });
|
|
15
|
+
}
|
|
16
|
+
absolutePath() { return this.file(); }
|
|
17
|
+
joinPath() { return this.file(); }
|
|
18
|
+
readTextFile() { return this.file(); }
|
|
19
|
+
openTextLineReader() { return this.file(); }
|
|
20
|
+
readTextLines() { return this.file(); }
|
|
21
|
+
readBinaryFile() { return this.file(); }
|
|
22
|
+
openBinaryReader() { return this.file(); }
|
|
23
|
+
writeFile() { return this.file(); }
|
|
24
|
+
appendFile() { return this.file(); }
|
|
25
|
+
truncateFile() { return this.file(); }
|
|
26
|
+
flushFile() { return this.file(); }
|
|
27
|
+
renameFile() { return this.file(); }
|
|
28
|
+
fileInfo() { return this.file(); }
|
|
29
|
+
listDir() { return this.file(); }
|
|
30
|
+
listFiles(_path, _options, _context) {
|
|
31
|
+
return this.file();
|
|
32
|
+
}
|
|
33
|
+
openDirReader() { return this.file(); }
|
|
34
|
+
watch(_targets, _onChange, _context) {
|
|
35
|
+
return this.file();
|
|
36
|
+
}
|
|
37
|
+
canonicalPath() { return this.file(); }
|
|
38
|
+
exists() { return this.file(); }
|
|
39
|
+
createDir() { return this.file(); }
|
|
40
|
+
remove() { return this.file(); }
|
|
41
|
+
createTempDir() { return this.file(); }
|
|
42
|
+
createTempFile() { return this.file(); }
|
|
43
|
+
cleanup() {
|
|
44
|
+
return Promise.reject(new FileError("permission_denied", this.message));
|
|
45
|
+
}
|
|
46
|
+
exec(_command, _options, _context) {
|
|
47
|
+
return this.shell();
|
|
48
|
+
}
|
|
49
|
+
}
|