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.
Files changed (62) hide show
  1. package/README.md +132 -340
  2. package/dist/act.d.ts +45 -0
  3. package/dist/act.d.ts.map +1 -1
  4. package/dist/act.js +22 -0
  5. package/dist/act.js.map +1 -1
  6. package/dist/action-executors.d.ts +17 -0
  7. package/dist/action-executors.d.ts.map +1 -1
  8. package/dist/action-executors.js +60 -0
  9. package/dist/action-executors.js.map +1 -1
  10. package/dist/assert.d.ts +20 -0
  11. package/dist/assert.d.ts.map +1 -1
  12. package/dist/assert.js +32 -0
  13. package/dist/assert.js.map +1 -1
  14. package/dist/authority.d.ts +34 -0
  15. package/dist/authority.d.ts.map +1 -0
  16. package/dist/authority.js +141 -0
  17. package/dist/authority.js.map +1 -0
  18. package/dist/cli/gateproof.js +81 -5
  19. package/dist/cli/gateproof.js.map +1 -1
  20. package/dist/filepath-backend.d.ts +64 -0
  21. package/dist/filepath-backend.d.ts.map +1 -0
  22. package/dist/filepath-backend.js +126 -0
  23. package/dist/filepath-backend.js.map +1 -0
  24. package/dist/filepath-protocol.d.ts +214 -0
  25. package/dist/filepath-protocol.d.ts.map +1 -0
  26. package/dist/filepath-protocol.js +239 -0
  27. package/dist/filepath-protocol.js.map +1 -0
  28. package/dist/filepath-runtime.d.ts +100 -0
  29. package/dist/filepath-runtime.d.ts.map +1 -0
  30. package/dist/filepath-runtime.js +190 -0
  31. package/dist/filepath-runtime.js.map +1 -0
  32. package/dist/http-backend.d.ts +9 -0
  33. package/dist/http-backend.d.ts.map +1 -1
  34. package/dist/http-backend.js +50 -8
  35. package/dist/http-backend.js.map +1 -1
  36. package/dist/index.d.ts +11 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +9 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/prd/index.d.ts +2 -0
  41. package/dist/prd/index.d.ts.map +1 -1
  42. package/dist/prd/index.js +4 -0
  43. package/dist/prd/index.js.map +1 -1
  44. package/dist/prd/loop.d.ts +160 -0
  45. package/dist/prd/loop.d.ts.map +1 -0
  46. package/dist/prd/loop.js +462 -0
  47. package/dist/prd/loop.js.map +1 -0
  48. package/dist/prd/runner.d.ts +2 -5
  49. package/dist/prd/runner.d.ts.map +1 -1
  50. package/dist/prd/runner.js +154 -122
  51. package/dist/prd/runner.js.map +1 -1
  52. package/dist/prd/scope-defaults.d.ts +75 -0
  53. package/dist/prd/scope-defaults.d.ts.map +1 -0
  54. package/dist/prd/scope-defaults.js +235 -0
  55. package/dist/prd/scope-defaults.js.map +1 -0
  56. package/dist/prd/types.d.ts +80 -0
  57. package/dist/prd/types.d.ts.map +1 -1
  58. package/dist/report.d.ts +70 -0
  59. package/dist/report.d.ts.map +1 -1
  60. package/dist/report.js +183 -0
  61. package/dist/report.js.map +1 -1
  62. 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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"}
@@ -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;CAC/B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAqJpF"}
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"}
@@ -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 fetch(config.url, {
21
- method: config.method || "GET",
22
- headers: config.headers,
23
- body: config.body,
24
- signal: AbortSignal.timeout(timeoutMs),
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: error.message || String(error),
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());