gateproof 0.2.1 → 0.2.4
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 +132 -340
- package/dist/act.d.ts +45 -0
- package/dist/act.d.ts.map +1 -1
- package/dist/act.js +22 -0
- package/dist/act.js.map +1 -1
- package/dist/action-executors.d.ts +17 -0
- package/dist/action-executors.d.ts.map +1 -1
- package/dist/action-executors.js +60 -0
- package/dist/action-executors.js.map +1 -1
- package/dist/assert.d.ts +20 -0
- package/dist/assert.d.ts.map +1 -1
- package/dist/assert.js +32 -0
- package/dist/assert.js.map +1 -1
- package/dist/authority.d.ts +34 -0
- package/dist/authority.d.ts.map +1 -0
- package/dist/authority.js +141 -0
- package/dist/authority.js.map +1 -0
- package/dist/cli/gateproof.js +81 -5
- package/dist/cli/gateproof.js.map +1 -1
- package/dist/filepath-backend.d.ts +64 -0
- package/dist/filepath-backend.d.ts.map +1 -0
- package/dist/filepath-backend.js +126 -0
- package/dist/filepath-backend.js.map +1 -0
- package/dist/filepath-protocol.d.ts +214 -0
- package/dist/filepath-protocol.d.ts.map +1 -0
- package/dist/filepath-protocol.js +239 -0
- package/dist/filepath-protocol.js.map +1 -0
- package/dist/filepath-runtime.d.ts +100 -0
- package/dist/filepath-runtime.d.ts.map +1 -0
- package/dist/filepath-runtime.js +190 -0
- package/dist/filepath-runtime.js.map +1 -0
- package/dist/http-backend.d.ts +9 -0
- package/dist/http-backend.d.ts.map +1 -1
- package/dist/http-backend.js +50 -8
- package/dist/http-backend.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/prd/index.d.ts +2 -0
- package/dist/prd/index.d.ts.map +1 -1
- package/dist/prd/index.js +4 -0
- package/dist/prd/index.js.map +1 -1
- package/dist/prd/loop.d.ts +160 -0
- package/dist/prd/loop.d.ts.map +1 -0
- package/dist/prd/loop.js +462 -0
- package/dist/prd/loop.js.map +1 -0
- package/dist/prd/runner.d.ts +2 -5
- package/dist/prd/runner.d.ts.map +1 -1
- package/dist/prd/runner.js +154 -122
- package/dist/prd/runner.js.map +1 -1
- package/dist/prd/scope-defaults.d.ts +75 -0
- package/dist/prd/scope-defaults.d.ts.map +1 -0
- package/dist/prd/scope-defaults.js +235 -0
- package/dist/prd/scope-defaults.js.map +1 -0
- package/dist/prd/types.d.ts +80 -0
- package/dist/prd/types.d.ts.map +1 -1
- package/dist/report.d.ts +70 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +183 -0
- package/dist/report.js.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filepath Agent Protocol (FAP) — Gateproof integration
|
|
3
|
+
*
|
|
4
|
+
* Zod schemas for the NDJSON event stream between Filepath containers
|
|
5
|
+
* and the Gateproof observe layer. Each agent emits structured events
|
|
6
|
+
* on stdout; we parse and map them to Log entries for gate assertions.
|
|
7
|
+
*
|
|
8
|
+
* Protocol source: https://github.com/ACoyfellow/filepath
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
// ─── Agent Status ───
|
|
12
|
+
export const AgentStatus = z.enum([
|
|
13
|
+
"idle",
|
|
14
|
+
"thinking",
|
|
15
|
+
"running",
|
|
16
|
+
"done",
|
|
17
|
+
"error",
|
|
18
|
+
]);
|
|
19
|
+
// ─── Output Events (agent stdout → gateproof) ───
|
|
20
|
+
export const TextEvent = z.object({
|
|
21
|
+
type: z.literal("text"),
|
|
22
|
+
content: z.string(),
|
|
23
|
+
});
|
|
24
|
+
export const ToolEvent = z.object({
|
|
25
|
+
type: z.literal("tool"),
|
|
26
|
+
name: z.string(),
|
|
27
|
+
path: z.string().optional(),
|
|
28
|
+
status: z.enum(["start", "done", "error"]),
|
|
29
|
+
output: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
export const CommandEvent = z.object({
|
|
32
|
+
type: z.literal("command"),
|
|
33
|
+
cmd: z.string(),
|
|
34
|
+
status: z.enum(["start", "done", "error"]),
|
|
35
|
+
exit: z.number().optional(),
|
|
36
|
+
stdout: z.string().optional(),
|
|
37
|
+
stderr: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
export const CommitEvent = z.object({
|
|
40
|
+
type: z.literal("commit"),
|
|
41
|
+
hash: z.string(),
|
|
42
|
+
message: z.string(),
|
|
43
|
+
});
|
|
44
|
+
export const SpawnEvent = z.object({
|
|
45
|
+
type: z.literal("spawn"),
|
|
46
|
+
name: z.string(),
|
|
47
|
+
agent: z.string(),
|
|
48
|
+
model: z.string(),
|
|
49
|
+
task: z.string().optional(),
|
|
50
|
+
});
|
|
51
|
+
export const WorkersEvent = z.object({
|
|
52
|
+
type: z.literal("workers"),
|
|
53
|
+
workers: z.array(z.object({
|
|
54
|
+
name: z.string(),
|
|
55
|
+
status: AgentStatus,
|
|
56
|
+
})),
|
|
57
|
+
});
|
|
58
|
+
export const StatusEvent = z.object({
|
|
59
|
+
type: z.literal("status"),
|
|
60
|
+
state: AgentStatus,
|
|
61
|
+
context_pct: z.number().min(0).max(1).optional(),
|
|
62
|
+
});
|
|
63
|
+
export const HandoffEvent = z.object({
|
|
64
|
+
type: z.literal("handoff"),
|
|
65
|
+
summary: z.string(),
|
|
66
|
+
});
|
|
67
|
+
export const DoneEvent = z.object({
|
|
68
|
+
type: z.literal("done"),
|
|
69
|
+
summary: z.string().optional(),
|
|
70
|
+
});
|
|
71
|
+
export const AgentEvent = z.discriminatedUnion("type", [
|
|
72
|
+
TextEvent,
|
|
73
|
+
ToolEvent,
|
|
74
|
+
CommandEvent,
|
|
75
|
+
CommitEvent,
|
|
76
|
+
SpawnEvent,
|
|
77
|
+
WorkersEvent,
|
|
78
|
+
StatusEvent,
|
|
79
|
+
HandoffEvent,
|
|
80
|
+
DoneEvent,
|
|
81
|
+
]);
|
|
82
|
+
// ─── Input Messages (gateproof → agent stdin) ───
|
|
83
|
+
export const UserMessage = z.object({
|
|
84
|
+
type: z.literal("message"),
|
|
85
|
+
from: z.enum(["user", "parent", "system"]),
|
|
86
|
+
content: z.string(),
|
|
87
|
+
});
|
|
88
|
+
export const SignalMessage = z.object({
|
|
89
|
+
type: z.literal("signal"),
|
|
90
|
+
action: z.enum(["stop", "pause", "resume"]),
|
|
91
|
+
});
|
|
92
|
+
export const AgentInput = z.discriminatedUnion("type", [
|
|
93
|
+
UserMessage,
|
|
94
|
+
SignalMessage,
|
|
95
|
+
]);
|
|
96
|
+
// ─── NDJSON Parsing ───
|
|
97
|
+
export function parseAgentEvent(line) {
|
|
98
|
+
try {
|
|
99
|
+
const json = JSON.parse(line);
|
|
100
|
+
const result = AgentEvent.safeParse(json);
|
|
101
|
+
return result.success ? result.data : null;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function serializeInput(input) {
|
|
108
|
+
return JSON.stringify(input);
|
|
109
|
+
}
|
|
110
|
+
// ─── Event → Log Mapping ───
|
|
111
|
+
function statusToLogStatus(status) {
|
|
112
|
+
switch (status) {
|
|
113
|
+
case "start": return "start";
|
|
114
|
+
case "done": return "success";
|
|
115
|
+
case "error": return "error";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function agentStateToLogStatus(state) {
|
|
119
|
+
switch (state) {
|
|
120
|
+
case "idle": return "info";
|
|
121
|
+
case "thinking": return "start";
|
|
122
|
+
case "running": return "start";
|
|
123
|
+
case "done": return "success";
|
|
124
|
+
case "error": return "error";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Maps a Filepath AgentEvent to a Gateproof Log entry.
|
|
129
|
+
*
|
|
130
|
+
* This is the bridge between the two systems: Filepath's NDJSON protocol
|
|
131
|
+
* becomes observable evidence for gate assertions.
|
|
132
|
+
*/
|
|
133
|
+
export function agentEventToLog(event, agentName) {
|
|
134
|
+
const base = {
|
|
135
|
+
timestamp: new Date().toISOString(),
|
|
136
|
+
stage: agentName ?? "agent",
|
|
137
|
+
};
|
|
138
|
+
switch (event.type) {
|
|
139
|
+
case "text":
|
|
140
|
+
return {
|
|
141
|
+
...base,
|
|
142
|
+
action: "text",
|
|
143
|
+
status: "info",
|
|
144
|
+
data: { content: event.content },
|
|
145
|
+
};
|
|
146
|
+
case "tool":
|
|
147
|
+
return {
|
|
148
|
+
...base,
|
|
149
|
+
action: `tool:${event.name}`,
|
|
150
|
+
status: statusToLogStatus(event.status),
|
|
151
|
+
data: {
|
|
152
|
+
name: event.name,
|
|
153
|
+
...(event.path && { path: event.path }),
|
|
154
|
+
...(event.output && { output: event.output }),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
case "command":
|
|
158
|
+
return {
|
|
159
|
+
...base,
|
|
160
|
+
action: `cmd:${event.cmd}`,
|
|
161
|
+
status: statusToLogStatus(event.status),
|
|
162
|
+
data: {
|
|
163
|
+
cmd: event.cmd,
|
|
164
|
+
...(event.exit !== undefined && { exit: event.exit }),
|
|
165
|
+
...(event.stdout && { stdout: event.stdout }),
|
|
166
|
+
...(event.stderr && { stderr: event.stderr }),
|
|
167
|
+
},
|
|
168
|
+
...(event.status === "error" && event.stderr && {
|
|
169
|
+
error: {
|
|
170
|
+
tag: "CommandError",
|
|
171
|
+
message: event.stderr,
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
case "commit":
|
|
176
|
+
return {
|
|
177
|
+
...base,
|
|
178
|
+
action: "commit",
|
|
179
|
+
status: "success",
|
|
180
|
+
data: { hash: event.hash, message: event.message },
|
|
181
|
+
};
|
|
182
|
+
case "spawn":
|
|
183
|
+
return {
|
|
184
|
+
...base,
|
|
185
|
+
action: "spawn",
|
|
186
|
+
status: "start",
|
|
187
|
+
data: {
|
|
188
|
+
name: event.name,
|
|
189
|
+
agent: event.agent,
|
|
190
|
+
model: event.model,
|
|
191
|
+
...(event.task && { task: event.task }),
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
case "workers":
|
|
195
|
+
return {
|
|
196
|
+
...base,
|
|
197
|
+
action: "workers",
|
|
198
|
+
status: "info",
|
|
199
|
+
data: { workers: event.workers },
|
|
200
|
+
};
|
|
201
|
+
case "status":
|
|
202
|
+
return {
|
|
203
|
+
...base,
|
|
204
|
+
action: "status",
|
|
205
|
+
status: agentStateToLogStatus(event.state),
|
|
206
|
+
data: {
|
|
207
|
+
state: event.state,
|
|
208
|
+
...(event.context_pct !== undefined && { context_pct: event.context_pct }),
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
case "handoff":
|
|
212
|
+
return {
|
|
213
|
+
...base,
|
|
214
|
+
action: "handoff",
|
|
215
|
+
status: "info",
|
|
216
|
+
data: { summary: event.summary },
|
|
217
|
+
};
|
|
218
|
+
case "done":
|
|
219
|
+
return {
|
|
220
|
+
...base,
|
|
221
|
+
action: "done",
|
|
222
|
+
status: "success",
|
|
223
|
+
data: {
|
|
224
|
+
...(event.summary && { summary: event.summary }),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Parses an NDJSON line and maps it to a Log entry.
|
|
231
|
+
* Returns null for unparseable lines (graceful degradation).
|
|
232
|
+
*/
|
|
233
|
+
export function ndjsonLineToLog(line, agentName) {
|
|
234
|
+
const event = parseAgentEvent(line);
|
|
235
|
+
if (!event)
|
|
236
|
+
return null;
|
|
237
|
+
return agentEventToLog(event, agentName);
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=filepath-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filepath-protocol.js","sourceRoot":"","sources":["../src/filepath-protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Sandbox Runtime — real FilepathRuntime for Cloudflare Workers
|
|
3
|
+
*
|
|
4
|
+
* Spawns agent processes inside Cloudflare Sandbox containers,
|
|
5
|
+
* wires NDJSON stdout to a broadcast AsyncIterable (solving the
|
|
6
|
+
* dual-reader problem), and maps stdin/stop to the sandbox process.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { getSandbox } from "@cloudflare/sandbox";
|
|
11
|
+
* import { CloudflareSandboxRuntime, setFilepathRuntime } from "gateproof";
|
|
12
|
+
*
|
|
13
|
+
* setFilepathRuntime(new CloudflareSandboxRuntime({
|
|
14
|
+
* getSandbox: (config) => getSandbox(env.Sandbox, `agent-${config.name}-${Date.now()}`),
|
|
15
|
+
* }));
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { FilepathContainer, FilepathRuntime } from "./filepath-backend";
|
|
19
|
+
import type { AgentActConfig } from "./act";
|
|
20
|
+
/**
|
|
21
|
+
* Minimal interface for a Cloudflare Sandbox instance.
|
|
22
|
+
* Matches the shape returned by `getSandbox()` from `@cloudflare/sandbox`.
|
|
23
|
+
*/
|
|
24
|
+
export interface SandboxInstance {
|
|
25
|
+
startProcess(command: string[], options?: {
|
|
26
|
+
env?: Record<string, string>;
|
|
27
|
+
cwd?: string;
|
|
28
|
+
}): Promise<SandboxProcess>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Minimal interface for a sandbox process.
|
|
32
|
+
* Matches the shape returned by `sandbox.startProcess()`.
|
|
33
|
+
*/
|
|
34
|
+
export interface SandboxProcess {
|
|
35
|
+
stdout: ReadableStream<Uint8Array>;
|
|
36
|
+
stdin?: WritableStream<Uint8Array>;
|
|
37
|
+
}
|
|
38
|
+
export interface CloudflareSandboxRuntimeOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Factory that creates a sandbox for each agent spawn.
|
|
41
|
+
* Called once per `spawn()` invocation.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { getSandbox } from "@cloudflare/sandbox";
|
|
46
|
+
* getSandbox: (config) => getSandbox(env.Sandbox, `agent-${config.name}-${Date.now()}`)
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
getSandbox: (config: AgentActConfig) => SandboxInstance | Promise<SandboxInstance>;
|
|
50
|
+
/**
|
|
51
|
+
* Override the command run inside the container.
|
|
52
|
+
* Receives the agent config and returns an argv array.
|
|
53
|
+
*
|
|
54
|
+
* Default: `[config.agent]` (the agent field is used as the binary name).
|
|
55
|
+
*/
|
|
56
|
+
command?: (config: AgentActConfig) => string[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A broadcast AsyncIterable that supports multiple concurrent readers.
|
|
60
|
+
*
|
|
61
|
+
* Each call to `[Symbol.asyncIterator]()` returns an independent iterator
|
|
62
|
+
* that starts from the beginning of the buffer and sees all items,
|
|
63
|
+
* including those pushed after the iterator was created.
|
|
64
|
+
*
|
|
65
|
+
* This solves the dual-reader problem: both the executor (lifecycle drain)
|
|
66
|
+
* and observe layer (log mapping) can read from the same `container.stdout`
|
|
67
|
+
* without interfering with each other.
|
|
68
|
+
*/
|
|
69
|
+
export declare class BroadcastIterable<T> implements AsyncIterable<T> {
|
|
70
|
+
private buffer;
|
|
71
|
+
private waiters;
|
|
72
|
+
private isDone;
|
|
73
|
+
push(item: T): void;
|
|
74
|
+
end(): void;
|
|
75
|
+
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Reads a `ReadableStream<Uint8Array>` and pushes complete NDJSON lines
|
|
79
|
+
* to a `BroadcastIterable<string>`.
|
|
80
|
+
*
|
|
81
|
+
* Handles:
|
|
82
|
+
* - Partial line buffering (chunks may split across line boundaries)
|
|
83
|
+
* - Stream completion (signals `end()` on the broadcast)
|
|
84
|
+
* - Stream errors (signals `end()` so readers don't hang)
|
|
85
|
+
*/
|
|
86
|
+
export declare function pipeReadableStreamToLines(stream: ReadableStream<Uint8Array>, broadcast: BroadcastIterable<string>): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Real FilepathRuntime backed by Cloudflare Sandbox.
|
|
89
|
+
*
|
|
90
|
+
* Spawns agent processes inside isolated Cloudflare Sandbox containers.
|
|
91
|
+
* Stdout is parsed as NDJSON lines and exposed as a broadcast
|
|
92
|
+
* `AsyncIterable<string>` so both the executor and observe layer
|
|
93
|
+
* can read concurrently.
|
|
94
|
+
*/
|
|
95
|
+
export declare class CloudflareSandboxRuntime implements FilepathRuntime {
|
|
96
|
+
private readonly options;
|
|
97
|
+
constructor(options: CloudflareSandboxRuntimeOptions);
|
|
98
|
+
spawn(config: AgentActConfig): Promise<FilepathContainer>;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=filepath-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filepath-runtime.d.ts","sourceRoot":"","sources":["../src/filepath-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,CACV,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GACvD,OAAO,CAAC,cAAc,CAAC,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,+BAA+B;IAC9C;;;;;;;;;OASG;IACH,UAAU,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAEnF;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,MAAM,EAAE,CAAC;CAChD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,iBAAiB,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,MAAM,CAAS;IAEvB,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;IAMnB,GAAG,IAAI,IAAI;IAMX,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;CA2B3C;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAeD;;;;;;;GAOG;AACH,qBAAa,wBAAyB,YAAW,eAAe;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;gBAE9C,OAAO,EAAE,+BAA+B;IAI9C,KAAK,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA0DhE"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Sandbox Runtime — real FilepathRuntime for Cloudflare Workers
|
|
3
|
+
*
|
|
4
|
+
* Spawns agent processes inside Cloudflare Sandbox containers,
|
|
5
|
+
* wires NDJSON stdout to a broadcast AsyncIterable (solving the
|
|
6
|
+
* dual-reader problem), and maps stdin/stop to the sandbox process.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { getSandbox } from "@cloudflare/sandbox";
|
|
11
|
+
* import { CloudflareSandboxRuntime, setFilepathRuntime } from "gateproof";
|
|
12
|
+
*
|
|
13
|
+
* setFilepathRuntime(new CloudflareSandboxRuntime({
|
|
14
|
+
* getSandbox: (config) => getSandbox(env.Sandbox, `agent-${config.name}-${Date.now()}`),
|
|
15
|
+
* }));
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* A broadcast AsyncIterable that supports multiple concurrent readers.
|
|
20
|
+
*
|
|
21
|
+
* Each call to `[Symbol.asyncIterator]()` returns an independent iterator
|
|
22
|
+
* that starts from the beginning of the buffer and sees all items,
|
|
23
|
+
* including those pushed after the iterator was created.
|
|
24
|
+
*
|
|
25
|
+
* This solves the dual-reader problem: both the executor (lifecycle drain)
|
|
26
|
+
* and observe layer (log mapping) can read from the same `container.stdout`
|
|
27
|
+
* without interfering with each other.
|
|
28
|
+
*/
|
|
29
|
+
export class BroadcastIterable {
|
|
30
|
+
buffer = [];
|
|
31
|
+
waiters = new Set();
|
|
32
|
+
isDone = false;
|
|
33
|
+
push(item) {
|
|
34
|
+
this.buffer.push(item);
|
|
35
|
+
for (const waiter of this.waiters)
|
|
36
|
+
waiter();
|
|
37
|
+
this.waiters.clear();
|
|
38
|
+
}
|
|
39
|
+
end() {
|
|
40
|
+
this.isDone = true;
|
|
41
|
+
for (const waiter of this.waiters)
|
|
42
|
+
waiter();
|
|
43
|
+
this.waiters.clear();
|
|
44
|
+
}
|
|
45
|
+
[Symbol.asyncIterator]() {
|
|
46
|
+
let index = 0;
|
|
47
|
+
const self = this;
|
|
48
|
+
return {
|
|
49
|
+
next() {
|
|
50
|
+
if (index < self.buffer.length) {
|
|
51
|
+
return Promise.resolve({ value: self.buffer[index++], done: false });
|
|
52
|
+
}
|
|
53
|
+
if (self.isDone) {
|
|
54
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
55
|
+
}
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
const check = () => {
|
|
58
|
+
if (index < self.buffer.length) {
|
|
59
|
+
resolve({ value: self.buffer[index++], done: false });
|
|
60
|
+
}
|
|
61
|
+
else if (self.isDone) {
|
|
62
|
+
resolve({ value: undefined, done: true });
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
self.waiters.add(check);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
check();
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reads a `ReadableStream<Uint8Array>` and pushes complete NDJSON lines
|
|
76
|
+
* to a `BroadcastIterable<string>`.
|
|
77
|
+
*
|
|
78
|
+
* Handles:
|
|
79
|
+
* - Partial line buffering (chunks may split across line boundaries)
|
|
80
|
+
* - Stream completion (signals `end()` on the broadcast)
|
|
81
|
+
* - Stream errors (signals `end()` so readers don't hang)
|
|
82
|
+
*/
|
|
83
|
+
export async function pipeReadableStreamToLines(stream, broadcast) {
|
|
84
|
+
const reader = stream.getReader();
|
|
85
|
+
const decoder = new TextDecoder();
|
|
86
|
+
let buffer = "";
|
|
87
|
+
try {
|
|
88
|
+
while (true) {
|
|
89
|
+
const { value, done } = await reader.read();
|
|
90
|
+
if (done)
|
|
91
|
+
break;
|
|
92
|
+
buffer += decoder.decode(value, { stream: true });
|
|
93
|
+
const lines = buffer.split("\n");
|
|
94
|
+
// Last element is incomplete (or empty if buffer ended with \n)
|
|
95
|
+
buffer = lines.pop();
|
|
96
|
+
for (const line of lines) {
|
|
97
|
+
if (line.length > 0) {
|
|
98
|
+
broadcast.push(line);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Flush any remaining partial line
|
|
103
|
+
const remaining = buffer + decoder.decode();
|
|
104
|
+
if (remaining.length > 0) {
|
|
105
|
+
broadcast.push(remaining);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
broadcast.end();
|
|
110
|
+
reader.releaseLock();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Builds the environment variables for the sandbox process.
|
|
115
|
+
*/
|
|
116
|
+
function buildEnv(config) {
|
|
117
|
+
return {
|
|
118
|
+
FILEPATH_TASK: config.task,
|
|
119
|
+
FILEPATH_AGENT_TYPE: config.agent,
|
|
120
|
+
FILEPATH_MODEL: config.model,
|
|
121
|
+
FILEPATH_WORKSPACE: "/workspace",
|
|
122
|
+
...config.env,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Real FilepathRuntime backed by Cloudflare Sandbox.
|
|
127
|
+
*
|
|
128
|
+
* Spawns agent processes inside isolated Cloudflare Sandbox containers.
|
|
129
|
+
* Stdout is parsed as NDJSON lines and exposed as a broadcast
|
|
130
|
+
* `AsyncIterable<string>` so both the executor and observe layer
|
|
131
|
+
* can read concurrently.
|
|
132
|
+
*/
|
|
133
|
+
export class CloudflareSandboxRuntime {
|
|
134
|
+
options;
|
|
135
|
+
constructor(options) {
|
|
136
|
+
this.options = options;
|
|
137
|
+
}
|
|
138
|
+
async spawn(config) {
|
|
139
|
+
const sandbox = await this.options.getSandbox(config);
|
|
140
|
+
const command = this.options.command
|
|
141
|
+
? this.options.command(config)
|
|
142
|
+
: [config.agent];
|
|
143
|
+
const env = buildEnv(config);
|
|
144
|
+
const proc = await sandbox.startProcess(command, {
|
|
145
|
+
env,
|
|
146
|
+
cwd: "/workspace",
|
|
147
|
+
});
|
|
148
|
+
const broadcast = new BroadcastIterable();
|
|
149
|
+
// Start piping stdout → broadcast in the background.
|
|
150
|
+
// Errors are swallowed — the broadcast just ends, and readers
|
|
151
|
+
// see the stream close (which the executor treats as container exit).
|
|
152
|
+
pipeReadableStreamToLines(proc.stdout, broadcast).catch(() => {
|
|
153
|
+
broadcast.end();
|
|
154
|
+
});
|
|
155
|
+
const encoder = new TextEncoder();
|
|
156
|
+
return {
|
|
157
|
+
stdout: broadcast,
|
|
158
|
+
async sendInput(line) {
|
|
159
|
+
if (!proc.stdin) {
|
|
160
|
+
throw new Error("Container stdin is not available");
|
|
161
|
+
}
|
|
162
|
+
const writer = proc.stdin.getWriter();
|
|
163
|
+
try {
|
|
164
|
+
await writer.write(encoder.encode(line + "\n"));
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
writer.releaseLock();
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
async stop() {
|
|
171
|
+
// Signal the agent to stop via stdin, then close streams.
|
|
172
|
+
// Cloudflare Sandbox doesn't expose SIGTERM — closing stdin
|
|
173
|
+
// is the convention for requesting graceful shutdown.
|
|
174
|
+
if (proc.stdin) {
|
|
175
|
+
try {
|
|
176
|
+
const writer = proc.stdin.getWriter();
|
|
177
|
+
await writer.write(encoder.encode(JSON.stringify({ type: "signal", action: "stop" }) + "\n"));
|
|
178
|
+
writer.releaseLock();
|
|
179
|
+
await proc.stdin.close();
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// stdin may already be closed
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
broadcast.end();
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=filepath-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filepath-runtime.js","sourceRoot":"","sources":["../src/filepath-runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/http-backend.d.ts
CHANGED
|
@@ -14,6 +14,15 @@ export interface HttpObserveConfig {
|
|
|
14
14
|
* If Content-Length exceeds this, the body is not read and an error log is emitted instead.
|
|
15
15
|
*/
|
|
16
16
|
maxResponseSizeBytes?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Max retries per poll on transient network errors (default: 2).
|
|
19
|
+
* Uses exponential backoff: 500ms, 1s, 2s.
|
|
20
|
+
*/
|
|
21
|
+
maxRetries?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Consecutive failures before the circuit breaker opens and slows polling (default: 5).
|
|
24
|
+
*/
|
|
25
|
+
circuitBreakerThreshold?: number;
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
19
28
|
* Creates an observe resource that polls an HTTP endpoint and captures responses as logs.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-backend.d.ts","sourceRoot":"","sources":["../src/http-backend.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4B,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAO3E,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"http-backend.d.ts","sourceRoot":"","sources":["../src/http-backend.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4B,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAO3E,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAoMpF"}
|
package/dist/http-backend.js
CHANGED
|
@@ -9,6 +9,35 @@ export function createHttpObserveResource(config) {
|
|
|
9
9
|
let queue = null;
|
|
10
10
|
let stopped = false;
|
|
11
11
|
let pollTimer = null;
|
|
12
|
+
const maxRetries = config.maxRetries ?? 2;
|
|
13
|
+
const circuitBreakerThreshold = config.circuitBreakerThreshold ?? 5;
|
|
14
|
+
const baseInterval = config.pollInterval ?? HTTP_DEFAULT_POLL_INTERVAL_MS;
|
|
15
|
+
let consecutiveFailures = 0;
|
|
16
|
+
let circuitOpen = false;
|
|
17
|
+
const fetchOnce = async (timeoutMs) => {
|
|
18
|
+
return fetch(config.url, {
|
|
19
|
+
method: config.method || "GET",
|
|
20
|
+
headers: config.headers,
|
|
21
|
+
body: config.body,
|
|
22
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const fetchWithRetry = async (timeoutMs) => {
|
|
26
|
+
let lastError;
|
|
27
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
28
|
+
try {
|
|
29
|
+
return await fetchOnce(timeoutMs);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
lastError = err;
|
|
33
|
+
if (attempt < maxRetries) {
|
|
34
|
+
const backoffMs = 500 * Math.pow(2, attempt); // 500ms, 1s, 2s
|
|
35
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
throw lastError;
|
|
40
|
+
};
|
|
12
41
|
const poll = async () => {
|
|
13
42
|
if (stopped || !queue)
|
|
14
43
|
return;
|
|
@@ -17,12 +46,14 @@ export function createHttpObserveResource(config) {
|
|
|
17
46
|
const timeoutMs = config.timeoutMs ?? HTTP_DEFAULT_TIMEOUT_MS;
|
|
18
47
|
const maxResponseSizeBytes = config.maxResponseSizeBytes ?? HTTP_MAX_RESPONSE_SIZE_BYTES;
|
|
19
48
|
try {
|
|
20
|
-
const response = await
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
49
|
+
const response = await fetchWithRetry(timeoutMs);
|
|
50
|
+
// Reset circuit breaker on success; restore normal interval if recovering
|
|
51
|
+
if (circuitOpen && pollTimer) {
|
|
52
|
+
clearInterval(pollTimer);
|
|
53
|
+
pollTimer = setInterval(() => poll(), baseInterval);
|
|
54
|
+
circuitOpen = false;
|
|
55
|
+
}
|
|
56
|
+
consecutiveFailures = 0;
|
|
26
57
|
const durationMs = Date.now() - startTime;
|
|
27
58
|
const contentType = response.headers.get("content-type") || "";
|
|
28
59
|
let body = null;
|
|
@@ -68,8 +99,10 @@ export function createHttpObserveResource(config) {
|
|
|
68
99
|
await Effect.runPromise(Queue.offer(queue, log).pipe(Effect.tapError((error) => Effect.logError("Failed to enqueue HTTP log", error)), Effect.catchAll(() => Effect.void)));
|
|
69
100
|
}
|
|
70
101
|
catch (unknownError) {
|
|
102
|
+
consecutiveFailures++;
|
|
71
103
|
const error = unknownError instanceof Error ? unknownError : new Error(String(unknownError));
|
|
72
104
|
const durationMs = Date.now() - startTime;
|
|
105
|
+
const isCircuitOpen = consecutiveFailures >= circuitBreakerThreshold;
|
|
73
106
|
const log = {
|
|
74
107
|
requestId,
|
|
75
108
|
timestamp: new Date().toISOString(),
|
|
@@ -78,18 +111,27 @@ export function createHttpObserveResource(config) {
|
|
|
78
111
|
status: "error",
|
|
79
112
|
durationMs,
|
|
80
113
|
error: {
|
|
81
|
-
tag: error.name || "HttpError",
|
|
82
|
-
message:
|
|
114
|
+
tag: isCircuitOpen ? "HttpCircuitOpen" : (error.name || "HttpError"),
|
|
115
|
+
message: isCircuitOpen
|
|
116
|
+
? `${consecutiveFailures} consecutive failures, backing off: ${error.message}`
|
|
117
|
+
: (error.message || String(error)),
|
|
83
118
|
stack: error.stack,
|
|
84
119
|
},
|
|
85
120
|
};
|
|
86
121
|
await Effect.runPromise(Queue.offer(queue, log).pipe(Effect.tapError((enqueueError) => Effect.logError("Failed to enqueue HTTP error log", enqueueError)), Effect.catchAll(() => Effect.void)));
|
|
122
|
+
// Circuit breaker: reschedule at a slower rate
|
|
123
|
+
if (isCircuitOpen && !circuitOpen && pollTimer) {
|
|
124
|
+
clearInterval(pollTimer);
|
|
125
|
+
pollTimer = setInterval(() => poll(), baseInterval * 5);
|
|
126
|
+
circuitOpen = true;
|
|
127
|
+
}
|
|
87
128
|
}
|
|
88
129
|
};
|
|
89
130
|
return {
|
|
90
131
|
start() {
|
|
91
132
|
return Effect.gen(function* () {
|
|
92
133
|
stopped = false;
|
|
134
|
+
consecutiveFailures = 0;
|
|
93
135
|
queue = yield* Queue.bounded(1000);
|
|
94
136
|
// Initial poll
|
|
95
137
|
yield* Effect.promise(() => poll());
|