@ai-sdk/harness 0.0.0-6b196531-20260710185421
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 +414 -0
- package/LICENSE +13 -0
- package/README.md +176 -0
- package/agent/index.ts +56 -0
- package/bridge/index.ts +10 -0
- package/dist/agent/index.d.ts +1631 -0
- package/dist/agent/index.js +3491 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/bridge/index.d.ts +129 -0
- package/dist/bridge/index.js +482 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/index.d.ts +1587 -0
- package/dist/index.js +517 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/index.d.ts +329 -0
- package/dist/utils/index.js +1241 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +100 -0
- package/src/agent/harness-agent-session.ts +518 -0
- package/src/agent/harness-agent-settings.ts +187 -0
- package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
- package/src/agent/harness-agent-tool-types.ts +15 -0
- package/src/agent/harness-agent-types.ts +50 -0
- package/src/agent/harness-agent.ts +865 -0
- package/src/agent/internal/bootstrap-recipe.ts +124 -0
- package/src/agent/internal/bridge-port-registry.ts +52 -0
- package/src/agent/internal/harness-stream-text-result.ts +731 -0
- package/src/agent/internal/lifecycle-state-validation.ts +95 -0
- package/src/agent/internal/permission-mode.ts +50 -0
- package/src/agent/internal/resolve-observability.ts +128 -0
- package/src/agent/internal/run-prompt.ts +901 -0
- package/src/agent/internal/sandbox-bootstrap.ts +266 -0
- package/src/agent/internal/strip-work-dir.ts +68 -0
- package/src/agent/internal/to-harness-stream.ts +75 -0
- package/src/agent/internal/tool-filtering.ts +114 -0
- package/src/agent/internal/translate-stream-part.ts +221 -0
- package/src/agent/internal/turn-telemetry.ts +361 -0
- package/src/agent/observability/file-reporter.ts +206 -0
- package/src/agent/observability/index.ts +15 -0
- package/src/agent/observability/trace-tree-reporter.ts +122 -0
- package/src/agent/observability/types.ts +86 -0
- package/src/agent/prepare-harness-sandbox-template.ts +68 -0
- package/src/agent/prepare-sandbox-for-harness.ts +165 -0
- package/src/bridge/index.ts +797 -0
- package/src/errors/harness-capability-unsupported-error.ts +41 -0
- package/src/errors/harness-error.ts +22 -0
- package/src/index.ts +3 -0
- package/src/utils/ai-gateway-auth.ts +15 -0
- package/src/utils/bridge-diagnostics.ts +213 -0
- package/src/utils/bridge-ready.ts +277 -0
- package/src/utils/classify-disk-log.ts +43 -0
- package/src/utils/index.ts +31 -0
- package/src/utils/sandbox-channel.ts +525 -0
- package/src/utils/sandbox-home-dir.ts +22 -0
- package/src/utils/shell-quote.ts +3 -0
- package/src/utils/write-skills.ts +141 -0
- package/src/v1/harness-v1-bootstrap.ts +46 -0
- package/src/v1/harness-v1-bridge-protocol.ts +342 -0
- package/src/v1/harness-v1-builtin-tool.ts +138 -0
- package/src/v1/harness-v1-call-warning.ts +22 -0
- package/src/v1/harness-v1-diagnostic.ts +66 -0
- package/src/v1/harness-v1-lifecycle-state.ts +65 -0
- package/src/v1/harness-v1-metadata.ts +13 -0
- package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
- package/src/v1/harness-v1-observability.ts +20 -0
- package/src/v1/harness-v1-permission-mode.ts +11 -0
- package/src/v1/harness-v1-prompt-control.ts +41 -0
- package/src/v1/harness-v1-prompt.ts +11 -0
- package/src/v1/harness-v1-sandbox-provider.ts +76 -0
- package/src/v1/harness-v1-session.ts +280 -0
- package/src/v1/harness-v1-skill.ts +36 -0
- package/src/v1/harness-v1-stream-part.ts +363 -0
- package/src/v1/harness-v1-tool-filtering.ts +25 -0
- package/src/v1/harness-v1-tool-spec.ts +31 -0
- package/src/v1/harness-v1.ts +94 -0
- package/src/v1/index.ts +99 -0
- package/utils/index.ts +1 -0
|
@@ -0,0 +1,1241 @@
|
|
|
1
|
+
// src/utils/sandbox-channel.ts
|
|
2
|
+
import {
|
|
3
|
+
safeParseJSON,
|
|
4
|
+
safeValidateTypes
|
|
5
|
+
} from "@ai-sdk/provider-utils";
|
|
6
|
+
var sleep = (ms) => new Promise((resolve) => {
|
|
7
|
+
var _a;
|
|
8
|
+
const t = setTimeout(resolve, ms);
|
|
9
|
+
(_a = t.unref) == null ? void 0 : _a.call(t);
|
|
10
|
+
});
|
|
11
|
+
var SandboxChannel = class {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
14
|
+
this.buffered = /* @__PURE__ */ new Map();
|
|
15
|
+
this.onCloseHandlers = /* @__PURE__ */ new Set();
|
|
16
|
+
this.connected = false;
|
|
17
|
+
/** Host has begun teardown; suppresses reconnect so a bridge-side close finalises. */
|
|
18
|
+
this.closing = false;
|
|
19
|
+
/**
|
|
20
|
+
* Host has gracefully suspended (slice boundary). Inbound frames are ignored
|
|
21
|
+
* from this point so the cursor stops advancing exactly at the last delivered
|
|
22
|
+
* event — the bridge keeps the turn running and the not-yet-delivered tail is
|
|
23
|
+
* replayed to the next process on `resume`.
|
|
24
|
+
*/
|
|
25
|
+
this.suspended = false;
|
|
26
|
+
/** Channel is fully torn down; `send` throws and `onClose` has fired. */
|
|
27
|
+
this.terminal = false;
|
|
28
|
+
this._lastSeenEventId = 0;
|
|
29
|
+
this.pendingSends = [];
|
|
30
|
+
this.dispatchChain = Promise.resolve();
|
|
31
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
32
|
+
this.connectThunk = options.connect;
|
|
33
|
+
this.outboundSchema = options.outboundSchema;
|
|
34
|
+
this.onDebug = options.onDebug;
|
|
35
|
+
this.onDiagnostic = options.onDiagnostic;
|
|
36
|
+
this.onBridgeError = options.onBridgeError;
|
|
37
|
+
this.maxElapsedMs = (_b = (_a = options.reconnect) == null ? void 0 : _a.maxElapsedMs) != null ? _b : 3e4;
|
|
38
|
+
this.initialDelayMs = (_d = (_c = options.reconnect) == null ? void 0 : _c.initialDelayMs) != null ? _d : 50;
|
|
39
|
+
this.maxDelayMs = (_f = (_e = options.reconnect) == null ? void 0 : _e.maxDelayMs) != null ? _f : 2e3;
|
|
40
|
+
this._lastSeenEventId = (_g = options.initialLastSeenEventId) != null ? _g : 0;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Highest bridge event `seq` this channel has observed. Persist it (e.g. via
|
|
44
|
+
* the adapter's resume handle) so a future process can seed
|
|
45
|
+
* {@link SandboxChannelOptions.initialLastSeenEventId} and attach.
|
|
46
|
+
*/
|
|
47
|
+
get lastSeenEventId() {
|
|
48
|
+
return this._lastSeenEventId;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Establish the initial connection. A single attempt — startup failures
|
|
52
|
+
* reject so the caller can fail `doStart` cleanly. Reconnect retries apply
|
|
53
|
+
* only to drops after a successful open.
|
|
54
|
+
*
|
|
55
|
+
* Pass `{ resume: true }` to attach to a bridge that is already mid-session:
|
|
56
|
+
* after the socket opens, the channel sends `{ type: 'resume', lastSeenEventId }`
|
|
57
|
+
* so the bridge replays everything past the seeded cursor. This is the
|
|
58
|
+
* cross-process attach handshake — identical to what a transient reconnect
|
|
59
|
+
* does, but triggered by the initial open from a new process.
|
|
60
|
+
*/
|
|
61
|
+
async open(opts) {
|
|
62
|
+
if (this.terminal) {
|
|
63
|
+
throw new Error("SandboxChannel: cannot open a closed channel.");
|
|
64
|
+
}
|
|
65
|
+
const ws = await this.connectThunk();
|
|
66
|
+
this.wire(ws);
|
|
67
|
+
this.ws = ws;
|
|
68
|
+
this.connected = true;
|
|
69
|
+
if (opts == null ? void 0 : opts.resume) {
|
|
70
|
+
this.rawSend(
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
type: "resume",
|
|
73
|
+
lastSeenEventId: this._lastSeenEventId
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
on(type, listener) {
|
|
79
|
+
let set = this.listeners.get(type);
|
|
80
|
+
if (!set) {
|
|
81
|
+
set = /* @__PURE__ */ new Set();
|
|
82
|
+
this.listeners.set(type, set);
|
|
83
|
+
}
|
|
84
|
+
set.add(listener);
|
|
85
|
+
const buffered = this.buffered.get(type);
|
|
86
|
+
if (buffered) {
|
|
87
|
+
this.buffered.delete(type);
|
|
88
|
+
for (const event of buffered) {
|
|
89
|
+
listener(event);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return () => {
|
|
93
|
+
set.delete(listener);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
onClose(handler) {
|
|
97
|
+
this.onCloseHandlers.add(handler);
|
|
98
|
+
}
|
|
99
|
+
send(message) {
|
|
100
|
+
if (this.terminal) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`SandboxChannel: cannot send ${message.type} \u2014 channel is closed.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
this.rawSend(JSON.stringify(message));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Mark that the host is tearing the session down. The next socket close is
|
|
109
|
+
* then treated as terminal rather than triggering a reconnect. Call before
|
|
110
|
+
* sending a `shutdown` / `detach` message whose ack the bridge follows with a
|
|
111
|
+
* socket close.
|
|
112
|
+
*/
|
|
113
|
+
beginClose() {
|
|
114
|
+
this.closing = true;
|
|
115
|
+
}
|
|
116
|
+
close() {
|
|
117
|
+
var _a;
|
|
118
|
+
if (this.terminal) return;
|
|
119
|
+
this.closing = true;
|
|
120
|
+
try {
|
|
121
|
+
(_a = this.ws) == null ? void 0 : _a.close();
|
|
122
|
+
} catch (e) {
|
|
123
|
+
}
|
|
124
|
+
this.enqueue(() => this.finalizeClose(1e3, "closed"));
|
|
125
|
+
}
|
|
126
|
+
interrupt(options) {
|
|
127
|
+
var _a;
|
|
128
|
+
const timeoutMs = (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 5e3;
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
var _a2;
|
|
131
|
+
let settled = false;
|
|
132
|
+
let unsub = () => {
|
|
133
|
+
};
|
|
134
|
+
const timer = setTimeout(() => {
|
|
135
|
+
complete(
|
|
136
|
+
new Error(
|
|
137
|
+
`SandboxChannel: interrupt was not acknowledged within ${timeoutMs}ms.`
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
(_a2 = timer.unref) == null ? void 0 : _a2.call(timer);
|
|
142
|
+
const complete = (error) => {
|
|
143
|
+
if (settled) return;
|
|
144
|
+
settled = true;
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
unsub();
|
|
147
|
+
if (error) {
|
|
148
|
+
reject(error);
|
|
149
|
+
} else {
|
|
150
|
+
resolve();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
unsub = this.on("bridge-interrupted", (event) => {
|
|
154
|
+
const response = event;
|
|
155
|
+
if (response.ok) {
|
|
156
|
+
complete();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
complete(
|
|
160
|
+
new Error(
|
|
161
|
+
`SandboxChannel: interrupt failed: ${formatControlError(
|
|
162
|
+
response.error
|
|
163
|
+
)}`
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
try {
|
|
168
|
+
this.send({ type: "interrupt" });
|
|
169
|
+
} catch (err) {
|
|
170
|
+
complete(err);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Gracefully suspend at a slice boundary: stop processing inbound frames
|
|
176
|
+
* (so the cursor freezes at the last delivered event), drain any frames
|
|
177
|
+
* already queued for dispatch, then close the socket and finalise with the
|
|
178
|
+
* close reason `'suspended'`. Resolves with the final `lastSeenEventId`.
|
|
179
|
+
*
|
|
180
|
+
* The bridge keeps the in-flight turn running (a host socket close never
|
|
181
|
+
* aborts it) and accumulates events past the cursor for the next process to
|
|
182
|
+
* `resume`. Unlike {@link close}, the consumer's active turn is wound down
|
|
183
|
+
* cleanly — adapters distinguish a suspend from an unexpected drop via the
|
|
184
|
+
* `'suspended'` close reason and resolve `done` successfully.
|
|
185
|
+
*/
|
|
186
|
+
suspend() {
|
|
187
|
+
return new Promise((resolve) => {
|
|
188
|
+
if (this.terminal) {
|
|
189
|
+
resolve(this._lastSeenEventId);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.suspended = true;
|
|
193
|
+
this.closing = true;
|
|
194
|
+
this.onClose(() => resolve(this._lastSeenEventId));
|
|
195
|
+
this.enqueue(() => {
|
|
196
|
+
var _a;
|
|
197
|
+
try {
|
|
198
|
+
(_a = this.ws) == null ? void 0 : _a.close();
|
|
199
|
+
} catch (e) {
|
|
200
|
+
}
|
|
201
|
+
this.finalizeClose(1e3, "suspended");
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
isClosed() {
|
|
206
|
+
return this.terminal;
|
|
207
|
+
}
|
|
208
|
+
// ─── internals ──────────────────────────────────────────────────────
|
|
209
|
+
wire(ws) {
|
|
210
|
+
let dropped = false;
|
|
211
|
+
const onDrop = (code, reason) => {
|
|
212
|
+
if (dropped) return;
|
|
213
|
+
dropped = true;
|
|
214
|
+
if (ws !== this.ws) return;
|
|
215
|
+
this.connected = false;
|
|
216
|
+
if (this.closing) {
|
|
217
|
+
this.enqueue(() => this.finalizeClose(code, reason));
|
|
218
|
+
} else {
|
|
219
|
+
this.enqueue(() => this.reconnectLoop());
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
ws.on("message", (raw) => {
|
|
223
|
+
if (this.suspended) return;
|
|
224
|
+
const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
|
|
225
|
+
this.enqueue(() => this.handleIncoming(text));
|
|
226
|
+
});
|
|
227
|
+
ws.on(
|
|
228
|
+
"close",
|
|
229
|
+
(code, reason) => {
|
|
230
|
+
var _a, _b;
|
|
231
|
+
return onDrop(code, (_b = (_a = reason == null ? void 0 : reason.toString) == null ? void 0 : _a.call(reason, "utf8")) != null ? _b : "");
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
ws.on("error", () => onDrop(1006, "socket error"));
|
|
235
|
+
}
|
|
236
|
+
async reconnectLoop() {
|
|
237
|
+
var _a, _b, _c;
|
|
238
|
+
if (this.terminal || this.closing) return;
|
|
239
|
+
const start = Date.now();
|
|
240
|
+
let attempt = 0;
|
|
241
|
+
let delay = this.initialDelayMs;
|
|
242
|
+
while (!this.terminal && !this.closing) {
|
|
243
|
+
attempt++;
|
|
244
|
+
(_a = this.onDebug) == null ? void 0 : _a.call(this, {
|
|
245
|
+
event: "reconnect-attempt",
|
|
246
|
+
attempt,
|
|
247
|
+
lastSeenEventId: this._lastSeenEventId
|
|
248
|
+
});
|
|
249
|
+
try {
|
|
250
|
+
const ws = await this.connectThunk();
|
|
251
|
+
if (this.terminal || this.closing) {
|
|
252
|
+
try {
|
|
253
|
+
ws.close();
|
|
254
|
+
} catch (e) {
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
this.wire(ws);
|
|
259
|
+
this.ws = ws;
|
|
260
|
+
this.connected = true;
|
|
261
|
+
this.rawSend(
|
|
262
|
+
JSON.stringify({
|
|
263
|
+
type: "resume",
|
|
264
|
+
lastSeenEventId: this._lastSeenEventId
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
this.flushPending();
|
|
268
|
+
(_b = this.onDebug) == null ? void 0 : _b.call(this, {
|
|
269
|
+
event: "reconnected",
|
|
270
|
+
attempt,
|
|
271
|
+
lastSeenEventId: this._lastSeenEventId
|
|
272
|
+
});
|
|
273
|
+
return;
|
|
274
|
+
} catch (cause) {
|
|
275
|
+
if (Date.now() - start >= this.maxElapsedMs) {
|
|
276
|
+
this.finalizeClose(1006, "reconnect failed");
|
|
277
|
+
(_c = this.onDebug) == null ? void 0 : _c.call(this, {
|
|
278
|
+
event: "reconnect-failed",
|
|
279
|
+
attempts: attempt,
|
|
280
|
+
lastSeenEventId: this._lastSeenEventId,
|
|
281
|
+
cause
|
|
282
|
+
});
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
await sleep(delay);
|
|
286
|
+
delay = Math.min(delay * 1.5, this.maxDelayMs);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
rawSend(text) {
|
|
291
|
+
if (this.connected && this.ws) {
|
|
292
|
+
this.ws.send(text);
|
|
293
|
+
} else {
|
|
294
|
+
this.pendingSends.push(text);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
flushPending() {
|
|
298
|
+
if (!this.connected || !this.ws) return;
|
|
299
|
+
const queued = this.pendingSends.splice(0);
|
|
300
|
+
for (const text of queued) this.ws.send(text);
|
|
301
|
+
}
|
|
302
|
+
enqueue(work) {
|
|
303
|
+
this.dispatchChain = this.dispatchChain.then(work);
|
|
304
|
+
}
|
|
305
|
+
async handleIncoming(text) {
|
|
306
|
+
const raw = await safeParseJSON({ text });
|
|
307
|
+
let seq;
|
|
308
|
+
if (raw.success && raw.value != null && typeof raw.value === "object" && typeof raw.value.seq === "number") {
|
|
309
|
+
seq = raw.value.seq;
|
|
310
|
+
}
|
|
311
|
+
const validated = await safeValidateTypes({
|
|
312
|
+
value: raw.success ? raw.value : void 0,
|
|
313
|
+
schema: this.outboundSchema
|
|
314
|
+
});
|
|
315
|
+
if (validated.success) {
|
|
316
|
+
this.dispatch(validated.value);
|
|
317
|
+
} else {
|
|
318
|
+
this.dispatch({
|
|
319
|
+
type: "error",
|
|
320
|
+
error: validated.error
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (seq !== void 0 && seq > this._lastSeenEventId) {
|
|
324
|
+
this._lastSeenEventId = seq;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
dispatch(message) {
|
|
328
|
+
var _a, _b;
|
|
329
|
+
if (message.type === "sandbox-log" || message.type === "debug-event") {
|
|
330
|
+
(_a = this.onDiagnostic) == null ? void 0 : _a.call(
|
|
331
|
+
this,
|
|
332
|
+
message
|
|
333
|
+
);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (message.type === "error") {
|
|
337
|
+
(_b = this.onBridgeError) == null ? void 0 : _b.call(this, message);
|
|
338
|
+
}
|
|
339
|
+
const type = message.type;
|
|
340
|
+
const set = this.listeners.get(type);
|
|
341
|
+
if (!set || set.size === 0) {
|
|
342
|
+
let bucket = this.buffered.get(type);
|
|
343
|
+
if (!bucket) {
|
|
344
|
+
bucket = [];
|
|
345
|
+
this.buffered.set(type, bucket);
|
|
346
|
+
}
|
|
347
|
+
bucket.push(message);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
for (const listener of set) {
|
|
351
|
+
listener(message);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
finalizeClose(code, reason) {
|
|
355
|
+
if (this.terminal) return;
|
|
356
|
+
this.terminal = true;
|
|
357
|
+
this.connected = false;
|
|
358
|
+
for (const h of this.onCloseHandlers) h(code, reason);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
function formatControlError(error) {
|
|
362
|
+
if (error instanceof Error) return error.message;
|
|
363
|
+
if (error && typeof error === "object") {
|
|
364
|
+
const message = error.message;
|
|
365
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
366
|
+
}
|
|
367
|
+
if (typeof error === "string" && error.length > 0) return error;
|
|
368
|
+
return "unknown error";
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/utils/classify-disk-log.ts
|
|
372
|
+
import { safeParseJSON as safeParseJSON2 } from "@ai-sdk/provider-utils";
|
|
373
|
+
async function classifyDiskLog(eventLog) {
|
|
374
|
+
if (!eventLog) return "rerun";
|
|
375
|
+
const lines = eventLog.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
376
|
+
const lastLine = lines.at(-1);
|
|
377
|
+
if (lastLine == null) return "rerun";
|
|
378
|
+
const parsed = await safeParseJSON2({ text: lastLine });
|
|
379
|
+
if (parsed.success && parsed.value != null && typeof parsed.value === "object" && parsed.value.type === "finish") {
|
|
380
|
+
return "replay";
|
|
381
|
+
}
|
|
382
|
+
return "rerun";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/utils/ai-gateway-auth.ts
|
|
386
|
+
var DEFAULT_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
|
|
387
|
+
function getAiGatewayAuthFromEnv({
|
|
388
|
+
env
|
|
389
|
+
}) {
|
|
390
|
+
var _a;
|
|
391
|
+
return {
|
|
392
|
+
apiKey: env.AI_GATEWAY_API_KEY || env.VERCEL_OIDC_TOKEN || void 0,
|
|
393
|
+
baseUrl: (_a = env.AI_GATEWAY_BASE_URL) != null ? _a : DEFAULT_AI_GATEWAY_BASE_URL
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/utils/sandbox-home-dir.ts
|
|
398
|
+
import path from "path";
|
|
399
|
+
async function resolveSandboxHomeDir({
|
|
400
|
+
sandbox,
|
|
401
|
+
abortSignal
|
|
402
|
+
}) {
|
|
403
|
+
const result = await sandbox.run({
|
|
404
|
+
command: 'printf "%s" "$HOME"',
|
|
405
|
+
abortSignal
|
|
406
|
+
});
|
|
407
|
+
const homeDir = result.stdout.trim();
|
|
408
|
+
if (result.exitCode !== 0 || !homeDir || !path.posix.isAbsolute(homeDir)) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Unable to resolve sandbox HOME directory: ${result.stderr || result.stdout}`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
return homeDir;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/utils/shell-quote.ts
|
|
417
|
+
function shellQuote(value) {
|
|
418
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/utils/write-skills.ts
|
|
422
|
+
import path2 from "path";
|
|
423
|
+
async function writeSkills({
|
|
424
|
+
sandbox,
|
|
425
|
+
rootDir,
|
|
426
|
+
skills,
|
|
427
|
+
abortSignal,
|
|
428
|
+
skillNamePattern = /^[A-Za-z0-9._-]+$/,
|
|
429
|
+
invalidSkillNameMessage = ({ name }) => `Invalid skill name: ${name}`,
|
|
430
|
+
filePathMode = "relative",
|
|
431
|
+
invalidSkillFilePathMessage = ({ skillName, filePath }) => `Invalid skill file path for ${skillName}: ${filePath}`,
|
|
432
|
+
trailingNewline = false
|
|
433
|
+
}) {
|
|
434
|
+
var _a, _b;
|
|
435
|
+
for (const skill of skills) {
|
|
436
|
+
validateSkillName({
|
|
437
|
+
name: skill.name,
|
|
438
|
+
pattern: skillNamePattern,
|
|
439
|
+
message: invalidSkillNameMessage
|
|
440
|
+
});
|
|
441
|
+
for (const file of (_a = skill.files) != null ? _a : []) {
|
|
442
|
+
normalizeSkillFilePath({
|
|
443
|
+
skillName: skill.name,
|
|
444
|
+
filePath: file.path,
|
|
445
|
+
mode: filePathMode,
|
|
446
|
+
message: invalidSkillFilePathMessage
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
await sandbox.run({
|
|
451
|
+
command: `mkdir -p ${shellQuote(rootDir)}`,
|
|
452
|
+
abortSignal
|
|
453
|
+
});
|
|
454
|
+
for (const skill of skills) {
|
|
455
|
+
const name = validateSkillName({
|
|
456
|
+
name: skill.name,
|
|
457
|
+
pattern: skillNamePattern,
|
|
458
|
+
message: invalidSkillNameMessage
|
|
459
|
+
});
|
|
460
|
+
const skillDir = path2.posix.join(rootDir, name);
|
|
461
|
+
await sandbox.writeTextFile({
|
|
462
|
+
path: path2.posix.join(skillDir, "SKILL.md"),
|
|
463
|
+
content: renderSkillFile({ skill, trailingNewline }),
|
|
464
|
+
abortSignal
|
|
465
|
+
});
|
|
466
|
+
for (const file of (_b = skill.files) != null ? _b : []) {
|
|
467
|
+
const filePath = normalizeSkillFilePath({
|
|
468
|
+
skillName: skill.name,
|
|
469
|
+
filePath: file.path,
|
|
470
|
+
mode: filePathMode,
|
|
471
|
+
message: invalidSkillFilePathMessage
|
|
472
|
+
});
|
|
473
|
+
await sandbox.writeTextFile({
|
|
474
|
+
path: path2.posix.join(skillDir, filePath),
|
|
475
|
+
content: file.content,
|
|
476
|
+
abortSignal
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function validateSkillName({
|
|
482
|
+
name,
|
|
483
|
+
pattern,
|
|
484
|
+
message
|
|
485
|
+
}) {
|
|
486
|
+
var _a;
|
|
487
|
+
if (!pattern.test(name) || name === "." || name === "..") {
|
|
488
|
+
throw new Error((_a = message == null ? void 0 : message({ name })) != null ? _a : `Invalid skill name: ${name}`);
|
|
489
|
+
}
|
|
490
|
+
return name;
|
|
491
|
+
}
|
|
492
|
+
function normalizeSkillFilePath({
|
|
493
|
+
skillName,
|
|
494
|
+
filePath,
|
|
495
|
+
mode,
|
|
496
|
+
message
|
|
497
|
+
}) {
|
|
498
|
+
var _a;
|
|
499
|
+
const normalized = mode === "strip-leading-slashes" ? filePath.replace(/^\/+/, "") : path2.posix.normalize(filePath);
|
|
500
|
+
const invalid = normalized === "" || mode === "relative" && normalized === "." || normalized.startsWith("../") || normalized.includes("/../") || normalized.endsWith("/..") || mode === "relative" && path2.posix.isAbsolute(normalized);
|
|
501
|
+
if (invalid) {
|
|
502
|
+
throw new Error(
|
|
503
|
+
(_a = message == null ? void 0 : message({ skillName: skillName != null ? skillName : "", filePath })) != null ? _a : `Invalid skill file path for ${skillName}: ${filePath}`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return normalized;
|
|
507
|
+
}
|
|
508
|
+
function renderSkillFile({
|
|
509
|
+
skill,
|
|
510
|
+
trailingNewline
|
|
511
|
+
}) {
|
|
512
|
+
const content = `---
|
|
513
|
+
name: ${skill.name}
|
|
514
|
+
description: ${skill.description}
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
${skill.content}`;
|
|
518
|
+
return trailingNewline ? `${content}
|
|
519
|
+
` : content;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/utils/bridge-ready.ts
|
|
523
|
+
import {
|
|
524
|
+
safeParseJSON as safeParseJSON3
|
|
525
|
+
} from "@ai-sdk/provider-utils";
|
|
526
|
+
import { z as z4 } from "zod/v4";
|
|
527
|
+
|
|
528
|
+
// src/v1/harness-v1-bridge-protocol.ts
|
|
529
|
+
import { z as z3 } from "zod/v4";
|
|
530
|
+
|
|
531
|
+
// src/v1/harness-v1-diagnostic.ts
|
|
532
|
+
import { z } from "zod/v4";
|
|
533
|
+
var harnessV1DebugLevelSchema = z.enum([
|
|
534
|
+
"error",
|
|
535
|
+
"warn",
|
|
536
|
+
"info",
|
|
537
|
+
"debug",
|
|
538
|
+
"trace"
|
|
539
|
+
]);
|
|
540
|
+
var harnessV1DebugConfigSchema = z.object({
|
|
541
|
+
enabled: z.boolean().optional(),
|
|
542
|
+
level: harnessV1DebugLevelSchema.optional(),
|
|
543
|
+
subsystems: z.array(z.string()).optional()
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// src/v1/harness-v1-stream-part.ts
|
|
547
|
+
import { z as z2 } from "zod/v4";
|
|
548
|
+
var harnessV1JsonValueSchema = z2.lazy(
|
|
549
|
+
() => z2.union([
|
|
550
|
+
z2.string(),
|
|
551
|
+
z2.number(),
|
|
552
|
+
z2.boolean(),
|
|
553
|
+
z2.null(),
|
|
554
|
+
z2.array(harnessV1JsonValueSchema),
|
|
555
|
+
z2.record(z2.string(), harnessV1JsonValueSchema)
|
|
556
|
+
])
|
|
557
|
+
);
|
|
558
|
+
var harnessV1ToolResultValueSchema = harnessV1JsonValueSchema;
|
|
559
|
+
var harnessV1JsonObjectSchema = z2.record(
|
|
560
|
+
z2.string(),
|
|
561
|
+
harnessV1JsonValueSchema
|
|
562
|
+
);
|
|
563
|
+
var harnessV1MetadataSchema = z2.record(
|
|
564
|
+
z2.string(),
|
|
565
|
+
z2.record(z2.string(), harnessV1JsonValueSchema)
|
|
566
|
+
);
|
|
567
|
+
var harnessV1ProviderMetadataSchema = z2.record(
|
|
568
|
+
z2.string(),
|
|
569
|
+
z2.record(z2.string(), harnessV1JsonValueSchema)
|
|
570
|
+
);
|
|
571
|
+
var harnessV1CallWarningSchema = z2.union([
|
|
572
|
+
z2.object({
|
|
573
|
+
type: z2.literal("unsupported-setting"),
|
|
574
|
+
setting: z2.string(),
|
|
575
|
+
details: z2.string().optional()
|
|
576
|
+
}),
|
|
577
|
+
z2.object({
|
|
578
|
+
type: z2.literal("unsupported-tool"),
|
|
579
|
+
tool: z2.string(),
|
|
580
|
+
details: z2.string().optional()
|
|
581
|
+
}),
|
|
582
|
+
z2.object({ type: z2.literal("other"), message: z2.string() })
|
|
583
|
+
]);
|
|
584
|
+
var harnessV1UsageSchema = z2.object({
|
|
585
|
+
inputTokens: z2.object({
|
|
586
|
+
total: z2.number().optional(),
|
|
587
|
+
noCache: z2.number().optional(),
|
|
588
|
+
cacheRead: z2.number().optional(),
|
|
589
|
+
cacheWrite: z2.number().optional()
|
|
590
|
+
}),
|
|
591
|
+
outputTokens: z2.object({
|
|
592
|
+
total: z2.number().optional(),
|
|
593
|
+
text: z2.number().optional(),
|
|
594
|
+
reasoning: z2.number().optional()
|
|
595
|
+
}),
|
|
596
|
+
raw: harnessV1JsonObjectSchema.optional()
|
|
597
|
+
});
|
|
598
|
+
var harnessV1FinishReasonSchema = z2.object({
|
|
599
|
+
unified: z2.enum([
|
|
600
|
+
"stop",
|
|
601
|
+
"length",
|
|
602
|
+
"content-filter",
|
|
603
|
+
"tool-calls",
|
|
604
|
+
"error",
|
|
605
|
+
"other"
|
|
606
|
+
]),
|
|
607
|
+
raw: z2.string().optional()
|
|
608
|
+
});
|
|
609
|
+
var harnessV1StreamStartPartSchema = z2.object({
|
|
610
|
+
type: z2.literal("stream-start"),
|
|
611
|
+
warnings: z2.array(harnessV1CallWarningSchema).readonly().optional(),
|
|
612
|
+
modelId: z2.string().optional()
|
|
613
|
+
});
|
|
614
|
+
var harnessV1TextStartPartSchema = z2.object({
|
|
615
|
+
type: z2.literal("text-start"),
|
|
616
|
+
id: z2.string(),
|
|
617
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
618
|
+
});
|
|
619
|
+
var harnessV1TextDeltaPartSchema = z2.object({
|
|
620
|
+
type: z2.literal("text-delta"),
|
|
621
|
+
id: z2.string(),
|
|
622
|
+
delta: z2.string(),
|
|
623
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
624
|
+
});
|
|
625
|
+
var harnessV1TextEndPartSchema = z2.object({
|
|
626
|
+
type: z2.literal("text-end"),
|
|
627
|
+
id: z2.string(),
|
|
628
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
629
|
+
});
|
|
630
|
+
var harnessV1ReasoningStartPartSchema = z2.object({
|
|
631
|
+
type: z2.literal("reasoning-start"),
|
|
632
|
+
id: z2.string(),
|
|
633
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
634
|
+
});
|
|
635
|
+
var harnessV1ReasoningDeltaPartSchema = z2.object({
|
|
636
|
+
type: z2.literal("reasoning-delta"),
|
|
637
|
+
id: z2.string(),
|
|
638
|
+
delta: z2.string(),
|
|
639
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
640
|
+
});
|
|
641
|
+
var harnessV1ReasoningEndPartSchema = z2.object({
|
|
642
|
+
type: z2.literal("reasoning-end"),
|
|
643
|
+
id: z2.string(),
|
|
644
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
645
|
+
});
|
|
646
|
+
var harnessV1ToolCallPartSchema = z2.object({
|
|
647
|
+
type: z2.literal("tool-call"),
|
|
648
|
+
toolCallId: z2.string(),
|
|
649
|
+
toolName: z2.string(),
|
|
650
|
+
input: z2.string(),
|
|
651
|
+
providerExecuted: z2.boolean().optional(),
|
|
652
|
+
dynamic: z2.boolean().optional(),
|
|
653
|
+
providerMetadata: harnessV1ProviderMetadataSchema.optional(),
|
|
654
|
+
nativeName: z2.string().optional()
|
|
655
|
+
});
|
|
656
|
+
var harnessV1ToolApprovalRequestPartSchema = z2.object({
|
|
657
|
+
type: z2.literal("tool-approval-request"),
|
|
658
|
+
approvalId: z2.string(),
|
|
659
|
+
toolCallId: z2.string(),
|
|
660
|
+
providerMetadata: harnessV1ProviderMetadataSchema.optional()
|
|
661
|
+
});
|
|
662
|
+
var harnessV1ToolResultPartSchema = z2.object({
|
|
663
|
+
type: z2.literal("tool-result"),
|
|
664
|
+
toolCallId: z2.string(),
|
|
665
|
+
toolName: z2.string(),
|
|
666
|
+
result: harnessV1ToolResultValueSchema,
|
|
667
|
+
isError: z2.boolean().optional(),
|
|
668
|
+
preliminary: z2.boolean().optional(),
|
|
669
|
+
dynamic: z2.boolean().optional(),
|
|
670
|
+
providerMetadata: harnessV1ProviderMetadataSchema.optional()
|
|
671
|
+
});
|
|
672
|
+
var harnessV1FinishStepPartSchema = z2.object({
|
|
673
|
+
type: z2.literal("finish-step"),
|
|
674
|
+
finishReason: harnessV1FinishReasonSchema,
|
|
675
|
+
usage: harnessV1UsageSchema,
|
|
676
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
677
|
+
});
|
|
678
|
+
var harnessV1FinishPartSchema = z2.object({
|
|
679
|
+
type: z2.literal("finish"),
|
|
680
|
+
finishReason: harnessV1FinishReasonSchema,
|
|
681
|
+
totalUsage: harnessV1UsageSchema,
|
|
682
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
683
|
+
});
|
|
684
|
+
var harnessV1FileChangePartSchema = z2.object({
|
|
685
|
+
type: z2.literal("file-change"),
|
|
686
|
+
event: z2.enum(["create", "modify", "delete"]),
|
|
687
|
+
path: z2.string(),
|
|
688
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
689
|
+
});
|
|
690
|
+
var harnessV1CompactionPartSchema = z2.object({
|
|
691
|
+
type: z2.literal("compaction"),
|
|
692
|
+
trigger: z2.enum(["manual", "auto"]),
|
|
693
|
+
summary: z2.string(),
|
|
694
|
+
tokensBefore: z2.number().optional(),
|
|
695
|
+
tokensAfter: z2.number().optional(),
|
|
696
|
+
harnessMetadata: harnessV1MetadataSchema.optional()
|
|
697
|
+
});
|
|
698
|
+
var harnessV1ErrorPartSchema = z2.object({
|
|
699
|
+
type: z2.literal("error"),
|
|
700
|
+
error: z2.unknown()
|
|
701
|
+
});
|
|
702
|
+
var harnessV1RawPartSchema = z2.object({
|
|
703
|
+
type: z2.literal("raw"),
|
|
704
|
+
rawValue: z2.unknown()
|
|
705
|
+
});
|
|
706
|
+
var harnessV1StreamPartSchema = z2.discriminatedUnion("type", [
|
|
707
|
+
harnessV1StreamStartPartSchema,
|
|
708
|
+
harnessV1TextStartPartSchema,
|
|
709
|
+
harnessV1TextDeltaPartSchema,
|
|
710
|
+
harnessV1TextEndPartSchema,
|
|
711
|
+
harnessV1ReasoningStartPartSchema,
|
|
712
|
+
harnessV1ReasoningDeltaPartSchema,
|
|
713
|
+
harnessV1ReasoningEndPartSchema,
|
|
714
|
+
harnessV1ToolCallPartSchema,
|
|
715
|
+
harnessV1ToolApprovalRequestPartSchema,
|
|
716
|
+
harnessV1ToolResultPartSchema,
|
|
717
|
+
harnessV1FinishStepPartSchema,
|
|
718
|
+
harnessV1FinishPartSchema,
|
|
719
|
+
harnessV1FileChangePartSchema,
|
|
720
|
+
harnessV1CompactionPartSchema,
|
|
721
|
+
harnessV1ErrorPartSchema,
|
|
722
|
+
harnessV1RawPartSchema
|
|
723
|
+
]);
|
|
724
|
+
|
|
725
|
+
// src/v1/harness-v1-bridge-protocol.ts
|
|
726
|
+
var harnessV1BridgeToolWireSchema = z3.object({
|
|
727
|
+
name: z3.string(),
|
|
728
|
+
description: z3.string().optional(),
|
|
729
|
+
inputSchema: z3.unknown().optional()
|
|
730
|
+
});
|
|
731
|
+
var harnessV1BridgePermissionModeSchema = z3.enum([
|
|
732
|
+
"allow-reads",
|
|
733
|
+
"allow-edits",
|
|
734
|
+
"allow-all"
|
|
735
|
+
]);
|
|
736
|
+
var harnessV1BridgeBuiltinToolFilteringSchema = z3.discriminatedUnion(
|
|
737
|
+
"mode",
|
|
738
|
+
[
|
|
739
|
+
z3.object({
|
|
740
|
+
mode: z3.literal("allow"),
|
|
741
|
+
toolNames: z3.array(z3.string())
|
|
742
|
+
}),
|
|
743
|
+
z3.object({
|
|
744
|
+
mode: z3.literal("deny"),
|
|
745
|
+
toolNames: z3.array(z3.string())
|
|
746
|
+
})
|
|
747
|
+
]
|
|
748
|
+
);
|
|
749
|
+
var harnessV1BridgeStartBaseSchema = z3.object({
|
|
750
|
+
type: z3.literal("start"),
|
|
751
|
+
prompt: z3.string(),
|
|
752
|
+
tools: z3.array(harnessV1BridgeToolWireSchema).optional(),
|
|
753
|
+
model: z3.string().optional(),
|
|
754
|
+
debug: harnessV1DebugConfigSchema.optional(),
|
|
755
|
+
permissionMode: harnessV1BridgePermissionModeSchema.optional(),
|
|
756
|
+
builtinToolFiltering: harnessV1BridgeBuiltinToolFilteringSchema.optional()
|
|
757
|
+
});
|
|
758
|
+
var harnessV1BridgeHelloSchema = z3.object({
|
|
759
|
+
type: z3.literal("bridge-hello"),
|
|
760
|
+
state: z3.string().optional(),
|
|
761
|
+
lastSeq: z3.number().optional()
|
|
762
|
+
});
|
|
763
|
+
var harnessV1BridgeDetachSchema = z3.object({
|
|
764
|
+
type: z3.literal("bridge-detach"),
|
|
765
|
+
data: z3.unknown()
|
|
766
|
+
});
|
|
767
|
+
var harnessV1BridgeThreadSchema = z3.object({
|
|
768
|
+
type: z3.literal("bridge-thread"),
|
|
769
|
+
threadId: z3.string()
|
|
770
|
+
});
|
|
771
|
+
var harnessV1BridgeInterruptedSchema = z3.object({
|
|
772
|
+
type: z3.literal("bridge-interrupted"),
|
|
773
|
+
ok: z3.boolean(),
|
|
774
|
+
error: z3.unknown().optional()
|
|
775
|
+
});
|
|
776
|
+
var harnessV1BridgeSandboxLogSchema = z3.object({
|
|
777
|
+
type: z3.literal("sandbox-log"),
|
|
778
|
+
source: z3.string(),
|
|
779
|
+
stream: z3.enum(["stdout", "stderr"]),
|
|
780
|
+
line: z3.string()
|
|
781
|
+
});
|
|
782
|
+
var harnessV1BridgeDebugEventSchema = z3.object({
|
|
783
|
+
type: z3.literal("debug-event"),
|
|
784
|
+
level: harnessV1DebugLevelSchema,
|
|
785
|
+
subsystem: z3.string(),
|
|
786
|
+
message: z3.string(),
|
|
787
|
+
attrs: z3.record(z3.string(), z3.unknown()).optional(),
|
|
788
|
+
error: z3.object({
|
|
789
|
+
name: z3.string().optional(),
|
|
790
|
+
message: z3.string(),
|
|
791
|
+
stack: z3.string().optional()
|
|
792
|
+
}).optional()
|
|
793
|
+
});
|
|
794
|
+
var harnessV1BridgeOutboundMessageSchema = z3.discriminatedUnion(
|
|
795
|
+
"type",
|
|
796
|
+
[
|
|
797
|
+
harnessV1StreamStartPartSchema,
|
|
798
|
+
harnessV1TextStartPartSchema,
|
|
799
|
+
harnessV1TextDeltaPartSchema,
|
|
800
|
+
harnessV1TextEndPartSchema,
|
|
801
|
+
harnessV1ReasoningStartPartSchema,
|
|
802
|
+
harnessV1ReasoningDeltaPartSchema,
|
|
803
|
+
harnessV1ReasoningEndPartSchema,
|
|
804
|
+
harnessV1ToolCallPartSchema,
|
|
805
|
+
harnessV1ToolApprovalRequestPartSchema,
|
|
806
|
+
harnessV1ToolResultPartSchema,
|
|
807
|
+
harnessV1FinishStepPartSchema,
|
|
808
|
+
harnessV1FinishPartSchema,
|
|
809
|
+
harnessV1FileChangePartSchema,
|
|
810
|
+
harnessV1CompactionPartSchema,
|
|
811
|
+
harnessV1ErrorPartSchema,
|
|
812
|
+
harnessV1RawPartSchema,
|
|
813
|
+
harnessV1BridgeHelloSchema,
|
|
814
|
+
harnessV1BridgeDetachSchema,
|
|
815
|
+
harnessV1BridgeThreadSchema,
|
|
816
|
+
harnessV1BridgeInterruptedSchema,
|
|
817
|
+
harnessV1BridgeSandboxLogSchema,
|
|
818
|
+
harnessV1BridgeDebugEventSchema
|
|
819
|
+
]
|
|
820
|
+
);
|
|
821
|
+
var harnessV1BridgeToolResultInboundSchema = z3.object({
|
|
822
|
+
type: z3.literal("tool-result"),
|
|
823
|
+
toolCallId: z3.string(),
|
|
824
|
+
output: z3.unknown(),
|
|
825
|
+
isError: z3.boolean().optional()
|
|
826
|
+
});
|
|
827
|
+
var harnessV1BridgeToolApprovalResponseInboundSchema = z3.object({
|
|
828
|
+
type: z3.literal("tool-approval-response"),
|
|
829
|
+
approvalId: z3.string(),
|
|
830
|
+
approved: z3.boolean(),
|
|
831
|
+
reason: z3.string().optional()
|
|
832
|
+
});
|
|
833
|
+
var harnessV1BridgeUserMessageInboundSchema = z3.object({
|
|
834
|
+
type: z3.literal("user-message"),
|
|
835
|
+
text: z3.string()
|
|
836
|
+
});
|
|
837
|
+
var harnessV1BridgeAbortInboundSchema = z3.object({
|
|
838
|
+
type: z3.literal("abort")
|
|
839
|
+
});
|
|
840
|
+
var harnessV1BridgeInterruptInboundSchema = z3.object({
|
|
841
|
+
type: z3.literal("interrupt")
|
|
842
|
+
});
|
|
843
|
+
var harnessV1BridgeShutdownInboundSchema = z3.object({
|
|
844
|
+
type: z3.literal("shutdown")
|
|
845
|
+
});
|
|
846
|
+
var harnessV1BridgeResumeInboundSchema = z3.object({
|
|
847
|
+
type: z3.literal("resume"),
|
|
848
|
+
lastSeenEventId: z3.number()
|
|
849
|
+
});
|
|
850
|
+
var harnessV1BridgeDetachInboundSchema = z3.object({
|
|
851
|
+
type: z3.literal("detach")
|
|
852
|
+
});
|
|
853
|
+
var harnessV1BridgeReadySchema = z3.object({
|
|
854
|
+
type: z3.literal("bridge-ready"),
|
|
855
|
+
port: z3.number()
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
// src/utils/bridge-ready.ts
|
|
859
|
+
var bridgeMetaSchema = z4.object({
|
|
860
|
+
type: z4.string().optional(),
|
|
861
|
+
port: z4.number().optional(),
|
|
862
|
+
state: z4.string().optional(),
|
|
863
|
+
pid: z4.number().optional()
|
|
864
|
+
});
|
|
865
|
+
async function markBridgeStarting({
|
|
866
|
+
sandbox,
|
|
867
|
+
bridgeStateDir,
|
|
868
|
+
bridgeType,
|
|
869
|
+
abortSignal
|
|
870
|
+
}) {
|
|
871
|
+
try {
|
|
872
|
+
await sandbox.writeTextFile({
|
|
873
|
+
path: bridgeMetaPath(bridgeStateDir),
|
|
874
|
+
content: JSON.stringify({ type: bridgeType, state: "starting" }),
|
|
875
|
+
abortSignal
|
|
876
|
+
});
|
|
877
|
+
} catch (e) {
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
async function waitForBridgeReady({
|
|
881
|
+
proc,
|
|
882
|
+
sandbox,
|
|
883
|
+
bridgeStateDir,
|
|
884
|
+
bridgeType,
|
|
885
|
+
timeoutMs,
|
|
886
|
+
abortSignal,
|
|
887
|
+
pollIntervalMs = 100,
|
|
888
|
+
createTimeoutError,
|
|
889
|
+
createExitError
|
|
890
|
+
}) {
|
|
891
|
+
var _a;
|
|
892
|
+
const reader = proc.stdout.pipeThrough(new TextDecoderStream()).getReader();
|
|
893
|
+
const decoder = lineDecoder();
|
|
894
|
+
const stdoutTail = [];
|
|
895
|
+
const deadline = Date.now() + timeoutMs;
|
|
896
|
+
let pendingStdoutRead;
|
|
897
|
+
let pendingMetaRead;
|
|
898
|
+
let nextMetaReadAt = 0;
|
|
899
|
+
let cancelReader = false;
|
|
900
|
+
try {
|
|
901
|
+
while (true) {
|
|
902
|
+
if (abortSignal == null ? void 0 : abortSignal.aborted) {
|
|
903
|
+
cancelReader = true;
|
|
904
|
+
await proc.kill();
|
|
905
|
+
throw (_a = abortSignal.reason) != null ? _a : new DOMException("Aborted", "AbortError");
|
|
906
|
+
}
|
|
907
|
+
const remaining = deadline - Date.now();
|
|
908
|
+
if (remaining <= 0) {
|
|
909
|
+
cancelReader = true;
|
|
910
|
+
await proc.kill();
|
|
911
|
+
throw await makeBridgeReadyError({
|
|
912
|
+
createError: createTimeoutError,
|
|
913
|
+
fallbackMessage: "bridge did not become ready in time.",
|
|
914
|
+
proc,
|
|
915
|
+
stdoutTail
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
pendingStdoutRead != null ? pendingStdoutRead : pendingStdoutRead = reader.read();
|
|
919
|
+
const metaReadDelayMs = Math.max(0, nextMetaReadAt - Date.now());
|
|
920
|
+
if (pendingMetaRead === void 0 && metaReadDelayMs === 0) {
|
|
921
|
+
pendingMetaRead = readBridgeMetaReady({
|
|
922
|
+
sandbox,
|
|
923
|
+
bridgeStateDir,
|
|
924
|
+
bridgeType,
|
|
925
|
+
abortSignal
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
const result = await Promise.race([
|
|
929
|
+
pendingStdoutRead.then((read) => ({ source: "stdout", read })),
|
|
930
|
+
...pendingMetaRead === void 0 ? [] : [
|
|
931
|
+
pendingMetaRead.then((port) => ({
|
|
932
|
+
source: "metadata",
|
|
933
|
+
port
|
|
934
|
+
}))
|
|
935
|
+
],
|
|
936
|
+
sleep2(
|
|
937
|
+
Math.min(
|
|
938
|
+
remaining,
|
|
939
|
+
pendingMetaRead === void 0 ? metaReadDelayMs : pollIntervalMs
|
|
940
|
+
)
|
|
941
|
+
).then(() => void 0)
|
|
942
|
+
]);
|
|
943
|
+
if (result === void 0) continue;
|
|
944
|
+
if (result.source === "metadata") {
|
|
945
|
+
pendingMetaRead = void 0;
|
|
946
|
+
if (result.port !== void 0) {
|
|
947
|
+
cancelReader = true;
|
|
948
|
+
return {
|
|
949
|
+
port: result.port,
|
|
950
|
+
source: "metadata",
|
|
951
|
+
stdoutTail: [...stdoutTail]
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
nextMetaReadAt = Date.now() + pollIntervalMs;
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
pendingStdoutRead = void 0;
|
|
958
|
+
const { value, done } = result.read;
|
|
959
|
+
if (done) {
|
|
960
|
+
for (const line of decoder.flush()) {
|
|
961
|
+
pushTail({ lines: stdoutTail, line });
|
|
962
|
+
}
|
|
963
|
+
throw await makeBridgeReadyError({
|
|
964
|
+
createError: createExitError,
|
|
965
|
+
fallbackMessage: "bridge exited before becoming ready.",
|
|
966
|
+
proc,
|
|
967
|
+
stdoutTail
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
if (value === void 0) continue;
|
|
971
|
+
for (const line of decoder.push(value)) {
|
|
972
|
+
pushTail({ lines: stdoutTail, line });
|
|
973
|
+
const parsed = await safeParseJSON3({
|
|
974
|
+
text: line,
|
|
975
|
+
schema: harnessV1BridgeReadySchema
|
|
976
|
+
});
|
|
977
|
+
if (parsed.success) {
|
|
978
|
+
return {
|
|
979
|
+
port: parsed.value.port,
|
|
980
|
+
source: "stdout",
|
|
981
|
+
stdoutTail: [...stdoutTail]
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
} finally {
|
|
987
|
+
if (cancelReader) {
|
|
988
|
+
await Promise.race([reader.cancel().catch(() => {
|
|
989
|
+
}), sleep2(100)]);
|
|
990
|
+
}
|
|
991
|
+
try {
|
|
992
|
+
reader.releaseLock();
|
|
993
|
+
} catch (e) {
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
async function readBridgeMetaReady({
|
|
998
|
+
sandbox,
|
|
999
|
+
bridgeStateDir,
|
|
1000
|
+
bridgeType,
|
|
1001
|
+
abortSignal
|
|
1002
|
+
}) {
|
|
1003
|
+
const raw = await Promise.resolve(
|
|
1004
|
+
sandbox.readTextFile({
|
|
1005
|
+
path: bridgeMetaPath(bridgeStateDir),
|
|
1006
|
+
abortSignal
|
|
1007
|
+
})
|
|
1008
|
+
).catch(() => null);
|
|
1009
|
+
if (raw == null) return void 0;
|
|
1010
|
+
const parsed = await safeParseJSON3({ text: raw, schema: bridgeMetaSchema });
|
|
1011
|
+
if (!parsed.success) return void 0;
|
|
1012
|
+
if (parsed.value.type !== bridgeType) return void 0;
|
|
1013
|
+
if (parsed.value.state !== "waiting") return void 0;
|
|
1014
|
+
if (parsed.value.port === void 0 || parsed.value.port <= 0) {
|
|
1015
|
+
return void 0;
|
|
1016
|
+
}
|
|
1017
|
+
return parsed.value.port;
|
|
1018
|
+
}
|
|
1019
|
+
async function makeBridgeReadyError({
|
|
1020
|
+
createError,
|
|
1021
|
+
fallbackMessage,
|
|
1022
|
+
proc,
|
|
1023
|
+
stdoutTail
|
|
1024
|
+
}) {
|
|
1025
|
+
if (createError) {
|
|
1026
|
+
return createError({ proc, stdoutTail: [...stdoutTail] });
|
|
1027
|
+
}
|
|
1028
|
+
return new Error(fallbackMessage);
|
|
1029
|
+
}
|
|
1030
|
+
function bridgeMetaPath(bridgeStateDir) {
|
|
1031
|
+
return `${bridgeStateDir}/bridge-meta.json`;
|
|
1032
|
+
}
|
|
1033
|
+
function pushTail({ lines, line }) {
|
|
1034
|
+
lines.push(line);
|
|
1035
|
+
if (lines.length > 20) lines.shift();
|
|
1036
|
+
}
|
|
1037
|
+
function sleep2(ms) {
|
|
1038
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1039
|
+
}
|
|
1040
|
+
function lineDecoder() {
|
|
1041
|
+
let buffer = "";
|
|
1042
|
+
return {
|
|
1043
|
+
push(chunk) {
|
|
1044
|
+
buffer += chunk;
|
|
1045
|
+
const lines = [];
|
|
1046
|
+
let nl;
|
|
1047
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
1048
|
+
const raw = buffer.slice(0, nl);
|
|
1049
|
+
buffer = buffer.slice(nl + 1);
|
|
1050
|
+
const line = raw.replace(/\r$/, "").trim();
|
|
1051
|
+
if (line.length > 0) lines.push(line);
|
|
1052
|
+
}
|
|
1053
|
+
return lines;
|
|
1054
|
+
},
|
|
1055
|
+
flush() {
|
|
1056
|
+
const line = buffer.replace(/\r$/, "").trim();
|
|
1057
|
+
buffer = "";
|
|
1058
|
+
return line.length > 0 ? [line] : [];
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/utils/bridge-diagnostics.ts
|
|
1064
|
+
var DEFAULT_TAIL_LIMIT = 20;
|
|
1065
|
+
function isSerializedError(error) {
|
|
1066
|
+
return typeof error === "object" && error != null && typeof error.message === "string";
|
|
1067
|
+
}
|
|
1068
|
+
function stringifyUnknown(value) {
|
|
1069
|
+
if (typeof value === "string") return value;
|
|
1070
|
+
try {
|
|
1071
|
+
return JSON.stringify(value);
|
|
1072
|
+
} catch (e) {
|
|
1073
|
+
return String(value);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
function formatBridgeError(error) {
|
|
1077
|
+
var _a, _b;
|
|
1078
|
+
if (error instanceof Error) {
|
|
1079
|
+
return (_a = error.stack) != null ? _a : `${error.name}: ${error.message}`;
|
|
1080
|
+
}
|
|
1081
|
+
if (isSerializedError(error)) {
|
|
1082
|
+
return (_b = error.stack) != null ? _b : `${error.name ? `${error.name}: ` : ""}${error.message}`;
|
|
1083
|
+
}
|
|
1084
|
+
return stringifyUnknown(error);
|
|
1085
|
+
}
|
|
1086
|
+
function writeToStderr(line) {
|
|
1087
|
+
try {
|
|
1088
|
+
process.stderr.write(line);
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function logBridgeError({
|
|
1093
|
+
harnessId,
|
|
1094
|
+
sessionId,
|
|
1095
|
+
context,
|
|
1096
|
+
error,
|
|
1097
|
+
write = writeToStderr
|
|
1098
|
+
}) {
|
|
1099
|
+
const prefix = `[harness:${harnessId}:error${sessionId ? ` session=${sessionId}` : ""}]`;
|
|
1100
|
+
const message = context ? `${context}: ${formatBridgeError(error)}` : formatBridgeError(error);
|
|
1101
|
+
for (const line of message.split("\n")) {
|
|
1102
|
+
if (line.trim().length > 0) {
|
|
1103
|
+
write(`${prefix} ${line}
|
|
1104
|
+
`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function createBridgeErrorHandler({
|
|
1109
|
+
harnessId,
|
|
1110
|
+
sessionId
|
|
1111
|
+
}) {
|
|
1112
|
+
return (event) => {
|
|
1113
|
+
logBridgeError({
|
|
1114
|
+
harnessId,
|
|
1115
|
+
sessionId,
|
|
1116
|
+
context: "bridge emitted an error frame",
|
|
1117
|
+
error: event.error
|
|
1118
|
+
});
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
async function createBridgeStartupError({
|
|
1122
|
+
message,
|
|
1123
|
+
proc,
|
|
1124
|
+
stdoutTail,
|
|
1125
|
+
stderrTail,
|
|
1126
|
+
stderrDone
|
|
1127
|
+
}) {
|
|
1128
|
+
if (stderrDone) {
|
|
1129
|
+
await Promise.race([stderrDone, sleep3(250)]).catch(() => {
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
let exitStatus = "";
|
|
1133
|
+
try {
|
|
1134
|
+
const result = await Promise.race([
|
|
1135
|
+
proc.wait(),
|
|
1136
|
+
sleep3(250).then(() => void 0)
|
|
1137
|
+
]);
|
|
1138
|
+
if ((result == null ? void 0 : result.exitCode) !== void 0) {
|
|
1139
|
+
exitStatus = ` Exit code: ${result.exitCode}.`;
|
|
1140
|
+
}
|
|
1141
|
+
} catch (e) {
|
|
1142
|
+
}
|
|
1143
|
+
const details = [];
|
|
1144
|
+
if (stdoutTail.length > 0) {
|
|
1145
|
+
details.push(`stdout:
|
|
1146
|
+
${stdoutTail.join("\n")}`);
|
|
1147
|
+
}
|
|
1148
|
+
if (stderrTail.length > 0) {
|
|
1149
|
+
details.push(`stderr:
|
|
1150
|
+
${stderrTail.join("\n")}`);
|
|
1151
|
+
}
|
|
1152
|
+
return new Error(
|
|
1153
|
+
`${message}${exitStatus}${details.length > 0 ? `
|
|
1154
|
+
|
|
1155
|
+
${details.join("\n\n")}` : ""}`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
async function forwardBridgeProcessStream({
|
|
1159
|
+
stream,
|
|
1160
|
+
streamName,
|
|
1161
|
+
source = "bridge",
|
|
1162
|
+
collectTail,
|
|
1163
|
+
tailLimit = DEFAULT_TAIL_LIMIT
|
|
1164
|
+
}) {
|
|
1165
|
+
try {
|
|
1166
|
+
const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
|
|
1167
|
+
const decoder = lineDecoder2();
|
|
1168
|
+
while (true) {
|
|
1169
|
+
const { value, done } = await reader.read();
|
|
1170
|
+
const lines = done ? decoder.flush() : value ? decoder.push(value) : [];
|
|
1171
|
+
for (const line of lines) {
|
|
1172
|
+
recordTail({ lines: collectTail, line, limit: tailLimit });
|
|
1173
|
+
writeToStderr(`[harness:${source}:${streamName}] ${line}
|
|
1174
|
+
`);
|
|
1175
|
+
}
|
|
1176
|
+
if (done) return;
|
|
1177
|
+
}
|
|
1178
|
+
} catch (e) {
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
async function drainBridgeProcessStream(stream) {
|
|
1182
|
+
try {
|
|
1183
|
+
const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
|
|
1184
|
+
while (true) {
|
|
1185
|
+
const { done } = await reader.read();
|
|
1186
|
+
if (done) return;
|
|
1187
|
+
}
|
|
1188
|
+
} catch (e) {
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function recordTail({
|
|
1192
|
+
lines,
|
|
1193
|
+
line,
|
|
1194
|
+
limit
|
|
1195
|
+
}) {
|
|
1196
|
+
if (!lines) return;
|
|
1197
|
+
lines.push(line);
|
|
1198
|
+
while (lines.length > limit) lines.shift();
|
|
1199
|
+
}
|
|
1200
|
+
function lineDecoder2() {
|
|
1201
|
+
let buffer = "";
|
|
1202
|
+
return {
|
|
1203
|
+
push(chunk) {
|
|
1204
|
+
buffer += chunk;
|
|
1205
|
+
const lines = [];
|
|
1206
|
+
let nl;
|
|
1207
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
1208
|
+
const raw = buffer.slice(0, nl);
|
|
1209
|
+
buffer = buffer.slice(nl + 1);
|
|
1210
|
+
const line = raw.replace(/\r$/, "").trim();
|
|
1211
|
+
if (line.length > 0) lines.push(line);
|
|
1212
|
+
}
|
|
1213
|
+
return lines;
|
|
1214
|
+
},
|
|
1215
|
+
flush() {
|
|
1216
|
+
const line = buffer.replace(/\r$/, "").trim();
|
|
1217
|
+
buffer = "";
|
|
1218
|
+
return line.length > 0 ? [line] : [];
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
function sleep3(ms) {
|
|
1223
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1224
|
+
}
|
|
1225
|
+
export {
|
|
1226
|
+
SandboxChannel,
|
|
1227
|
+
classifyDiskLog,
|
|
1228
|
+
createBridgeErrorHandler,
|
|
1229
|
+
createBridgeStartupError,
|
|
1230
|
+
drainBridgeProcessStream,
|
|
1231
|
+
formatBridgeError,
|
|
1232
|
+
forwardBridgeProcessStream,
|
|
1233
|
+
getAiGatewayAuthFromEnv,
|
|
1234
|
+
logBridgeError,
|
|
1235
|
+
markBridgeStarting,
|
|
1236
|
+
resolveSandboxHomeDir,
|
|
1237
|
+
shellQuote,
|
|
1238
|
+
waitForBridgeReady,
|
|
1239
|
+
writeSkills
|
|
1240
|
+
};
|
|
1241
|
+
//# sourceMappingURL=index.js.map
|