@fungi.computer/computer 0.1.0 → 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/README.md +38 -10
- package/dist/computer-backends.d.ts +0 -1
- package/dist/computer-backends.js +0 -3
- package/dist/exec-output.d.ts +13 -0
- package/dist/exec-output.js +24 -0
- package/dist/execution-lifecycle.d.ts +35 -0
- package/dist/execution-lifecycle.js +78 -0
- package/dist/files-patch.d.ts +12 -4
- package/dist/files-patch.js +8 -14
- package/dist/files.d.ts +65 -15
- package/dist/files.js +152 -31
- package/dist/fleet/index.d.ts +1 -1
- package/dist/fleet/index.js +1 -1
- package/dist/fleet/inventory-wire.d.ts +6 -2
- package/dist/fleet/inventory-wire.js +7 -2
- package/dist/fleet/lifecycle.d.ts +7 -1
- package/dist/fleet/lifecycle.js +22 -0
- package/dist/fleet/wire.d.ts +12 -12
- package/dist/fleet/wire.js +1 -1
- package/dist/index.d.ts +33 -15
- package/dist/index.js +422 -105
- package/dist/internet-access.d.ts +4 -0
- package/dist/internet-access.js +14 -0
- package/dist/local-backend-replay.d.ts +10 -0
- package/dist/local-backend-replay.js +43 -0
- package/dist/local-backend.d.ts +2 -1
- package/dist/local-backend.js +4 -4
- package/dist/on-box-vm.d.ts +11 -0
- package/dist/on-box-vm.js +36 -13
- package/dist/operation-record.d.ts +1 -1
- package/dist/operation-record.js +1 -1
- package/dist/process-inventory.d.ts +3 -1
- package/dist/process-inventory.js +2 -2
- package/dist/team-candidate-builder.mjs +8375 -334
- package/package.json +17 -24
- package/dist/light-shell.d.ts +0 -44
- package/dist/light-shell.js +0 -376
- package/dist/operation-contract.d.ts +0 -456
- package/dist/operation-contract.js +0 -450
package/README.md
CHANGED
|
@@ -13,6 +13,13 @@ from `@fungi.computer/computer/fleet/wire`, and backing descriptions from
|
|
|
13
13
|
declarations. The `./fleet` lifecycle and main Computer entry require the host
|
|
14
14
|
runtime's Cloudflare Workers types.
|
|
15
15
|
|
|
16
|
+
The packed stranger check exercises these Node-safe entries and the egress
|
|
17
|
+
helpers after a cold install of the runtime closure. The fork's companion check
|
|
18
|
+
parses the published shell-module groups used by the Computer image with Acorn
|
|
19
|
+
and checks their declarations. Neither check starts a Computer, executes a
|
|
20
|
+
Worker shell, or qualifies the Worker/Workspace/Git SDK in a Node type
|
|
21
|
+
environment.
|
|
22
|
+
|
|
16
23
|
`@fungi.computer/computer/fleet` owns the host-neutral grant check, backing
|
|
17
24
|
records and fact contracts. A host authenticates its caller and supplies a
|
|
18
25
|
`ComputerGrant`: one Computer, explicit actions, an expiry, optional host-owned
|
|
@@ -82,11 +89,11 @@ publish an App or replace a production Computer image.
|
|
|
82
89
|
SQLite provider. Files belong to that workspace, not to a running shell process.
|
|
83
90
|
A file read can work with no available execution backend.
|
|
84
91
|
|
|
85
|
-
[Operation contracts](
|
|
86
|
-
reads/writes, directories, command execution,
|
|
87
|
-
[Files adapter](src/files.ts) implements the
|
|
88
|
-
contract for Agent tools. Failed containment,
|
|
89
|
-
ports remain explicit errors.
|
|
92
|
+
[Operation contracts](../computer-contract/README.md), in their own package,
|
|
93
|
+
declare discovery, bounded file reads/writes, directories, command execution,
|
|
94
|
+
processes and port exposure. The [Files adapter](src/files.ts) implements the
|
|
95
|
+
same read and compare/replace contract for Agent tools. Failed containment,
|
|
96
|
+
conflicting contents and reserved ports remain explicit errors.
|
|
90
97
|
|
|
91
98
|
An execution request carries a replay key. The
|
|
92
99
|
[operation record](src/operation-record.ts) admits the immutable input and
|
|
@@ -130,6 +137,27 @@ registered host to the Workspace backend interface. Platform containers use the
|
|
|
130
137
|
maintained Cloudflare container backend. Neither backend label nor a green
|
|
131
138
|
container fixture certifies an installed VM host or production routing.
|
|
132
139
|
|
|
140
|
+
Local dial verification binds Seam's replay-admission port to the existing
|
|
141
|
+
Computer Durable Object's SQLite database. That database is the receiver
|
|
142
|
+
namespace across reconstruction, shared by its attached devices; nonce identity
|
|
143
|
+
inside it is nonce-only. One native transaction prunes only strictly expired
|
|
144
|
+
records, refuses duplicates or the private 4,096-record ceiling, and commits the
|
|
145
|
+
nonce before admission succeeds. Seam owns the inclusive retention deadline;
|
|
146
|
+
duplicates never renew it and capacity never evicts live history. Storage
|
|
147
|
+
failure refuses admission without a process-local fallback. Attachment,
|
|
148
|
+
device-key, signature, audience and revocation authority remain unchanged.
|
|
149
|
+
|
|
150
|
+
`createLocalBackendReplayStore(owner)` is exported for another native Local-dial
|
|
151
|
+
receiver that uses its own existing SQLite owner, including a Team machine-link
|
|
152
|
+
receiver. The caller owns pins, audience, membership and revocation; this store
|
|
153
|
+
owns only atomic, bounded nonce retention. It neither creates a Computer nor
|
|
154
|
+
shares nonce history across separate databases.
|
|
155
|
+
|
|
156
|
+
`test:unit` includes the `machine-link-reopen-replay` proof: two physical Node
|
|
157
|
+
processes call the actual local-dial verifier against the same SQLite file. This
|
|
158
|
+
proves reconstructed-process replay refusal, not a complete HTTP/WebSocket
|
|
159
|
+
route, deployed host or container-health acceptance.
|
|
160
|
+
|
|
133
161
|
## Processes, ports and lifetime
|
|
134
162
|
|
|
135
163
|
Start requires a host-owned opaque `requestId` and `requestScope`, plus a
|
|
@@ -196,11 +224,11 @@ were saved in a hosted Computer. Do not copy its temporary output into source.
|
|
|
196
224
|
## Proof and reference
|
|
197
225
|
|
|
198
226
|
[package.json](package.json) declares the private entrypoints `.` and `./files`,
|
|
199
|
-
`./backing`, `./host-config
|
|
200
|
-
[Runtime proof](test/runtime-proof.mjs)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
[Terminal proof](test/terminal-container-proof.mjs),
|
|
227
|
+
`./backing`, `./host-config`. Operation schemas come from
|
|
228
|
+
`@fungi.computer/computer-contract`. [Runtime proof](test/runtime-proof.mjs)
|
|
229
|
+
checks native workspace operations and reconstruction.
|
|
230
|
+
[Process proof](test/process-proof.mjs) checks retained process identity against
|
|
231
|
+
the real container. [Terminal proof](test/terminal-container-proof.mjs),
|
|
204
232
|
[resource proof](test/container-resource-proof.mjs) and
|
|
205
233
|
[backend tests](test/computer-backends.test.mjs) cover their separate
|
|
206
234
|
boundaries. The declared nightly egress proof is separate from the ordinary
|
|
@@ -44,7 +44,6 @@ export declare const attachedBackendsSchema: z.ZodLazy<z.ZodArray<z.ZodDiscrimin
|
|
|
44
44
|
hostLabel: z.ZodLazy<z.ZodString>;
|
|
45
45
|
folderLabel: z.ZodLazy<z.ZodString>;
|
|
46
46
|
}, z.core.$strict>], "kind">>>;
|
|
47
|
-
export declare const attachedBackendIdSchema: z.ZodLazy<z.ZodString>;
|
|
48
47
|
export type AttachedBackendStatus = "ready" | "online" | "offline";
|
|
49
48
|
/** What a Computer reports about one attached backend. */
|
|
50
49
|
export type AttachedBackendView = Readonly<{
|
|
@@ -44,9 +44,6 @@ export const attachedBackendsSchema = z.lazy(() => z
|
|
|
44
44
|
.array(attachedBackendSchema)
|
|
45
45
|
.max(16)
|
|
46
46
|
.refine((backends) => new Set(backends.map(attachedBackendId)).size === backends.length, "attached backend ids must be unique"));
|
|
47
|
-
export const attachedBackendIdSchema = z.lazy(() => z
|
|
48
|
-
.string()
|
|
49
|
-
.regex(/^(container|vm-[a-z0-9][a-z0-9-]{0,62}|local-[a-z2-7]{16})$/));
|
|
50
47
|
export function describeAttachedBackend(backend, online) {
|
|
51
48
|
const id = attachedBackendId(backend);
|
|
52
49
|
switch (backend.kind) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep one command stream within `maximumStreamBytes`. A cut stream ends with
|
|
3
|
+
* a visible marker line (counted inside the bound, excluded from the totals)
|
|
4
|
+
* and reports a typed entry.
|
|
5
|
+
*/
|
|
6
|
+
export declare function boundExecStream(value: string, stream: "stdout" | "stderr"): {
|
|
7
|
+
value: string;
|
|
8
|
+
truncation: readonly {
|
|
9
|
+
stream: "stdout" | "stderr";
|
|
10
|
+
keptBytes: number;
|
|
11
|
+
totalBytes: number;
|
|
12
|
+
}[];
|
|
13
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { execTruncationMarker, maximumStreamBytes, truncationMarkerReserveBytes, utf8Prefix, } from "@fungi.computer/computer-contract";
|
|
2
|
+
const utf8 = new TextEncoder();
|
|
3
|
+
const noTruncation = [];
|
|
4
|
+
/**
|
|
5
|
+
* Keep one command stream within `maximumStreamBytes`. A cut stream ends with
|
|
6
|
+
* a visible marker line (counted inside the bound, excluded from the totals)
|
|
7
|
+
* and reports a typed entry.
|
|
8
|
+
*/
|
|
9
|
+
export function boundExecStream(value, stream) {
|
|
10
|
+
const bytes = utf8.encode(value);
|
|
11
|
+
if (bytes.byteLength <= maximumStreamBytes) {
|
|
12
|
+
return { value, truncation: noTruncation };
|
|
13
|
+
}
|
|
14
|
+
const kept = utf8Prefix(bytes, maximumStreamBytes - truncationMarkerReserveBytes);
|
|
15
|
+
const entry = {
|
|
16
|
+
stream,
|
|
17
|
+
keptBytes: kept.byteLength,
|
|
18
|
+
totalBytes: bytes.byteLength,
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
value: `${new TextDecoder().decode(kept)}${execTruncationMarker(entry)}`,
|
|
22
|
+
truncation: [entry],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ComputerExecutionCloseReason, type ComputerExecutionInvocation, type ComputerValue } from "@fungi.computer/computer-contract";
|
|
2
|
+
type ExecutionStorage = Pick<DurableObjectStorage, "sql" | "transactionSync">;
|
|
3
|
+
type Phase = "opening" | "dispatching" | "open" | "closed";
|
|
4
|
+
type ComputerExecutionRecord = Readonly<{
|
|
5
|
+
invocation: ComputerExecutionInvocation;
|
|
6
|
+
phase: Phase;
|
|
7
|
+
backend: string;
|
|
8
|
+
nativeOpened: boolean;
|
|
9
|
+
closeReason: ComputerExecutionCloseReason | null;
|
|
10
|
+
context: ComputerValue;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Only public host context and native invocation identity are durable here.
|
|
14
|
+
* The operation/process owners continue to own admission, replay and retention;
|
|
15
|
+
* neither credentials nor a second process inventory belong in this table.
|
|
16
|
+
*/
|
|
17
|
+
declare class ComputerExecutionLifecycle {
|
|
18
|
+
#private;
|
|
19
|
+
readonly reconstructed: readonly ComputerExecutionRecord[];
|
|
20
|
+
constructor(storage: ExecutionStorage);
|
|
21
|
+
get(id: string): ComputerExecutionRecord | undefined;
|
|
22
|
+
/** Called synchronously after the existing owner's atomic NEW admission. */
|
|
23
|
+
begin(invocation: ComputerExecutionInvocation, backend: string, context: ComputerValue): void;
|
|
24
|
+
/** The last synchronous fence before invoking the maintained native SDK. */
|
|
25
|
+
dispatch(id: string): void;
|
|
26
|
+
/** A native handle arrived; this phase alone is not proof of native liveness. */
|
|
27
|
+
opened(id: string): void;
|
|
28
|
+
/** Persist revocation before the caller attempts the remote host callback. */
|
|
29
|
+
close(id: string, reason: ComputerExecutionCloseReason): ComputerExecutionRecord | undefined;
|
|
30
|
+
/** Closed metadata cannot outlive the existing operation/process owner. */
|
|
31
|
+
prune(): void;
|
|
32
|
+
}
|
|
33
|
+
/** Construct the ComputerDO's internal lifecycle owner, not a package API. */
|
|
34
|
+
export declare function createComputerExecutionLifecycle(storage: Pick<DurableObjectStorage, "sql" | "transactionSync">): ComputerExecutionLifecycle;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { computerValueSchema, } from "@fungi.computer/computer-contract";
|
|
2
|
+
function record(row) {
|
|
3
|
+
return {
|
|
4
|
+
invocation: { id: row.id, kind: row.kind },
|
|
5
|
+
phase: row.phase,
|
|
6
|
+
backend: row.backend,
|
|
7
|
+
nativeOpened: row.native_opened === 1,
|
|
8
|
+
closeReason: row.close_reason,
|
|
9
|
+
context: computerValueSchema.parse(JSON.parse(row.context_json)),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Only public host context and native invocation identity are durable here.
|
|
14
|
+
* The operation/process owners continue to own admission, replay and retention;
|
|
15
|
+
* neither credentials nor a second process inventory belong in this table.
|
|
16
|
+
*/
|
|
17
|
+
class ComputerExecutionLifecycle {
|
|
18
|
+
#storage;
|
|
19
|
+
reconstructed;
|
|
20
|
+
constructor(storage) {
|
|
21
|
+
this.#storage = storage;
|
|
22
|
+
this.reconstructed = storage.transactionSync(() => {
|
|
23
|
+
// The first partial checkpoint's preview metadata is disposable. Keep
|
|
24
|
+
// only this current representation, never an old-shape reader or copy.
|
|
25
|
+
storage.sql.exec("DROP TABLE IF EXISTS computer_execution_lifecycle");
|
|
26
|
+
storage.sql.exec("CREATE TABLE IF NOT EXISTS computer_execution_invocations (id TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK (kind IN ('exec', 'process')), phase TEXT NOT NULL CHECK (phase IN ('opening', 'dispatching', 'open', 'closed')), backend TEXT NOT NULL, native_opened INTEGER NOT NULL DEFAULT 0 CHECK (native_opened IN (0, 1)), context_json TEXT NOT NULL, close_reason TEXT CHECK (close_reason IN ('refused_start', 'native_exit', 'unknown', 'reconstructed'))) STRICT");
|
|
27
|
+
const interrupted = storage.sql
|
|
28
|
+
.exec("SELECT id, kind, phase, backend, native_opened, context_json, close_reason FROM computer_execution_invocations WHERE phase <> 'closed' AND (kind = 'exec' OR phase = 'opening')")
|
|
29
|
+
.toArray()
|
|
30
|
+
.map(record);
|
|
31
|
+
// A cold exec never probes or dispatches again. Process reconciliation
|
|
32
|
+
// will require native proof; an unfinished issuer open has no such proof.
|
|
33
|
+
storage.sql.exec("UPDATE computer_execution_invocations SET phase = 'closed', close_reason = 'reconstructed' WHERE phase <> 'closed' AND (kind = 'exec' OR phase = 'opening')");
|
|
34
|
+
return interrupted;
|
|
35
|
+
});
|
|
36
|
+
this.prune();
|
|
37
|
+
}
|
|
38
|
+
get(id) {
|
|
39
|
+
const row = this.#storage.sql
|
|
40
|
+
.exec("SELECT id, kind, phase, backend, native_opened, context_json, close_reason FROM computer_execution_invocations WHERE id = ?", id)
|
|
41
|
+
.toArray()[0];
|
|
42
|
+
return row === undefined ? undefined : record(row);
|
|
43
|
+
}
|
|
44
|
+
/** Called synchronously after the existing owner's atomic NEW admission. */
|
|
45
|
+
begin(invocation, backend, context) {
|
|
46
|
+
const publicContext = computerValueSchema.parse(context);
|
|
47
|
+
this.prune();
|
|
48
|
+
this.#storage.sql.exec("INSERT INTO computer_execution_invocations (id, kind, phase, backend, context_json) VALUES (?, ?, 'opening', ?, ?)", invocation.id, invocation.kind, backend, JSON.stringify(publicContext));
|
|
49
|
+
}
|
|
50
|
+
/** The last synchronous fence before invoking the maintained native SDK. */
|
|
51
|
+
dispatch(id) {
|
|
52
|
+
if (this.get(id)?.phase !== "opening")
|
|
53
|
+
throw new Error("Computer execution is no longer open for dispatch");
|
|
54
|
+
this.#storage.sql.exec("UPDATE computer_execution_invocations SET phase = 'dispatching' WHERE id = ?", id);
|
|
55
|
+
}
|
|
56
|
+
/** A native handle arrived; this phase alone is not proof of native liveness. */
|
|
57
|
+
opened(id) {
|
|
58
|
+
this.#storage.sql.exec("UPDATE computer_execution_invocations SET native_opened = 1, phase = CASE WHEN phase = 'dispatching' THEN 'open' ELSE phase END WHERE id = ?", id);
|
|
59
|
+
}
|
|
60
|
+
/** Persist revocation before the caller attempts the remote host callback. */
|
|
61
|
+
close(id, reason) {
|
|
62
|
+
return this.#storage.transactionSync(() => {
|
|
63
|
+
const current = this.get(id);
|
|
64
|
+
if (current === undefined || current.phase === "closed")
|
|
65
|
+
return undefined;
|
|
66
|
+
this.#storage.sql.exec("UPDATE computer_execution_invocations SET phase = 'closed', close_reason = ? WHERE id = ?", reason, id);
|
|
67
|
+
return current;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** Closed metadata cannot outlive the existing operation/process owner. */
|
|
71
|
+
prune() {
|
|
72
|
+
this.#storage.sql.exec("DELETE FROM computer_execution_invocations WHERE phase = 'closed' AND ((kind = 'exec' AND NOT EXISTS (SELECT 1 FROM computer_operations WHERE 'exec:' || request_id = computer_execution_invocations.id)) OR (kind = 'process' AND NOT EXISTS (SELECT 1 FROM computer_processes WHERE id = computer_execution_invocations.id)))");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Construct the ComputerDO's internal lifecycle owner, not a package API. */
|
|
76
|
+
export function createComputerExecutionLifecycle(storage) {
|
|
77
|
+
return new ComputerExecutionLifecycle(storage);
|
|
78
|
+
}
|
package/dist/files-patch.d.ts
CHANGED
|
@@ -3,17 +3,25 @@ import type { Workspace } from "@fungi.computer/cloudflare-computer";
|
|
|
3
3
|
export declare function applyWorkspacePatch(workspace: Pick<Workspace, "runtime">, backend: string, input: {
|
|
4
4
|
patch: string;
|
|
5
5
|
}): Promise<{
|
|
6
|
-
status: "
|
|
6
|
+
status: "failed" | "completed" | "cancelled";
|
|
7
7
|
exitCode: number;
|
|
8
8
|
stdout: string;
|
|
9
9
|
stderr: string;
|
|
10
|
-
truncated:
|
|
10
|
+
truncated: false | {
|
|
11
|
+
stream: "stdout" | "stderr";
|
|
12
|
+
keptBytes: number;
|
|
13
|
+
totalBytes: number;
|
|
14
|
+
}[];
|
|
11
15
|
} | {
|
|
12
|
-
status: "
|
|
16
|
+
status: "failed" | "completed" | "cancelled";
|
|
13
17
|
exitCode: number;
|
|
14
18
|
stdout: string;
|
|
15
19
|
stderr: string;
|
|
16
|
-
truncated:
|
|
20
|
+
truncated: false | {
|
|
21
|
+
stream: "stdout" | "stderr";
|
|
22
|
+
keptBytes: number;
|
|
23
|
+
totalBytes: number;
|
|
24
|
+
}[];
|
|
17
25
|
error: {
|
|
18
26
|
kind: "mismatch";
|
|
19
27
|
};
|
package/dist/files-patch.js
CHANGED
|
@@ -50,7 +50,8 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import { patchInputSchema, patchOutputSchema,
|
|
53
|
+
import { patchInputSchema, patchOutputSchema, } from "@fungi.computer/computer-contract";
|
|
54
|
+
import { boundExecStream } from "./exec-output.js";
|
|
54
55
|
/** The packaged, pinned Codex engine owns patch parsing and the Workspace jail. */
|
|
55
56
|
const nativePatch = {
|
|
56
57
|
executable: "/opt/fungi/fungi-apply-patch",
|
|
@@ -58,15 +59,6 @@ const nativePatch = {
|
|
|
58
59
|
timeoutMs: 30_000,
|
|
59
60
|
mismatchExitCode: 23,
|
|
60
61
|
};
|
|
61
|
-
function bounded(value) {
|
|
62
|
-
const bytes = new TextEncoder().encode(value);
|
|
63
|
-
return bytes.length <= maximumStreamBytes
|
|
64
|
-
? { value, truncated: false }
|
|
65
|
-
: {
|
|
66
|
-
value: new TextDecoder().decode(bytes.slice(0, maximumStreamBytes)),
|
|
67
|
-
truncated: true,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
62
|
/** Await Workspace synchronization even when the native engine reports a partial failure. */
|
|
71
63
|
export async function applyWorkspacePatch(workspace, backend, input) {
|
|
72
64
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
@@ -81,10 +73,10 @@ export async function applyWorkspacePatch(workspace, backend, input) {
|
|
|
81
73
|
}), false);
|
|
82
74
|
const result = await run.result();
|
|
83
75
|
const synchronizationFailure = result.sync.status === "pending" ? result.sync.error : null;
|
|
84
|
-
const stdout =
|
|
85
|
-
const stderr =
|
|
76
|
+
const stdout = boundExecStream(result.stdout, "stdout");
|
|
77
|
+
const stderr = boundExecStream(synchronizationFailure !== null
|
|
86
78
|
? `${result.stderr}\nPatch filesystem synchronization failed: ${synchronizationFailure}. Earlier patch writes may remain.`
|
|
87
|
-
: result.stderr);
|
|
79
|
+
: result.stderr, "stderr");
|
|
88
80
|
const output = {
|
|
89
81
|
status: synchronizationFailure !== null ? "failed" : result.status,
|
|
90
82
|
exitCode: synchronizationFailure !== null && result.exitCode === 0
|
|
@@ -92,7 +84,9 @@ export async function applyWorkspacePatch(workspace, backend, input) {
|
|
|
92
84
|
: result.exitCode,
|
|
93
85
|
stdout: stdout.value,
|
|
94
86
|
stderr: stderr.value,
|
|
95
|
-
truncated: stdout.
|
|
87
|
+
truncated: stdout.truncation.length + stderr.truncation.length === 0
|
|
88
|
+
? false
|
|
89
|
+
: [...stdout.truncation, ...stderr.truncation],
|
|
96
90
|
};
|
|
97
91
|
return patchOutputSchema.parse(result.exitCode === nativePatch.mismatchExitCode
|
|
98
92
|
? { ...output, error: { kind: "mismatch" } }
|
package/dist/files.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { SQLiteWorkspaceProvider, WorkspaceClient } from "@fungi.computer/cloudflare-computer";
|
|
2
2
|
/** Files need only the Workspace filesystem, local or leased through a stub. */
|
|
3
3
|
export type WorkspaceFiles = Pick<WorkspaceClient, "fs">;
|
|
4
|
-
import
|
|
4
|
+
import type { FilesPort } from "@fungi.computer/knapsack";
|
|
5
|
+
import { type ComputerFileGlobInput, type ComputerFileGrepInput, type ComputerFileListInput, type ComputerFileReplacement, type ComputerFileReplacementResult } from "@fungi.computer/computer-contract";
|
|
5
6
|
export type WorkspaceFilesOwner = Readonly<{
|
|
6
7
|
replaceFileIfUnchanged: (input: ComputerFileReplacement) => Promise<ComputerFileReplacementResult>;
|
|
7
8
|
}>;
|
|
@@ -15,11 +16,48 @@ export declare function decodeUtf8Page(bytes: Uint8Array, path: string, moreByte
|
|
|
15
16
|
export declare function readBoundedWorkspaceText(provider: SQLiteWorkspaceProvider, path: string, maximumBytes: number): string;
|
|
16
17
|
/** Adapt native Workspace.fs at the one real file boundary. */
|
|
17
18
|
export declare function createWorkspaceFilesPort(workspace: WorkspaceFiles, owner: WorkspaceFilesOwner): Readonly<{
|
|
19
|
+
rest: Readonly<{
|
|
20
|
+
list: (input: ComputerFileListInput) => Promise<{
|
|
21
|
+
path: string;
|
|
22
|
+
entries: {
|
|
23
|
+
name: string;
|
|
24
|
+
size: number;
|
|
25
|
+
modifiedAt: number;
|
|
26
|
+
type: "file" | "directory" | "symlink";
|
|
27
|
+
}[];
|
|
28
|
+
nextOffset: number | null;
|
|
29
|
+
}>;
|
|
30
|
+
glob(input: ComputerFileGlobInput): Promise<{
|
|
31
|
+
path: string;
|
|
32
|
+
matches: string[];
|
|
33
|
+
truncated: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
grep(input: ComputerFileGrepInput): Promise<{
|
|
36
|
+
path: string;
|
|
37
|
+
matches: {
|
|
38
|
+
path: string;
|
|
39
|
+
line: number;
|
|
40
|
+
text: string;
|
|
41
|
+
}[];
|
|
42
|
+
truncated: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
}>;
|
|
18
45
|
read(input: {
|
|
19
46
|
path: string;
|
|
20
47
|
offset: number;
|
|
21
48
|
limit: number;
|
|
22
49
|
}): Promise<{
|
|
50
|
+
path: string;
|
|
51
|
+
image: {
|
|
52
|
+
mimeType: "image/png" | "image/jpeg" | "image/gif" | "image/webp";
|
|
53
|
+
data: string;
|
|
54
|
+
bytes: number;
|
|
55
|
+
};
|
|
56
|
+
file?: {
|
|
57
|
+
computerId: string;
|
|
58
|
+
path: string;
|
|
59
|
+
};
|
|
60
|
+
} | {
|
|
23
61
|
path: string;
|
|
24
62
|
content: string;
|
|
25
63
|
offset: number;
|
|
@@ -29,25 +67,25 @@ export declare function createWorkspaceFilesPort(workspace: WorkspaceFiles, owne
|
|
|
29
67
|
list(input: {
|
|
30
68
|
path: string;
|
|
31
69
|
limit: number;
|
|
32
|
-
}): Promise<{
|
|
33
|
-
|
|
34
|
-
entries: {
|
|
70
|
+
}, context?: Parameters<FilesPort["list"]>[1]): Promise<{
|
|
71
|
+
results: {
|
|
35
72
|
path: string;
|
|
36
|
-
name: string;
|
|
37
73
|
type: "file" | "directory" | "symlink";
|
|
38
|
-
size: number;
|
|
39
|
-
modifiedAt: number;
|
|
40
74
|
}[];
|
|
41
|
-
truncated:
|
|
75
|
+
truncated: "limit" | "scan" | null;
|
|
76
|
+
path: string;
|
|
42
77
|
}>;
|
|
43
78
|
glob(input: {
|
|
44
79
|
path: string;
|
|
45
80
|
pattern: string;
|
|
46
81
|
limit: number;
|
|
47
|
-
}): Promise<{
|
|
82
|
+
}, context?: Parameters<FilesPort["glob"]>[1]): Promise<{
|
|
83
|
+
results: {
|
|
84
|
+
path: string;
|
|
85
|
+
type: "file" | "directory";
|
|
86
|
+
}[];
|
|
87
|
+
truncated: "limit" | "scan" | null;
|
|
48
88
|
path: string;
|
|
49
|
-
matches: string[];
|
|
50
|
-
truncated: boolean;
|
|
51
89
|
}>;
|
|
52
90
|
grep(input: {
|
|
53
91
|
path: string;
|
|
@@ -55,14 +93,15 @@ export declare function createWorkspaceFilesPort(workspace: WorkspaceFiles, owne
|
|
|
55
93
|
regex: boolean;
|
|
56
94
|
ignoreCase: boolean;
|
|
57
95
|
limit: number;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
include?: string | undefined;
|
|
97
|
+
}, context?: Parameters<FilesPort["grep"]>[1]): Promise<{
|
|
98
|
+
results: {
|
|
61
99
|
path: string;
|
|
62
100
|
line: number;
|
|
63
101
|
text: string;
|
|
64
102
|
}[];
|
|
65
|
-
truncated:
|
|
103
|
+
truncated: "limit" | "scan" | null;
|
|
104
|
+
path: string;
|
|
66
105
|
}>;
|
|
67
106
|
replace(input: Readonly<{
|
|
68
107
|
path: string;
|
|
@@ -73,3 +112,14 @@ export declare function createWorkspaceFilesPort(workspace: WorkspaceFiles, owne
|
|
|
73
112
|
bytes: number;
|
|
74
113
|
}>;
|
|
75
114
|
}>;
|
|
115
|
+
/** Shared REST listing: offsets count visible entries, never ignored candidates. */
|
|
116
|
+
export declare function listWorkspaceFiles(workspace: WorkspaceFiles, input: ComputerFileListInput): Promise<{
|
|
117
|
+
path: string;
|
|
118
|
+
entries: {
|
|
119
|
+
name: string;
|
|
120
|
+
size: number;
|
|
121
|
+
modifiedAt: number;
|
|
122
|
+
type: "file" | "directory" | "symlink";
|
|
123
|
+
}[];
|
|
124
|
+
nextOffset: number | null;
|
|
125
|
+
}>;
|