@ai-sdk/harness 1.0.77 → 1.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/bridge/index.ts +2 -0
- package/dist/agent/index.d.ts +42 -23
- package/dist/agent/index.js +168 -61
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.d.ts +12 -7
- package/dist/bridge/index.js +140 -5
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +38 -13
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +177 -1
- package/dist/utils/index.js +121 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-session.ts +136 -12
- package/src/agent/harness-agent.ts +54 -22
- package/src/agent/internal/run-prompt.ts +2 -0
- package/src/agent/internal/sandbox-bootstrap.ts +3 -28
- package/src/agent/prepare-sandbox-for-harness.ts +3 -3
- package/src/bridge/index.ts +184 -12
- package/src/utils/bridge-user-message-submitter.ts +96 -0
- package/src/utils/get-restricted-sandbox-session.ts +10 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/resolve-sandbox-default-working-directory.ts +33 -0
- package/src/utils/sandbox-channel.ts +9 -0
- package/src/v1/harness-v1-bridge-protocol.ts +23 -0
- package/src/v1/harness-v1-session.ts +9 -12
- package/src/v1/index.ts +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @ai-sdk/harness
|
|
2
2
|
|
|
3
|
+
## 1.0.79
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [9a37469]
|
|
8
|
+
- ai@7.0.71
|
|
9
|
+
|
|
10
|
+
## 1.0.78
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- eace6fb: feat(harness): add experimental support for steering agent conversations mid-turn
|
|
15
|
+
- c0595b4: feat(harness): support passing a filesystem and process restricted sandbox session to `HarnessAgentSession`, using fallbacks in favor of the preferred network sandbox session methods
|
|
16
|
+
|
|
3
17
|
## 1.0.77
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/bridge/index.ts
CHANGED
package/dist/agent/index.d.ts
CHANGED
|
@@ -692,20 +692,16 @@ type HarnessV1StartOptions = {
|
|
|
692
692
|
*/
|
|
693
693
|
readonly observability?: HarnessV1Observability;
|
|
694
694
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
* `setRequestTransformations`, `addRequestTransformations`) for bridge
|
|
700
|
-
* wiring. Adapters must not call `stop()` themselves; the agent does that
|
|
701
|
-
* during cleanup.
|
|
695
|
+
* Sandbox session the adapter operates against. Network sandbox sessions
|
|
696
|
+
* expose optional infrastructure capabilities for bridge wiring; caller-
|
|
697
|
+
* provided basic sandbox sessions expose only filesystem and process APIs.
|
|
698
|
+
* Adapters must not stop or destroy the sandbox themselves.
|
|
702
699
|
*/
|
|
703
|
-
readonly sandboxSession: HarnessV1NetworkSandboxSession;
|
|
700
|
+
readonly sandboxSession: HarnessV1NetworkSandboxSession | Experimental_SandboxSession;
|
|
704
701
|
/**
|
|
705
|
-
* Absolute path the adapter runs the agent in for this session. Composed
|
|
706
|
-
* the
|
|
707
|
-
*
|
|
708
|
-
* deriving its own provider-specific path.
|
|
702
|
+
* Absolute path the adapter runs the agent in for this session. Composed
|
|
703
|
+
* underneath the sandbox's resolved default working directory and created
|
|
704
|
+
* before `doStart`.
|
|
709
705
|
*/
|
|
710
706
|
readonly sessionWorkDir: string;
|
|
711
707
|
};
|
|
@@ -1402,7 +1398,7 @@ type HarnessAgentTurnState = 'idle' | 'running' | 'awaiting-approval' | 'awaitin
|
|
|
1402
1398
|
* Live harness session held by the caller.
|
|
1403
1399
|
*
|
|
1404
1400
|
* Created by {@link import('./harness-agent').HarnessAgent.createSession}.
|
|
1405
|
-
* Owns the underlying adapter session and
|
|
1401
|
+
* Owns the underlying adapter session and holds its sandbox session.
|
|
1406
1402
|
*
|
|
1407
1403
|
* Pass the instance back to `agent.generate` / `agent.stream` on every
|
|
1408
1404
|
* call; end the local handle with `detach()`, `stop()`, or `destroy()`.
|
|
@@ -1429,6 +1425,7 @@ declare class HarnessAgentSession {
|
|
|
1429
1425
|
private turnState;
|
|
1430
1426
|
private turnSequence;
|
|
1431
1427
|
private activeTurnSequence;
|
|
1428
|
+
private activePromptControl;
|
|
1432
1429
|
private suspendedTurnState;
|
|
1433
1430
|
/**
|
|
1434
1431
|
* Whether this session was created from `resumeFrom` or `continueFrom`.
|
|
@@ -1439,7 +1436,7 @@ declare class HarnessAgentSession {
|
|
|
1439
1436
|
sessionId: string;
|
|
1440
1437
|
harness: HarnessAgentAdapter;
|
|
1441
1438
|
underlyingSession: HarnessAgentAdapterSession;
|
|
1442
|
-
sandboxSession: HarnessV1NetworkSandboxSession;
|
|
1439
|
+
sandboxSession: HarnessV1NetworkSandboxSession | Experimental_SandboxSession;
|
|
1443
1440
|
ownsSandboxLifecycle?: boolean;
|
|
1444
1441
|
sessionWorkDir: string;
|
|
1445
1442
|
toolApproval: HarnessAgentToolApprovalConfiguration | undefined;
|
|
@@ -1488,6 +1485,13 @@ declare class HarnessAgentSession {
|
|
|
1488
1485
|
* the hood). Throws if the session has ended.
|
|
1489
1486
|
*/
|
|
1490
1487
|
compact(customInstructions?: string): Promise<void>;
|
|
1488
|
+
/**
|
|
1489
|
+
* Submit another user message to the active turn.
|
|
1490
|
+
*
|
|
1491
|
+
* The runtime accepts the message for its next safe input boundary. Output
|
|
1492
|
+
* caused by the message remains part of the active turn's result stream.
|
|
1493
|
+
*/
|
|
1494
|
+
experimental_steerTurn(text: string): Promise<void>;
|
|
1491
1495
|
/**
|
|
1492
1496
|
* Park the session, returning a payload the caller can persist and later
|
|
1493
1497
|
* pass to `agent.createSession({ sessionId, resumeFrom })` to reconnect.
|
|
@@ -1531,6 +1535,9 @@ declare class HarnessAgentSession {
|
|
|
1531
1535
|
private markAwaitingApprovalIfActive;
|
|
1532
1536
|
private markAwaitingToolResultIfActive;
|
|
1533
1537
|
private startTrackedTurn;
|
|
1538
|
+
private setPromptControl;
|
|
1539
|
+
private settleActivePromptControl;
|
|
1540
|
+
private clearActivePromptControl;
|
|
1534
1541
|
private finishTrackedTurn;
|
|
1535
1542
|
private endLocalHandle;
|
|
1536
1543
|
private requireReusableSession;
|
|
@@ -1576,12 +1583,13 @@ interface HarnessAgentCallExtensions {
|
|
|
1576
1583
|
* Adapter builtin tools (e.g. Claude Code's `Bash`) pass through
|
|
1577
1584
|
* untouched.
|
|
1578
1585
|
* - **Sandbox propagation.** On `createSession`, the agent uses a
|
|
1579
|
-
* caller-provided network sandbox session when present; otherwise
|
|
1580
|
-
* the configured provider's `createSession()` (or
|
|
1581
|
-
* passes the selected session into `doStart`.
|
|
1582
|
-
* `
|
|
1583
|
-
* via `experimental_sandbox`. Caller-provided sandboxes
|
|
1584
|
-
* caller and are not stopped or destroyed by the
|
|
1586
|
+
* caller-provided network or basic sandbox session when present; otherwise
|
|
1587
|
+
* it calls the configured provider's `createSession()` (or
|
|
1588
|
+
* `resumeSession()`). It passes the selected session into `doStart`. A
|
|
1589
|
+
* tool-safe `SandboxSession` is handed to user-tool
|
|
1590
|
+
* `execute()` calls via `experimental_sandbox`. Caller-provided sandboxes
|
|
1591
|
+
* remain owned by the caller and are not stopped or destroyed by the
|
|
1592
|
+
* harness layer.
|
|
1585
1593
|
*/
|
|
1586
1594
|
declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAgentAdapter, TUserTools extends ToolSet = {}, RUNTIME_CONTEXT extends Context = Context, OUTPUT extends OutputInterface = never> implements Agent<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT> {
|
|
1587
1595
|
readonly version: "agent-v1";
|
|
@@ -1631,10 +1639,10 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
|
|
|
1631
1639
|
*/
|
|
1632
1640
|
continueFrom?: HarnessAgentContinueTurnState;
|
|
1633
1641
|
/**
|
|
1634
|
-
* Existing
|
|
1635
|
-
*
|
|
1642
|
+
* Existing sandbox session to run the harness in. When provided, the
|
|
1643
|
+
* caller retains ownership of the sandbox lifecycle.
|
|
1636
1644
|
*/
|
|
1637
|
-
sandboxSession?: HarnessV1NetworkSandboxSession;
|
|
1645
|
+
sandboxSession?: HarnessV1NetworkSandboxSession | Experimental_SandboxSession;
|
|
1638
1646
|
abortSignal?: AbortSignal;
|
|
1639
1647
|
}): Promise<HarnessAgentSession>;
|
|
1640
1648
|
generate(options: AgentCallParameters<never, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT> & HarnessAgentCallExtensions): Promise<GenerateTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT>>;
|
|
@@ -1664,6 +1672,17 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
|
|
|
1664
1672
|
toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
|
|
1665
1673
|
abortSignal?: AbortSignal;
|
|
1666
1674
|
}): Promise<StreamTextResult<HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT, OUTPUT>>;
|
|
1675
|
+
/**
|
|
1676
|
+
* Submit another user message to a currently running session turn.
|
|
1677
|
+
*
|
|
1678
|
+
* The returned promise resolves after the runtime has accepted the message
|
|
1679
|
+
* for its next safe input boundary. Output caused by the message remains in
|
|
1680
|
+
* the current turn's stream.
|
|
1681
|
+
*/
|
|
1682
|
+
experimental_steer(options: {
|
|
1683
|
+
session: HarnessAgentSession;
|
|
1684
|
+
text: string;
|
|
1685
|
+
}): Promise<void>;
|
|
1667
1686
|
private _startTurn;
|
|
1668
1687
|
private _resolveTurnInput;
|
|
1669
1688
|
private _toToolSpecs;
|
package/dist/agent/index.js
CHANGED
|
@@ -1511,7 +1511,7 @@ function runPrompt(input) {
|
|
|
1511
1511
|
result.fail(err);
|
|
1512
1512
|
};
|
|
1513
1513
|
const done = (async () => {
|
|
1514
|
-
var _a5, _b5, _c2, _d2, _e2, _f2, _g2, _h, _i, _j, _k, _l;
|
|
1514
|
+
var _a5, _b5, _c2, _d2, _e2, _f2, _g2, _h, _i, _j, _k, _l, _m;
|
|
1515
1515
|
let bridge;
|
|
1516
1516
|
try {
|
|
1517
1517
|
bridge = await toHarnessStream({
|
|
@@ -1549,6 +1549,7 @@ function runPrompt(input) {
|
|
|
1549
1549
|
return;
|
|
1550
1550
|
}
|
|
1551
1551
|
const { stream, control } = bridge;
|
|
1552
|
+
(_a5 = input.onPromptControlAvailable) == null ? void 0 : _a5.call(input, control);
|
|
1552
1553
|
const reader = stream.getReader();
|
|
1553
1554
|
const toolCallsByToolCallId = /* @__PURE__ */ new Map();
|
|
1554
1555
|
const rawToolCallsByToolCallId = /* @__PURE__ */ new Map();
|
|
@@ -1565,7 +1566,7 @@ function runPrompt(input) {
|
|
|
1565
1566
|
pendingToolApprovals.map((approval) => [approval.toolCallId, approval])
|
|
1566
1567
|
);
|
|
1567
1568
|
const continuationsByApprovalId = new Map(
|
|
1568
|
-
((
|
|
1569
|
+
((_b5 = input.toolApprovalContinuations) != null ? _b5 : []).map((continuation) => [
|
|
1569
1570
|
continuation.approvalResponse.approvalId,
|
|
1570
1571
|
continuation
|
|
1571
1572
|
])
|
|
@@ -1577,7 +1578,7 @@ function runPrompt(input) {
|
|
|
1577
1578
|
])
|
|
1578
1579
|
);
|
|
1579
1580
|
const continuationsByToolCallId = new Map(
|
|
1580
|
-
((
|
|
1581
|
+
((_c2 = input.toolResultContinuations) != null ? _c2 : []).map((continuation) => [
|
|
1581
1582
|
continuation.toolCallId,
|
|
1582
1583
|
continuation
|
|
1583
1584
|
])
|
|
@@ -1870,7 +1871,7 @@ function runPrompt(input) {
|
|
|
1870
1871
|
(condition) => condition({ steps: completedSteps })
|
|
1871
1872
|
)
|
|
1872
1873
|
)).some(Boolean)) {
|
|
1873
|
-
await ((
|
|
1874
|
+
await ((_d2 = input.onStopConditionMet) == null ? void 0 : _d2.call(input));
|
|
1874
1875
|
const { finishReason, usage } = pendingStopBoundary;
|
|
1875
1876
|
releasePendingStopBoundary();
|
|
1876
1877
|
await telemetry.end({ finishReason, usage });
|
|
@@ -1881,7 +1882,7 @@ function runPrompt(input) {
|
|
|
1881
1882
|
}
|
|
1882
1883
|
}
|
|
1883
1884
|
if (value.type === "stream-start") {
|
|
1884
|
-
await telemetry.start((
|
|
1885
|
+
await telemetry.start((_e2 = value.modelId) != null ? _e2 : input.session.modelId);
|
|
1885
1886
|
}
|
|
1886
1887
|
if (value.type !== "stream-start" && value.type !== "finish-step" && value.type !== "finish" && value.type !== "error") {
|
|
1887
1888
|
await telemetry.ensureStepOpen();
|
|
@@ -1907,7 +1908,7 @@ function runPrompt(input) {
|
|
|
1907
1908
|
const rawToolCall = rawToolCallsByToolCallId.get(
|
|
1908
1909
|
displayValue.toolCallId
|
|
1909
1910
|
);
|
|
1910
|
-
const toolName = (
|
|
1911
|
+
const toolName = (_f2 = rawToolCall == null ? void 0 : rawToolCall.toolName) != null ? _f2 : toolCall.toolName;
|
|
1911
1912
|
if (!isHarnessV1BuiltinToolIncluded({
|
|
1912
1913
|
toolName,
|
|
1913
1914
|
toolFiltering: input.builtinToolFiltering
|
|
@@ -1987,13 +1988,13 @@ function runPrompt(input) {
|
|
|
1987
1988
|
);
|
|
1988
1989
|
}
|
|
1989
1990
|
const rawToolCall = rawToolCallsByToolCallId.get(value.toolCallId);
|
|
1990
|
-
const pendingApproval = (
|
|
1991
|
+
const pendingApproval = (_i = pendingApprovalsByApprovalId.get(value.approvalId)) != null ? _i : {
|
|
1991
1992
|
approvalId: value.approvalId,
|
|
1992
1993
|
toolCallId: value.toolCallId,
|
|
1993
1994
|
toolName: toolCall.toolName,
|
|
1994
|
-
input: (
|
|
1995
|
+
input: (_g2 = rawToolCall == null ? void 0 : rawToolCall.input) != null ? _g2 : JSON.stringify(toolCall.input),
|
|
1995
1996
|
kind: "builtin",
|
|
1996
|
-
providerExecuted: (
|
|
1997
|
+
providerExecuted: (_h = rawToolCall == null ? void 0 : rawToolCall.providerExecuted) != null ? _h : true,
|
|
1997
1998
|
...(rawToolCall == null ? void 0 : rawToolCall.nativeName) !== void 0 ? { nativeName: rawToolCall.nativeName } : {}
|
|
1998
1999
|
};
|
|
1999
2000
|
pendingApprovalsByApprovalId.set(
|
|
@@ -2098,7 +2099,7 @@ function runPrompt(input) {
|
|
|
2098
2099
|
await telemetry.toolEnd(toolCall.toolCallId, { ok: true, output });
|
|
2099
2100
|
continue;
|
|
2100
2101
|
}
|
|
2101
|
-
const pendingApproval = (
|
|
2102
|
+
const pendingApproval = (_j = pendingApprovalsByToolCallId.get(toolCall.toolCallId)) != null ? _j : customToolApprovalDecision.type === "request" ? {
|
|
2102
2103
|
approvalId: generateId4(),
|
|
2103
2104
|
toolCallId: toolCall.toolCallId,
|
|
2104
2105
|
toolName: toolCall.toolName,
|
|
@@ -2189,15 +2190,15 @@ function runPrompt(input) {
|
|
|
2189
2190
|
}
|
|
2190
2191
|
}
|
|
2191
2192
|
await waitForOutstandingHostToolExecutions();
|
|
2192
|
-
const isTurnSuspending = ((
|
|
2193
|
+
const isTurnSuspending = ((_k = input.isTurnSuspending) == null ? void 0 : _k.call(input)) === true;
|
|
2193
2194
|
if (isTurnSuspending) {
|
|
2194
2195
|
if (finalFinish == null) {
|
|
2195
2196
|
result.discardCurrentStepContent();
|
|
2196
2197
|
}
|
|
2197
2198
|
} else if (finalFinish != null) {
|
|
2198
|
-
(
|
|
2199
|
+
(_l = input.onTurnFinished) == null ? void 0 : _l.call(input);
|
|
2199
2200
|
} else {
|
|
2200
|
-
(
|
|
2201
|
+
(_m = input.onTurnFailed) == null ? void 0 : _m.call(input);
|
|
2201
2202
|
}
|
|
2202
2203
|
await result.finish(
|
|
2203
2204
|
finalFinish ? {
|
|
@@ -2317,6 +2318,11 @@ function promptToText(prompt) {
|
|
|
2317
2318
|
return "";
|
|
2318
2319
|
}
|
|
2319
2320
|
|
|
2321
|
+
// src/utils/get-restricted-sandbox-session.ts
|
|
2322
|
+
function getRestrictedSandboxSession(sandboxSession) {
|
|
2323
|
+
return "restricted" in sandboxSession ? sandboxSession.restricted() : sandboxSession;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2320
2326
|
// src/agent/harness-agent-session.ts
|
|
2321
2327
|
var HarnessAgentSession = class {
|
|
2322
2328
|
constructor(options) {
|
|
@@ -2343,7 +2349,7 @@ var HarnessAgentSession = class {
|
|
|
2343
2349
|
this.isResume = options.underlyingSession.isResume;
|
|
2344
2350
|
}
|
|
2345
2351
|
/**
|
|
2346
|
-
* Active
|
|
2352
|
+
* Active sandbox session.
|
|
2347
2353
|
*
|
|
2348
2354
|
* @internal — accessed by session turn and lifecycle drivers.
|
|
2349
2355
|
*/
|
|
@@ -2382,7 +2388,7 @@ var HarnessAgentSession = class {
|
|
|
2382
2388
|
activeTools: options.activeTools,
|
|
2383
2389
|
toolSpecs: options.toolSpecs,
|
|
2384
2390
|
builtinToolFiltering: options.builtinToolFiltering,
|
|
2385
|
-
sandboxSession: sandboxSession
|
|
2391
|
+
sandboxSession: getRestrictedSandboxSession(sandboxSession),
|
|
2386
2392
|
sessionWorkDir: this.sessionWorkDir,
|
|
2387
2393
|
runtimeContext: options.runtimeContext,
|
|
2388
2394
|
abortSignal: options.abortSignal,
|
|
@@ -2413,6 +2419,9 @@ var HarnessAgentSession = class {
|
|
|
2413
2419
|
onTurnFailed: () => {
|
|
2414
2420
|
this.finishTrackedTurn({ turnId });
|
|
2415
2421
|
},
|
|
2422
|
+
onPromptControlAvailable: (control) => {
|
|
2423
|
+
this.setPromptControl({ turnId, control });
|
|
2424
|
+
},
|
|
2416
2425
|
isTurnSuspending: () => this.activeTurnSequence === turnId && this.suspendedTurnState != null,
|
|
2417
2426
|
onStopConditionMet: () => this.captureStopConditionBoundary({ session, turnId })
|
|
2418
2427
|
});
|
|
@@ -2437,7 +2446,7 @@ var HarnessAgentSession = class {
|
|
|
2437
2446
|
activeTools: options.activeTools,
|
|
2438
2447
|
toolSpecs: options.toolSpecs,
|
|
2439
2448
|
builtinToolFiltering: options.builtinToolFiltering,
|
|
2440
|
-
sandboxSession: sandboxSession
|
|
2449
|
+
sandboxSession: getRestrictedSandboxSession(sandboxSession),
|
|
2441
2450
|
sessionWorkDir: this.sessionWorkDir,
|
|
2442
2451
|
runtimeContext: options.runtimeContext,
|
|
2443
2452
|
abortSignal: options.abortSignal,
|
|
@@ -2470,6 +2479,9 @@ var HarnessAgentSession = class {
|
|
|
2470
2479
|
onTurnFailed: () => {
|
|
2471
2480
|
this.finishTrackedTurn({ turnId });
|
|
2472
2481
|
},
|
|
2482
|
+
onPromptControlAvailable: (control) => {
|
|
2483
|
+
this.setPromptControl({ turnId, control });
|
|
2484
|
+
},
|
|
2473
2485
|
isTurnSuspending: () => this.activeTurnSequence === turnId && this.suspendedTurnState != null,
|
|
2474
2486
|
onStopConditionMet: () => this.captureStopConditionBoundary({ session, turnId })
|
|
2475
2487
|
});
|
|
@@ -2492,6 +2504,34 @@ var HarnessAgentSession = class {
|
|
|
2492
2504
|
async compact(customInstructions) {
|
|
2493
2505
|
await this.requireReusableSession().doCompact(customInstructions);
|
|
2494
2506
|
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Submit another user message to the active turn.
|
|
2509
|
+
*
|
|
2510
|
+
* The runtime accepts the message for its next safe input boundary. Output
|
|
2511
|
+
* caused by the message remains part of the active turn's result stream.
|
|
2512
|
+
*/
|
|
2513
|
+
async experimental_steerTurn(text) {
|
|
2514
|
+
this.requireReusableSession();
|
|
2515
|
+
const activePromptControl = this.activePromptControl;
|
|
2516
|
+
if (this.turnState !== "running" || this.suspendedTurnState != null || activePromptControl == null) {
|
|
2517
|
+
throw new Error(
|
|
2518
|
+
`Harness session ${this.sessionId} has no running turn to steer.`
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
const control = await activePromptControl.promise;
|
|
2522
|
+
if (control == null || this.sessionState !== "active" || this.turnState !== "running" || this.suspendedTurnState != null || this.activePromptControl !== activePromptControl || this.activeTurnSequence !== activePromptControl.turnId) {
|
|
2523
|
+
throw new Error(
|
|
2524
|
+
`Harness session ${this.sessionId} no longer has the running turn targeted for steering.`
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
if (control.submitUserMessage == null) {
|
|
2528
|
+
throw new HarnessCapabilityUnsupportedError({
|
|
2529
|
+
message: `Harness '${this.harness.harnessId}' does not support steering active turns.`,
|
|
2530
|
+
harnessId: this.harness.harnessId
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
await control.submitUserMessage(text);
|
|
2534
|
+
}
|
|
2495
2535
|
/**
|
|
2496
2536
|
* Park the session, returning a payload the caller can persist and later
|
|
2497
2537
|
* pass to `agent.createSession({ sessionId, resumeFrom })` to reconnect.
|
|
@@ -2551,7 +2591,7 @@ var HarnessAgentSession = class {
|
|
|
2551
2591
|
return validated;
|
|
2552
2592
|
} finally {
|
|
2553
2593
|
this.endLocalHandle({ sessionState: "stopped" });
|
|
2554
|
-
if (this.ownsSandboxLifecycle) {
|
|
2594
|
+
if (this.ownsSandboxLifecycle && "stop" in sandboxSession) {
|
|
2555
2595
|
await Promise.resolve(sandboxSession.stop()).catch(() => {
|
|
2556
2596
|
});
|
|
2557
2597
|
}
|
|
@@ -2572,10 +2612,12 @@ var HarnessAgentSession = class {
|
|
|
2572
2612
|
});
|
|
2573
2613
|
}
|
|
2574
2614
|
if (!this.ownsSandboxLifecycle) return;
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2615
|
+
if ("stop" in sandboxSession) {
|
|
2616
|
+
await Promise.resolve(
|
|
2617
|
+
(_b4 = (_a4 = sandboxSession.destroy) == null ? void 0 : _a4.call(sandboxSession)) != null ? _b4 : sandboxSession.stop()
|
|
2618
|
+
).catch(() => {
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2579
2621
|
}
|
|
2580
2622
|
/**
|
|
2581
2623
|
* Gracefully freeze the active turn at the slice boundary and return the
|
|
@@ -2632,6 +2674,7 @@ var HarnessAgentSession = class {
|
|
|
2632
2674
|
}
|
|
2633
2675
|
async suspendCurrentTurn(options) {
|
|
2634
2676
|
var _a4;
|
|
2677
|
+
this.clearActivePromptControl();
|
|
2635
2678
|
(_a4 = this.suspendedTurnState) != null ? _a4 : this.suspendedTurnState = (async () => {
|
|
2636
2679
|
const raw = await options.session.doSuspendTurn();
|
|
2637
2680
|
const validated = await validateLifecycleStateData({
|
|
@@ -2687,11 +2730,13 @@ var HarnessAgentSession = class {
|
|
|
2687
2730
|
}
|
|
2688
2731
|
markAwaitingApprovalIfActive() {
|
|
2689
2732
|
if (this.sessionState === "active") {
|
|
2733
|
+
this.clearActivePromptControl();
|
|
2690
2734
|
this.turnState = "awaiting-approval";
|
|
2691
2735
|
}
|
|
2692
2736
|
}
|
|
2693
2737
|
markAwaitingToolResultIfActive() {
|
|
2694
2738
|
if (this.sessionState === "active") {
|
|
2739
|
+
this.clearActivePromptControl();
|
|
2695
2740
|
this.turnState = "awaiting-tool-result";
|
|
2696
2741
|
}
|
|
2697
2742
|
}
|
|
@@ -2700,17 +2745,52 @@ var HarnessAgentSession = class {
|
|
|
2700
2745
|
this.activeTurnSequence = turnId;
|
|
2701
2746
|
this.suspendedTurnState = void 0;
|
|
2702
2747
|
this.turnState = "running";
|
|
2748
|
+
this.clearActivePromptControl();
|
|
2749
|
+
let resolve;
|
|
2750
|
+
const promise = new Promise(
|
|
2751
|
+
(resolvePromise) => {
|
|
2752
|
+
resolve = resolvePromise;
|
|
2753
|
+
}
|
|
2754
|
+
);
|
|
2755
|
+
this.activePromptControl = {
|
|
2756
|
+
turnId,
|
|
2757
|
+
promise,
|
|
2758
|
+
resolve,
|
|
2759
|
+
settled: false
|
|
2760
|
+
};
|
|
2703
2761
|
return turnId;
|
|
2704
2762
|
}
|
|
2763
|
+
setPromptControl(options) {
|
|
2764
|
+
const activePromptControl = this.activePromptControl;
|
|
2765
|
+
if (this.sessionState !== "active" || this.turnState !== "running" || this.activeTurnSequence !== options.turnId || (activePromptControl == null ? void 0 : activePromptControl.turnId) !== options.turnId) {
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
this.settleActivePromptControl(options.control);
|
|
2769
|
+
}
|
|
2770
|
+
settleActivePromptControl(control) {
|
|
2771
|
+
const activePromptControl = this.activePromptControl;
|
|
2772
|
+
if (activePromptControl == null || activePromptControl.settled) return;
|
|
2773
|
+
activePromptControl.settled = true;
|
|
2774
|
+
activePromptControl.resolve(control);
|
|
2775
|
+
}
|
|
2776
|
+
clearActivePromptControl(turnId) {
|
|
2777
|
+
if (turnId != null && this.activePromptControl != null && this.activePromptControl.turnId !== turnId) {
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
this.settleActivePromptControl(void 0);
|
|
2781
|
+
this.activePromptControl = void 0;
|
|
2782
|
+
}
|
|
2705
2783
|
finishTrackedTurn(options) {
|
|
2706
2784
|
if (this.sessionState !== "active") return;
|
|
2707
2785
|
if (this.activeTurnSequence !== options.turnId) return;
|
|
2786
|
+
this.clearActivePromptControl(options.turnId);
|
|
2708
2787
|
this.pendingToolApprovals.clear();
|
|
2709
2788
|
this.pendingToolResults.clear();
|
|
2710
2789
|
this.suspendedTurnState = void 0;
|
|
2711
2790
|
this.turnState = "idle";
|
|
2712
2791
|
}
|
|
2713
2792
|
endLocalHandle(options) {
|
|
2793
|
+
this.clearActivePromptControl();
|
|
2714
2794
|
this.sessionState = options.sessionState;
|
|
2715
2795
|
this.underlyingSession = void 0;
|
|
2716
2796
|
this.sandboxSession = void 0;
|
|
@@ -2942,7 +3022,36 @@ function resolveBootstrapPath({
|
|
|
2942
3022
|
}
|
|
2943
3023
|
|
|
2944
3024
|
// src/agent/internal/sandbox-bootstrap.ts
|
|
3025
|
+
import { posix as posix3 } from "path";
|
|
3026
|
+
|
|
3027
|
+
// src/utils/resolve-sandbox-default-working-directory.ts
|
|
2945
3028
|
import { posix as posix2 } from "path";
|
|
3029
|
+
async function resolveSandboxDefaultWorkingDirectory({
|
|
3030
|
+
sandboxSession,
|
|
3031
|
+
abortSignal
|
|
3032
|
+
}) {
|
|
3033
|
+
if ("defaultWorkingDirectory" in sandboxSession) {
|
|
3034
|
+
return sandboxSession.defaultWorkingDirectory;
|
|
3035
|
+
}
|
|
3036
|
+
const result = await sandboxSession.run({
|
|
3037
|
+
command: "pwd",
|
|
3038
|
+
abortSignal
|
|
3039
|
+
});
|
|
3040
|
+
if (result.exitCode !== 0) {
|
|
3041
|
+
throw new Error(
|
|
3042
|
+
`Failed to resolve sandbox default working directory (exit ${result.exitCode}): ${result.stderr || result.stdout}`
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
const cwd = result.stdout.trim();
|
|
3046
|
+
if (!posix2.isAbsolute(cwd)) {
|
|
3047
|
+
throw new Error(
|
|
3048
|
+
`Failed to resolve sandbox default working directory: expected an absolute path, got ${JSON.stringify(cwd)}.`
|
|
3049
|
+
);
|
|
3050
|
+
}
|
|
3051
|
+
return cwd === "/" ? cwd : cwd.replace(/\/+$/, "");
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
// src/agent/internal/sandbox-bootstrap.ts
|
|
2946
3055
|
var SANDBOX_BOOTSTRAP_IDENTITY_VERSION = 1;
|
|
2947
3056
|
function validateSandboxBootstrapSettings(settings) {
|
|
2948
3057
|
if (settings.onBootstrap == null !== (settings.bootstrapHash == null)) {
|
|
@@ -2968,10 +3077,10 @@ function normalizeSandboxWorkDir(workDir) {
|
|
|
2968
3077
|
"HarnessAgent: `sandboxConfig.workDir` must use POSIX path separators."
|
|
2969
3078
|
);
|
|
2970
3079
|
}
|
|
2971
|
-
if (
|
|
3080
|
+
if (posix3.isAbsolute(workDir)) {
|
|
2972
3081
|
throw new Error("HarnessAgent: `sandboxConfig.workDir` must be relative.");
|
|
2973
3082
|
}
|
|
2974
|
-
const normalized =
|
|
3083
|
+
const normalized = posix3.normalize(workDir);
|
|
2975
3084
|
if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
2976
3085
|
throw new Error(
|
|
2977
3086
|
"HarnessAgent: `sandboxConfig.workDir` must stay inside the sandbox default working directory."
|
|
@@ -3030,8 +3139,8 @@ async function runSandboxBootstrap({
|
|
|
3030
3139
|
abortSignal
|
|
3031
3140
|
}) {
|
|
3032
3141
|
if (recipe == null && onBootstrap == null) return;
|
|
3033
|
-
const resolvedDefaultWorkingDirectory = defaultWorkingDirectory != null ? defaultWorkingDirectory : await
|
|
3034
|
-
session,
|
|
3142
|
+
const resolvedDefaultWorkingDirectory = defaultWorkingDirectory != null ? defaultWorkingDirectory : await resolveSandboxDefaultWorkingDirectory({
|
|
3143
|
+
sandboxSession: session,
|
|
3035
3144
|
abortSignal
|
|
3036
3145
|
});
|
|
3037
3146
|
if (recipe != null && recipeIdentity != null) {
|
|
@@ -3055,27 +3164,6 @@ async function runSandboxBootstrap({
|
|
|
3055
3164
|
});
|
|
3056
3165
|
await onBootstrap({ session, workDir: bootstrapWorkDir, abortSignal });
|
|
3057
3166
|
}
|
|
3058
|
-
async function resolveDefaultWorkingDirectory({
|
|
3059
|
-
session,
|
|
3060
|
-
abortSignal
|
|
3061
|
-
}) {
|
|
3062
|
-
const result = await session.run({
|
|
3063
|
-
command: "pwd",
|
|
3064
|
-
abortSignal
|
|
3065
|
-
});
|
|
3066
|
-
if (result.exitCode !== 0) {
|
|
3067
|
-
throw new Error(
|
|
3068
|
-
`Failed to resolve sandbox default working directory (exit ${result.exitCode}): ${result.stderr || result.stdout}`
|
|
3069
|
-
);
|
|
3070
|
-
}
|
|
3071
|
-
const cwd = result.stdout.trim();
|
|
3072
|
-
if (!posix2.isAbsolute(cwd)) {
|
|
3073
|
-
throw new Error(
|
|
3074
|
-
`Failed to resolve sandbox default working directory: expected an absolute path, got ${JSON.stringify(cwd)}.`
|
|
3075
|
-
);
|
|
3076
|
-
}
|
|
3077
|
-
return cwd === "/" ? cwd : cwd.replace(/\/+$/, "");
|
|
3078
|
-
}
|
|
3079
3167
|
async function ensureSandboxDirectory({
|
|
3080
3168
|
session,
|
|
3081
3169
|
workDir,
|
|
@@ -3126,7 +3214,7 @@ function joinSandboxPath({
|
|
|
3126
3214
|
base,
|
|
3127
3215
|
path
|
|
3128
3216
|
}) {
|
|
3129
|
-
return
|
|
3217
|
+
return posix3.join(base, path);
|
|
3130
3218
|
}
|
|
3131
3219
|
|
|
3132
3220
|
// src/agent/internal/resolve-observability.ts
|
|
@@ -3347,8 +3435,13 @@ var HarnessAgent = class {
|
|
|
3347
3435
|
let sessionWorkDir;
|
|
3348
3436
|
if (providedSandboxSession != null) {
|
|
3349
3437
|
sandboxSession = providedSandboxSession;
|
|
3438
|
+
const toolSafeSandboxSession = getRestrictedSandboxSession(sandboxSession);
|
|
3439
|
+
const defaultWorkingDirectory = await resolveSandboxDefaultWorkingDirectory({
|
|
3440
|
+
sandboxSession,
|
|
3441
|
+
abortSignal
|
|
3442
|
+
});
|
|
3350
3443
|
sessionWorkDir = resolveSessionWorkDir({
|
|
3351
|
-
defaultWorkingDirectory
|
|
3444
|
+
defaultWorkingDirectory,
|
|
3352
3445
|
harnessId: harness.harnessId,
|
|
3353
3446
|
sessionId,
|
|
3354
3447
|
workDir: this.sandboxConfig.workDir
|
|
@@ -3358,10 +3451,10 @@ var HarnessAgent = class {
|
|
|
3358
3451
|
const recipeIdentity = await hashHarnessBootstrap(recipe);
|
|
3359
3452
|
try {
|
|
3360
3453
|
await applyBootstrapRecipe({
|
|
3361
|
-
session:
|
|
3454
|
+
session: toolSafeSandboxSession,
|
|
3362
3455
|
recipe,
|
|
3363
3456
|
identity: recipeIdentity,
|
|
3364
|
-
defaultWorkingDirectory
|
|
3457
|
+
defaultWorkingDirectory,
|
|
3365
3458
|
abortSignal
|
|
3366
3459
|
});
|
|
3367
3460
|
} catch (err) {
|
|
@@ -3385,12 +3478,13 @@ var HarnessAgent = class {
|
|
|
3385
3478
|
harnessId: harness.harnessId
|
|
3386
3479
|
});
|
|
3387
3480
|
}
|
|
3388
|
-
|
|
3481
|
+
const resumedSandboxSession = await sandboxProvider.resumeSession({
|
|
3389
3482
|
sessionId,
|
|
3390
3483
|
abortSignal
|
|
3391
3484
|
});
|
|
3485
|
+
sandboxSession = resumedSandboxSession;
|
|
3392
3486
|
sessionWorkDir = resolveSessionWorkDir({
|
|
3393
|
-
defaultWorkingDirectory:
|
|
3487
|
+
defaultWorkingDirectory: resumedSandboxSession.defaultWorkingDirectory,
|
|
3394
3488
|
harnessId: harness.harnessId,
|
|
3395
3489
|
sessionId,
|
|
3396
3490
|
workDir: this.sandboxConfig.workDir
|
|
@@ -3404,14 +3498,15 @@ var HarnessAgent = class {
|
|
|
3404
3498
|
recipe,
|
|
3405
3499
|
settings: this.sandboxConfig
|
|
3406
3500
|
});
|
|
3407
|
-
|
|
3501
|
+
const createdSandboxSession = await sandboxProvider.createSession({
|
|
3408
3502
|
sessionId,
|
|
3409
3503
|
abortSignal,
|
|
3410
3504
|
identity: sandboxBootstrapPlan.identity,
|
|
3411
3505
|
onFirstCreate: sandboxBootstrapPlan.onFirstCreate
|
|
3412
3506
|
});
|
|
3507
|
+
sandboxSession = createdSandboxSession;
|
|
3413
3508
|
sessionWorkDir = resolveSessionWorkDir({
|
|
3414
|
-
defaultWorkingDirectory:
|
|
3509
|
+
defaultWorkingDirectory: createdSandboxSession.defaultWorkingDirectory,
|
|
3415
3510
|
harnessId: harness.harnessId,
|
|
3416
3511
|
sessionId,
|
|
3417
3512
|
workDir: sandboxBootstrapPlan.workDir
|
|
@@ -3419,10 +3514,10 @@ var HarnessAgent = class {
|
|
|
3419
3514
|
if (sandboxBootstrapPlan.recipe != null && sandboxBootstrapPlan.recipeIdentity != null) {
|
|
3420
3515
|
try {
|
|
3421
3516
|
await applyBootstrapRecipe({
|
|
3422
|
-
session:
|
|
3517
|
+
session: createdSandboxSession.restricted(),
|
|
3423
3518
|
recipe: sandboxBootstrapPlan.recipe,
|
|
3424
3519
|
identity: sandboxBootstrapPlan.recipeIdentity,
|
|
3425
|
-
defaultWorkingDirectory:
|
|
3520
|
+
defaultWorkingDirectory: createdSandboxSession.defaultWorkingDirectory,
|
|
3426
3521
|
abortSignal
|
|
3427
3522
|
});
|
|
3428
3523
|
} catch (err) {
|
|
@@ -3443,7 +3538,7 @@ var HarnessAgent = class {
|
|
|
3443
3538
|
});
|
|
3444
3539
|
if (this.sandboxConfig.onSession != null) {
|
|
3445
3540
|
await this.sandboxConfig.onSession({
|
|
3446
|
-
session: sandboxSession
|
|
3541
|
+
session: getRestrictedSandboxSession(sandboxSession),
|
|
3447
3542
|
sessionWorkDir,
|
|
3448
3543
|
abortSignal
|
|
3449
3544
|
});
|
|
@@ -3566,6 +3661,16 @@ var HarnessAgent = class {
|
|
|
3566
3661
|
});
|
|
3567
3662
|
return result;
|
|
3568
3663
|
}
|
|
3664
|
+
/**
|
|
3665
|
+
* Submit another user message to a currently running session turn.
|
|
3666
|
+
*
|
|
3667
|
+
* The returned promise resolves after the runtime has accepted the message
|
|
3668
|
+
* for its next safe input boundary. Output caused by the message remains in
|
|
3669
|
+
* the current turn's stream.
|
|
3670
|
+
*/
|
|
3671
|
+
async experimental_steer(options) {
|
|
3672
|
+
await options.session.experimental_steerTurn(options.text);
|
|
3673
|
+
}
|
|
3569
3674
|
// ─── Internals ──────────────────────────────────────────────────────
|
|
3570
3675
|
_startTurn(input) {
|
|
3571
3676
|
if (input.turnInput.mode === "continue") {
|
|
@@ -3768,8 +3873,10 @@ function resolveSandboxConfig(settings) {
|
|
|
3768
3873
|
}
|
|
3769
3874
|
async function cleanupAfterStartFailure(input) {
|
|
3770
3875
|
if (!input.ownsSandboxLifecycle) return;
|
|
3771
|
-
|
|
3772
|
-
|
|
3876
|
+
if ("stop" in input.sandboxSession) {
|
|
3877
|
+
await Promise.resolve(input.sandboxSession.stop()).catch(() => {
|
|
3878
|
+
});
|
|
3879
|
+
}
|
|
3773
3880
|
}
|
|
3774
3881
|
|
|
3775
3882
|
// src/agent/prepare-harness-sandbox-template.ts
|
|
@@ -3841,8 +3948,8 @@ async function prepareSandboxForHarness(options) {
|
|
|
3841
3948
|
}
|
|
3842
3949
|
const recipeIdentity = await hashHarnessBootstrap(recipe);
|
|
3843
3950
|
recipeIdentities[harness.harnessId] = recipeIdentity;
|
|
3844
|
-
defaultWorkingDirectory != null ? defaultWorkingDirectory : defaultWorkingDirectory = await
|
|
3845
|
-
|
|
3951
|
+
defaultWorkingDirectory != null ? defaultWorkingDirectory : defaultWorkingDirectory = await resolveSandboxDefaultWorkingDirectory({
|
|
3952
|
+
sandboxSession: options.session,
|
|
3846
3953
|
abortSignal: options.abortSignal
|
|
3847
3954
|
});
|
|
3848
3955
|
await applyBootstrapRecipe({
|