@ai-sdk/harness-claude-code 1.0.126 → 1.0.127
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 +15 -0
- package/dist/bridge/index.mjs +2 -3
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +34 -18
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/index.ts +1 -1
- package/src/bridge/json-schema-to-zod.ts +1 -1
- package/src/claude-code-bootstrap.ts +7 -9
- package/src/claude-code-harness.ts +42 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-claude-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.127",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
30
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.123",
|
|
30
|
+
"@ai-sdk/provider-utils": "5.0.47",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
package/src/bridge/index.ts
CHANGED
|
@@ -390,7 +390,7 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
390
390
|
|
|
391
391
|
const streamEventState = createClaudeStreamEventState();
|
|
392
392
|
|
|
393
|
-
const mcpServers: Record<string, unknown> = { ...
|
|
393
|
+
const mcpServers: Record<string, unknown> = { ...start.mcpServers };
|
|
394
394
|
if (start.tools && start.tools.length > 0) {
|
|
395
395
|
const server = new mcpModule.McpServer({
|
|
396
396
|
name: 'harness-tools',
|
|
@@ -37,7 +37,7 @@ function toZodObject(schema: JsonSchemaObject) {
|
|
|
37
37
|
|
|
38
38
|
function toZodShape(schema: JsonSchemaObject | undefined): ZodShape {
|
|
39
39
|
if (!schema?.properties) return {};
|
|
40
|
-
const required = new Set(schema.required
|
|
40
|
+
const required = new Set(schema.required);
|
|
41
41
|
const shape: ZodShape = {};
|
|
42
42
|
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
43
43
|
const propType = toZodType(propSchema);
|
|
@@ -16,16 +16,14 @@ const readBridgeAsset = createReadBridgeAsset({
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
/*
|
|
19
|
-
* Bootstrap is derived state stored under
|
|
20
|
-
* directory
|
|
21
|
-
* bridge, and recipe marker
|
|
19
|
+
* Bootstrap is derived state stored under `$HOME/.ai-sdk-harness`, outside
|
|
20
|
+
* the agent's working directory. Snapshot-capable providers preserve the
|
|
21
|
+
* installed CLI, bridge, and recipe marker there.
|
|
22
22
|
*
|
|
23
|
-
* The session work dir (`startOpts.sessionWorkDir`)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* history is keyed by working directory) and the bridge state files survive
|
|
28
|
-
* both detach -> attach/replay and stop -> snapshot -> resume cycles.
|
|
23
|
+
* The session work dir (`startOpts.sessionWorkDir`) lives under the sandbox's
|
|
24
|
+
* default working directory, while the bridge-state dir lives under
|
|
25
|
+
* `$HOME/.ai-sdk-harness/.agent-runs`. Claude's project history is keyed by
|
|
26
|
+
* the working directory, so the same work dir is needed across resumes.
|
|
29
27
|
*/
|
|
30
28
|
export const CLAUDE_CODE_BOOTSTRAP_DIR = '.harness-bootstrap/claude-code';
|
|
31
29
|
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
type HarnessV1Session,
|
|
22
22
|
type HarnessV1Skill,
|
|
23
23
|
type HarnessV1StreamPart,
|
|
24
|
+
harnessStateDirectoryPath,
|
|
25
|
+
harnessSessionDataDirectoryPath,
|
|
24
26
|
} from '@ai-sdk/harness';
|
|
25
27
|
import {
|
|
26
28
|
applyCredentialForwarding,
|
|
@@ -34,7 +36,6 @@ import {
|
|
|
34
36
|
forwardBridgeProcessStream,
|
|
35
37
|
getRestrictedSandboxSession,
|
|
36
38
|
markBridgeStarting,
|
|
37
|
-
resolveSandboxDefaultWorkingDirectory,
|
|
38
39
|
resolveSandboxHomeDir,
|
|
39
40
|
SandboxChannel,
|
|
40
41
|
shellQuote,
|
|
@@ -842,11 +843,6 @@ export function createClaudeCode(
|
|
|
842
843
|
'The Claude Code harness cannot use `mintBridgeToken` with a sandbox session that does not expose an id.',
|
|
843
844
|
});
|
|
844
845
|
}
|
|
845
|
-
const defaultWorkingDirectory =
|
|
846
|
-
await resolveSandboxDefaultWorkingDirectory({
|
|
847
|
-
sandboxSession,
|
|
848
|
-
abortSignal: startOpts.abortSignal,
|
|
849
|
-
});
|
|
850
846
|
const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
|
|
851
847
|
const isResume = lifecycleState != null;
|
|
852
848
|
const isContinue = startOpts.continueFrom != null;
|
|
@@ -932,21 +928,25 @@ export function createClaudeCode(
|
|
|
932
928
|
CLAUDE_CODE_CREDENTIAL_ENVIRONMENT_VARIABLES,
|
|
933
929
|
});
|
|
934
930
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
931
|
+
// Harness SDK state (bootstrap, per-session runs) always lives under
|
|
932
|
+
// the sandbox's own HOME, never the working directory, so it stays
|
|
933
|
+
// out of a user-owned workspace.
|
|
934
|
+
const sandboxHomeDir = await resolveSandboxHomeDir({
|
|
935
|
+
sandbox: toolSafeSandboxSession,
|
|
936
|
+
abortSignal: startOpts.abortSignal,
|
|
937
|
+
});
|
|
938
|
+
const stateDir = harnessStateDirectoryPath({ sandboxHomeDir });
|
|
939
|
+
const bootstrapDir = posix.resolve(stateDir, BOOTSTRAP_DIR);
|
|
939
940
|
// The conversation the host wants back, when it is known. Absent on
|
|
940
941
|
// state written before this field existed; those resumes fall back to
|
|
941
942
|
// the `continue` flag as before.
|
|
942
943
|
const resumeSessionId = resumeData?.claudeSessionId;
|
|
943
944
|
|
|
944
945
|
const workDir = startOpts.sessionWorkDir;
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
946
|
+
const sessionDataDir = harnessSessionDataDirectoryPath({
|
|
947
|
+
stateDirectory: stateDir,
|
|
948
|
+
sessionId: startOpts.sessionId,
|
|
948
949
|
});
|
|
949
|
-
const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
|
|
950
950
|
const bridgeStateDir = `${sessionDataDir}/bridge`;
|
|
951
951
|
const timeoutMs = settings.startupTimeoutMs ?? 120_000;
|
|
952
952
|
|
|
@@ -1016,10 +1016,19 @@ export function createClaudeCode(
|
|
|
1016
1016
|
onBridgeError,
|
|
1017
1017
|
reconnect: settings.reconnect,
|
|
1018
1018
|
});
|
|
1019
|
-
|
|
1019
|
+
const finishListenerAttachment = isContinue
|
|
1020
|
+
? attachChannel.beginListenerAttachment()
|
|
1021
|
+
: undefined;
|
|
1022
|
+
try {
|
|
1023
|
+
await attachChannel.open(isContinue ? { resume: true } : undefined);
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
finishListenerAttachment?.();
|
|
1026
|
+
throw error;
|
|
1027
|
+
}
|
|
1020
1028
|
return createSession({
|
|
1021
1029
|
sessionId: startOpts.sessionId,
|
|
1022
1030
|
channel: attachChannel,
|
|
1031
|
+
finishListenerAttachment,
|
|
1023
1032
|
...(resumeSessionId ? { resumeSessionId } : {}),
|
|
1024
1033
|
// The live bridge was spawned by another process; this one owns no
|
|
1025
1034
|
// process handle. The session lifecycle method decides whether the
|
|
@@ -1168,13 +1177,23 @@ export function createClaudeCode(
|
|
|
1168
1177
|
? { initialLastSeenEventId: coords?.lastSeenEventId ?? 0 }
|
|
1169
1178
|
: {}),
|
|
1170
1179
|
});
|
|
1171
|
-
|
|
1172
|
-
respawnStrategy === 'replay'
|
|
1173
|
-
|
|
1180
|
+
const finishListenerAttachment =
|
|
1181
|
+
respawnStrategy === 'replay'
|
|
1182
|
+
? channel.beginListenerAttachment()
|
|
1183
|
+
: undefined;
|
|
1184
|
+
try {
|
|
1185
|
+
await channel.open(
|
|
1186
|
+
respawnStrategy === 'replay' ? { resume: true } : undefined,
|
|
1187
|
+
);
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
finishListenerAttachment?.();
|
|
1190
|
+
throw error;
|
|
1191
|
+
}
|
|
1174
1192
|
|
|
1175
1193
|
return createSession({
|
|
1176
1194
|
sessionId: startOpts.sessionId,
|
|
1177
1195
|
channel,
|
|
1196
|
+
finishListenerAttachment,
|
|
1178
1197
|
proc,
|
|
1179
1198
|
maxTurns: settings.maxTurns,
|
|
1180
1199
|
env: sandboxClaudeEnvironment,
|
|
@@ -1490,6 +1509,7 @@ function formatUnknownError(error: unknown): string {
|
|
|
1490
1509
|
function createSession({
|
|
1491
1510
|
sessionId,
|
|
1492
1511
|
channel,
|
|
1512
|
+
finishListenerAttachment,
|
|
1493
1513
|
proc,
|
|
1494
1514
|
maxTurns,
|
|
1495
1515
|
env,
|
|
@@ -1513,6 +1533,7 @@ function createSession({
|
|
|
1513
1533
|
}: {
|
|
1514
1534
|
sessionId: string;
|
|
1515
1535
|
channel: ClaudeCodeChannel;
|
|
1536
|
+
finishListenerAttachment: (() => void) | undefined;
|
|
1516
1537
|
/** Undefined on `attach` — the live bridge was spawned by another process. */
|
|
1517
1538
|
proc: Experimental_SandboxProcess | undefined;
|
|
1518
1539
|
maxTurns: number | undefined;
|
|
@@ -1621,6 +1642,7 @@ function createSession({
|
|
|
1621
1642
|
'tool-approval-request',
|
|
1622
1643
|
'tool-result',
|
|
1623
1644
|
'finish-step',
|
|
1645
|
+
'compaction',
|
|
1624
1646
|
'raw',
|
|
1625
1647
|
] as const;
|
|
1626
1648
|
for (const type of eventTypes) {
|
|
@@ -1650,6 +1672,8 @@ function createSession({
|
|
|
1650
1672
|
settleError(msg.error);
|
|
1651
1673
|
}),
|
|
1652
1674
|
);
|
|
1675
|
+
finishListenerAttachment?.();
|
|
1676
|
+
finishListenerAttachment = undefined;
|
|
1653
1677
|
|
|
1654
1678
|
/*
|
|
1655
1679
|
* A `'suspended'` close is a graceful slice-boundary freeze the host
|