@ai-sdk/harness-claude-code 1.0.96 → 1.0.98

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/dist/index.js CHANGED
@@ -258,7 +258,15 @@ var startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
258
258
  // Resume signal. When true, the bridge passes `{ continue: true }` to the
259
259
  // Claude SDK so the in-workdir thread state is rehydrated. The host sets this
260
260
  // on the first prompt after a cross-process resume.
261
- continue: z.boolean().optional()
261
+ continue: z.boolean().optional(),
262
+ // Exact conversation to resume, as the Claude SDK's `resume` option.
263
+ //
264
+ // Preferred over `continue` whenever the host knows which conversation it
265
+ // means: `continue` resolves to the most recent thread in the workdir, which
266
+ // is the wrong one as soon as anything else has run there since — including
267
+ // a resume that itself started a new thread. Mutually exclusive with
268
+ // `continue` in the SDK, so the bridge sends one or the other.
269
+ resumeSessionId: z.string().optional()
262
270
  });
263
271
  var inboundMessageSchema = z.discriminatedUnion("type", [
264
272
  startMessageSchema,
@@ -266,7 +274,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
266
274
  ]);
267
275
 
268
276
  // src/version.ts
269
- var VERSION = true ? "1.0.96" : "0.0.0-test";
277
+ var VERSION = true ? "1.0.98" : "0.0.0-test";
270
278
 
271
279
  // src/claude-code-harness.ts
272
280
  var CLAUDE_CODE_CLIENT_APP = `ai-sdk/harness-claude-code/${VERSION}`;
@@ -871,7 +879,18 @@ var claudeCodeBridgeCoordsSchema = z2.object({
871
879
  });
872
880
  var claudeCodeResumeStateSchema = z2.looseObject({
873
881
  bridge: claudeCodeBridgeCoordsSchema.optional(),
874
- sandboxCredentialEnvironment: z2.record(z2.string(), z2.string()).optional()
882
+ sandboxCredentialEnvironment: z2.record(z2.string(), z2.string()).optional(),
883
+ /**
884
+ * The exact Claude conversation to rehydrate on resume. Written by the
885
+ * adapter on `doStop()`/`doDetach()`/`doSuspendTurn()` from the session id
886
+ * the bridge observed, so a resume names the conversation instead of
887
+ * relying on the SDK's `continue` flag — "most recent thread in this
888
+ * workdir" — which silently picks the wrong one once a second thread
889
+ * exists there. Hosts that captured the id themselves (it is surfaced as
890
+ * `harnessMetadata['claude-code'].sessionId` on `finish` parts) may also
891
+ * set it explicitly.
892
+ */
893
+ claudeSessionId: z2.string().optional()
875
894
  });
876
895
  function createClaudeCode(settings = {}) {
877
896
  if (settings.mcpServers != null && Object.prototype.hasOwnProperty.call(settings.mcpServers, "harness-tools")) {
@@ -964,7 +983,12 @@ function createClaudeCode(settings = {}) {
964
983
  defaultWorkingDirectory,
965
984
  CLAUDE_CODE_BOOTSTRAP_DIR
966
985
  );
986
+ const resumeSessionId = resumeData?.claudeSessionId;
967
987
  const workDir = startOpts.sessionWorkDir;
988
+ const sandboxHomeDir = await resolveSandboxHomeDir({
989
+ sandbox: toolSafeSandboxSession,
990
+ abortSignal: startOpts.abortSignal
991
+ });
968
992
  const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
969
993
  const bridgeStateDir = `${sessionDataDir}/bridge`;
970
994
  const timeoutMs = settings.startupTimeoutMs ?? 12e4;
@@ -1011,6 +1035,7 @@ function createClaudeCode(settings = {}) {
1011
1035
  return createSession({
1012
1036
  sessionId: startOpts.sessionId,
1013
1037
  channel: attachChannel,
1038
+ ...resumeSessionId ? { resumeSessionId } : {},
1014
1039
  // The live bridge was spawned by another process; this one owns no
1015
1040
  // process handle. The session lifecycle method decides whether the
1016
1041
  // sandbox is left running, stopped, or destroyed.
@@ -1030,7 +1055,8 @@ function createClaudeCode(settings = {}) {
1030
1055
  debug: startOpts.observability?.debug,
1031
1056
  permissionMode: startOpts.permissionMode,
1032
1057
  builtinToolFiltering: startOpts.builtinToolFiltering,
1033
- skills: startOpts.skills ?? [],
1058
+ sandbox: toolSafeSandboxSession,
1059
+ sandboxHomeDir,
1034
1060
  mcpServers: settings.mcpServers,
1035
1061
  supportsUserMessageResponses: () => supportsUserMessageResponses
1036
1062
  });
@@ -1049,10 +1075,6 @@ function createClaudeCode(settings = {}) {
1049
1075
  respawnStrategy = "replay";
1050
1076
  }
1051
1077
  }
1052
- const sandboxHomeDir = startOpts.skills && startOpts.skills.length > 0 ? await resolveSandboxHomeDir({
1053
- sandbox: toolSafeSandboxSession,
1054
- abortSignal: startOpts.abortSignal
1055
- }) : void 0;
1056
1078
  const port = resolveBridgePort({
1057
1079
  sandboxSession,
1058
1080
  override: settings.port
@@ -1061,7 +1083,6 @@ function createClaudeCode(settings = {}) {
1061
1083
  const env = {
1062
1084
  BRIDGE_CHANNEL_TOKEN: token,
1063
1085
  BRIDGE_WS_PORT: String(port),
1064
- ...sandboxHomeDir ? { HOME: sandboxHomeDir } : {},
1065
1086
  ...respawnStrategy === "replay" ? { BRIDGE_REPLAY_FROM_DISK: "1" } : {}
1066
1087
  };
1067
1088
  if (respawnStrategy === void 0) {
@@ -1069,17 +1090,6 @@ function createClaudeCode(settings = {}) {
1069
1090
  command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
1070
1091
  abortSignal: startOpts.abortSignal
1071
1092
  });
1072
- if (startOpts.skills && startOpts.skills.length > 0) {
1073
- if (!sandboxHomeDir) {
1074
- throw new Error("Unable to resolve sandbox HOME directory.");
1075
- }
1076
- await writeClaudeCodeSkills({
1077
- sandbox: toolSafeSandboxSession,
1078
- homeDir: sandboxHomeDir,
1079
- skills: startOpts.skills,
1080
- abortSignal: startOpts.abortSignal
1081
- });
1082
- }
1083
1093
  }
1084
1094
  await markBridgeStarting({
1085
1095
  sandbox: toolSafeSandboxSession,
@@ -1153,6 +1163,7 @@ function createClaudeCode(settings = {}) {
1153
1163
  effort: settings.effort,
1154
1164
  isResume: respawnStrategy !== void 0,
1155
1165
  continueOnFirstPrompt: respawnStrategy !== void 0,
1166
+ ...resumeSessionId ? { resumeSessionId } : {},
1156
1167
  rerunContinue: respawnStrategy === "rerun",
1157
1168
  bridgePort: boundPort,
1158
1169
  bridgeToken: token,
@@ -1161,7 +1172,8 @@ function createClaudeCode(settings = {}) {
1161
1172
  debug: startOpts.observability?.debug,
1162
1173
  permissionMode: startOpts.permissionMode,
1163
1174
  builtinToolFiltering: startOpts.builtinToolFiltering,
1164
- skills: startOpts.skills ?? [],
1175
+ sandbox: toolSafeSandboxSession,
1176
+ sandboxHomeDir,
1165
1177
  mcpServers: settings.mcpServers,
1166
1178
  supportsUserMessageResponses: () => supportsUserMessageResponses
1167
1179
  });
@@ -1401,6 +1413,7 @@ function createSession({
1401
1413
  effort,
1402
1414
  isResume,
1403
1415
  continueOnFirstPrompt,
1416
+ resumeSessionId,
1404
1417
  rerunContinue,
1405
1418
  bridgePort,
1406
1419
  bridgeToken,
@@ -1409,13 +1422,15 @@ function createSession({
1409
1422
  debug,
1410
1423
  permissionMode,
1411
1424
  builtinToolFiltering,
1412
- skills,
1425
+ sandbox,
1426
+ sandboxHomeDir,
1413
1427
  mcpServers,
1414
1428
  supportsUserMessageResponses
1415
1429
  }) {
1416
1430
  let stopped = false;
1417
1431
  let stopPromise;
1418
1432
  let pendingResumeFlag = continueOnFirstPrompt;
1433
+ let lastClaudeSessionId = resumeSessionId;
1419
1434
  const wireTurn = (turnOpts) => {
1420
1435
  let pendingResolve;
1421
1436
  let pendingReject;
@@ -1458,6 +1473,9 @@ function createSession({
1458
1473
  "reasoning-start",
1459
1474
  "reasoning-delta",
1460
1475
  "reasoning-end",
1476
+ "tool-input-start",
1477
+ "tool-input-delta",
1478
+ "tool-input-end",
1461
1479
  "tool-call",
1462
1480
  "tool-approval-request",
1463
1481
  "tool-result",
@@ -1473,6 +1491,10 @@ function createSession({
1473
1491
  }
1474
1492
  unsubs.push(
1475
1493
  channel.on("finish", (msg) => {
1494
+ const metadata = msg.harnessMetadata?.["claude-code"]?.sessionId;
1495
+ if (typeof metadata === "string" && metadata.length > 0) {
1496
+ lastClaudeSessionId = metadata;
1497
+ }
1476
1498
  forward(msg);
1477
1499
  settleSuccess();
1478
1500
  })
@@ -1541,7 +1563,6 @@ function createSession({
1541
1563
  return {
1542
1564
  sessionId,
1543
1565
  isResume,
1544
- modelId: model,
1545
1566
  doPromptTurn: async (promptOpts) => {
1546
1567
  if (promptOpts.responseFormat?.type === "json" && promptOpts.responseFormat.schema == null) {
1547
1568
  throw new HarnessCapabilityUnsupportedError({
@@ -1549,10 +1570,19 @@ function createSession({
1549
1570
  harnessId: "claude-code"
1550
1571
  });
1551
1572
  }
1573
+ await writeClaudeCodeSkills({
1574
+ sandbox,
1575
+ homeDir: sandboxHomeDir,
1576
+ skills: promptOpts.skills,
1577
+ abortSignal: promptOpts.abortSignal
1578
+ });
1552
1579
  const control = wireTurn({
1553
1580
  emit: promptOpts.emit,
1554
1581
  abortSignal: promptOpts.abortSignal
1555
1582
  });
1583
+ if (promptOpts.abortSignal?.aborted) {
1584
+ return control;
1585
+ }
1556
1586
  const startMessage = {
1557
1587
  type: "start",
1558
1588
  prompt: extractUserText(promptOpts.prompt),
@@ -1563,17 +1593,17 @@ function createSession({
1563
1593
  })),
1564
1594
  ...promptOpts.responseFormat == null ? {} : { responseFormat: promptOpts.responseFormat },
1565
1595
  ...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
1566
- model,
1596
+ model: promptOpts.model ?? model,
1567
1597
  maxTurns,
1568
1598
  ...env !== void 0 ? { env } : {},
1569
1599
  thinking,
1570
1600
  ...effort !== void 0 ? { effort } : {},
1571
- ...skills.length > 0 ? { skills: skills.map((skill) => skill.name) } : {},
1601
+ ...promptOpts.skills.length > 0 ? { skills: promptOpts.skills.map((skill) => skill.name) } : {},
1572
1602
  ...mcpServers == null ? {} : { mcpServers },
1573
1603
  ...permissionMode ? { permissionMode } : {},
1574
1604
  ...builtinToolFiltering ? { builtinToolFiltering } : {},
1575
1605
  ...debug ? { debug } : {},
1576
- ...pendingResumeFlag ? { continue: true } : {}
1606
+ ...lastClaudeSessionId ? { resumeSessionId: lastClaudeSessionId } : pendingResumeFlag ? { continue: true } : {}
1577
1607
  };
1578
1608
  pendingResumeFlag = false;
1579
1609
  channel.send(startMessage);
@@ -1586,11 +1616,17 @@ function createSession({
1586
1616
  harnessId: "claude-code"
1587
1617
  });
1588
1618
  }
1619
+ await writeClaudeCodeSkills({
1620
+ sandbox,
1621
+ homeDir: sandboxHomeDir,
1622
+ skills: continueOpts.skills,
1623
+ abortSignal: continueOpts.abortSignal
1624
+ });
1589
1625
  const control = wireTurn({
1590
1626
  emit: continueOpts.emit,
1591
1627
  abortSignal: continueOpts.abortSignal
1592
1628
  });
1593
- if (rerunContinue) {
1629
+ if (rerunContinue && !continueOpts.abortSignal?.aborted) {
1594
1630
  pendingResumeFlag = false;
1595
1631
  channel.send({
1596
1632
  type: "start",
@@ -1609,17 +1645,17 @@ function createSession({
1609
1645
  })),
1610
1646
  ...continueOpts.responseFormat == null ? {} : { responseFormat: continueOpts.responseFormat },
1611
1647
  ...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
1612
- model,
1648
+ model: continueOpts.model ?? model,
1613
1649
  maxTurns,
1614
1650
  ...env !== void 0 ? { env } : {},
1615
1651
  thinking,
1616
1652
  ...effort !== void 0 ? { effort } : {},
1617
- ...skills.length > 0 ? { skills: skills.map((skill) => skill.name) } : {},
1653
+ ...continueOpts.skills.length > 0 ? { skills: continueOpts.skills.map((skill) => skill.name) } : {},
1618
1654
  ...mcpServers == null ? {} : { mcpServers },
1619
1655
  ...permissionMode ? { permissionMode } : {},
1620
1656
  ...builtinToolFiltering ? { builtinToolFiltering } : {},
1621
1657
  ...debug ? { debug } : {},
1622
- continue: true
1658
+ ...lastClaudeSessionId ? { resumeSessionId: lastClaudeSessionId } : { continue: true }
1623
1659
  });
1624
1660
  }
1625
1661
  return control;
@@ -1647,7 +1683,8 @@ function createSession({
1647
1683
  token: bridgeToken,
1648
1684
  lastSeenEventId,
1649
1685
  ...sandboxId == null ? {} : { sandboxId }
1650
- }
1686
+ },
1687
+ ...lastClaudeSessionId ? { claudeSessionId: lastClaudeSessionId } : {}
1651
1688
  }
1652
1689
  };
1653
1690
  return payload;
@@ -1740,7 +1777,11 @@ function createSession({
1740
1777
  type: "resume-session",
1741
1778
  harnessId: "claude-code",
1742
1779
  specificationVersion: "harness-v1",
1780
+ // The bridge's stop reply carries the session id it observed; the
1781
+ // adapter's own record backfills it when the reply predates the
1782
+ // field or the channel was already closed.
1743
1783
  data: {
1784
+ ...lastClaudeSessionId ? { claudeSessionId: lastClaudeSessionId } : {},
1744
1785
  ...lifecycleData,
1745
1786
  ...sandboxCredentialEnvironment == null ? {} : { sandboxCredentialEnvironment }
1746
1787
  }
@@ -1766,7 +1807,8 @@ function createSession({
1766
1807
  token: bridgeToken,
1767
1808
  lastSeenEventId,
1768
1809
  ...sandboxId == null ? {} : { sandboxId }
1769
- }
1810
+ },
1811
+ ...lastClaudeSessionId ? { claudeSessionId: lastClaudeSessionId } : {}
1770
1812
  }
1771
1813
  };
1772
1814
  return payload;