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

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.
@@ -90,6 +90,8 @@ export type ClaudeCodeHarnessSettings = {
90
90
  /**
91
91
  * Anthropic model id the underlying `claude` CLI should use. Leaving this
92
92
  * unset defers to the CLI's default.
93
+ *
94
+ * @deprecated Use `model` on `HarnessAgent` instead.
93
95
  */
94
96
  readonly model?: string;
95
97
  /**
@@ -829,6 +831,7 @@ export function createClaudeCode(
829
831
  lifecycleStateSchema: claudeCodeResumeStateSchema,
830
832
  getBootstrap: getClaudeCodeBootstrap,
831
833
  doStart: async startOpts => {
834
+ const model = startOpts.model ?? settings.model;
832
835
  const sandboxSession = startOpts.sandboxSession;
833
836
  const toolSafeSandboxSession =
834
837
  getRestrictedSandboxSession(sandboxSession);
@@ -920,6 +923,10 @@ export function createClaudeCode(
920
923
  );
921
924
 
922
925
  const workDir = startOpts.sessionWorkDir;
926
+ const sandboxHomeDir = await resolveSandboxHomeDir({
927
+ sandbox: toolSafeSandboxSession,
928
+ abortSignal: startOpts.abortSignal,
929
+ });
923
930
  const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
924
931
  const bridgeStateDir = `${sessionDataDir}/bridge`;
925
932
  const timeoutMs = settings.startupTimeoutMs ?? 120_000;
@@ -991,7 +998,7 @@ export function createClaudeCode(
991
998
  // process handle. The session lifecycle method decides whether the
992
999
  // sandbox is left running, stopped, or destroyed.
993
1000
  proc: undefined,
994
- model: settings.model,
1001
+ model,
995
1002
  maxTurns: settings.maxTurns,
996
1003
  env: sandboxClaudeEnvironment,
997
1004
  thinking,
@@ -1006,7 +1013,8 @@ export function createClaudeCode(
1006
1013
  debug: startOpts.observability?.debug,
1007
1014
  permissionMode: startOpts.permissionMode,
1008
1015
  builtinToolFiltering: startOpts.builtinToolFiltering,
1009
- skills: startOpts.skills ?? [],
1016
+ sandbox: toolSafeSandboxSession,
1017
+ sandboxHomeDir,
1010
1018
  mcpServers: settings.mcpServers,
1011
1019
  supportsUserMessageResponses: () => supportsUserMessageResponses,
1012
1020
  });
@@ -1039,13 +1047,6 @@ export function createClaudeCode(
1039
1047
  }
1040
1048
  }
1041
1049
 
1042
- const sandboxHomeDir =
1043
- startOpts.skills && startOpts.skills.length > 0
1044
- ? await resolveSandboxHomeDir({
1045
- sandbox: toolSafeSandboxSession,
1046
- abortSignal: startOpts.abortSignal,
1047
- })
1048
- : undefined;
1049
1050
  const port = resolveBridgePort({
1050
1051
  sandboxSession,
1051
1052
  override: settings.port,
@@ -1057,35 +1058,21 @@ export function createClaudeCode(
1057
1058
  const env = {
1058
1059
  BRIDGE_CHANNEL_TOKEN: token,
1059
1060
  BRIDGE_WS_PORT: String(port),
1060
- ...(sandboxHomeDir ? { HOME: sandboxHomeDir } : {}),
1061
1061
  ...(respawnStrategy === 'replay'
1062
1062
  ? { BRIDGE_REPLAY_FROM_DISK: '1' }
1063
1063
  : {}),
1064
1064
  };
1065
1065
 
1066
1066
  /*
1067
- * On a fresh start the workdir, skill files, and bridge-state directory
1068
- * must be created. On any resume they already exist in the
1069
- * sandbox snapshot, so skip the rewrite. The env is sent fresh on every
1070
- * spawn — `BRIDGE_CHANNEL_TOKEN` rotates per start.
1067
+ * On a fresh start the workdir and bridge-state directory must be
1068
+ * created. The env is sent fresh on every spawn —
1069
+ * `BRIDGE_CHANNEL_TOKEN` rotates per start.
1071
1070
  */
1072
1071
  if (respawnStrategy === undefined) {
1073
1072
  await toolSafeSandboxSession.run({
1074
1073
  command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
1075
1074
  abortSignal: startOpts.abortSignal,
1076
1075
  });
1077
-
1078
- if (startOpts.skills && startOpts.skills.length > 0) {
1079
- if (!sandboxHomeDir) {
1080
- throw new Error('Unable to resolve sandbox HOME directory.');
1081
- }
1082
- await writeClaudeCodeSkills({
1083
- sandbox: toolSafeSandboxSession,
1084
- homeDir: sandboxHomeDir,
1085
- skills: startOpts.skills,
1086
- abortSignal: startOpts.abortSignal,
1087
- });
1088
- }
1089
1076
  }
1090
1077
 
1091
1078
  await markBridgeStarting({
@@ -1162,7 +1149,7 @@ export function createClaudeCode(
1162
1149
  sessionId: startOpts.sessionId,
1163
1150
  channel,
1164
1151
  proc,
1165
- model: settings.model,
1152
+ model,
1166
1153
  maxTurns: settings.maxTurns,
1167
1154
  env: sandboxClaudeEnvironment,
1168
1155
  thinking,
@@ -1177,7 +1164,8 @@ export function createClaudeCode(
1177
1164
  debug: startOpts.observability?.debug,
1178
1165
  permissionMode: startOpts.permissionMode,
1179
1166
  builtinToolFiltering: startOpts.builtinToolFiltering,
1180
- skills: startOpts.skills ?? [],
1167
+ sandbox: toolSafeSandboxSession,
1168
+ sandboxHomeDir,
1181
1169
  mcpServers: settings.mcpServers,
1182
1170
  supportsUserMessageResponses: () => supportsUserMessageResponses,
1183
1171
  });
@@ -1506,7 +1494,8 @@ function createSession({
1506
1494
  debug,
1507
1495
  permissionMode,
1508
1496
  builtinToolFiltering,
1509
- skills,
1497
+ sandbox,
1498
+ sandboxHomeDir,
1510
1499
  mcpServers,
1511
1500
  supportsUserMessageResponses,
1512
1501
  }: {
@@ -1529,7 +1518,8 @@ function createSession({
1529
1518
  debug: HarnessV1DebugConfig | undefined;
1530
1519
  permissionMode: HarnessV1PermissionMode | undefined;
1531
1520
  builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
1532
- skills: ReadonlyArray<HarnessV1Skill>;
1521
+ sandbox: SandboxSession;
1522
+ sandboxHomeDir: string;
1533
1523
  mcpServers: Record<string, unknown> | undefined;
1534
1524
  supportsUserMessageResponses: () => boolean;
1535
1525
  }): HarnessV1Session {
@@ -1705,11 +1695,27 @@ function createSession({
1705
1695
  harnessId: 'claude-code',
1706
1696
  });
1707
1697
  }
1698
+ await writeClaudeCodeSkills({
1699
+ sandbox,
1700
+ homeDir: sandboxHomeDir,
1701
+ skills: promptOpts.skills,
1702
+ abortSignal: promptOpts.abortSignal,
1703
+ });
1708
1704
  const control = wireTurn({
1709
1705
  emit: promptOpts.emit,
1710
1706
  abortSignal: promptOpts.abortSignal,
1711
1707
  });
1712
1708
 
1709
+ /*
1710
+ * A signal that was already aborted has settled the turn inside
1711
+ * `wireTurn`. Sending `start` anyway would run a full unattended turn
1712
+ * in the bridge — consuming tokens and possibly acting on the
1713
+ * workspace — after the caller has already observed the abort.
1714
+ */
1715
+ if (promptOpts.abortSignal?.aborted) {
1716
+ return control;
1717
+ }
1718
+
1713
1719
  const startMessage = {
1714
1720
  type: 'start' as const,
1715
1721
  prompt: extractUserText(promptOpts.prompt),
@@ -1729,8 +1735,8 @@ function createSession({
1729
1735
  ...(env !== undefined ? { env } : {}),
1730
1736
  thinking,
1731
1737
  ...(effort !== undefined ? { effort } : {}),
1732
- ...(skills.length > 0
1733
- ? { skills: skills.map(skill => skill.name) }
1738
+ ...(promptOpts.skills.length > 0
1739
+ ? { skills: promptOpts.skills.map(skill => skill.name) }
1734
1740
  : {}),
1735
1741
  ...(mcpServers == null ? {} : { mcpServers }),
1736
1742
  ...(permissionMode ? { permissionMode } : {}),
@@ -1754,6 +1760,12 @@ function createSession({
1754
1760
  harnessId: 'claude-code',
1755
1761
  });
1756
1762
  }
1763
+ await writeClaudeCodeSkills({
1764
+ sandbox,
1765
+ homeDir: sandboxHomeDir,
1766
+ skills: continueOpts.skills,
1767
+ abortSignal: continueOpts.abortSignal,
1768
+ });
1757
1769
  const control = wireTurn({
1758
1770
  emit: continueOpts.emit,
1759
1771
  abortSignal: continueOpts.abortSignal,
@@ -1772,7 +1784,9 @@ function createSession({
1772
1784
  * recomputed. This is the rare bridge-died fallback; the common slice path
1773
1785
  * is `attach`.
1774
1786
  */
1775
- if (rerunContinue) {
1787
+ // Same as `doPromptTurn`: an already-aborted signal settled the turn in
1788
+ // `wireTurn`, so don't re-drive the thread nobody is waiting on.
1789
+ if (rerunContinue && !continueOpts.abortSignal?.aborted) {
1776
1790
  pendingResumeFlag = false;
1777
1791
  channel.send({
1778
1792
  type: 'start' as const,
@@ -1800,8 +1814,8 @@ function createSession({
1800
1814
  ...(env !== undefined ? { env } : {}),
1801
1815
  thinking,
1802
1816
  ...(effort !== undefined ? { effort } : {}),
1803
- ...(skills.length > 0
1804
- ? { skills: skills.map(skill => skill.name) }
1817
+ ...(continueOpts.skills.length > 0
1818
+ ? { skills: continueOpts.skills.map(skill => skill.name) }
1805
1819
  : {}),
1806
1820
  ...(mcpServers == null ? {} : { mcpServers }),
1807
1821
  ...(permissionMode ? { permissionMode } : {}),