@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +414 -0
  2. package/LICENSE +13 -0
  3. package/README.md +176 -0
  4. package/agent/index.ts +56 -0
  5. package/bridge/index.ts +10 -0
  6. package/dist/agent/index.d.ts +1631 -0
  7. package/dist/agent/index.js +3491 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/bridge/index.d.ts +129 -0
  10. package/dist/bridge/index.js +482 -0
  11. package/dist/bridge/index.js.map +1 -0
  12. package/dist/index.d.ts +1587 -0
  13. package/dist/index.js +517 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/utils/index.d.ts +329 -0
  16. package/dist/utils/index.js +1241 -0
  17. package/dist/utils/index.js.map +1 -0
  18. package/package.json +100 -0
  19. package/src/agent/harness-agent-session.ts +518 -0
  20. package/src/agent/harness-agent-settings.ts +187 -0
  21. package/src/agent/harness-agent-tool-approval-continuation.ts +94 -0
  22. package/src/agent/harness-agent-tool-types.ts +15 -0
  23. package/src/agent/harness-agent-types.ts +50 -0
  24. package/src/agent/harness-agent.ts +865 -0
  25. package/src/agent/internal/bootstrap-recipe.ts +124 -0
  26. package/src/agent/internal/bridge-port-registry.ts +52 -0
  27. package/src/agent/internal/harness-stream-text-result.ts +731 -0
  28. package/src/agent/internal/lifecycle-state-validation.ts +95 -0
  29. package/src/agent/internal/permission-mode.ts +50 -0
  30. package/src/agent/internal/resolve-observability.ts +128 -0
  31. package/src/agent/internal/run-prompt.ts +901 -0
  32. package/src/agent/internal/sandbox-bootstrap.ts +266 -0
  33. package/src/agent/internal/strip-work-dir.ts +68 -0
  34. package/src/agent/internal/to-harness-stream.ts +75 -0
  35. package/src/agent/internal/tool-filtering.ts +114 -0
  36. package/src/agent/internal/translate-stream-part.ts +221 -0
  37. package/src/agent/internal/turn-telemetry.ts +361 -0
  38. package/src/agent/observability/file-reporter.ts +206 -0
  39. package/src/agent/observability/index.ts +15 -0
  40. package/src/agent/observability/trace-tree-reporter.ts +122 -0
  41. package/src/agent/observability/types.ts +86 -0
  42. package/src/agent/prepare-harness-sandbox-template.ts +68 -0
  43. package/src/agent/prepare-sandbox-for-harness.ts +165 -0
  44. package/src/bridge/index.ts +797 -0
  45. package/src/errors/harness-capability-unsupported-error.ts +41 -0
  46. package/src/errors/harness-error.ts +22 -0
  47. package/src/index.ts +3 -0
  48. package/src/utils/ai-gateway-auth.ts +15 -0
  49. package/src/utils/bridge-diagnostics.ts +213 -0
  50. package/src/utils/bridge-ready.ts +277 -0
  51. package/src/utils/classify-disk-log.ts +43 -0
  52. package/src/utils/index.ts +31 -0
  53. package/src/utils/sandbox-channel.ts +525 -0
  54. package/src/utils/sandbox-home-dir.ts +22 -0
  55. package/src/utils/shell-quote.ts +3 -0
  56. package/src/utils/write-skills.ts +141 -0
  57. package/src/v1/harness-v1-bootstrap.ts +46 -0
  58. package/src/v1/harness-v1-bridge-protocol.ts +342 -0
  59. package/src/v1/harness-v1-builtin-tool.ts +138 -0
  60. package/src/v1/harness-v1-call-warning.ts +22 -0
  61. package/src/v1/harness-v1-diagnostic.ts +66 -0
  62. package/src/v1/harness-v1-lifecycle-state.ts +65 -0
  63. package/src/v1/harness-v1-metadata.ts +13 -0
  64. package/src/v1/harness-v1-network-sandbox-session.ts +123 -0
  65. package/src/v1/harness-v1-observability.ts +20 -0
  66. package/src/v1/harness-v1-permission-mode.ts +11 -0
  67. package/src/v1/harness-v1-prompt-control.ts +41 -0
  68. package/src/v1/harness-v1-prompt.ts +11 -0
  69. package/src/v1/harness-v1-sandbox-provider.ts +76 -0
  70. package/src/v1/harness-v1-session.ts +280 -0
  71. package/src/v1/harness-v1-skill.ts +36 -0
  72. package/src/v1/harness-v1-stream-part.ts +363 -0
  73. package/src/v1/harness-v1-tool-filtering.ts +25 -0
  74. package/src/v1/harness-v1-tool-spec.ts +31 -0
  75. package/src/v1/harness-v1.ts +94 -0
  76. package/src/v1/index.ts +99 -0
  77. package/utils/index.ts +1 -0
@@ -0,0 +1,3491 @@
1
+ // src/errors/harness-capability-unsupported-error.ts
2
+ import { AISDKError as AISDKError2 } from "@ai-sdk/provider";
3
+
4
+ // src/errors/harness-error.ts
5
+ import { AISDKError } from "@ai-sdk/provider";
6
+ var name = "AI_HarnessError";
7
+ var marker = `vercel.ai.error.${name}`;
8
+ var symbol = Symbol.for(marker);
9
+ var _a, _b;
10
+ var HarnessError = class extends (_b = AISDKError, _a = symbol, _b) {
11
+ constructor({ message, cause }) {
12
+ super({ name, message, cause });
13
+ this[_a] = true;
14
+ }
15
+ static isInstance(error) {
16
+ return AISDKError.hasMarker(error, marker);
17
+ }
18
+ };
19
+
20
+ // src/errors/harness-capability-unsupported-error.ts
21
+ var name2 = "AI_HarnessCapabilityUnsupportedError";
22
+ var marker2 = `vercel.ai.error.${name2}`;
23
+ var symbol2 = Symbol.for(marker2);
24
+ var _a2, _b2;
25
+ var HarnessCapabilityUnsupportedError = class extends (_b2 = HarnessError, _a2 = symbol2, _b2) {
26
+ constructor({
27
+ message,
28
+ harnessId,
29
+ cause
30
+ }) {
31
+ super({ message, cause });
32
+ this[_a2] = true;
33
+ Object.defineProperty(this, "name", { value: name2 });
34
+ this.harnessId = harnessId;
35
+ }
36
+ static isInstance(error) {
37
+ return AISDKError2.hasMarker(error, marker2);
38
+ }
39
+ };
40
+
41
+ // src/agent/harness-agent.ts
42
+ import {
43
+ asSchema,
44
+ generateId as generateId5
45
+ } from "@ai-sdk/provider-utils";
46
+
47
+ // src/agent/internal/bridge-port-registry.ts
48
+ var registries = /* @__PURE__ */ new WeakMap();
49
+ function acquireBridgePort(options) {
50
+ let entry = registries.get(options.poolKey);
51
+ if (entry == null) {
52
+ entry = { pool: options.pool, leases: /* @__PURE__ */ new Map() };
53
+ registries.set(options.poolKey, entry);
54
+ }
55
+ const existing = entry.leases.get(options.sessionId);
56
+ if (existing != null) return existing;
57
+ const leased = new Set(entry.leases.values());
58
+ for (const port of entry.pool) {
59
+ if (!leased.has(port)) {
60
+ entry.leases.set(options.sessionId, port);
61
+ return port;
62
+ }
63
+ }
64
+ throw new Error(
65
+ `No available bridge port \u2014 pool of ${entry.pool.length} ports is fully leased.`
66
+ );
67
+ }
68
+ function releaseBridgePort(options) {
69
+ const entry = registries.get(options.poolKey);
70
+ if (entry == null) return;
71
+ entry.leases.delete(options.sessionId);
72
+ }
73
+
74
+ // src/agent/internal/lifecycle-state-validation.ts
75
+ import { safeValidateTypes } from "@ai-sdk/provider-utils";
76
+ async function validateLifecycleStateData(input) {
77
+ const { harness, state } = input;
78
+ if (state.type !== input.expectedType) {
79
+ throw new HarnessError({
80
+ message: `Lifecycle state has unexpected type '${state.type}'; expected '${input.expectedType}'.`
81
+ });
82
+ }
83
+ if (state.specificationVersion !== "harness-v1") {
84
+ throw new HarnessError({
85
+ message: `Lifecycle state has unexpected specificationVersion '${state.specificationVersion}'; expected 'harness-v1'.`
86
+ });
87
+ }
88
+ if (state.harnessId !== harness.harnessId) {
89
+ throw new HarnessError({
90
+ message: `Lifecycle state was produced by harness '${state.harnessId}' but this agent uses '${harness.harnessId}'.`
91
+ });
92
+ }
93
+ if (state.type === "resume-session" && "pendingToolApprovals" in state && state.pendingToolApprovals !== void 0) {
94
+ throw new HarnessError({
95
+ message: "Resume session state cannot contain pending tool approvals; unfinished turns must be stored as `continueFrom`."
96
+ });
97
+ }
98
+ const data = harness.lifecycleStateSchema == null ? state.data : await (async () => {
99
+ const result = await safeValidateTypes({
100
+ value: state.data,
101
+ schema: harness.lifecycleStateSchema
102
+ });
103
+ if (!result.success) {
104
+ throw new HarnessError({
105
+ message: `Lifecycle state failed schema validation for harness '${harness.harnessId}': ${result.error.message}`,
106
+ cause: result.error
107
+ });
108
+ }
109
+ return result.value;
110
+ })();
111
+ if (state.type === "resume-session") {
112
+ const continueFrom = state.continueFrom == null ? void 0 : await validateLifecycleStateData({
113
+ harness,
114
+ state: state.continueFrom,
115
+ expectedType: "continue-turn"
116
+ });
117
+ return {
118
+ type: state.type,
119
+ harnessId: state.harnessId,
120
+ specificationVersion: state.specificationVersion,
121
+ data,
122
+ ...continueFrom !== void 0 ? { continueFrom } : {}
123
+ };
124
+ }
125
+ return {
126
+ type: state.type,
127
+ harnessId: state.harnessId,
128
+ specificationVersion: state.specificationVersion,
129
+ data,
130
+ ...state.pendingToolApprovals !== void 0 ? { pendingToolApprovals: state.pendingToolApprovals } : {}
131
+ };
132
+ }
133
+
134
+ // src/v1/harness-v1-tool-filtering.ts
135
+ function isHarnessV1BuiltinToolIncluded(input) {
136
+ if (input.toolFiltering == null) return true;
137
+ return input.toolFiltering.mode === "allow" ? input.toolFiltering.toolNames.includes(input.toolName) : !input.toolFiltering.toolNames.includes(input.toolName);
138
+ }
139
+ function getHarnessV1BuiltinToolFilteringDenialReason(input) {
140
+ return `Tool '${input.toolName}' is inactive due to the HarnessAgent tool filtering policy.`;
141
+ }
142
+
143
+ // src/agent/internal/to-harness-stream.ts
144
+ async function toHarnessStream(options) {
145
+ let controller;
146
+ let closed = false;
147
+ const stream = new ReadableStream({
148
+ start(c) {
149
+ controller = c;
150
+ }
151
+ });
152
+ const safeEnqueue = (part) => {
153
+ if (closed) return;
154
+ controller.enqueue(part);
155
+ };
156
+ const safeClose = () => {
157
+ if (closed) return;
158
+ closed = true;
159
+ controller.close();
160
+ };
161
+ const control = await options.invoke(safeEnqueue);
162
+ Promise.resolve(control.done).then(
163
+ () => safeClose(),
164
+ (err) => {
165
+ safeEnqueue({ type: "error", error: err });
166
+ safeClose();
167
+ }
168
+ ).catch(() => {
169
+ });
170
+ return { stream, control };
171
+ }
172
+
173
+ // src/agent/internal/run-prompt.ts
174
+ import {
175
+ executeTool,
176
+ generateId as generateId4,
177
+ isExecutableTool,
178
+ safeParseJSON
179
+ } from "@ai-sdk/provider-utils";
180
+ import { parseToolCall } from "ai/internal";
181
+
182
+ // src/agent/internal/harness-stream-text-result.ts
183
+ import {
184
+ DelayedPromise,
185
+ generateId
186
+ } from "@ai-sdk/provider-utils";
187
+ import {
188
+ addLanguageModelUsage,
189
+ asLanguageModelUsage,
190
+ createAsyncIterableStream,
191
+ createNullLanguageModelUsage,
192
+ DefaultStepResult,
193
+ toResponseMessages
194
+ } from "ai/internal";
195
+ import {
196
+ createUIMessageStreamResponse,
197
+ toUIMessageStream as toUIMessageStreamHelper
198
+ } from "ai";
199
+ var HarnessStreamTextResult = class {
200
+ constructor(options) {
201
+ // Delayed promises backing every PromiseLike accessor. Each is typed
202
+ // against the corresponding `StreamTextResult` property so the public
203
+ // surface stays in lockstep with AI SDK's interface as it evolves.
204
+ this._content = new DelayedPromise();
205
+ this._text = new DelayedPromise();
206
+ this._reasoning = new DelayedPromise();
207
+ this._reasoningText = new DelayedPromise();
208
+ this._files = new DelayedPromise();
209
+ this._sources = new DelayedPromise();
210
+ this._toolCalls = new DelayedPromise();
211
+ this._staticToolCalls = new DelayedPromise();
212
+ this._dynamicToolCalls = new DelayedPromise();
213
+ this._toolResults = new DelayedPromise();
214
+ this._staticToolResults = new DelayedPromise();
215
+ this._dynamicToolResults = new DelayedPromise();
216
+ this._finishReason = new DelayedPromise();
217
+ this._rawFinishReason = new DelayedPromise();
218
+ this._usage = new DelayedPromise();
219
+ this._warnings = new DelayedPromise();
220
+ this._steps = new DelayedPromise();
221
+ this._finalStep = new DelayedPromise();
222
+ this._request = new DelayedPromise();
223
+ this._response = new DelayedPromise();
224
+ this._responseMessages = new DelayedPromise();
225
+ this._providerMetadata = new DelayedPromise();
226
+ this.stepsBuffer = [];
227
+ this.currentStepContent = [];
228
+ this.currentStepWarnings = [];
229
+ this.stepNumber = 0;
230
+ // Accumulators that span the whole turn.
231
+ this.accumulatedUsage = createNullLanguageModelUsage();
232
+ this.finalProviderMetadata = void 0;
233
+ this.finalFinishReason = "other";
234
+ this.finalRawFinishReason = void 0;
235
+ this.aggregateWarnings = [];
236
+ this.settled = false;
237
+ this.tools = options.tools;
238
+ this.runtimeContext = options.runtimeContext;
239
+ this.toolsContext = options.toolsContext;
240
+ this.providerName = `harness:${options.harnessId}`;
241
+ this.modelId = options.sessionId;
242
+ let controllerRef;
243
+ const baseStream = new ReadableStream({
244
+ start(c) {
245
+ controllerRef = c;
246
+ }
247
+ });
248
+ this.fullStreamController = controllerRef;
249
+ const [forFull, forText] = baseStream.tee();
250
+ this.stream = forFull;
251
+ this.fullStream = this.stream;
252
+ this.textStream = forText.pipeThrough(
253
+ new TransformStream({
254
+ transform(part, controller) {
255
+ if (part.type === "text-delta") {
256
+ controller.enqueue(part.text);
257
+ }
258
+ }
259
+ })
260
+ );
261
+ }
262
+ // ─── Writer-side methods used by the driver ────────────────────────
263
+ /**
264
+ * Push a translated `TextStreamPart` into `fullStream` and accumulate it
265
+ * into the current step's content array where applicable.
266
+ */
267
+ enqueue(part) {
268
+ this.fullStreamController.enqueue(part);
269
+ this.appendToCurrentStepContent(part);
270
+ }
271
+ /**
272
+ * Mark the end of a step. Builds a `StepResult` from the accumulated
273
+ * content and records it in the steps array. Accepts the V4-shaped
274
+ * finish reason / usage the harness emits and normalizes to AI SDK's
275
+ * flat shape internally.
276
+ */
277
+ finishStep(input) {
278
+ const normalizedUsage = asLanguageModelUsage(input.usage);
279
+ const finishReason = input.finishReason.unified;
280
+ const rawFinishReason = input.finishReason.raw;
281
+ const step = new DefaultStepResult({
282
+ callId: generateId(),
283
+ stepNumber: this.stepNumber,
284
+ provider: this.providerName,
285
+ modelId: this.modelId,
286
+ runtimeContext: this.runtimeContext,
287
+ toolsContext: this.toolsContext,
288
+ content: this.currentStepContent,
289
+ finishReason,
290
+ rawFinishReason,
291
+ usage: normalizedUsage,
292
+ performance: createEmptyPerformance(),
293
+ warnings: input.warnings.length > 0 ? input.warnings : void 0,
294
+ request: {},
295
+ response: {
296
+ id: generateId(),
297
+ timestamp: /* @__PURE__ */ new Date(),
298
+ modelId: this.modelId,
299
+ messages: []
300
+ },
301
+ providerMetadata: input.providerMetadata
302
+ });
303
+ this.stepsBuffer.push(step);
304
+ this.fullStreamController.enqueue({
305
+ type: "finish-step",
306
+ finishReason,
307
+ rawFinishReason,
308
+ usage: normalizedUsage,
309
+ providerMetadata: input.providerMetadata,
310
+ response: step.response,
311
+ performance: createEmptyPerformance()
312
+ });
313
+ this.accumulatedUsage = addLanguageModelUsage(
314
+ this.accumulatedUsage,
315
+ normalizedUsage
316
+ );
317
+ this.finalFinishReason = finishReason;
318
+ this.finalRawFinishReason = rawFinishReason;
319
+ this.finalProviderMetadata = input.providerMetadata;
320
+ if (input.warnings.length > 0)
321
+ this.aggregateWarnings.push(...input.warnings);
322
+ this.stepNumber += 1;
323
+ this.currentStepContent = [];
324
+ this.currentStepWarnings = [];
325
+ }
326
+ /**
327
+ * Resolve every delayed promise and close `fullStream`. Idempotent.
328
+ */
329
+ async finish(input) {
330
+ if (this.settled) return;
331
+ this.settled = true;
332
+ if (input != null) {
333
+ this.finalFinishReason = input.finishReason.unified;
334
+ this.finalRawFinishReason = input.finishReason.raw;
335
+ this.finalProviderMetadata = input.providerMetadata;
336
+ this.accumulatedUsage = asLanguageModelUsage(input.totalUsage);
337
+ }
338
+ if (this.currentStepContent.length > 0) {
339
+ const trailingStep = new DefaultStepResult({
340
+ callId: generateId(),
341
+ stepNumber: this.stepNumber,
342
+ provider: this.providerName,
343
+ modelId: this.modelId,
344
+ runtimeContext: this.runtimeContext,
345
+ toolsContext: this.toolsContext,
346
+ content: this.currentStepContent,
347
+ finishReason: this.finalFinishReason,
348
+ rawFinishReason: this.finalRawFinishReason,
349
+ usage: createNullLanguageModelUsage(),
350
+ performance: createEmptyPerformance(),
351
+ warnings: this.currentStepWarnings.length > 0 ? this.currentStepWarnings : void 0,
352
+ request: {},
353
+ response: {
354
+ id: generateId(),
355
+ timestamp: /* @__PURE__ */ new Date(),
356
+ modelId: this.modelId,
357
+ messages: []
358
+ },
359
+ providerMetadata: this.finalProviderMetadata
360
+ });
361
+ this.stepsBuffer.push(trailingStep);
362
+ this.currentStepContent = [];
363
+ this.currentStepWarnings = [];
364
+ }
365
+ const finalStep = this.stepsBuffer.length > 0 ? this.stepsBuffer[this.stepsBuffer.length - 1] : new DefaultStepResult({
366
+ callId: generateId(),
367
+ stepNumber: 0,
368
+ provider: this.providerName,
369
+ modelId: this.modelId,
370
+ runtimeContext: this.runtimeContext,
371
+ toolsContext: this.toolsContext,
372
+ content: [],
373
+ finishReason: this.finalFinishReason,
374
+ rawFinishReason: this.finalRawFinishReason,
375
+ usage: createNullLanguageModelUsage(),
376
+ performance: createEmptyPerformance(),
377
+ warnings: void 0,
378
+ request: {},
379
+ response: {
380
+ id: generateId(),
381
+ timestamp: /* @__PURE__ */ new Date(),
382
+ modelId: this.modelId,
383
+ messages: []
384
+ },
385
+ providerMetadata: void 0
386
+ });
387
+ const aggregatedContent = this.stepsBuffer.flatMap((s) => s.content);
388
+ this._content.resolve(
389
+ aggregatedContent
390
+ );
391
+ this._text.resolve(finalStep.text);
392
+ this._reasoning.resolve(
393
+ []
394
+ );
395
+ this._reasoningText.resolve(void 0);
396
+ this._files.resolve(this.stepsBuffer.flatMap((s) => s.files));
397
+ this._sources.resolve(this.stepsBuffer.flatMap((s) => s.sources));
398
+ this._toolCalls.resolve(
399
+ this.stepsBuffer.flatMap((s) => s.toolCalls)
400
+ );
401
+ this._staticToolCalls.resolve(
402
+ this.stepsBuffer.flatMap((s) => s.staticToolCalls)
403
+ );
404
+ this._dynamicToolCalls.resolve(
405
+ this.stepsBuffer.flatMap((s) => s.dynamicToolCalls)
406
+ );
407
+ this._toolResults.resolve(
408
+ this.stepsBuffer.flatMap((s) => s.toolResults)
409
+ );
410
+ this._staticToolResults.resolve(
411
+ this.stepsBuffer.flatMap((s) => s.staticToolResults)
412
+ );
413
+ this._dynamicToolResults.resolve(
414
+ this.stepsBuffer.flatMap((s) => s.dynamicToolResults)
415
+ );
416
+ this._finishReason.resolve(this.finalFinishReason);
417
+ this._rawFinishReason.resolve(this.finalRawFinishReason);
418
+ this._usage.resolve(this.accumulatedUsage);
419
+ this._warnings.resolve(
420
+ this.aggregateWarnings.length > 0 ? this.aggregateWarnings : void 0
421
+ );
422
+ this._steps.resolve(this.stepsBuffer);
423
+ this._finalStep.resolve(finalStep);
424
+ this._request.resolve(finalStep.request);
425
+ this._response.resolve(finalStep.response);
426
+ this._providerMetadata.resolve(this.finalProviderMetadata);
427
+ const responseMessages = await toResponseMessages({
428
+ content: aggregatedContent,
429
+ tools: this.tools
430
+ });
431
+ this._responseMessages.resolve(responseMessages);
432
+ this.fullStreamController.enqueue({
433
+ type: "finish",
434
+ finishReason: this.finalFinishReason,
435
+ rawFinishReason: this.finalRawFinishReason,
436
+ totalUsage: this.accumulatedUsage,
437
+ providerMetadata: this.finalProviderMetadata
438
+ });
439
+ this.fullStreamController.close();
440
+ }
441
+ /**
442
+ * Surface a fatal error as a stream `error` part + reject every delayed
443
+ * promise so awaiting consumers stop hanging. Idempotent.
444
+ */
445
+ fail(error) {
446
+ if (this.settled) return;
447
+ this.settled = true;
448
+ this.fullStreamController.enqueue({
449
+ type: "error",
450
+ error
451
+ });
452
+ this.fullStreamController.close();
453
+ for (const dp of [
454
+ this._content,
455
+ this._text,
456
+ this._reasoning,
457
+ this._reasoningText,
458
+ this._files,
459
+ this._sources,
460
+ this._toolCalls,
461
+ this._staticToolCalls,
462
+ this._dynamicToolCalls,
463
+ this._toolResults,
464
+ this._staticToolResults,
465
+ this._dynamicToolResults,
466
+ this._finishReason,
467
+ this._rawFinishReason,
468
+ this._usage,
469
+ this._warnings,
470
+ this._steps,
471
+ this._finalStep,
472
+ this._request,
473
+ this._response,
474
+ this._responseMessages,
475
+ this._providerMetadata
476
+ ]) {
477
+ try {
478
+ dp.reject(error);
479
+ } catch (e) {
480
+ }
481
+ }
482
+ }
483
+ // ─── Reader-side public surface (StreamTextResult contract) ────────
484
+ get content() {
485
+ return this._content.promise;
486
+ }
487
+ get text() {
488
+ return this._text.promise;
489
+ }
490
+ get reasoning() {
491
+ return this._reasoning.promise;
492
+ }
493
+ get reasoningText() {
494
+ return this._reasoningText.promise;
495
+ }
496
+ get files() {
497
+ return this._files.promise;
498
+ }
499
+ get sources() {
500
+ return this._sources.promise;
501
+ }
502
+ get toolCalls() {
503
+ return this._toolCalls.promise;
504
+ }
505
+ get staticToolCalls() {
506
+ return this._staticToolCalls.promise;
507
+ }
508
+ get dynamicToolCalls() {
509
+ return this._dynamicToolCalls.promise;
510
+ }
511
+ get toolResults() {
512
+ return this._toolResults.promise;
513
+ }
514
+ get staticToolResults() {
515
+ return this._staticToolResults.promise;
516
+ }
517
+ get dynamicToolResults() {
518
+ return this._dynamicToolResults.promise;
519
+ }
520
+ get finishReason() {
521
+ return this._finishReason.promise;
522
+ }
523
+ get rawFinishReason() {
524
+ return this._rawFinishReason.promise;
525
+ }
526
+ get usage() {
527
+ return this._usage.promise;
528
+ }
529
+ get totalUsage() {
530
+ return this._usage.promise;
531
+ }
532
+ get warnings() {
533
+ return this._warnings.promise;
534
+ }
535
+ get steps() {
536
+ return this._steps.promise;
537
+ }
538
+ get finalStep() {
539
+ return this._finalStep.promise;
540
+ }
541
+ get request() {
542
+ return this._request.promise;
543
+ }
544
+ get response() {
545
+ return this._response.promise;
546
+ }
547
+ get responseMessages() {
548
+ return this._responseMessages.promise;
549
+ }
550
+ get providerMetadata() {
551
+ return this._providerMetadata.promise;
552
+ }
553
+ // Output-specification surfaces are not yet supported.
554
+ get experimental_partialOutputStream() {
555
+ throw notSupportedYet("partial output stream");
556
+ }
557
+ get partialOutputStream() {
558
+ throw notSupportedYet("partial output stream");
559
+ }
560
+ get elementStream() {
561
+ throw notSupportedYet("element stream");
562
+ }
563
+ get output() {
564
+ throw notSupportedYet("structured output");
565
+ }
566
+ async consumeStream() {
567
+ const reader = this.fullStream.getReader();
568
+ try {
569
+ while (true) {
570
+ const { done } = await reader.read();
571
+ if (done) return;
572
+ }
573
+ } finally {
574
+ reader.releaseLock();
575
+ }
576
+ }
577
+ toUIMessageStream({
578
+ originalMessages,
579
+ generateMessageId,
580
+ onFinish,
581
+ messageMetadata,
582
+ sendReasoning,
583
+ sendSources,
584
+ sendStart,
585
+ sendFinish,
586
+ onError
587
+ } = {}) {
588
+ return createAsyncIterableStream(
589
+ toUIMessageStreamHelper({
590
+ stream: this.stream,
591
+ tools: this.tools,
592
+ originalMessages,
593
+ generateMessageId,
594
+ onFinish,
595
+ messageMetadata,
596
+ sendReasoning,
597
+ sendSources,
598
+ sendStart,
599
+ sendFinish,
600
+ onError
601
+ })
602
+ );
603
+ }
604
+ pipeUIMessageStreamToResponse() {
605
+ throw notSupportedYet("pipeUIMessageStreamToResponse");
606
+ }
607
+ pipeTextStreamToResponse() {
608
+ throw notSupportedYet("pipeTextStreamToResponse");
609
+ }
610
+ toUIMessageStreamResponse({
611
+ originalMessages,
612
+ generateMessageId,
613
+ onFinish,
614
+ messageMetadata,
615
+ sendReasoning,
616
+ sendSources,
617
+ sendStart,
618
+ sendFinish,
619
+ onError,
620
+ ...init
621
+ } = {}) {
622
+ return createUIMessageStreamResponse({
623
+ stream: this.toUIMessageStream({
624
+ originalMessages,
625
+ generateMessageId,
626
+ onFinish,
627
+ messageMetadata,
628
+ sendReasoning,
629
+ sendSources,
630
+ sendStart,
631
+ sendFinish,
632
+ onError
633
+ }),
634
+ ...init
635
+ });
636
+ }
637
+ toTextStreamResponse() {
638
+ throw notSupportedYet("toTextStreamResponse");
639
+ }
640
+ // ─── Helpers ────────────────────────────────────────────────────────
641
+ appendToCurrentStepContent(part) {
642
+ switch (part.type) {
643
+ case "text-delta": {
644
+ const last = this.currentStepContent[this.currentStepContent.length - 1];
645
+ if (last && last.type === "text") {
646
+ last.text += part.text;
647
+ } else {
648
+ this.currentStepContent.push({
649
+ type: "text",
650
+ text: part.text
651
+ });
652
+ }
653
+ return;
654
+ }
655
+ case "tool-call":
656
+ this.currentStepContent.push({
657
+ ...part
658
+ });
659
+ return;
660
+ case "tool-approval-request":
661
+ this.currentStepContent.push({
662
+ ...part
663
+ });
664
+ return;
665
+ case "tool-approval-response":
666
+ this.currentStepContent.push({
667
+ ...part
668
+ });
669
+ return;
670
+ case "tool-result":
671
+ this.currentStepContent.push({
672
+ ...part
673
+ });
674
+ return;
675
+ default:
676
+ return;
677
+ }
678
+ }
679
+ };
680
+ function createEmptyPerformance() {
681
+ return {
682
+ effectiveOutputTokensPerSecond: 0,
683
+ outputTokensPerSecond: void 0,
684
+ inputTokensPerSecond: void 0,
685
+ effectiveTotalTokensPerSecond: 0,
686
+ stepTimeMs: 0,
687
+ responseTimeMs: 0,
688
+ toolExecutionMs: {},
689
+ timeToFirstOutputMs: void 0
690
+ };
691
+ }
692
+ function notSupportedYet(feature) {
693
+ return new Error(
694
+ `HarnessAgent: ${feature} is not implemented yet. Track the foundation review for follow-up.`
695
+ );
696
+ }
697
+
698
+ // src/agent/internal/translate-stream-part.ts
699
+ import { generateId as generateId2 } from "@ai-sdk/provider-utils";
700
+ function translateStreamPart(event) {
701
+ switch (event.type) {
702
+ case "stream-start":
703
+ return [];
704
+ case "text-start":
705
+ return [
706
+ {
707
+ type: "text-start",
708
+ id: event.id,
709
+ providerMetadata: event.harnessMetadata
710
+ }
711
+ ];
712
+ case "text-delta":
713
+ return [
714
+ {
715
+ type: "text-delta",
716
+ id: event.id,
717
+ text: event.delta,
718
+ providerMetadata: event.harnessMetadata
719
+ }
720
+ ];
721
+ case "text-end":
722
+ return [
723
+ {
724
+ type: "text-end",
725
+ id: event.id,
726
+ providerMetadata: event.harnessMetadata
727
+ }
728
+ ];
729
+ case "reasoning-start":
730
+ return [
731
+ {
732
+ type: "reasoning-start",
733
+ id: event.id,
734
+ providerMetadata: event.harnessMetadata
735
+ }
736
+ ];
737
+ case "reasoning-delta":
738
+ return [
739
+ {
740
+ type: "reasoning-delta",
741
+ id: event.id,
742
+ text: event.delta,
743
+ providerMetadata: event.harnessMetadata
744
+ }
745
+ ];
746
+ case "reasoning-end":
747
+ return [
748
+ {
749
+ type: "reasoning-end",
750
+ id: event.id,
751
+ providerMetadata: event.harnessMetadata
752
+ }
753
+ ];
754
+ case "tool-call":
755
+ return [];
756
+ case "tool-approval-request":
757
+ return [];
758
+ case "tool-result":
759
+ return [
760
+ {
761
+ type: "tool-result",
762
+ toolCallId: event.toolCallId,
763
+ toolName: event.toolName,
764
+ input: void 0,
765
+ output: event.result,
766
+ ...event.preliminary !== void 0 ? { preliminary: event.preliminary } : {},
767
+ ...event.providerMetadata !== void 0 ? { providerMetadata: event.providerMetadata } : {}
768
+ }
769
+ ];
770
+ case "file-change": {
771
+ const toolCallId = `harness-file-change-${generateId2()}`;
772
+ const payload = { event: event.event, path: event.path };
773
+ return [
774
+ {
775
+ type: "tool-call",
776
+ toolCallId,
777
+ toolName: "fileChange",
778
+ input: payload,
779
+ dynamic: true,
780
+ providerExecuted: true,
781
+ ...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
782
+ },
783
+ {
784
+ type: "tool-result",
785
+ toolCallId,
786
+ toolName: "fileChange",
787
+ input: payload,
788
+ output: payload,
789
+ dynamic: true,
790
+ providerExecuted: true,
791
+ ...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
792
+ }
793
+ ];
794
+ }
795
+ case "compaction": {
796
+ const toolCallId = `harness-compaction-${generateId2()}`;
797
+ const output = {
798
+ trigger: event.trigger,
799
+ summary: event.summary,
800
+ ...event.tokensBefore !== void 0 ? { tokensBefore: event.tokensBefore } : {},
801
+ ...event.tokensAfter !== void 0 ? { tokensAfter: event.tokensAfter } : {}
802
+ };
803
+ return [
804
+ {
805
+ type: "tool-call",
806
+ toolCallId,
807
+ toolName: "compaction",
808
+ input: {},
809
+ dynamic: true,
810
+ providerExecuted: true,
811
+ ...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
812
+ },
813
+ {
814
+ type: "tool-result",
815
+ toolCallId,
816
+ toolName: "compaction",
817
+ input: {},
818
+ output,
819
+ dynamic: true,
820
+ providerExecuted: true,
821
+ ...event.harnessMetadata !== void 0 ? { providerMetadata: event.harnessMetadata } : {}
822
+ }
823
+ ];
824
+ }
825
+ case "error":
826
+ return [{ type: "error", error: event.error }];
827
+ case "raw":
828
+ return [
829
+ { type: "raw", rawValue: event.rawValue }
830
+ ];
831
+ case "finish-step":
832
+ case "finish":
833
+ return [];
834
+ }
835
+ }
836
+
837
+ // src/agent/internal/strip-work-dir.ts
838
+ function stripWorkDir(part, sessionWorkDir) {
839
+ if (sessionWorkDir.length === 0) return part;
840
+ switch (part.type) {
841
+ case "tool-call":
842
+ return { ...part, input: stripString(part.input, sessionWorkDir) };
843
+ case "tool-result":
844
+ return {
845
+ ...part,
846
+ result: stripDeep(part.result, sessionWorkDir)
847
+ };
848
+ case "file-change":
849
+ return { ...part, path: stripString(part.path, sessionWorkDir) };
850
+ default:
851
+ return part;
852
+ }
853
+ }
854
+ function stripString(value, workDir) {
855
+ return value.split(`${workDir}/`).join("").split(workDir).join(".");
856
+ }
857
+ function stripDeep(value, workDir) {
858
+ if (typeof value === "string") return stripString(value, workDir);
859
+ if (Array.isArray(value)) return value.map((item) => stripDeep(item, workDir));
860
+ if (value !== null && typeof value === "object") {
861
+ const out = {};
862
+ for (const [key, val] of Object.entries(value)) {
863
+ out[key] = stripDeep(val, workDir);
864
+ }
865
+ return out;
866
+ }
867
+ return value;
868
+ }
869
+
870
+ // src/agent/internal/turn-telemetry.ts
871
+ import { generateId as generateId3 } from "@ai-sdk/provider-utils";
872
+ import { createTelemetryDispatcher } from "ai/internal";
873
+ var NOOP = {
874
+ start() {
875
+ },
876
+ ensureStepOpen() {
877
+ },
878
+ stepFinish() {
879
+ },
880
+ toolStart() {
881
+ },
882
+ toolEnd() {
883
+ },
884
+ end() {
885
+ },
886
+ error() {
887
+ }
888
+ };
889
+ function createTurnTelemetry(opts) {
890
+ var _a3;
891
+ if (opts.telemetry == null) return NOOP;
892
+ const dispatcher = createTelemetryDispatcher({ telemetry: opts.telemetry });
893
+ const callId = generateId3();
894
+ const provider = opts.harnessId;
895
+ let modelId = (_a3 = opts.modelId) != null ? _a3 : "";
896
+ const runtimeContext = opts.runtimeContext;
897
+ const inputMessages = [
898
+ { role: "user", content: opts.promptText }
899
+ ];
900
+ let started = false;
901
+ let stepOpen = false;
902
+ let stepNumber = 0;
903
+ let ended = false;
904
+ const openTools = /* @__PURE__ */ new Map();
905
+ const cast = (event) => event;
906
+ const fireStart = () => {
907
+ var _a4;
908
+ if (started) return;
909
+ started = true;
910
+ (_a4 = dispatcher.onStart) == null ? void 0 : _a4.call(
911
+ dispatcher,
912
+ cast({
913
+ callId,
914
+ operationId: "ai.harness",
915
+ provider,
916
+ modelId,
917
+ tools: void 0,
918
+ toolChoice: void 0,
919
+ activeTools: void 0,
920
+ maxRetries: 0,
921
+ timeout: void 0,
922
+ headers: void 0,
923
+ providerOptions: void 0,
924
+ output: void 0,
925
+ toolsContext: void 0,
926
+ runtimeContext,
927
+ instructions: opts.instructions,
928
+ messages: inputMessages
929
+ })
930
+ );
931
+ };
932
+ const start = (overrideModelId) => {
933
+ if (started) return;
934
+ if (overrideModelId) modelId = overrideModelId;
935
+ fireStart();
936
+ };
937
+ const ensureStepOpen = () => {
938
+ var _a4, _b3;
939
+ if (!started) fireStart();
940
+ if (stepOpen || ended) return;
941
+ stepOpen = true;
942
+ (_a4 = dispatcher.onStepStart) == null ? void 0 : _a4.call(
943
+ dispatcher,
944
+ cast({
945
+ callId,
946
+ provider,
947
+ modelId,
948
+ stepNumber,
949
+ tools: void 0,
950
+ toolChoice: void 0,
951
+ activeTools: void 0,
952
+ steps: new Array(stepNumber),
953
+ providerOptions: void 0,
954
+ output: void 0,
955
+ runtimeContext,
956
+ messages: inputMessages
957
+ })
958
+ );
959
+ (_b3 = dispatcher.onLanguageModelCallStart) == null ? void 0 : _b3.call(
960
+ dispatcher,
961
+ cast({
962
+ callId,
963
+ provider,
964
+ modelId,
965
+ messages: inputMessages,
966
+ tools: void 0
967
+ })
968
+ );
969
+ };
970
+ const inferenceEnd = (info) => {
971
+ var _a4;
972
+ (_a4 = dispatcher.onLanguageModelCallEnd) == null ? void 0 : _a4.call(
973
+ dispatcher,
974
+ cast({
975
+ callId,
976
+ finishReason: info.finishReason,
977
+ responseId: callId,
978
+ usage: info.usage,
979
+ content: info.content
980
+ })
981
+ );
982
+ };
983
+ const closeOpenTools = () => {
984
+ var _a4;
985
+ for (const call of openTools.values()) {
986
+ (_a4 = dispatcher.onToolExecutionEnd) == null ? void 0 : _a4.call(
987
+ dispatcher,
988
+ cast({
989
+ callId,
990
+ toolExecutionMs: 0,
991
+ messages: [],
992
+ toolCall: {
993
+ type: "tool-call",
994
+ toolCallId: call.toolCallId,
995
+ toolName: call.toolName,
996
+ input: call.input,
997
+ dynamic: true
998
+ },
999
+ toolContext: void 0,
1000
+ toolOutput: { type: "error", error: new Error("tool span unclosed") }
1001
+ })
1002
+ );
1003
+ }
1004
+ openTools.clear();
1005
+ };
1006
+ return {
1007
+ start,
1008
+ ensureStepOpen,
1009
+ stepFinish(info) {
1010
+ var _a4, _b3;
1011
+ if (!stepOpen) return;
1012
+ const content = (_a4 = info.content) != null ? _a4 : [];
1013
+ closeOpenTools();
1014
+ inferenceEnd({
1015
+ finishReason: info.finishReason,
1016
+ usage: info.usage,
1017
+ content
1018
+ });
1019
+ (_b3 = dispatcher.onStepEnd) == null ? void 0 : _b3.call(
1020
+ dispatcher,
1021
+ cast({
1022
+ callId,
1023
+ stepNumber,
1024
+ finishReason: info.finishReason,
1025
+ usage: info.usage,
1026
+ providerMetadata: info.providerMetadata,
1027
+ content,
1028
+ response: {
1029
+ id: callId,
1030
+ modelId,
1031
+ timestamp: /* @__PURE__ */ new Date(0),
1032
+ messages: []
1033
+ }
1034
+ })
1035
+ );
1036
+ stepOpen = false;
1037
+ stepNumber += 1;
1038
+ },
1039
+ toolStart(call) {
1040
+ var _a4;
1041
+ ensureStepOpen();
1042
+ openTools.set(call.toolCallId, call);
1043
+ (_a4 = dispatcher.onToolExecutionStart) == null ? void 0 : _a4.call(
1044
+ dispatcher,
1045
+ cast({
1046
+ callId,
1047
+ messages: [],
1048
+ toolCall: {
1049
+ type: "tool-call",
1050
+ toolCallId: call.toolCallId,
1051
+ toolName: call.toolName,
1052
+ input: call.input,
1053
+ dynamic: true
1054
+ },
1055
+ toolContext: void 0
1056
+ })
1057
+ );
1058
+ },
1059
+ toolEnd(toolCallId, output) {
1060
+ var _a4;
1061
+ const call = openTools.get(toolCallId);
1062
+ if (call == null) return;
1063
+ openTools.delete(toolCallId);
1064
+ (_a4 = dispatcher.onToolExecutionEnd) == null ? void 0 : _a4.call(
1065
+ dispatcher,
1066
+ cast({
1067
+ callId,
1068
+ toolExecutionMs: 0,
1069
+ messages: [],
1070
+ toolCall: {
1071
+ type: "tool-call",
1072
+ toolCallId: call.toolCallId,
1073
+ toolName: call.toolName,
1074
+ input: call.input,
1075
+ dynamic: true
1076
+ },
1077
+ toolContext: void 0,
1078
+ toolOutput: output.ok ? { type: "tool-result", output: output.output } : { type: "error", error: output.error }
1079
+ })
1080
+ );
1081
+ },
1082
+ end(info) {
1083
+ var _a4, _b3;
1084
+ if (ended) return;
1085
+ if (!started) fireStart();
1086
+ if (stepOpen) {
1087
+ closeOpenTools();
1088
+ inferenceEnd({
1089
+ finishReason: info.finishReason,
1090
+ usage: info.usage,
1091
+ content: []
1092
+ });
1093
+ (_a4 = dispatcher.onStepEnd) == null ? void 0 : _a4.call(
1094
+ dispatcher,
1095
+ cast({
1096
+ callId,
1097
+ stepNumber,
1098
+ finishReason: info.finishReason,
1099
+ usage: info.usage,
1100
+ providerMetadata: void 0,
1101
+ content: [],
1102
+ response: {
1103
+ id: callId,
1104
+ modelId,
1105
+ timestamp: /* @__PURE__ */ new Date(0),
1106
+ messages: []
1107
+ }
1108
+ })
1109
+ );
1110
+ stepOpen = false;
1111
+ }
1112
+ ended = true;
1113
+ (_b3 = dispatcher.onEnd) == null ? void 0 : _b3.call(
1114
+ dispatcher,
1115
+ cast({
1116
+ callId,
1117
+ operationId: "ai.harness",
1118
+ finishReason: info.finishReason,
1119
+ usage: info.usage,
1120
+ totalUsage: info.usage,
1121
+ content: [],
1122
+ steps: new Array(stepNumber),
1123
+ response: {
1124
+ id: callId,
1125
+ modelId,
1126
+ timestamp: /* @__PURE__ */ new Date(0),
1127
+ messages: []
1128
+ },
1129
+ runtimeContext
1130
+ })
1131
+ );
1132
+ },
1133
+ error(err) {
1134
+ var _a4;
1135
+ if (ended) return;
1136
+ if (!started) fireStart();
1137
+ closeOpenTools();
1138
+ ended = true;
1139
+ (_a4 = dispatcher.onError) == null ? void 0 : _a4.call(dispatcher, err);
1140
+ }
1141
+ };
1142
+ }
1143
+
1144
+ // src/agent/internal/permission-mode.ts
1145
+ var DEFAULT_PERMISSION_MODE = "allow-all";
1146
+ function resolvePermissionMode(input) {
1147
+ var _a3;
1148
+ return (_a3 = input.permissionMode) != null ? _a3 : DEFAULT_PERMISSION_MODE;
1149
+ }
1150
+ function permissionModeNeedsBuiltinSupport(input) {
1151
+ return input.permissionMode !== "allow-all";
1152
+ }
1153
+ function resolveCustomToolApproval(input) {
1154
+ var _a3;
1155
+ const status = normalizeToolApprovalStatus({
1156
+ status: (_a3 = input.toolApproval) == null ? void 0 : _a3[input.toolName]
1157
+ });
1158
+ switch (status.type) {
1159
+ case "not-applicable":
1160
+ case "approved":
1161
+ return { type: "allow", reason: status.reason };
1162
+ case "denied":
1163
+ return { type: "deny", reason: status.reason };
1164
+ case "user-approval":
1165
+ return { type: "request" };
1166
+ }
1167
+ }
1168
+ function normalizeToolApprovalStatus(input) {
1169
+ if (input.status === void 0) return { type: "not-applicable" };
1170
+ if (typeof input.status === "string") return { type: input.status };
1171
+ return input.status;
1172
+ }
1173
+
1174
+ // src/utils/bridge-diagnostics.ts
1175
+ function isSerializedError(error) {
1176
+ return typeof error === "object" && error != null && typeof error.message === "string";
1177
+ }
1178
+ function stringifyUnknown(value) {
1179
+ if (typeof value === "string") return value;
1180
+ try {
1181
+ return JSON.stringify(value);
1182
+ } catch (e) {
1183
+ return String(value);
1184
+ }
1185
+ }
1186
+ function formatBridgeError(error) {
1187
+ var _a3, _b3;
1188
+ if (error instanceof Error) {
1189
+ return (_a3 = error.stack) != null ? _a3 : `${error.name}: ${error.message}`;
1190
+ }
1191
+ if (isSerializedError(error)) {
1192
+ return (_b3 = error.stack) != null ? _b3 : `${error.name ? `${error.name}: ` : ""}${error.message}`;
1193
+ }
1194
+ return stringifyUnknown(error);
1195
+ }
1196
+ function writeToStderr(line) {
1197
+ try {
1198
+ process.stderr.write(line);
1199
+ } catch (e) {
1200
+ }
1201
+ }
1202
+ function logBridgeError({
1203
+ harnessId,
1204
+ sessionId,
1205
+ context,
1206
+ error,
1207
+ write = writeToStderr
1208
+ }) {
1209
+ const prefix = `[harness:${harnessId}:error${sessionId ? ` session=${sessionId}` : ""}]`;
1210
+ const message = context ? `${context}: ${formatBridgeError(error)}` : formatBridgeError(error);
1211
+ for (const line of message.split("\n")) {
1212
+ if (line.trim().length > 0) {
1213
+ write(`${prefix} ${line}
1214
+ `);
1215
+ }
1216
+ }
1217
+ }
1218
+
1219
+ // src/agent/internal/run-prompt.ts
1220
+ function runPrompt(input) {
1221
+ var _a3, _b3, _c, _d;
1222
+ const result = new HarnessStreamTextResult({
1223
+ tools: input.tools,
1224
+ runtimeContext: input.runtimeContext,
1225
+ // toolsContext is not configurable for harnesses; pass undefined cast.
1226
+ toolsContext: void 0,
1227
+ harnessId: input.harness.harnessId,
1228
+ sessionId: input.session.sessionId
1229
+ });
1230
+ const pendingToolApprovals = (_a3 = input.pendingToolApprovals) != null ? _a3 : [];
1231
+ const onPendingToolApproval = (_b3 = input.onPendingToolApproval) != null ? _b3 : (() => {
1232
+ });
1233
+ const onToolApprovalSettled = (_c = input.onToolApprovalSettled) != null ? _c : (() => {
1234
+ });
1235
+ const activeTools = (_d = input.activeTools) != null ? _d : input.tools;
1236
+ const telemetry = createTurnTelemetry({
1237
+ telemetry: input.telemetry,
1238
+ harnessId: input.harness.harnessId,
1239
+ modelId: input.session.modelId,
1240
+ instructions: input.instructions,
1241
+ promptText: input.prompt != null ? promptToText(input.prompt) : "",
1242
+ runtimeContext: input.runtimeContext
1243
+ });
1244
+ const done = (async () => {
1245
+ var _a4, _b4, _c2, _d2, _e, _f, _g, _h;
1246
+ let bridge;
1247
+ try {
1248
+ bridge = await toHarnessStream({
1249
+ invoke: input.mode === "continue" ? (emit) => input.session.doContinueTurn({
1250
+ tools: input.toolSpecs,
1251
+ abortSignal: input.abortSignal,
1252
+ emit
1253
+ }) : (emit) => {
1254
+ if (input.prompt == null) {
1255
+ throw new Error(
1256
+ 'runPrompt: `prompt` is required for mode "prompt".'
1257
+ );
1258
+ }
1259
+ return input.session.doPromptTurn({
1260
+ prompt: input.prompt,
1261
+ tools: input.toolSpecs,
1262
+ instructions: input.instructions,
1263
+ abortSignal: input.abortSignal,
1264
+ emit
1265
+ });
1266
+ }
1267
+ });
1268
+ } catch (err) {
1269
+ telemetry.error(err);
1270
+ logBridgeError({
1271
+ harnessId: input.harness.harnessId,
1272
+ sessionId: input.session.sessionId,
1273
+ context: "failed to start harness turn",
1274
+ error: err
1275
+ });
1276
+ result.fail(err);
1277
+ return;
1278
+ }
1279
+ const { stream, control } = bridge;
1280
+ const reader = stream.getReader();
1281
+ const toolCallsByToolCallId = /* @__PURE__ */ new Map();
1282
+ const rawToolCallsByToolCallId = /* @__PURE__ */ new Map();
1283
+ const pendingApprovalsByApprovalId = new Map(
1284
+ pendingToolApprovals.map((approval) => [approval.approvalId, approval])
1285
+ );
1286
+ const pendingApprovalsByToolCallId = new Map(
1287
+ pendingToolApprovals.map((approval) => [approval.toolCallId, approval])
1288
+ );
1289
+ const continuationsByApprovalId = new Map(
1290
+ ((_a4 = input.toolApprovalContinuations) != null ? _a4 : []).map((continuation) => [
1291
+ continuation.approvalResponse.approvalId,
1292
+ continuation
1293
+ ])
1294
+ );
1295
+ const settledApprovalToolCallIds = /* @__PURE__ */ new Set();
1296
+ let finalFinish;
1297
+ let stepText = "";
1298
+ let stepReasoning = "";
1299
+ let stepToolCalls = [];
1300
+ const buildStepContent = () => {
1301
+ const parts = [];
1302
+ if (stepText) parts.push({ type: "text", text: stepText });
1303
+ if (stepReasoning) parts.push({ type: "reasoning", text: stepReasoning });
1304
+ parts.push(...stepToolCalls);
1305
+ return parts;
1306
+ };
1307
+ const resetStepContent = () => {
1308
+ stepText = "";
1309
+ stepReasoning = "";
1310
+ stepToolCalls = [];
1311
+ };
1312
+ const zeroUsage = {
1313
+ inputTokens: {
1314
+ total: void 0,
1315
+ noCache: void 0,
1316
+ cacheRead: void 0,
1317
+ cacheWrite: void 0
1318
+ },
1319
+ outputTokens: {
1320
+ total: void 0,
1321
+ text: void 0,
1322
+ reasoning: void 0
1323
+ }
1324
+ };
1325
+ const toolCallsFinishReason = {
1326
+ unified: "tool-calls",
1327
+ raw: void 0
1328
+ };
1329
+ const finishForToolApprovalPause = async () => {
1330
+ telemetry.stepFinish({
1331
+ finishReason: toolCallsFinishReason,
1332
+ usage: zeroUsage,
1333
+ content: buildStepContent()
1334
+ });
1335
+ resetStepContent();
1336
+ result.finishStep({
1337
+ finishReason: toolCallsFinishReason,
1338
+ usage: zeroUsage,
1339
+ providerMetadata: void 0,
1340
+ warnings: []
1341
+ });
1342
+ telemetry.end({
1343
+ finishReason: toolCallsFinishReason,
1344
+ usage: zeroUsage
1345
+ });
1346
+ await result.finish();
1347
+ };
1348
+ const enqueueApprovalRequest = (approval) => {
1349
+ result.enqueue({
1350
+ type: "tool-approval-request",
1351
+ approvalId: approval.approvalId,
1352
+ toolCall: approval.toolCall,
1353
+ ...approval.isAutomatic !== void 0 ? { isAutomatic: approval.isAutomatic } : {}
1354
+ });
1355
+ };
1356
+ const enqueueAutomaticApprovalResponse = (input2) => {
1357
+ result.enqueue({
1358
+ type: "tool-approval-response",
1359
+ approvalId: input2.approvalId,
1360
+ toolCall: input2.toolCall,
1361
+ approved: input2.approved,
1362
+ ...input2.reason !== void 0 ? { reason: input2.reason } : {},
1363
+ ...input2.providerExecuted !== void 0 ? { providerExecuted: input2.providerExecuted } : {}
1364
+ });
1365
+ };
1366
+ const enqueueApprovalResponse = (approval, continuation) => {
1367
+ result.enqueue({
1368
+ type: "tool-approval-response",
1369
+ approvalId: approval.approvalId,
1370
+ toolCall: continuation.toolCall,
1371
+ approved: continuation.approvalResponse.approved,
1372
+ ...continuation.approvalResponse.reason !== void 0 ? { reason: continuation.approvalResponse.reason } : {},
1373
+ ...approval.providerExecuted !== void 0 ? { providerExecuted: approval.providerExecuted } : {}
1374
+ });
1375
+ };
1376
+ const processPendingApprovalContinuation = async (approval, continuation) => {
1377
+ var _a5;
1378
+ enqueueApprovalResponse(approval, continuation);
1379
+ onToolApprovalSettled(approval.approvalId);
1380
+ pendingApprovalsByApprovalId.delete(approval.approvalId);
1381
+ pendingApprovalsByToolCallId.delete(approval.toolCallId);
1382
+ settledApprovalToolCallIds.add(approval.toolCallId);
1383
+ if (approval.kind === "builtin") {
1384
+ if (control.submitToolApproval == null) {
1385
+ throw new Error(
1386
+ `Harness '${input.harness.harnessId}' emitted a built-in tool approval request but does not support approval responses.`
1387
+ );
1388
+ }
1389
+ await control.submitToolApproval({
1390
+ approvalId: approval.approvalId,
1391
+ approved: continuation.approvalResponse.approved,
1392
+ reason: continuation.approvalResponse.reason
1393
+ });
1394
+ return;
1395
+ }
1396
+ if (!continuation.approvalResponse.approved) {
1397
+ await control.submitToolResult({
1398
+ toolCallId: approval.toolCallId,
1399
+ output: {
1400
+ type: "execution-denied",
1401
+ reason: continuation.approvalResponse.reason
1402
+ }
1403
+ });
1404
+ return;
1405
+ }
1406
+ const rawToolCall = (_a5 = rawToolCallsByToolCallId.get(approval.toolCallId)) != null ? _a5 : {
1407
+ type: "tool-call",
1408
+ toolCallId: approval.toolCallId,
1409
+ toolName: approval.toolName,
1410
+ input: approval.input
1411
+ };
1412
+ const outcome = await maybeExecuteHostTool({
1413
+ event: rawToolCall,
1414
+ tools: activeTools,
1415
+ sandboxSession: input.sandboxSession,
1416
+ abortSignal: input.abortSignal,
1417
+ control,
1418
+ onPreliminaryResult: (preliminaryOutput) => {
1419
+ const stripped = stripWorkDir(
1420
+ {
1421
+ type: "tool-result",
1422
+ toolCallId: rawToolCall.toolCallId,
1423
+ toolName: rawToolCall.toolName,
1424
+ result: preliminaryOutput
1425
+ },
1426
+ input.sessionWorkDir
1427
+ );
1428
+ result.enqueue({
1429
+ type: "tool-result",
1430
+ toolCallId: rawToolCall.toolCallId,
1431
+ toolName: rawToolCall.toolName,
1432
+ input: void 0,
1433
+ output: stripped.result,
1434
+ preliminary: true
1435
+ });
1436
+ }
1437
+ });
1438
+ telemetry.toolEnd(rawToolCall.toolCallId, outcome);
1439
+ };
1440
+ try {
1441
+ for (const approval of pendingToolApprovals) {
1442
+ const continuation = continuationsByApprovalId.get(approval.approvalId);
1443
+ if (continuation != null) {
1444
+ await processPendingApprovalContinuation(approval, continuation);
1445
+ }
1446
+ }
1447
+ while (true) {
1448
+ const { value, done: done2 } = await reader.read();
1449
+ if (done2) break;
1450
+ if (value == null) continue;
1451
+ if (value.type === "stream-start") {
1452
+ telemetry.start((_b4 = value.modelId) != null ? _b4 : input.session.modelId);
1453
+ }
1454
+ if (value.type !== "stream-start" && value.type !== "finish-step" && value.type !== "finish" && value.type !== "error") {
1455
+ telemetry.ensureStepOpen();
1456
+ }
1457
+ const displayValue = stripWorkDir(value, input.sessionWorkDir);
1458
+ const settledApprovalToolCallReplay = displayValue.type === "tool-call" && !displayValue.providerExecuted && settledApprovalToolCallIds.has(displayValue.toolCallId);
1459
+ if (settledApprovalToolCallReplay) {
1460
+ continue;
1461
+ }
1462
+ if (displayValue.type === "tool-approval-request") {
1463
+ const toolCall = toolCallsByToolCallId.get(displayValue.toolCallId);
1464
+ if (toolCall == null) {
1465
+ throw new Error(
1466
+ `Harness '${input.harness.harnessId}' emitted approval request '${displayValue.approvalId}' for unknown tool call '${displayValue.toolCallId}'.`
1467
+ );
1468
+ }
1469
+ const rawToolCall = rawToolCallsByToolCallId.get(
1470
+ displayValue.toolCallId
1471
+ );
1472
+ const toolName = (_c2 = rawToolCall == null ? void 0 : rawToolCall.toolName) != null ? _c2 : toolCall.toolName;
1473
+ if (!isHarnessV1BuiltinToolIncluded({
1474
+ toolName,
1475
+ toolFiltering: input.builtinToolFiltering
1476
+ })) {
1477
+ if (control.submitToolApproval == null) {
1478
+ throw new Error(
1479
+ `Harness '${input.harness.harnessId}' emitted a built-in tool approval request but does not support approval responses.`
1480
+ );
1481
+ }
1482
+ await control.submitToolApproval({
1483
+ approvalId: displayValue.approvalId,
1484
+ approved: false,
1485
+ reason: getHarnessV1BuiltinToolFilteringDenialReason({
1486
+ toolName
1487
+ })
1488
+ });
1489
+ continue;
1490
+ }
1491
+ }
1492
+ for (const part of translateStreamPart(displayValue)) {
1493
+ result.enqueue(part);
1494
+ }
1495
+ if (displayValue.type === "tool-call") {
1496
+ const parsed = await validateToolCall({
1497
+ event: displayValue,
1498
+ tools: input.tools
1499
+ });
1500
+ const parsedToolCall = asToolCallTextStreamPart({ part: parsed });
1501
+ rawToolCallsByToolCallId.set(displayValue.toolCallId, displayValue);
1502
+ toolCallsByToolCallId.set(displayValue.toolCallId, parsedToolCall);
1503
+ result.enqueue(parsed);
1504
+ }
1505
+ if (value.type === "text-delta") {
1506
+ stepText += value.delta;
1507
+ } else if (value.type === "reasoning-delta") {
1508
+ stepReasoning += value.delta;
1509
+ }
1510
+ if (value.type === "tool-call") {
1511
+ stepToolCalls.push({
1512
+ type: "tool-call",
1513
+ toolCallId: value.toolCallId,
1514
+ toolName: value.toolName,
1515
+ input: value.input
1516
+ });
1517
+ telemetry.toolStart({
1518
+ toolCallId: value.toolCallId,
1519
+ toolName: value.toolName,
1520
+ input: value.input
1521
+ });
1522
+ }
1523
+ if (value.type === "tool-result") {
1524
+ telemetry.toolEnd(
1525
+ value.toolCallId,
1526
+ value.isError ? { ok: false, error: value.result } : { ok: true, output: value.result }
1527
+ );
1528
+ }
1529
+ if (value.type === "tool-approval-request") {
1530
+ const toolCall = toolCallsByToolCallId.get(value.toolCallId);
1531
+ if (toolCall == null) {
1532
+ throw new Error(
1533
+ `Harness '${input.harness.harnessId}' emitted approval request '${value.approvalId}' for unknown tool call '${value.toolCallId}'.`
1534
+ );
1535
+ }
1536
+ const rawToolCall = rawToolCallsByToolCallId.get(value.toolCallId);
1537
+ const pendingApproval = (_f = pendingApprovalsByApprovalId.get(value.approvalId)) != null ? _f : {
1538
+ approvalId: value.approvalId,
1539
+ toolCallId: value.toolCallId,
1540
+ toolName: toolCall.toolName,
1541
+ input: (_d2 = rawToolCall == null ? void 0 : rawToolCall.input) != null ? _d2 : JSON.stringify(toolCall.input),
1542
+ kind: "builtin",
1543
+ providerExecuted: (_e = rawToolCall == null ? void 0 : rawToolCall.providerExecuted) != null ? _e : true,
1544
+ ...(rawToolCall == null ? void 0 : rawToolCall.nativeName) !== void 0 ? { nativeName: rawToolCall.nativeName } : {}
1545
+ };
1546
+ pendingApprovalsByApprovalId.set(
1547
+ pendingApproval.approvalId,
1548
+ pendingApproval
1549
+ );
1550
+ pendingApprovalsByToolCallId.set(
1551
+ pendingApproval.toolCallId,
1552
+ pendingApproval
1553
+ );
1554
+ const continuation = continuationsByApprovalId.get(
1555
+ pendingApproval.approvalId
1556
+ );
1557
+ if (continuation != null) {
1558
+ await processPendingApprovalContinuation(
1559
+ pendingApproval,
1560
+ continuation
1561
+ );
1562
+ continue;
1563
+ }
1564
+ onPendingToolApproval(pendingApproval);
1565
+ enqueueApprovalRequest({
1566
+ approvalId: pendingApproval.approvalId,
1567
+ toolCall
1568
+ });
1569
+ await finishForToolApprovalPause();
1570
+ return;
1571
+ }
1572
+ if (value.type === "finish-step") {
1573
+ telemetry.stepFinish({
1574
+ finishReason: value.finishReason,
1575
+ usage: value.usage,
1576
+ providerMetadata: value.harnessMetadata,
1577
+ content: buildStepContent()
1578
+ });
1579
+ resetStepContent();
1580
+ result.finishStep({
1581
+ finishReason: value.finishReason,
1582
+ usage: value.usage,
1583
+ providerMetadata: value.harnessMetadata,
1584
+ warnings: []
1585
+ });
1586
+ }
1587
+ if (value.type === "finish") {
1588
+ finalFinish = value;
1589
+ telemetry.end({
1590
+ finishReason: value.finishReason,
1591
+ usage: value.totalUsage
1592
+ });
1593
+ }
1594
+ if (value.type === "tool-call" && !value.providerExecuted) {
1595
+ const toolCall = value;
1596
+ const parsedToolCall = toolCallsByToolCallId.get(toolCall.toolCallId);
1597
+ if (parsedToolCall == null) {
1598
+ throw new Error(
1599
+ `Harness '${input.harness.harnessId}' could not find parsed tool call '${toolCall.toolCallId}' for custom tool approval.`
1600
+ );
1601
+ }
1602
+ if (!hasTool({ tools: activeTools, toolName: toolCall.toolName })) {
1603
+ const output = {
1604
+ type: "execution-denied",
1605
+ reason: getHarnessV1BuiltinToolFilteringDenialReason({
1606
+ toolName: toolCall.toolName
1607
+ })
1608
+ };
1609
+ await control.submitToolResult({
1610
+ toolCallId: toolCall.toolCallId,
1611
+ output
1612
+ });
1613
+ telemetry.toolEnd(toolCall.toolCallId, { ok: true, output });
1614
+ continue;
1615
+ }
1616
+ const customToolApprovalDecision = resolveCustomToolApproval({
1617
+ toolName: toolCall.toolName,
1618
+ toolApproval: input.toolApproval
1619
+ });
1620
+ if (customToolApprovalDecision.type === "deny") {
1621
+ const approvalId = generateId4();
1622
+ enqueueApprovalRequest({
1623
+ approvalId,
1624
+ toolCall: parsedToolCall,
1625
+ isAutomatic: true
1626
+ });
1627
+ enqueueAutomaticApprovalResponse({
1628
+ approvalId,
1629
+ toolCall: parsedToolCall,
1630
+ approved: false,
1631
+ reason: customToolApprovalDecision.reason,
1632
+ providerExecuted: false
1633
+ });
1634
+ const output = {
1635
+ type: "execution-denied",
1636
+ reason: customToolApprovalDecision.reason
1637
+ };
1638
+ await control.submitToolResult({
1639
+ toolCallId: toolCall.toolCallId,
1640
+ output
1641
+ });
1642
+ telemetry.toolEnd(toolCall.toolCallId, { ok: true, output });
1643
+ continue;
1644
+ }
1645
+ const pendingApproval = (_g = pendingApprovalsByToolCallId.get(toolCall.toolCallId)) != null ? _g : customToolApprovalDecision.type === "request" ? {
1646
+ approvalId: generateId4(),
1647
+ toolCallId: toolCall.toolCallId,
1648
+ toolName: toolCall.toolName,
1649
+ input: toolCall.input,
1650
+ kind: "custom",
1651
+ providerExecuted: false,
1652
+ ...toolCall.nativeName !== void 0 ? { nativeName: toolCall.nativeName } : {}
1653
+ } : void 0;
1654
+ if (pendingApproval != null) {
1655
+ pendingApprovalsByApprovalId.set(
1656
+ pendingApproval.approvalId,
1657
+ pendingApproval
1658
+ );
1659
+ pendingApprovalsByToolCallId.set(
1660
+ pendingApproval.toolCallId,
1661
+ pendingApproval
1662
+ );
1663
+ const continuation = continuationsByApprovalId.get(
1664
+ pendingApproval.approvalId
1665
+ );
1666
+ if (continuation != null) {
1667
+ await processPendingApprovalContinuation(
1668
+ pendingApproval,
1669
+ continuation
1670
+ );
1671
+ continue;
1672
+ }
1673
+ const pendingParsedToolCall = toolCallsByToolCallId.get(
1674
+ pendingApproval.toolCallId
1675
+ );
1676
+ if (pendingParsedToolCall == null) {
1677
+ throw new Error(
1678
+ `Harness '${input.harness.harnessId}' could not find parsed tool call '${pendingApproval.toolCallId}' for approval request '${pendingApproval.approvalId}'.`
1679
+ );
1680
+ }
1681
+ onPendingToolApproval(pendingApproval);
1682
+ enqueueApprovalRequest({
1683
+ approvalId: pendingApproval.approvalId,
1684
+ toolCall: pendingParsedToolCall
1685
+ });
1686
+ await finishForToolApprovalPause();
1687
+ return;
1688
+ }
1689
+ const outcome = await maybeExecuteHostTool({
1690
+ event: toolCall,
1691
+ tools: activeTools,
1692
+ sandboxSession: input.sandboxSession,
1693
+ abortSignal: input.abortSignal,
1694
+ control,
1695
+ onPreliminaryResult: (preliminaryOutput) => {
1696
+ const stripped = stripWorkDir(
1697
+ {
1698
+ type: "tool-result",
1699
+ toolCallId: toolCall.toolCallId,
1700
+ toolName: toolCall.toolName,
1701
+ result: preliminaryOutput
1702
+ },
1703
+ input.sessionWorkDir
1704
+ );
1705
+ result.enqueue({
1706
+ type: "tool-result",
1707
+ toolCallId: toolCall.toolCallId,
1708
+ toolName: toolCall.toolName,
1709
+ input: void 0,
1710
+ output: stripped.result,
1711
+ preliminary: true
1712
+ });
1713
+ }
1714
+ });
1715
+ telemetry.toolEnd(toolCall.toolCallId, outcome);
1716
+ }
1717
+ if (value.type === "error") {
1718
+ telemetry.error(value.error);
1719
+ logBridgeError({
1720
+ harnessId: input.harness.harnessId,
1721
+ sessionId: input.session.sessionId,
1722
+ context: "harness stream error",
1723
+ error: value.error
1724
+ });
1725
+ result.fail(value.error);
1726
+ return;
1727
+ }
1728
+ }
1729
+ (_h = input.onTurnFinished) == null ? void 0 : _h.call(input);
1730
+ await result.finish(
1731
+ finalFinish ? {
1732
+ finishReason: finalFinish.finishReason,
1733
+ totalUsage: finalFinish.totalUsage,
1734
+ providerMetadata: finalFinish.harnessMetadata
1735
+ } : void 0
1736
+ );
1737
+ } catch (err) {
1738
+ telemetry.error(err);
1739
+ logBridgeError({
1740
+ harnessId: input.harness.harnessId,
1741
+ sessionId: input.session.sessionId,
1742
+ context: "harness turn failed",
1743
+ error: err
1744
+ });
1745
+ result.fail(err);
1746
+ } finally {
1747
+ reader.releaseLock();
1748
+ }
1749
+ })();
1750
+ done.catch(() => {
1751
+ });
1752
+ return { result, done };
1753
+ }
1754
+ function asToolCallTextStreamPart(input) {
1755
+ if (input.part.type !== "tool-call") {
1756
+ throw new Error(
1757
+ `Expected parsed tool-call stream part, got '${input.part.type}'.`
1758
+ );
1759
+ }
1760
+ return input.part;
1761
+ }
1762
+ function hasTool(input) {
1763
+ return Object.prototype.hasOwnProperty.call(input.tools, input.toolName);
1764
+ }
1765
+ async function maybeExecuteHostTool(input) {
1766
+ const tool = input.tools[input.event.toolName];
1767
+ if (!isExecutableTool(tool)) return { ok: true, output: void 0 };
1768
+ const parsed = await safeParseJSON({ text: input.event.input });
1769
+ const args = parsed.success ? parsed.value : input.event.input;
1770
+ try {
1771
+ let output;
1772
+ const stream = executeTool({
1773
+ tool,
1774
+ input: args,
1775
+ options: {
1776
+ toolCallId: input.event.toolCallId,
1777
+ messages: [],
1778
+ abortSignal: input.abortSignal,
1779
+ context: void 0,
1780
+ experimental_sandbox: input.sandboxSession
1781
+ }
1782
+ });
1783
+ for await (const part of stream) {
1784
+ if (part.type === "preliminary") {
1785
+ input.onPreliminaryResult(part.output);
1786
+ } else {
1787
+ output = part.output;
1788
+ }
1789
+ }
1790
+ await input.control.submitToolResult({
1791
+ toolCallId: input.event.toolCallId,
1792
+ output
1793
+ });
1794
+ return { ok: true, output };
1795
+ } catch (err) {
1796
+ await input.control.submitToolResult({
1797
+ toolCallId: input.event.toolCallId,
1798
+ output: { error: String(err) },
1799
+ isError: true
1800
+ });
1801
+ return { ok: false, error: err };
1802
+ }
1803
+ }
1804
+ async function validateToolCall(args) {
1805
+ const { event, tools } = args;
1806
+ const toolCall = {
1807
+ type: "tool-call",
1808
+ toolCallId: event.toolCallId,
1809
+ toolName: event.toolName,
1810
+ input: event.input,
1811
+ ...event.providerExecuted !== void 0 ? { providerExecuted: event.providerExecuted } : {},
1812
+ ...event.providerMetadata !== void 0 ? { providerMetadata: event.providerMetadata } : {}
1813
+ };
1814
+ const parsed = await parseToolCall({
1815
+ toolCall,
1816
+ tools,
1817
+ repairToolCall: void 0,
1818
+ refineToolInput: void 0,
1819
+ instructions: void 0,
1820
+ messages: []
1821
+ });
1822
+ return parsed;
1823
+ }
1824
+ function promptToText(prompt) {
1825
+ if (typeof prompt === "string") return prompt;
1826
+ const content = prompt.content;
1827
+ if (typeof content === "string") return content;
1828
+ if (Array.isArray(content)) {
1829
+ return content.filter(
1830
+ (part) => typeof part === "object" && part != null && part.type === "text"
1831
+ ).map((part) => part.text).join("");
1832
+ }
1833
+ return "";
1834
+ }
1835
+
1836
+ // src/agent/harness-agent-session.ts
1837
+ var HarnessAgentSession = class {
1838
+ constructor(options) {
1839
+ this.pendingToolApprovals = /* @__PURE__ */ new Map();
1840
+ this.sessionState = "active";
1841
+ this.turnSequence = 0;
1842
+ this.activeTurnSequence = 0;
1843
+ var _a3, _b3;
1844
+ this.sessionId = options.sessionId;
1845
+ this.harness = options.harness;
1846
+ this.underlyingSession = options.underlyingSession;
1847
+ this.sandboxSession = options.sandboxSession;
1848
+ this.sandboxProvider = options.sandboxProvider;
1849
+ this.leasedBridgePort = options.leasedBridgePort;
1850
+ this.sessionWorkDir = options.sessionWorkDir;
1851
+ this.toolApproval = options.toolApproval;
1852
+ for (const approval of (_a3 = options.pendingToolApprovals) != null ? _a3 : []) {
1853
+ this.pendingToolApprovals.set(approval.approvalId, approval);
1854
+ }
1855
+ this.turnState = (_b3 = options.turnState) != null ? _b3 : this.pendingToolApprovals.size > 0 ? "awaiting-approval" : "idle";
1856
+ this.isResume = options.underlyingSession.isResume;
1857
+ }
1858
+ /**
1859
+ * Active network sandbox session.
1860
+ *
1861
+ * @internal — accessed by session turn and lifecycle drivers.
1862
+ */
1863
+ getSandboxSession() {
1864
+ if (this.sessionState !== "active" || this.sandboxSession == null) {
1865
+ throw new Error(
1866
+ `Harness session ${this.sessionId} has ended and cannot be reused.`
1867
+ );
1868
+ }
1869
+ return this.sandboxSession;
1870
+ }
1871
+ /**
1872
+ * Working directory the agent runs in for this session. Used to strip the
1873
+ * prefix from absolute paths in stream events before they reach consumers.
1874
+ *
1875
+ * @internal — accessed by session turn drivers.
1876
+ */
1877
+ getSessionWorkDir() {
1878
+ return this.sessionWorkDir;
1879
+ }
1880
+ promptTurn(options) {
1881
+ const session = this.requireReusableSession();
1882
+ this.requirePromptableTurn();
1883
+ const sandboxSession = this.getSandboxSession();
1884
+ const turnId = this.startTrackedTurn();
1885
+ try {
1886
+ const turn = runPrompt({
1887
+ harness: this.harness,
1888
+ session,
1889
+ prompt: options.prompt,
1890
+ instructions: options.instructions,
1891
+ tools: options.tools,
1892
+ activeTools: options.activeTools,
1893
+ toolSpecs: options.toolSpecs,
1894
+ builtinToolFiltering: options.builtinToolFiltering,
1895
+ sandboxSession: sandboxSession.restricted(),
1896
+ sessionWorkDir: this.sessionWorkDir,
1897
+ runtimeContext: options.runtimeContext,
1898
+ abortSignal: options.abortSignal,
1899
+ telemetry: options.telemetry,
1900
+ toolApproval: this.toolApproval,
1901
+ pendingToolApprovals: this.getPendingToolApprovals(),
1902
+ onPendingToolApproval: (approval) => {
1903
+ this.pendingToolApprovals.set(approval.approvalId, approval);
1904
+ this.markAwaitingApprovalIfActive();
1905
+ },
1906
+ onToolApprovalSettled: (approvalId) => {
1907
+ this.pendingToolApprovals.delete(approvalId);
1908
+ },
1909
+ onTurnFinished: () => {
1910
+ this.finishTrackedTurn({ turnId });
1911
+ }
1912
+ });
1913
+ this.trackTurnCompletion({ done: turn.done, turnId });
1914
+ return turn;
1915
+ } catch (error) {
1916
+ this.finishTrackedTurn({ turnId });
1917
+ throw error;
1918
+ }
1919
+ }
1920
+ continueTurn(options) {
1921
+ const session = this.requireReusableSession();
1922
+ this.requireContinuableTurn();
1923
+ const sandboxSession = this.getSandboxSession();
1924
+ const turnId = this.startTrackedTurn();
1925
+ try {
1926
+ const turn = runPrompt({
1927
+ harness: this.harness,
1928
+ session,
1929
+ mode: "continue",
1930
+ instructions: options.instructions,
1931
+ tools: options.tools,
1932
+ activeTools: options.activeTools,
1933
+ toolSpecs: options.toolSpecs,
1934
+ builtinToolFiltering: options.builtinToolFiltering,
1935
+ sandboxSession: sandboxSession.restricted(),
1936
+ sessionWorkDir: this.sessionWorkDir,
1937
+ runtimeContext: options.runtimeContext,
1938
+ abortSignal: options.abortSignal,
1939
+ telemetry: options.telemetry,
1940
+ toolApproval: this.toolApproval,
1941
+ pendingToolApprovals: this.getPendingToolApprovals(),
1942
+ toolApprovalContinuations: options.toolApprovalContinuations,
1943
+ onPendingToolApproval: (approval) => {
1944
+ this.pendingToolApprovals.set(approval.approvalId, approval);
1945
+ this.markAwaitingApprovalIfActive();
1946
+ },
1947
+ onToolApprovalSettled: (approvalId) => {
1948
+ this.pendingToolApprovals.delete(approvalId);
1949
+ },
1950
+ onTurnFinished: () => {
1951
+ this.finishTrackedTurn({ turnId });
1952
+ }
1953
+ });
1954
+ this.trackTurnCompletion({ done: turn.done, turnId });
1955
+ return turn;
1956
+ } catch (error) {
1957
+ this.finishTrackedTurn({ turnId });
1958
+ throw error;
1959
+ }
1960
+ }
1961
+ /**
1962
+ * Ask the underlying runtime to compact its context. The runtime performs
1963
+ * the compaction itself; when it completes, a `compaction` part appears on
1964
+ * the active (or next) turn's stream. Safe to call between turns for
1965
+ * runtimes whose compaction is session-scoped (e.g. Pi).
1966
+ *
1967
+ * Throws `HarnessCapabilityUnsupportedError` for harnesses that cannot
1968
+ * trigger compaction manually (e.g. Codex, which still auto-compacts under
1969
+ * the hood). Throws if the session has ended.
1970
+ */
1971
+ async compact(customInstructions) {
1972
+ await this.requireReusableSession().doCompact(customInstructions);
1973
+ }
1974
+ /**
1975
+ * Park the session, returning a payload the caller can persist and later
1976
+ * pass to `agent.createSession({ sessionId, resumeFrom })` to reconnect.
1977
+ * The runtime and sandbox keep running; this local session handle becomes
1978
+ * unusable.
1979
+ */
1980
+ async detach() {
1981
+ if (this.sessionState !== "active" || this.underlyingSession == null) {
1982
+ throw new Error(
1983
+ `Harness session ${this.sessionId} is not active and cannot be detached.`
1984
+ );
1985
+ }
1986
+ const session = this.underlyingSession;
1987
+ try {
1988
+ if (this.turnState !== "idle") {
1989
+ return this.toResumeStateWithContinuation({
1990
+ continueFrom: await this.suspendCurrentTurn({ session })
1991
+ });
1992
+ }
1993
+ const raw = await session.doDetach();
1994
+ const validated = await validateLifecycleStateData({
1995
+ harness: this.harness,
1996
+ state: raw,
1997
+ expectedType: "resume-session"
1998
+ });
1999
+ return validated;
2000
+ } finally {
2001
+ this.endLocalHandle({
2002
+ sessionState: "detached",
2003
+ releasePortLease: false
2004
+ });
2005
+ }
2006
+ }
2007
+ /**
2008
+ * Persist enough state to resume later, then stop the runtime and sandbox.
2009
+ * Returns the resume state for a future
2010
+ * `agent.createSession({ sessionId, resumeFrom })` call.
2011
+ */
2012
+ async stop() {
2013
+ if (this.sessionState !== "active" || this.underlyingSession == null) {
2014
+ throw new Error(
2015
+ `Harness session ${this.sessionId} is not active and cannot be stopped.`
2016
+ );
2017
+ }
2018
+ const session = this.underlyingSession;
2019
+ const sandboxSession = this.getSandboxSession();
2020
+ try {
2021
+ if (this.turnState !== "idle") {
2022
+ return this.toResumeStateWithContinuation({
2023
+ continueFrom: await this.suspendCurrentTurn({ session })
2024
+ });
2025
+ }
2026
+ const raw = await session.doStop();
2027
+ const validated = await validateLifecycleStateData({
2028
+ harness: this.harness,
2029
+ state: raw,
2030
+ expectedType: "resume-session"
2031
+ });
2032
+ return validated;
2033
+ } finally {
2034
+ this.endLocalHandle({
2035
+ sessionState: "stopped",
2036
+ releasePortLease: true
2037
+ });
2038
+ await Promise.resolve(sandboxSession.stop()).catch(() => {
2039
+ });
2040
+ }
2041
+ }
2042
+ /**
2043
+ * Stop the runtime and discard resumability. The sandbox is destroyed when
2044
+ * the provider supports destruction; otherwise it is stopped.
2045
+ */
2046
+ async destroy() {
2047
+ var _a3, _b3;
2048
+ if (this.sessionState !== "active") return;
2049
+ const session = this.underlyingSession;
2050
+ const sandboxSession = this.getSandboxSession();
2051
+ this.endLocalHandle({ sessionState: "destroyed", releasePortLease: true });
2052
+ if (session != null) {
2053
+ await Promise.resolve(session.doDestroy()).catch(() => {
2054
+ });
2055
+ }
2056
+ await Promise.resolve(
2057
+ (_b3 = (_a3 = sandboxSession.destroy) == null ? void 0 : _a3.call(sandboxSession)) != null ? _b3 : sandboxSession.stop()
2058
+ ).catch(() => {
2059
+ });
2060
+ }
2061
+ /**
2062
+ * Gracefully freeze the active turn at the slice boundary and return the
2063
+ * continuation payload, **leaving the sandbox/runtime running** so the next
2064
+ * process can continue. Resolves once the in-flight `stream()` /
2065
+ * `continueStream()` has cleanly wound down at a precise cursor (see
2066
+ * `doSuspendTurn`).
2067
+ *
2068
+ * After this call the session is detached. This in-process handle no
2069
+ * longer drives turns; a future slice creates a fresh session from the
2070
+ * returned state. The sandbox is **not** stopped and no port lease is
2071
+ * released, because bridge-backed adapters may still have a live bridge on
2072
+ * that port.
2073
+ */
2074
+ async suspendTurn() {
2075
+ if (this.sessionState !== "active" || this.underlyingSession == null) {
2076
+ throw new Error(
2077
+ `Harness session ${this.sessionId} is not active and cannot be suspended.`
2078
+ );
2079
+ }
2080
+ if (this.turnState === "idle") {
2081
+ throw new Error(
2082
+ `Harness session ${this.sessionId} has no unfinished turn to suspend.`
2083
+ );
2084
+ }
2085
+ const session = this.underlyingSession;
2086
+ try {
2087
+ return await this.suspendCurrentTurn({ session });
2088
+ } finally {
2089
+ this.endLocalHandle({
2090
+ sessionState: "detached",
2091
+ releasePortLease: false
2092
+ });
2093
+ }
2094
+ }
2095
+ getPendingToolApprovals() {
2096
+ return Array.from(this.pendingToolApprovals.values());
2097
+ }
2098
+ addPendingToolApprovals(state) {
2099
+ const pendingToolApprovals = this.getPendingToolApprovals();
2100
+ if (pendingToolApprovals.length === 0) {
2101
+ return {
2102
+ type: state.type,
2103
+ harnessId: state.harnessId,
2104
+ specificationVersion: state.specificationVersion,
2105
+ data: state.data
2106
+ };
2107
+ }
2108
+ return {
2109
+ ...state,
2110
+ pendingToolApprovals
2111
+ };
2112
+ }
2113
+ async suspendCurrentTurn(options) {
2114
+ const raw = await options.session.doSuspendTurn();
2115
+ const validated = await validateLifecycleStateData({
2116
+ harness: this.harness,
2117
+ state: raw,
2118
+ expectedType: "continue-turn"
2119
+ });
2120
+ this.turnState = "suspended";
2121
+ return this.addPendingToolApprovals(validated);
2122
+ }
2123
+ toResumeStateWithContinuation(options) {
2124
+ const { continueFrom } = options;
2125
+ return {
2126
+ type: "resume-session",
2127
+ harnessId: continueFrom.harnessId,
2128
+ specificationVersion: continueFrom.specificationVersion,
2129
+ data: continueFrom.data,
2130
+ continueFrom
2131
+ };
2132
+ }
2133
+ requirePromptableTurn() {
2134
+ if (this.turnState === "idle") return;
2135
+ if (this.turnState === "running") {
2136
+ throw new Error(
2137
+ `Harness session ${this.sessionId} already has a turn in progress.`
2138
+ );
2139
+ }
2140
+ throw new Error(
2141
+ `Harness session ${this.sessionId} has an unfinished turn and must be continued before accepting a new prompt.`
2142
+ );
2143
+ }
2144
+ requireContinuableTurn() {
2145
+ if (this.turnState === "awaiting-approval" || this.turnState === "suspended") {
2146
+ return;
2147
+ }
2148
+ if (this.turnState === "running") {
2149
+ throw new Error(
2150
+ `Harness session ${this.sessionId} already has a turn in progress.`
2151
+ );
2152
+ }
2153
+ throw new Error(
2154
+ `Harness session ${this.sessionId} has no unfinished turn to continue.`
2155
+ );
2156
+ }
2157
+ markAwaitingApprovalIfActive() {
2158
+ if (this.sessionState === "active") {
2159
+ this.turnState = "awaiting-approval";
2160
+ }
2161
+ }
2162
+ startTrackedTurn() {
2163
+ const turnId = ++this.turnSequence;
2164
+ this.activeTurnSequence = turnId;
2165
+ this.turnState = "running";
2166
+ return turnId;
2167
+ }
2168
+ trackTurnCompletion(options) {
2169
+ void Promise.resolve(options.done).finally(() => {
2170
+ this.finishTrackedTurn({ turnId: options.turnId });
2171
+ }).catch(() => {
2172
+ });
2173
+ }
2174
+ finishTrackedTurn(options) {
2175
+ if (this.sessionState !== "active") return;
2176
+ if (this.activeTurnSequence !== options.turnId) return;
2177
+ this.turnState = this.pendingToolApprovals.size > 0 ? "awaiting-approval" : "idle";
2178
+ }
2179
+ endLocalHandle(options) {
2180
+ this.sessionState = options.sessionState;
2181
+ this.underlyingSession = void 0;
2182
+ this.sandboxSession = void 0;
2183
+ if (options.releasePortLease) {
2184
+ this.releasePortLease();
2185
+ }
2186
+ }
2187
+ releasePortLease() {
2188
+ if (this.leasedBridgePort == null) return;
2189
+ releaseBridgePort({
2190
+ poolKey: this.sandboxProvider,
2191
+ sessionId: this.sessionId
2192
+ });
2193
+ this.leasedBridgePort = void 0;
2194
+ }
2195
+ requireReusableSession() {
2196
+ if (this.sessionState !== "active" || this.underlyingSession == null) {
2197
+ throw new Error(
2198
+ `Harness session ${this.sessionId} has ended and cannot be reused.`
2199
+ );
2200
+ }
2201
+ return this.underlyingSession;
2202
+ }
2203
+ };
2204
+
2205
+ // src/agent/harness-agent-tool-approval-continuation.ts
2206
+ function collectHarnessAgentToolApprovalContinuations(input) {
2207
+ const lastMessage = input.messages.at(-1);
2208
+ if ((lastMessage == null ? void 0 : lastMessage.role) !== "tool") return [];
2209
+ const toolCallsByToolCallId = /* @__PURE__ */ new Map();
2210
+ const approvalRequestsByApprovalId = /* @__PURE__ */ new Map();
2211
+ for (const message of input.messages) {
2212
+ if (message.role !== "assistant" || typeof message.content === "string") {
2213
+ continue;
2214
+ }
2215
+ for (const part of message.content) {
2216
+ if (part.type === "tool-call") {
2217
+ toolCallsByToolCallId.set(part.toolCallId, {
2218
+ type: "tool-call",
2219
+ toolCallId: part.toolCallId,
2220
+ toolName: part.toolName,
2221
+ input: part.input,
2222
+ ...part.providerExecuted !== void 0 ? { providerExecuted: part.providerExecuted } : {}
2223
+ });
2224
+ } else if (part.type === "tool-approval-request") {
2225
+ approvalRequestsByApprovalId.set(part.approvalId, part);
2226
+ }
2227
+ }
2228
+ }
2229
+ const toolResultIds = /* @__PURE__ */ new Set();
2230
+ for (const part of lastMessage.content) {
2231
+ if (part.type === "tool-result") {
2232
+ toolResultIds.add(part.toolCallId);
2233
+ }
2234
+ }
2235
+ const continuations = [];
2236
+ for (const part of lastMessage.content) {
2237
+ if (part.type !== "tool-approval-response") continue;
2238
+ const approvalRequest = approvalRequestsByApprovalId.get(part.approvalId);
2239
+ if (approvalRequest == null) {
2240
+ throw new HarnessError({
2241
+ message: `Tool approval response '${part.approvalId}' does not match a prior tool approval request.`
2242
+ });
2243
+ }
2244
+ if (toolResultIds.has(approvalRequest.toolCallId)) continue;
2245
+ const toolCall = toolCallsByToolCallId.get(approvalRequest.toolCallId);
2246
+ if (toolCall == null) {
2247
+ throw new HarnessError({
2248
+ message: `Tool approval request '${approvalRequest.approvalId}' references unknown tool call '${approvalRequest.toolCallId}'.`
2249
+ });
2250
+ }
2251
+ continuations.push({
2252
+ approvalResponse: part,
2253
+ toolCall
2254
+ });
2255
+ }
2256
+ return continuations;
2257
+ }
2258
+
2259
+ // src/agent/internal/bootstrap-recipe.ts
2260
+ var BOOTSTRAP_SCHEMA_VERSION = 1;
2261
+ async function hashHarnessBootstrap(recipe) {
2262
+ const encoder = new TextEncoder();
2263
+ const chunks = [];
2264
+ const pushString = (value) => {
2265
+ chunks.push(encoder.encode(value));
2266
+ chunks.push(encoder.encode("\0"));
2267
+ };
2268
+ pushString(recipe.harnessId);
2269
+ pushString(recipe.bootstrapDir);
2270
+ const sortedFiles = [...recipe.files].sort(
2271
+ (a, b) => a.path.localeCompare(b.path)
2272
+ );
2273
+ for (const file of sortedFiles) {
2274
+ pushString(file.path);
2275
+ pushString(file.content);
2276
+ }
2277
+ pushString(JSON.stringify(recipe.commands));
2278
+ pushString(String(BOOTSTRAP_SCHEMA_VERSION));
2279
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
2280
+ const buffer = new Uint8Array(totalLength);
2281
+ let offset = 0;
2282
+ for (const chunk of chunks) {
2283
+ buffer.set(chunk, offset);
2284
+ offset += chunk.length;
2285
+ }
2286
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
2287
+ const bytes = new Uint8Array(digest);
2288
+ let hex = "";
2289
+ for (let i = 0; i < 8; i++) {
2290
+ hex += bytes[i].toString(16).padStart(2, "0");
2291
+ }
2292
+ return hex;
2293
+ }
2294
+ function bootstrapMarkerPath(recipe, identity) {
2295
+ return `${recipe.bootstrapDir}/.bootstrap-${identity}.ok`;
2296
+ }
2297
+ async function applyBootstrapRecipe(session, recipe, identity, options) {
2298
+ const markerPath = bootstrapMarkerPath(recipe, identity);
2299
+ const existingMarker = await session.readTextFile({
2300
+ path: markerPath,
2301
+ abortSignal: options == null ? void 0 : options.abortSignal
2302
+ });
2303
+ if (existingMarker !== null) {
2304
+ return;
2305
+ }
2306
+ for (const file of recipe.files) {
2307
+ await session.writeTextFile({
2308
+ path: file.path,
2309
+ content: file.content,
2310
+ abortSignal: options == null ? void 0 : options.abortSignal
2311
+ });
2312
+ }
2313
+ for (const cmd of recipe.commands) {
2314
+ const result = await session.run({
2315
+ command: cmd.command,
2316
+ workingDirectory: cmd.workingDirectory,
2317
+ abortSignal: options == null ? void 0 : options.abortSignal
2318
+ });
2319
+ if (result.exitCode !== 0) {
2320
+ throw new Error(
2321
+ `Bootstrap command failed for harness '${recipe.harnessId}' (exit ${result.exitCode}): ${cmd.command}
2322
+ ${result.stderr || result.stdout}`
2323
+ );
2324
+ }
2325
+ }
2326
+ await session.writeTextFile({
2327
+ path: markerPath,
2328
+ content: "",
2329
+ abortSignal: options == null ? void 0 : options.abortSignal
2330
+ });
2331
+ }
2332
+
2333
+ // src/agent/internal/sandbox-bootstrap.ts
2334
+ import { posix } from "path";
2335
+ var SANDBOX_BOOTSTRAP_IDENTITY_VERSION = 1;
2336
+ function validateSandboxBootstrapSettings(settings) {
2337
+ if (settings.onBootstrap == null !== (settings.bootstrapHash == null)) {
2338
+ throw new Error(
2339
+ "HarnessAgent: `sandboxConfig.onBootstrap` and `sandboxConfig.bootstrapHash` must be provided together."
2340
+ );
2341
+ }
2342
+ if (settings.workDir != null) {
2343
+ normalizeSandboxWorkDir(settings.workDir);
2344
+ }
2345
+ }
2346
+ function normalizeSandboxWorkDir(workDir) {
2347
+ if (workDir.length === 0) {
2348
+ throw new Error("HarnessAgent: `sandboxConfig.workDir` must not be empty.");
2349
+ }
2350
+ if (workDir.includes("\0")) {
2351
+ throw new Error(
2352
+ "HarnessAgent: `sandboxConfig.workDir` must not contain NUL."
2353
+ );
2354
+ }
2355
+ if (workDir.includes("\\")) {
2356
+ throw new Error(
2357
+ "HarnessAgent: `sandboxConfig.workDir` must use POSIX path separators."
2358
+ );
2359
+ }
2360
+ if (posix.isAbsolute(workDir)) {
2361
+ throw new Error("HarnessAgent: `sandboxConfig.workDir` must be relative.");
2362
+ }
2363
+ const normalized = posix.normalize(workDir);
2364
+ if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
2365
+ throw new Error(
2366
+ "HarnessAgent: `sandboxConfig.workDir` must stay inside the sandbox default working directory."
2367
+ );
2368
+ }
2369
+ return normalized;
2370
+ }
2371
+ function resolveSessionWorkDir({
2372
+ defaultWorkingDirectory,
2373
+ harnessId,
2374
+ sessionId,
2375
+ workDir
2376
+ }) {
2377
+ return joinSandboxPath({
2378
+ base: defaultWorkingDirectory,
2379
+ path: workDir != null ? workDir : `${harnessId}-${sessionId}`
2380
+ });
2381
+ }
2382
+ async function createSandboxBootstrapPlan({
2383
+ recipe,
2384
+ settings
2385
+ }) {
2386
+ const workDir = settings.workDir == null ? void 0 : normalizeSandboxWorkDir(settings.workDir);
2387
+ const recipeIdentity = recipe == null ? void 0 : await hashHarnessBootstrap(recipe);
2388
+ const hasCallerBootstrap = settings.onBootstrap != null;
2389
+ const needsCombinedIdentity = hasCallerBootstrap || recipeIdentity != null && workDir != null;
2390
+ const identity = needsCombinedIdentity && (recipeIdentity != null || hasCallerBootstrap) ? await hashSandboxBootstrapIdentity({
2391
+ recipeIdentity,
2392
+ bootstrapHash: settings.bootstrapHash,
2393
+ workDir
2394
+ }) : recipeIdentity;
2395
+ return {
2396
+ ...recipe != null ? { recipe } : {},
2397
+ ...recipeIdentity != null ? { recipeIdentity } : {},
2398
+ ...identity != null ? { identity } : {},
2399
+ ...workDir != null ? { workDir } : {},
2400
+ ...recipe != null || settings.onBootstrap != null ? {
2401
+ onFirstCreate: (session, opts) => runSandboxBootstrap({
2402
+ session,
2403
+ recipe,
2404
+ recipeIdentity,
2405
+ workDir,
2406
+ onBootstrap: settings.onBootstrap,
2407
+ abortSignal: opts.abortSignal
2408
+ })
2409
+ } : {}
2410
+ };
2411
+ }
2412
+ async function runSandboxBootstrap({
2413
+ session,
2414
+ recipe,
2415
+ recipeIdentity,
2416
+ workDir,
2417
+ onBootstrap,
2418
+ abortSignal
2419
+ }) {
2420
+ if (recipe != null && recipeIdentity != null) {
2421
+ await applyBootstrapRecipe(session, recipe, recipeIdentity, {
2422
+ abortSignal
2423
+ });
2424
+ }
2425
+ if (onBootstrap == null) return;
2426
+ const defaultWorkingDirectory = await resolveDefaultWorkingDirectory({
2427
+ session,
2428
+ abortSignal
2429
+ });
2430
+ const bootstrapWorkDir = workDir == null ? defaultWorkingDirectory : joinSandboxPath({
2431
+ base: defaultWorkingDirectory,
2432
+ path: workDir
2433
+ });
2434
+ await ensureSandboxDirectory({
2435
+ session,
2436
+ workDir: bootstrapWorkDir,
2437
+ abortSignal
2438
+ });
2439
+ await onBootstrap({ session, workDir: bootstrapWorkDir, abortSignal });
2440
+ }
2441
+ async function resolveDefaultWorkingDirectory({
2442
+ session,
2443
+ abortSignal
2444
+ }) {
2445
+ const result = await session.run({
2446
+ command: "pwd",
2447
+ abortSignal
2448
+ });
2449
+ if (result.exitCode !== 0) {
2450
+ throw new Error(
2451
+ `Failed to resolve sandbox default working directory (exit ${result.exitCode}): ${result.stderr || result.stdout}`
2452
+ );
2453
+ }
2454
+ const cwd = result.stdout.trim();
2455
+ if (!posix.isAbsolute(cwd)) {
2456
+ throw new Error(
2457
+ `Failed to resolve sandbox default working directory: expected an absolute path, got ${JSON.stringify(cwd)}.`
2458
+ );
2459
+ }
2460
+ return cwd === "/" ? cwd : cwd.replace(/\/+$/, "");
2461
+ }
2462
+ async function ensureSandboxDirectory({
2463
+ session,
2464
+ workDir,
2465
+ abortSignal
2466
+ }) {
2467
+ const result = await session.run({
2468
+ command: 'mkdir -p "$WORK_DIR"',
2469
+ env: { WORK_DIR: workDir },
2470
+ abortSignal
2471
+ });
2472
+ if (result.exitCode !== 0) {
2473
+ throw new Error(
2474
+ `Failed to create sandbox work directory ${workDir} (exit ${result.exitCode}): ${result.stderr || result.stdout}`
2475
+ );
2476
+ }
2477
+ }
2478
+ async function hashSandboxBootstrapIdentity({
2479
+ recipeIdentity,
2480
+ bootstrapHash,
2481
+ workDir
2482
+ }) {
2483
+ const encoder = new TextEncoder();
2484
+ const chunks = [];
2485
+ const pushString = (value) => {
2486
+ chunks.push(encoder.encode(value));
2487
+ chunks.push(encoder.encode("\0"));
2488
+ };
2489
+ pushString(String(SANDBOX_BOOTSTRAP_IDENTITY_VERSION));
2490
+ pushString(recipeIdentity != null ? recipeIdentity : "");
2491
+ pushString(bootstrapHash != null ? bootstrapHash : "");
2492
+ pushString(workDir != null ? workDir : "");
2493
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
2494
+ const buffer = new Uint8Array(totalLength);
2495
+ let offset = 0;
2496
+ for (const chunk of chunks) {
2497
+ buffer.set(chunk, offset);
2498
+ offset += chunk.length;
2499
+ }
2500
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
2501
+ const bytes = new Uint8Array(digest);
2502
+ let hex = "";
2503
+ for (let i = 0; i < 8; i++) {
2504
+ hex += bytes[i].toString(16).padStart(2, "0");
2505
+ }
2506
+ return hex;
2507
+ }
2508
+ function joinSandboxPath({
2509
+ base,
2510
+ path
2511
+ }) {
2512
+ return posix.join(base, path);
2513
+ }
2514
+
2515
+ // src/agent/internal/resolve-observability.ts
2516
+ import { asArray } from "@ai-sdk/provider-utils";
2517
+ var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
2518
+ function parseList(value) {
2519
+ if (!value) return void 0;
2520
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
2521
+ return items.length > 0 ? items : void 0;
2522
+ }
2523
+ function resolveDebugConfig(debug, env = process.env) {
2524
+ var _a3, _b3, _c, _d;
2525
+ const enabled = (_b3 = debug == null ? void 0 : debug.enabled) != null ? _b3 : ENV_TRUTHY.has(((_a3 = env.HARNESS_DEBUG) != null ? _a3 : "").toLowerCase());
2526
+ const level = (_d = (_c = debug == null ? void 0 : debug.level) != null ? _c : env.HARNESS_DEBUG_LEVEL) != null ? _d : "debug";
2527
+ const subsystems = (debug == null ? void 0 : debug.subsystems) ? [...debug.subsystems] : parseList(env.HARNESS_DEBUG_SUBSYSTEMS);
2528
+ return { enabled, level, subsystems };
2529
+ }
2530
+ function toHarnessDiagnostic(d) {
2531
+ return {
2532
+ level: d.level,
2533
+ message: d.message,
2534
+ subsystem: d.subsystem,
2535
+ kind: d.kind,
2536
+ source: d.source,
2537
+ stream: d.stream,
2538
+ attrs: d.attrs,
2539
+ error: d.error,
2540
+ sessionId: d.sessionId,
2541
+ timestamp: d.timestamp
2542
+ };
2543
+ }
2544
+ function formatForStderr(d) {
2545
+ var _a3;
2546
+ const parts = [`[harness:${d.level}]`, d.subsystem, d.message].filter(
2547
+ Boolean
2548
+ );
2549
+ let line = parts.join(" ");
2550
+ if (d.error) {
2551
+ line += ` (${(_a3 = d.error.name) != null ? _a3 : "Error"}: ${d.error.message})`;
2552
+ }
2553
+ return `${line}
2554
+ `;
2555
+ }
2556
+ function buildObservability(options) {
2557
+ var _a3;
2558
+ const resolved = resolveDebugConfig(options.settings.debug);
2559
+ if (!resolved.enabled) return void 0;
2560
+ const onLog = options.settings.onLog;
2561
+ const integrations = ((_a3 = options.settings.telemetry) == null ? void 0 : _a3.integrations) ? asArray(options.settings.telemetry.integrations) : [];
2562
+ const diagnosticConsumers = integrations.filter(
2563
+ (integration) => typeof integration.ingestDiagnostic === "function"
2564
+ );
2565
+ const report = (emitted) => {
2566
+ var _a4;
2567
+ const diagnostic = toHarnessDiagnostic(emitted);
2568
+ try {
2569
+ process.stderr.write(formatForStderr(diagnostic));
2570
+ } catch (e) {
2571
+ }
2572
+ onLog == null ? void 0 : onLog(diagnostic);
2573
+ for (const consumer of diagnosticConsumers) {
2574
+ (_a4 = consumer.ingestDiagnostic) == null ? void 0 : _a4.call(consumer, diagnostic);
2575
+ }
2576
+ };
2577
+ return {
2578
+ debug: {
2579
+ enabled: true,
2580
+ level: resolved.level,
2581
+ ...resolved.subsystems ? { subsystems: resolved.subsystems } : {}
2582
+ },
2583
+ report
2584
+ };
2585
+ }
2586
+
2587
+ // src/agent/internal/tool-filtering.ts
2588
+ import { NoSuchToolError } from "ai";
2589
+ function resolveHarnessAgentToolFiltering(input) {
2590
+ if (input.activeTools !== void 0 && input.inactiveTools !== void 0) {
2591
+ throw new Error(
2592
+ "HarnessAgent: pass either `activeTools` or `inactiveTools`, not both."
2593
+ );
2594
+ }
2595
+ const allToolNames = Object.keys(input.allTools);
2596
+ const activeTools = dedupeToolNames({ toolNames: input.activeTools });
2597
+ const inactiveTools = dedupeToolNames({ toolNames: input.inactiveTools });
2598
+ validateToolNames({
2599
+ requestedToolNames: activeTools != null ? activeTools : inactiveTools,
2600
+ availableToolNames: allToolNames
2601
+ });
2602
+ const userToolNames = Object.keys(input.userTools);
2603
+ const activeUserToolNames = activeTools != null ? userToolNames.filter((name3) => activeTools.includes(name3)) : inactiveTools != null ? userToolNames.filter((name3) => !inactiveTools.includes(name3)) : userToolNames;
2604
+ const builtinToolNames = Object.keys(input.harness.builtinTools);
2605
+ const disabledBuiltinToolNames = activeTools != null ? builtinToolNames.filter((name3) => !activeTools.includes(name3)) : inactiveTools != null ? builtinToolNames.filter((name3) => inactiveTools.includes(name3)) : [];
2606
+ const builtinToolFiltering = disabledBuiltinToolNames.length > 0 ? activeTools != null ? {
2607
+ mode: "allow",
2608
+ toolNames: builtinToolNames.filter(
2609
+ (name3) => activeTools.includes(name3)
2610
+ )
2611
+ } : { mode: "deny", toolNames: disabledBuiltinToolNames } : void 0;
2612
+ if (builtinToolFiltering != null && input.harness.supportsBuiltinToolFiltering !== true && input.harness.supportsBuiltinToolApprovals !== true) {
2613
+ throw new HarnessCapabilityUnsupportedError({
2614
+ message: `Harness '${input.harness.harnessId}' does not support built-in tool filtering controls.`,
2615
+ harnessId: input.harness.harnessId
2616
+ });
2617
+ }
2618
+ return {
2619
+ activeUserTools: filterToolSet({
2620
+ tools: input.userTools,
2621
+ toolNames: activeUserToolNames
2622
+ }),
2623
+ ...builtinToolFiltering != null ? { builtinToolFiltering } : {}
2624
+ };
2625
+ }
2626
+ function dedupeToolNames(input) {
2627
+ return input.toolNames == null ? void 0 : Array.from(new Set(input.toolNames));
2628
+ }
2629
+ function validateToolNames(input) {
2630
+ if (input.requestedToolNames == null) return;
2631
+ for (const toolName of input.requestedToolNames) {
2632
+ if (!input.availableToolNames.includes(toolName)) {
2633
+ throw new NoSuchToolError({
2634
+ toolName,
2635
+ availableTools: [...input.availableToolNames]
2636
+ });
2637
+ }
2638
+ }
2639
+ }
2640
+ function filterToolSet(input) {
2641
+ const allowed = new Set(input.toolNames);
2642
+ return Object.fromEntries(
2643
+ Object.entries(input.tools).filter(([name3]) => allowed.has(name3))
2644
+ );
2645
+ }
2646
+
2647
+ // src/agent/harness-agent.ts
2648
+ var HarnessAgent = class {
2649
+ constructor(settings) {
2650
+ this.version = "agent-v1";
2651
+ var _a3;
2652
+ const sandboxConfig = resolveSandboxConfig(settings);
2653
+ validateSandboxBootstrapSettings(sandboxConfig);
2654
+ this.settings = settings;
2655
+ this.sandboxConfig = sandboxConfig;
2656
+ this.id = settings.id;
2657
+ const userTools = (_a3 = settings.tools) != null ? _a3 : {};
2658
+ this.permissionMode = resolvePermissionMode({
2659
+ permissionMode: settings.permissionMode
2660
+ });
2661
+ const tools = {
2662
+ ...settings.harness.builtinTools,
2663
+ ...userTools
2664
+ };
2665
+ const toolFiltering = resolveHarnessAgentToolFiltering({
2666
+ harness: settings.harness,
2667
+ userTools,
2668
+ allTools: tools,
2669
+ activeTools: settings.activeTools,
2670
+ inactiveTools: settings.inactiveTools
2671
+ });
2672
+ this.activeUserTools = toolFiltering.activeUserTools;
2673
+ this.builtinToolFiltering = toolFiltering.builtinToolFiltering;
2674
+ if (Object.keys(settings.harness.builtinTools).length > 0 && permissionModeNeedsBuiltinSupport({
2675
+ permissionMode: this.permissionMode
2676
+ }) && settings.harness.supportsBuiltinToolApprovals !== true) {
2677
+ throw new HarnessCapabilityUnsupportedError({
2678
+ message: `Harness '${settings.harness.harnessId}' does not support built-in tool approval requests; use permissionMode: 'allow-all'.`,
2679
+ harnessId: settings.harness.harnessId
2680
+ });
2681
+ }
2682
+ this.tools = tools;
2683
+ }
2684
+ /** Identifier of the harness backing this agent. */
2685
+ get harnessId() {
2686
+ return this.settings.harness.harnessId;
2687
+ }
2688
+ /**
2689
+ * Start a fresh session, or resume from state previously returned by
2690
+ * `session.detach()` or `session.stop()`. The returned
2691
+ * `HarnessAgentSession` must be passed to subsequent `generate` / `stream`
2692
+ * calls; end it with `session.detach()`, `session.stop()`, or
2693
+ * `session.destroy()`.
2694
+ */
2695
+ async createSession(options) {
2696
+ var _a3;
2697
+ const sessionId = (_a3 = options == null ? void 0 : options.sessionId) != null ? _a3 : generateId5();
2698
+ const resumeFrom = options == null ? void 0 : options.resumeFrom;
2699
+ const continueFrom = options == null ? void 0 : options.continueFrom;
2700
+ const abortSignal = options == null ? void 0 : options.abortSignal;
2701
+ const harness = this.settings.harness;
2702
+ const sandboxProvider = this.settings.sandbox;
2703
+ if (resumeFrom != null && continueFrom != null) {
2704
+ throw new Error(
2705
+ "HarnessAgent.createSession: pass either `resumeFrom` or `continueFrom`, not both."
2706
+ );
2707
+ }
2708
+ let validatedResumeFrom;
2709
+ if (resumeFrom != null) {
2710
+ validatedResumeFrom = await validateLifecycleStateData({
2711
+ harness,
2712
+ state: resumeFrom,
2713
+ expectedType: "resume-session"
2714
+ });
2715
+ }
2716
+ let validatedContinueFrom;
2717
+ if (continueFrom != null) {
2718
+ validatedContinueFrom = await validateLifecycleStateData({
2719
+ harness,
2720
+ state: continueFrom,
2721
+ expectedType: "continue-turn"
2722
+ });
2723
+ }
2724
+ const effectiveContinueFrom = validatedContinueFrom != null ? validatedContinueFrom : validatedResumeFrom == null ? void 0 : validatedResumeFrom.continueFrom;
2725
+ const isResumedSession = validatedResumeFrom != null || effectiveContinueFrom != null;
2726
+ let recipe;
2727
+ if (harness.getBootstrap != null) {
2728
+ recipe = await harness.getBootstrap({ abortSignal });
2729
+ }
2730
+ const sandboxBootstrapPlan = await createSandboxBootstrapPlan({
2731
+ recipe,
2732
+ settings: this.sandboxConfig
2733
+ });
2734
+ const acquiredSandboxSession = await this._acquireSandbox({
2735
+ sandboxProvider,
2736
+ sessionId,
2737
+ isResume: isResumedSession,
2738
+ bootstrapPlan: sandboxBootstrapPlan,
2739
+ abortSignal
2740
+ });
2741
+ const leased = applyPortLease({
2742
+ provider: sandboxProvider,
2743
+ sandboxSession: acquiredSandboxSession,
2744
+ sessionId
2745
+ });
2746
+ const sandboxSession = leased.sandboxSession;
2747
+ const leasedBridgePort = leased.port;
2748
+ const sessionWorkDir = resolveSessionWorkDir({
2749
+ defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
2750
+ harnessId: harness.harnessId,
2751
+ sessionId,
2752
+ workDir: sandboxBootstrapPlan.workDir
2753
+ });
2754
+ try {
2755
+ if (!isResumedSession && sandboxBootstrapPlan.recipe != null && sandboxBootstrapPlan.recipeIdentity != null) {
2756
+ await applyBootstrapRecipe(
2757
+ sandboxSession.restricted(),
2758
+ sandboxBootstrapPlan.recipe,
2759
+ sandboxBootstrapPlan.recipeIdentity,
2760
+ { abortSignal }
2761
+ );
2762
+ }
2763
+ await ensureSandboxDirectory({
2764
+ session: sandboxSession,
2765
+ workDir: sessionWorkDir,
2766
+ abortSignal
2767
+ });
2768
+ if (this.sandboxConfig.onSession != null) {
2769
+ await this.sandboxConfig.onSession({
2770
+ session: sandboxSession.restricted(),
2771
+ sessionWorkDir,
2772
+ abortSignal
2773
+ });
2774
+ }
2775
+ } catch (err) {
2776
+ await cleanupAfterStartFailure({
2777
+ sandboxProvider,
2778
+ sandboxSession,
2779
+ sessionId,
2780
+ leasedBridgePort
2781
+ });
2782
+ throw err;
2783
+ }
2784
+ try {
2785
+ const baseStartOptions = {
2786
+ sessionId,
2787
+ skills: this.settings.skills,
2788
+ resumeFrom: validatedResumeFrom,
2789
+ continueFrom: effectiveContinueFrom,
2790
+ permissionMode: this.permissionMode,
2791
+ builtinToolFiltering: this.builtinToolFiltering,
2792
+ abortSignal,
2793
+ observability: buildObservability({ settings: this.settings })
2794
+ };
2795
+ const underlyingSession = await harness.doStart({
2796
+ ...baseStartOptions,
2797
+ sandboxSession,
2798
+ sessionWorkDir
2799
+ });
2800
+ return new HarnessAgentSession({
2801
+ sessionId,
2802
+ harness,
2803
+ underlyingSession,
2804
+ sandboxSession,
2805
+ sandboxProvider,
2806
+ leasedBridgePort,
2807
+ sessionWorkDir,
2808
+ toolApproval: this.settings.toolApproval,
2809
+ pendingToolApprovals: effectiveContinueFrom == null ? void 0 : effectiveContinueFrom.pendingToolApprovals,
2810
+ turnState: effectiveContinueFrom == null ? "idle" : effectiveContinueFrom.pendingToolApprovals != null && effectiveContinueFrom.pendingToolApprovals.length > 0 ? "awaiting-approval" : "suspended"
2811
+ });
2812
+ } catch (error) {
2813
+ await cleanupAfterStartFailure({
2814
+ sandboxProvider,
2815
+ sandboxSession,
2816
+ sessionId,
2817
+ leasedBridgePort
2818
+ });
2819
+ throw error;
2820
+ }
2821
+ }
2822
+ async generate(options) {
2823
+ const turnInput = this._resolveTurnInput(options);
2824
+ const runtimeContext = {};
2825
+ const { result, done } = this._startTurn({
2826
+ session: options.session,
2827
+ turnInput,
2828
+ runtimeContext,
2829
+ abortSignal: options.abortSignal
2830
+ });
2831
+ await done;
2832
+ return this._toGenerateResult(result);
2833
+ }
2834
+ async stream(options) {
2835
+ const turnInput = this._resolveTurnInput(options);
2836
+ const runtimeContext = {};
2837
+ const { result } = this._startTurn({
2838
+ session: options.session,
2839
+ turnInput,
2840
+ runtimeContext,
2841
+ abortSignal: options.abortSignal
2842
+ });
2843
+ return result;
2844
+ }
2845
+ /**
2846
+ * Continue the in-flight turn **without a new prompt**, draining it like
2847
+ * {@link generate}. Used after `createSession({ continueFrom })` to finish
2848
+ * consuming a turn that crossed a process boundary.
2849
+ */
2850
+ async continueGenerate(options) {
2851
+ var _a3;
2852
+ const runtimeContext = {};
2853
+ const { result, done } = this._startTurn({
2854
+ session: options.session,
2855
+ turnInput: {
2856
+ mode: "continue",
2857
+ toolApprovalContinuations: (_a3 = options.toolApprovalContinuations) != null ? _a3 : []
2858
+ },
2859
+ runtimeContext,
2860
+ abortSignal: options.abortSignal
2861
+ });
2862
+ await done;
2863
+ return this._toGenerateResult(result);
2864
+ }
2865
+ /**
2866
+ * Continue the in-flight turn **without a new prompt**, streaming its events
2867
+ * like {@link stream}. Used to keep consuming a turn that is still running
2868
+ * (or finished) in the runtime after a process boundary — the workflow slice
2869
+ * loop calls this on every slice after the first. Routes through the adapter's
2870
+ * `doContinueTurn`; what it can guarantee (lossless attach vs. lossy rerun)
2871
+ * follows from how the adapter resumed the session.
2872
+ */
2873
+ async continueStream(options) {
2874
+ var _a3;
2875
+ const runtimeContext = {};
2876
+ const { result } = this._startTurn({
2877
+ session: options.session,
2878
+ turnInput: {
2879
+ mode: "continue",
2880
+ toolApprovalContinuations: (_a3 = options.toolApprovalContinuations) != null ? _a3 : []
2881
+ },
2882
+ runtimeContext,
2883
+ abortSignal: options.abortSignal
2884
+ });
2885
+ return result;
2886
+ }
2887
+ // ─── Internals ──────────────────────────────────────────────────────
2888
+ _startTurn(input) {
2889
+ if (input.turnInput.mode === "continue") {
2890
+ return input.session.continueTurn({
2891
+ instructions: this.settings.instructions,
2892
+ tools: this.tools,
2893
+ activeTools: this.activeUserTools,
2894
+ toolSpecs: this._toToolSpecs(),
2895
+ builtinToolFiltering: this.builtinToolFiltering,
2896
+ runtimeContext: input.runtimeContext,
2897
+ abortSignal: input.abortSignal,
2898
+ telemetry: this.settings.telemetry,
2899
+ toolApprovalContinuations: input.turnInput.toolApprovalContinuations
2900
+ });
2901
+ }
2902
+ return input.session.promptTurn({
2903
+ prompt: input.turnInput.prompt,
2904
+ instructions: this.settings.instructions,
2905
+ tools: this.tools,
2906
+ activeTools: this.activeUserTools,
2907
+ toolSpecs: this._toToolSpecs(),
2908
+ builtinToolFiltering: this.builtinToolFiltering,
2909
+ runtimeContext: input.runtimeContext,
2910
+ abortSignal: input.abortSignal,
2911
+ telemetry: this.settings.telemetry
2912
+ });
2913
+ }
2914
+ async _acquireSandbox(input) {
2915
+ const { sandboxProvider } = input;
2916
+ if (input.isResume) {
2917
+ if (sandboxProvider.resumeSession == null) {
2918
+ throw new HarnessCapabilityUnsupportedError({
2919
+ message: `Sandbox provider '${sandboxProvider.providerId}' does not support resume.`,
2920
+ harnessId: this.settings.harness.harnessId
2921
+ });
2922
+ }
2923
+ return sandboxProvider.resumeSession({
2924
+ sessionId: input.sessionId,
2925
+ abortSignal: input.abortSignal
2926
+ });
2927
+ }
2928
+ return sandboxProvider.createSession({
2929
+ sessionId: input.sessionId,
2930
+ abortSignal: input.abortSignal,
2931
+ identity: input.bootstrapPlan.identity,
2932
+ onFirstCreate: input.bootstrapPlan.onFirstCreate
2933
+ });
2934
+ }
2935
+ /*
2936
+ * Reduce AI SDK input to the single user message the harness should run
2937
+ * for this turn. The harness session owns prior-turn state (system
2938
+ * prompt, assistant turns, tool results) — we never replay it. A bare
2939
+ * string is forwarded as-is; a message array is collapsed to its last
2940
+ * `role: 'user'` entry. Inputs whose only messages are non-user (system,
2941
+ * assistant, tool) have no fresh user input and are rejected.
2942
+ */
2943
+ _resolveTurnInput(options) {
2944
+ if (typeof options.prompt === "string") {
2945
+ return { mode: "prompt", prompt: options.prompt };
2946
+ }
2947
+ const messages = Array.isArray(options.prompt) ? options.prompt : options.messages;
2948
+ if (Array.isArray(messages)) {
2949
+ const toolApprovalContinuations = collectHarnessAgentToolApprovalContinuations({ messages });
2950
+ if (toolApprovalContinuations.length > 0) {
2951
+ return {
2952
+ mode: "continue",
2953
+ toolApprovalContinuations
2954
+ };
2955
+ }
2956
+ for (let i = messages.length - 1; i >= 0; i--) {
2957
+ const message = messages[i];
2958
+ if ((message == null ? void 0 : message.role) === "user")
2959
+ return { mode: "prompt", prompt: message };
2960
+ }
2961
+ throw new Error(
2962
+ 'HarnessAgent: messages must contain at least one `role: "user"` entry.'
2963
+ );
2964
+ }
2965
+ throw new Error("HarnessAgent: either `prompt` or `messages` is required.");
2966
+ }
2967
+ /*
2968
+ * Wire-format projection of user-defined tools only. Harness builtins are
2969
+ * executed by the runtime and the bridge already knows about them — we
2970
+ * never re-declare them over the wire.
2971
+ */
2972
+ _toToolSpecs() {
2973
+ const specs = [];
2974
+ for (const [name3, tool] of Object.entries(
2975
+ this.activeUserTools
2976
+ )) {
2977
+ const t = tool;
2978
+ let inputSchema;
2979
+ if (t.inputSchema != null) {
2980
+ try {
2981
+ inputSchema = asSchema(
2982
+ t.inputSchema
2983
+ ).jsonSchema;
2984
+ } catch (e) {
2985
+ }
2986
+ }
2987
+ specs.push({ name: name3, description: t.description, inputSchema });
2988
+ }
2989
+ return specs;
2990
+ }
2991
+ async _toGenerateResult(streamResult) {
2992
+ const [steps, usage, responseMessages] = await Promise.all([
2993
+ streamResult.steps,
2994
+ streamResult.usage,
2995
+ streamResult.responseMessages
2996
+ ]);
2997
+ return new HarnessGenerateTextResult({ steps, usage, responseMessages });
2998
+ }
2999
+ };
3000
+ var HarnessGenerateTextResult = class {
3001
+ constructor(options) {
3002
+ this.output = void 0;
3003
+ this.steps = options.steps;
3004
+ this.usage = options.usage;
3005
+ this.responseMessages = options.responseMessages;
3006
+ }
3007
+ get finalStep() {
3008
+ return this.steps.at(-1);
3009
+ }
3010
+ get content() {
3011
+ return this.steps.flatMap((step) => step.content);
3012
+ }
3013
+ get text() {
3014
+ return this.finalStep.text;
3015
+ }
3016
+ get files() {
3017
+ return this.steps.flatMap((step) => step.files);
3018
+ }
3019
+ get sources() {
3020
+ return this.steps.flatMap((step) => step.sources);
3021
+ }
3022
+ get toolCalls() {
3023
+ return this.steps.flatMap((step) => step.toolCalls);
3024
+ }
3025
+ get staticToolCalls() {
3026
+ return this.steps.flatMap((step) => step.staticToolCalls);
3027
+ }
3028
+ get dynamicToolCalls() {
3029
+ return this.steps.flatMap((step) => step.dynamicToolCalls);
3030
+ }
3031
+ get toolResults() {
3032
+ return this.steps.flatMap((step) => step.toolResults);
3033
+ }
3034
+ get staticToolResults() {
3035
+ return this.steps.flatMap((step) => step.staticToolResults);
3036
+ }
3037
+ get dynamicToolResults() {
3038
+ return this.steps.flatMap((step) => step.dynamicToolResults);
3039
+ }
3040
+ get finishReason() {
3041
+ return this.finalStep.finishReason;
3042
+ }
3043
+ get rawFinishReason() {
3044
+ return this.finalStep.rawFinishReason;
3045
+ }
3046
+ get warnings() {
3047
+ return this.steps.flatMap((step) => {
3048
+ var _a3;
3049
+ return (_a3 = step.warnings) != null ? _a3 : [];
3050
+ });
3051
+ }
3052
+ get reasoning() {
3053
+ return this.finalStep.content.filter(
3054
+ (part) => part.type === "reasoning" || part.type === "reasoning-file"
3055
+ );
3056
+ }
3057
+ get reasoningText() {
3058
+ return this.finalStep.reasoningText;
3059
+ }
3060
+ get totalUsage() {
3061
+ return this.usage;
3062
+ }
3063
+ get request() {
3064
+ return this.finalStep.request;
3065
+ }
3066
+ get response() {
3067
+ return this.finalStep.response;
3068
+ }
3069
+ get providerMetadata() {
3070
+ return this.finalStep.providerMetadata;
3071
+ }
3072
+ };
3073
+ function resolveSandboxConfig(settings) {
3074
+ var _a3;
3075
+ if (settings.onSandboxSession != null) {
3076
+ console.warn(
3077
+ "HarnessAgent: `onSandboxSession` is deprecated. Use `sandboxConfig.onSession` instead."
3078
+ );
3079
+ }
3080
+ return {
3081
+ ...settings.sandboxConfig,
3082
+ ...((_a3 = settings.sandboxConfig) == null ? void 0 : _a3.onSession) == null && settings.onSandboxSession != null ? { onSession: settings.onSandboxSession } : {}
3083
+ };
3084
+ }
3085
+ function applyPortLease(input) {
3086
+ const pool = input.provider.bridgePorts;
3087
+ if (pool == null || pool.length === 0) {
3088
+ return { sandboxSession: input.sandboxSession, port: void 0 };
3089
+ }
3090
+ const port = acquireBridgePort({
3091
+ poolKey: input.provider,
3092
+ pool,
3093
+ sessionId: input.sessionId
3094
+ });
3095
+ return {
3096
+ sandboxSession: narrowNetworkSessionPorts(input.sandboxSession, port),
3097
+ port
3098
+ };
3099
+ }
3100
+ function narrowNetworkSessionPorts(sandboxSession, leasedPort) {
3101
+ return Object.create(sandboxSession, {
3102
+ ports: {
3103
+ value: [leasedPort],
3104
+ enumerable: true
3105
+ }
3106
+ });
3107
+ }
3108
+ async function cleanupAfterStartFailure(input) {
3109
+ await Promise.resolve(input.sandboxSession.stop()).catch(() => {
3110
+ });
3111
+ if (input.leasedBridgePort != null) {
3112
+ releaseBridgePort({
3113
+ poolKey: input.sandboxProvider,
3114
+ sessionId: input.sessionId
3115
+ });
3116
+ }
3117
+ }
3118
+
3119
+ // src/agent/prepare-harness-sandbox-template.ts
3120
+ async function prepareHarnessSandboxTemplate(options) {
3121
+ var _a3, _b3, _c;
3122
+ const sandboxConfig = (_a3 = options.sandboxConfig) != null ? _a3 : {};
3123
+ validateSandboxBootstrapSettings(sandboxConfig);
3124
+ const recipe = await ((_c = (_b3 = options.harness).getBootstrap) == null ? void 0 : _c.call(_b3, {
3125
+ abortSignal: options.abortSignal
3126
+ }));
3127
+ const bootstrapPlan = await createSandboxBootstrapPlan({
3128
+ recipe,
3129
+ settings: sandboxConfig
3130
+ });
3131
+ if (bootstrapPlan.identity == null || bootstrapPlan.onFirstCreate == null) {
3132
+ return;
3133
+ }
3134
+ const sandboxSession = await options.sandboxProvider.createSession({
3135
+ abortSignal: options.abortSignal,
3136
+ identity: bootstrapPlan.identity,
3137
+ onFirstCreate: bootstrapPlan.onFirstCreate
3138
+ });
3139
+ try {
3140
+ if (bootstrapPlan.recipe != null && bootstrapPlan.recipeIdentity != null) {
3141
+ await applyBootstrapRecipe(
3142
+ sandboxSession.restricted(),
3143
+ bootstrapPlan.recipe,
3144
+ bootstrapPlan.recipeIdentity,
3145
+ {
3146
+ abortSignal: options.abortSignal
3147
+ }
3148
+ );
3149
+ }
3150
+ } finally {
3151
+ await Promise.resolve(sandboxSession.stop()).catch(() => {
3152
+ });
3153
+ }
3154
+ }
3155
+ var prewarmHarness = prepareHarnessSandboxTemplate;
3156
+
3157
+ // src/agent/prepare-sandbox-for-harness.ts
3158
+ var PREPARED_SANDBOX_IDENTITY_VERSION = 1;
3159
+ async function prepareSandboxForHarness(options) {
3160
+ var _a3, _b3;
3161
+ const sandboxConfig = (_a3 = options.sandboxConfig) != null ? _a3 : {};
3162
+ validateSandboxBootstrapSettings(sandboxConfig);
3163
+ if (options.harnesses.length === 0) {
3164
+ throw new Error(
3165
+ "prepareSandboxForHarness: at least one harness must be provided."
3166
+ );
3167
+ }
3168
+ const harnesses = [...options.harnesses].sort(
3169
+ (a, b) => a.harnessId.localeCompare(b.harnessId)
3170
+ );
3171
+ assertUniqueHarnessIds(harnesses);
3172
+ const workDir = sandboxConfig.workDir == null ? void 0 : normalizeSandboxWorkDir(sandboxConfig.workDir);
3173
+ const recipeIdentities = {};
3174
+ const skippedHarnessIds = [];
3175
+ for (const harness of harnesses) {
3176
+ const recipe = await ((_b3 = harness.getBootstrap) == null ? void 0 : _b3.call(harness, {
3177
+ abortSignal: options.abortSignal
3178
+ }));
3179
+ if (recipe == null) {
3180
+ skippedHarnessIds.push(harness.harnessId);
3181
+ continue;
3182
+ }
3183
+ const recipeIdentity = await hashHarnessBootstrap(recipe);
3184
+ recipeIdentities[harness.harnessId] = recipeIdentity;
3185
+ await applyBootstrapRecipe(options.session, recipe, recipeIdentity, {
3186
+ abortSignal: options.abortSignal
3187
+ });
3188
+ }
3189
+ if (sandboxConfig.onBootstrap != null) {
3190
+ await runSandboxBootstrap({
3191
+ session: options.session,
3192
+ workDir,
3193
+ onBootstrap: sandboxConfig.onBootstrap,
3194
+ abortSignal: options.abortSignal
3195
+ });
3196
+ }
3197
+ const identity = await resolvePreparedSandboxIdentity({
3198
+ recipeIdentities,
3199
+ bootstrapHash: sandboxConfig.bootstrapHash,
3200
+ workDir
3201
+ });
3202
+ return {
3203
+ ...identity != null ? { identity } : {},
3204
+ recipeIdentities,
3205
+ skippedHarnessIds
3206
+ };
3207
+ }
3208
+ function assertUniqueHarnessIds(harnesses) {
3209
+ const seen = /* @__PURE__ */ new Set();
3210
+ for (const harness of harnesses) {
3211
+ if (seen.has(harness.harnessId)) {
3212
+ throw new Error(
3213
+ `prepareSandboxForHarness: duplicate harness id "${harness.harnessId}".`
3214
+ );
3215
+ }
3216
+ seen.add(harness.harnessId);
3217
+ }
3218
+ }
3219
+ async function resolvePreparedSandboxIdentity({
3220
+ recipeIdentities,
3221
+ bootstrapHash,
3222
+ workDir
3223
+ }) {
3224
+ const entries = Object.entries(recipeIdentities).sort(
3225
+ ([a], [b]) => a.localeCompare(b)
3226
+ );
3227
+ if (entries.length === 0 && bootstrapHash == null) {
3228
+ return void 0;
3229
+ }
3230
+ const encoder = new TextEncoder();
3231
+ const chunks = [];
3232
+ const pushString = (value) => {
3233
+ chunks.push(encoder.encode(value));
3234
+ chunks.push(encoder.encode("\0"));
3235
+ };
3236
+ pushString(String(PREPARED_SANDBOX_IDENTITY_VERSION));
3237
+ pushString(workDir != null ? workDir : "");
3238
+ pushString(bootstrapHash != null ? bootstrapHash : "");
3239
+ for (const [harnessId, identity] of entries) {
3240
+ pushString(harnessId);
3241
+ pushString(identity);
3242
+ }
3243
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
3244
+ const buffer = new Uint8Array(totalLength);
3245
+ let offset = 0;
3246
+ for (const chunk of chunks) {
3247
+ buffer.set(chunk, offset);
3248
+ offset += chunk.length;
3249
+ }
3250
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
3251
+ const bytes = new Uint8Array(digest);
3252
+ let hex = "";
3253
+ for (let i = 0; i < 8; i++) {
3254
+ hex += bytes[i].toString(16).padStart(2, "0");
3255
+ }
3256
+ return hex;
3257
+ }
3258
+
3259
+ // src/agent/observability/file-reporter.ts
3260
+ import { appendFileSync, mkdirSync } from "fs";
3261
+ function createFileReporter(options) {
3262
+ var _a3, _b3;
3263
+ const fileName = (_a3 = options.fileName) != null ? _a3 : "events.jsonl";
3264
+ const path = `${options.dir}/${fileName}`;
3265
+ const failOnly = (_b3 = options.failOnly) != null ? _b3 : false;
3266
+ const turns = /* @__PURE__ */ new Map();
3267
+ let lastOpenCallId;
3268
+ let dirReady = false;
3269
+ const bucketFor = (callId) => {
3270
+ let bucket = turns.get(callId);
3271
+ if (!bucket) {
3272
+ bucket = { lines: [], errored: false };
3273
+ turns.set(callId, bucket);
3274
+ }
3275
+ return bucket;
3276
+ };
3277
+ const record = (callId, rec) => {
3278
+ const id = callId != null ? callId : lastOpenCallId;
3279
+ if (id == null) {
3280
+ flushLines([rec]);
3281
+ return;
3282
+ }
3283
+ bucketFor(id).lines.push(rec);
3284
+ };
3285
+ const flushLines = (lines) => {
3286
+ if (lines.length === 0) return;
3287
+ try {
3288
+ if (!dirReady) {
3289
+ mkdirSync(options.dir, { recursive: true });
3290
+ dirReady = true;
3291
+ }
3292
+ appendFileSync(path, lines.map((l) => JSON.stringify(l)).join("\n") + "\n");
3293
+ } catch (e) {
3294
+ }
3295
+ };
3296
+ const finishTurn = (callId) => {
3297
+ const bucket = turns.get(callId);
3298
+ if (!bucket) return;
3299
+ turns.delete(callId);
3300
+ if (lastOpenCallId === callId) lastOpenCallId = void 0;
3301
+ if (failOnly && !bucket.errored) return;
3302
+ flushLines(bucket.lines);
3303
+ };
3304
+ return {
3305
+ onStart(event) {
3306
+ const e = event;
3307
+ lastOpenCallId = e.callId;
3308
+ record(e.callId, {
3309
+ ts: Date.now(),
3310
+ kind: "turn-start",
3311
+ callId: e.callId,
3312
+ operationId: e.operationId,
3313
+ provider: e.provider,
3314
+ modelId: e.modelId,
3315
+ // Input prompt, unless the consumer opted out via `recordInputs: false`.
3316
+ ...e.recordInputs === false ? {} : { input: { messages: e.messages, instructions: e.instructions } }
3317
+ });
3318
+ },
3319
+ onStepStart(event) {
3320
+ const e = event;
3321
+ record(e.callId, {
3322
+ ts: Date.now(),
3323
+ kind: "step-start",
3324
+ callId: e.callId,
3325
+ step: e.stepNumber
3326
+ });
3327
+ },
3328
+ onToolExecutionStart(event) {
3329
+ const e = event;
3330
+ record(e.callId, {
3331
+ ts: Date.now(),
3332
+ kind: "tool-start",
3333
+ callId: e.callId,
3334
+ toolName: e.toolCall.toolName,
3335
+ toolCallId: e.toolCall.toolCallId,
3336
+ input: e.toolCall.input
3337
+ });
3338
+ },
3339
+ onToolExecutionEnd(event) {
3340
+ const e = event;
3341
+ const isError = e.toolOutput.type === "error";
3342
+ if (isError) bucketFor(e.callId).errored = true;
3343
+ record(e.callId, {
3344
+ ts: Date.now(),
3345
+ kind: "tool-end",
3346
+ callId: e.callId,
3347
+ toolCallId: e.toolCall.toolCallId,
3348
+ isError,
3349
+ output: isError ? e.toolOutput.error : e.toolOutput.output
3350
+ });
3351
+ },
3352
+ onStepFinish(event) {
3353
+ const e = event;
3354
+ record(e.callId, {
3355
+ ts: Date.now(),
3356
+ kind: "step-finish",
3357
+ callId: e.callId,
3358
+ usage: e.usage,
3359
+ // The model's output content, unless `recordOutputs: false`.
3360
+ ...e.recordOutputs === false || e.content == null ? {} : { output: e.content }
3361
+ });
3362
+ },
3363
+ onEnd(event) {
3364
+ var _a4;
3365
+ const e = event;
3366
+ record(e.callId, {
3367
+ ts: Date.now(),
3368
+ kind: "turn-finish",
3369
+ callId: e.callId,
3370
+ finishReason: e.finishReason,
3371
+ usage: (_a4 = e.totalUsage) != null ? _a4 : e.usage
3372
+ });
3373
+ finishTurn(e.callId);
3374
+ },
3375
+ onError(error) {
3376
+ if (lastOpenCallId != null) bucketFor(lastOpenCallId).errored = true;
3377
+ record(lastOpenCallId, {
3378
+ ts: Date.now(),
3379
+ kind: "error",
3380
+ error: error instanceof Error ? { name: error.name, message: error.message } : error
3381
+ });
3382
+ },
3383
+ ingestDiagnostic(diagnostic) {
3384
+ if (diagnostic.level === "error" && lastOpenCallId != null) {
3385
+ bucketFor(lastOpenCallId).errored = true;
3386
+ }
3387
+ record(lastOpenCallId, {
3388
+ ts: diagnostic.timestamp,
3389
+ kind: "diagnostic",
3390
+ diagnostic
3391
+ });
3392
+ }
3393
+ };
3394
+ }
3395
+
3396
+ // src/agent/observability/trace-tree-reporter.ts
3397
+ function createTraceTreeReporter(options = {}) {
3398
+ var _a3;
3399
+ const write = (_a3 = options.write) != null ? _a3 : ((chunk) => void process.stderr.write(chunk));
3400
+ const turns = /* @__PURE__ */ new Map();
3401
+ const render = (node, depth) => {
3402
+ const indent = " ".repeat(depth);
3403
+ const dur = node.endMs != null ? `${Math.max(0, Math.round(node.endMs - node.startMs))}ms` : "(open)";
3404
+ let out = `${indent}- ${node.label} ${dur}
3405
+ `;
3406
+ for (const child of node.children) out += render(child, depth + 1);
3407
+ return out;
3408
+ };
3409
+ return {
3410
+ onStart(event) {
3411
+ var _a4;
3412
+ const e = event;
3413
+ turns.set(e.callId, {
3414
+ root: {
3415
+ label: `${(_a4 = e.operationId) != null ? _a4 : "turn"}${e.modelId ? ` ${e.modelId}` : ""}`,
3416
+ startMs: Date.now(),
3417
+ children: []
3418
+ },
3419
+ tools: /* @__PURE__ */ new Map()
3420
+ });
3421
+ },
3422
+ onStepStart(event) {
3423
+ var _a4;
3424
+ const e = event;
3425
+ const turn = turns.get(e.callId);
3426
+ if (!turn) return;
3427
+ const step = {
3428
+ label: `step ${((_a4 = e.stepNumber) != null ? _a4 : turn.root.children.length) + 1}`,
3429
+ startMs: Date.now(),
3430
+ children: []
3431
+ };
3432
+ turn.step = step;
3433
+ turn.root.children.push(step);
3434
+ },
3435
+ onToolExecutionStart(event) {
3436
+ var _a4;
3437
+ const e = event;
3438
+ const turn = turns.get(e.callId);
3439
+ const parent = (_a4 = turn == null ? void 0 : turn.step) != null ? _a4 : turn == null ? void 0 : turn.root;
3440
+ if (!turn || !parent) return;
3441
+ const node = {
3442
+ label: `tool ${e.toolCall.toolName}`,
3443
+ startMs: Date.now(),
3444
+ children: []
3445
+ };
3446
+ turn.tools.set(e.toolCall.toolCallId, node);
3447
+ parent.children.push(node);
3448
+ },
3449
+ onToolExecutionEnd(event) {
3450
+ var _a4;
3451
+ const e = event;
3452
+ const node = (_a4 = turns.get(e.callId)) == null ? void 0 : _a4.tools.get(e.toolCall.toolCallId);
3453
+ if (!node) return;
3454
+ node.endMs = Date.now();
3455
+ if (e.toolOutput.type === "error") node.label += " [error]";
3456
+ },
3457
+ onStepFinish(event) {
3458
+ const e = event;
3459
+ const turn = turns.get(e.callId);
3460
+ if (turn == null ? void 0 : turn.step) {
3461
+ turn.step.endMs = Date.now();
3462
+ turn.step = void 0;
3463
+ }
3464
+ },
3465
+ onEnd(event) {
3466
+ const e = event;
3467
+ const turn = turns.get(e.callId);
3468
+ if (!turn) return;
3469
+ turn.root.endMs = Date.now();
3470
+ turns.delete(e.callId);
3471
+ try {
3472
+ write(`
3473
+ ${render(turn.root, 0)}`);
3474
+ } catch (e2) {
3475
+ }
3476
+ }
3477
+ };
3478
+ }
3479
+ export {
3480
+ HarnessAgent,
3481
+ HarnessAgentSession,
3482
+ HarnessCapabilityUnsupportedError,
3483
+ HarnessError,
3484
+ collectHarnessAgentToolApprovalContinuations,
3485
+ createFileReporter,
3486
+ createTraceTreeReporter,
3487
+ prepareHarnessSandboxTemplate,
3488
+ prepareSandboxForHarness,
3489
+ prewarmHarness
3490
+ };
3491
+ //# sourceMappingURL=index.js.map