agentbox-sdk 0.1.302 → 0.1.304

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.
@@ -2,7 +2,7 @@ import {
2
2
  createNormalizedEvent,
3
3
  normalizeRawAgentEvent,
4
4
  toAISDKStream
5
- } from "./chunk-NWAXQ7UD.js";
5
+ } from "./chunk-EK6GC6YA.js";
6
6
  import {
7
7
  AgentBoxError,
8
8
  AsyncQueue,
@@ -998,6 +998,28 @@ var HostSetupTarget = class {
998
998
  }
999
999
  );
1000
1000
  }
1001
+ async probe(command, extraEnv) {
1002
+ return time(
1003
+ debugRuntime,
1004
+ `host probe ${shortLabel(command)}`,
1005
+ async () => {
1006
+ const handle = spawnCommand({
1007
+ command: process.env.SHELL || "sh",
1008
+ args: ["-c", command],
1009
+ cwd: this.cwd,
1010
+ env: {
1011
+ ...process.env,
1012
+ ...this.baseEnv,
1013
+ ...this.env,
1014
+ ...extraEnv ?? {}
1015
+ }
1016
+ });
1017
+ const exitCode = await handle.wait();
1018
+ return exitCode === 0;
1019
+ },
1020
+ (ok) => ({ ok })
1021
+ );
1022
+ }
1001
1023
  async cleanup() {
1002
1024
  await rm(this.layout.rootDir, { recursive: true, force: true });
1003
1025
  }
@@ -1052,13 +1074,34 @@ var SandboxSetupTarget = class {
1052
1074
  }
1053
1075
  });
1054
1076
  if (result && result.exitCode !== 0) {
1077
+ const output = result.combinedOutput?.trim();
1078
+ const detail = output ? `
1079
+ ${output}` : "";
1055
1080
  throw new Error(
1056
- `Sandbox setup command failed (${result.exitCode}): ${command}`
1081
+ `Sandbox setup command failed (${result.exitCode}): ${command}${detail}`
1057
1082
  );
1058
1083
  }
1059
1084
  }
1060
1085
  );
1061
1086
  }
1087
+ async probe(command, extraEnv) {
1088
+ return time(
1089
+ debugRuntime,
1090
+ `sandbox probe ${shortLabel(command)}`,
1091
+ async () => {
1092
+ const result = await this.options.sandbox?.run(command, {
1093
+ cwd: this.options.cwd,
1094
+ env: {
1095
+ ...this.options.env ?? {},
1096
+ ...this.env,
1097
+ ...extraEnv ?? {}
1098
+ }
1099
+ });
1100
+ return Boolean(result && result.exitCode === 0);
1101
+ },
1102
+ (ok) => ({ ok })
1103
+ );
1104
+ }
1062
1105
  async cleanup() {
1063
1106
  }
1064
1107
  };
@@ -1092,6 +1135,7 @@ import path5 from "path";
1092
1135
  var MANIFEST_FILENAME = "setup-manifest.json";
1093
1136
  var TARGET_MANIFEST_FILENAME = "setup-target.json";
1094
1137
  var INSTALL_SCRIPT_FILENAME = "install.sh";
1138
+ var SETUP_ID_FILENAME = "setup.id";
1095
1139
  var MANIFEST_VERSION = 1;
1096
1140
  function hashArtifact(artifact) {
1097
1141
  const hasher = createHash("sha256");
@@ -1110,6 +1154,74 @@ function computeTargetArtifacts(artifacts) {
1110
1154
  }
1111
1155
  return result;
1112
1156
  }
1157
+ function computeSetupId(parts) {
1158
+ const hasher = createHash("sha256");
1159
+ hasher.update(`v${MANIFEST_VERSION}
1160
+ `);
1161
+ for (const a of [...parts.artifacts ?? []].sort(
1162
+ (x, y) => x.path.localeCompare(y.path)
1163
+ )) {
1164
+ hasher.update(`a:${a.path}:${hashArtifact(a)}
1165
+ `);
1166
+ }
1167
+ for (const cmd of parts.installCommands ?? []) {
1168
+ hasher.update(`c:${hashCommand(cmd)}
1169
+ `);
1170
+ }
1171
+ if (parts.daemon) {
1172
+ hasher.update(
1173
+ `d:${parts.daemon.port}:${parts.daemon.healthPath}:${parts.daemon.expectedVersionMatch ?? ""}
1174
+ `
1175
+ );
1176
+ }
1177
+ for (const extra of parts.extras ?? []) {
1178
+ hasher.update(`x:${extra}
1179
+ `);
1180
+ }
1181
+ return hasher.digest("hex");
1182
+ }
1183
+ async function preflightSetup(target, setupId, daemon) {
1184
+ return time(
1185
+ debugSetup,
1186
+ `preflightSetup ${target.provider}`,
1187
+ async () => {
1188
+ const setupIdFile = path5.posix.join(
1189
+ target.layout.rootDir,
1190
+ SETUP_ID_FILENAME
1191
+ );
1192
+ const checks = [
1193
+ `[ -f ${shellQuote(setupIdFile)} ]`,
1194
+ `[ "$(cat ${shellQuote(setupIdFile)} 2>/dev/null)" = ${shellQuote(setupId)} ]`
1195
+ ];
1196
+ if (daemon) {
1197
+ const url = `http://127.0.0.1:${daemon.port}${daemon.healthPath}`;
1198
+ if (daemon.expectedVersionMatch) {
1199
+ checks.push(
1200
+ `curl -fsS --max-time 2 ${shellQuote(url)} 2>/dev/null | grep -q ${shellQuote(daemon.expectedVersionMatch)}`
1201
+ );
1202
+ } else {
1203
+ checks.push(
1204
+ `curl -fsS --max-time 2 ${shellQuote(url)} >/dev/null 2>&1`
1205
+ );
1206
+ }
1207
+ }
1208
+ return target.probe(checks.join(" && "));
1209
+ },
1210
+ (ok) => ({ ok })
1211
+ );
1212
+ }
1213
+ async function markSetupComplete(target, setupId) {
1214
+ await time(
1215
+ debugSetup,
1216
+ `markSetupComplete ${target.provider}`,
1217
+ () => target.runCommand(
1218
+ [
1219
+ `mkdir -p ${shellQuote(target.layout.rootDir)}`,
1220
+ `printf '%s' ${shellQuote(setupId)} > ${shellQuote(path5.posix.join(target.layout.rootDir, SETUP_ID_FILENAME))}`
1221
+ ].join(" && ")
1222
+ )
1223
+ );
1224
+ }
1113
1225
  function buildInstallScript(rootDir, installCommandsByKey) {
1114
1226
  const commandsB64 = Buffer.from(
1115
1227
  JSON.stringify(installCommandsByKey),
@@ -1935,6 +2047,11 @@ var ClaudeCodeAgentAdapter = class {
1935
2047
  /**
1936
2048
  * Sandbox-side preparation. Uploads `.claude/` artifacts and ensures
1937
2049
  * the daemon is running. `execute()` then dials the daemon directly.
2050
+ *
2051
+ * Warm-path short-circuit: a single no-upload `preflightSetup` probes
2052
+ * the `setup.id` marker + daemon `/__version` on loopback. If both
2053
+ * match what we'd produce, we skip the artifact upload AND the daemon
2054
+ * boot entirely.
1938
2055
  */
1939
2056
  async setup(request) {
1940
2057
  await time(debugClaude, "claude-code setup()", async () => {
@@ -1968,6 +2085,20 @@ var ClaudeCodeAgentAdapter = class {
1968
2085
  { path: settingsPath, content: JSON.stringify(hookSettings, null, 2) },
1969
2086
  { path: mcpConfigPath, content: mcpConfigJson }
1970
2087
  ];
2088
+ const daemonInfo = {
2089
+ port: DAEMON_PORT,
2090
+ healthPath: "/__version",
2091
+ expectedVersionMatch: DAEMON_PROTOCOL_VERSION
2092
+ };
2093
+ const setupId = computeSetupId({
2094
+ artifacts,
2095
+ installCommands,
2096
+ daemon: daemonInfo
2097
+ });
2098
+ if (await preflightSetup(target, setupId, daemonInfo)) {
2099
+ debugClaude("claude-code setup() preflight hit \u2014 skipping");
2100
+ return;
2101
+ }
1971
2102
  const env = { ...options.env ?? {}, ...target.env };
1972
2103
  await Promise.all([
1973
2104
  time(
@@ -1977,6 +2108,7 @@ var ClaudeCodeAgentAdapter = class {
1977
2108
  ),
1978
2109
  ensureClaudeCodeDaemon(options, env)
1979
2110
  ]);
2111
+ await markSetupComplete(target, setupId);
1980
2112
  });
1981
2113
  }
1982
2114
  async execute(request, sink) {
@@ -2372,6 +2504,31 @@ async function* streamSse(url, init) {
2372
2504
  })();
2373
2505
  yield* queue;
2374
2506
  }
2507
+ async function* streamSseResilient(url, init) {
2508
+ let lastEventId;
2509
+ let attempt = 0;
2510
+ const signal = init?.signal;
2511
+ while (true) {
2512
+ try {
2513
+ const headers = {
2514
+ ...init?.headers ?? {},
2515
+ ...lastEventId ? { "Last-Event-ID": lastEventId } : {}
2516
+ };
2517
+ for await (const ev of streamSse(url, { ...init, headers })) {
2518
+ attempt = 0;
2519
+ if (ev.id) lastEventId = ev.id;
2520
+ yield ev;
2521
+ }
2522
+ return;
2523
+ } catch (err) {
2524
+ if (signal?.aborted) throw err;
2525
+ const delay = Math.min(500 * Math.pow(2, attempt), 5e3);
2526
+ attempt++;
2527
+ await new Promise((resolve) => setTimeout(resolve, delay));
2528
+ if (signal?.aborted) throw err;
2529
+ }
2530
+ }
2531
+ }
2375
2532
  async function connectJsonRpcWebSocket(url, options) {
2376
2533
  const notifications = new AsyncQueue();
2377
2534
  const socket = new WebSocket(url, { headers: options?.headers });
@@ -2819,7 +2976,12 @@ async function ensureCodexLoginViaConfig(request, target) {
2819
2976
  [
2820
2977
  'if [ -z "${OPENAI_API_KEY:-}" ]; then exit 0; fi',
2821
2978
  'mkdir -p "${CODEX_HOME:-$HOME/.codex}"',
2822
- "printenv OPENAI_API_KEY | env -u XDG_CONFIG_HOME codex login --with-api-key"
2979
+ // Merge stderr into stdout so providers that don't surface stderr
2980
+ // (e.g. Daytona's `executeCommand`, which collapses to a single
2981
+ // `result` field) still propagate the underlying error message
2982
+ // back to the caller — otherwise a failed login leaves us with a
2983
+ // bare "exit 1" and no diagnostic.
2984
+ "printenv OPENAI_API_KEY | env -u XDG_CONFIG_HOME codex login --with-api-key 2>&1"
2823
2985
  ].join("; "),
2824
2986
  Object.keys(extraEnv).length > 0 ? extraEnv : void 0
2825
2987
  );
@@ -2925,12 +3087,26 @@ async function setupCodex(request) {
2925
3087
  ...sharedTarget.env,
2926
3088
  ...options.provider?.env ?? {}
2927
3089
  });
3090
+ const { artifacts: serverArtifacts } = buildArtifactsFor(sharedTarget);
3091
+ const { artifacts: skillArtifacts2, installCommands: installCommands2 } = await prepareSkillArtifacts(provider, options.skills, target2.layout);
3092
+ const daemonInfo = {
3093
+ port: REMOTE_CODEX_APP_SERVER_PORT,
3094
+ healthPath: "/readyz"
3095
+ };
3096
+ const setupId2 = computeSetupId({
3097
+ artifacts: [...serverArtifacts, ...skillArtifacts2],
3098
+ installCommands: installCommands2,
3099
+ daemon: daemonInfo
3100
+ });
3101
+ if (await preflightSetup(sharedTarget, setupId2, daemonInfo)) {
3102
+ debugCodex("codex remote setup() preflight hit \u2014 skipping");
3103
+ return;
3104
+ }
2928
3105
  await time(
2929
3106
  debugCodex,
2930
3107
  "ensureCodexLogin",
2931
3108
  () => ensureCodexLoginViaConfig(request, sharedTarget)
2932
3109
  );
2933
- const { artifacts: serverArtifacts } = buildArtifactsFor(sharedTarget);
2934
3110
  await applyDifferentialSetup(sharedTarget, serverArtifacts, []);
2935
3111
  const binary = options.provider?.binary ?? "codex";
2936
3112
  const pidFilePath = path9.posix.join(
@@ -2978,28 +3154,34 @@ async function setupCodex(request) {
2978
3154
  );
2979
3155
  }
2980
3156
  try {
2981
- const { artifacts: skillArtifacts2, installCommands: installCommands2 } = await prepareSkillArtifacts(provider, options.skills, target2.layout);
2982
3157
  await applyDifferentialSetup(target2, skillArtifacts2, installCommands2);
2983
3158
  } catch (error) {
2984
3159
  await target2.cleanup().catch(() => void 0);
2985
3160
  throw error;
2986
3161
  }
3162
+ await markSetupComplete(sharedTarget, setupId2);
2987
3163
  return;
2988
3164
  }
2989
3165
  const target = await createSetupTarget(provider, "shared-setup", options);
3166
+ const { artifacts: skillArtifacts, installCommands } = await prepareSkillArtifacts(provider, options.skills, target.layout);
3167
+ const { artifacts: configArtifacts } = buildArtifactsFor(target);
3168
+ const allArtifacts = [...skillArtifacts, ...configArtifacts];
3169
+ const setupId = computeSetupId({
3170
+ artifacts: allArtifacts,
3171
+ installCommands
3172
+ });
3173
+ if (await preflightSetup(target, setupId)) {
3174
+ debugCodex("codex local setup() preflight hit \u2014 skipping");
3175
+ return;
3176
+ }
2990
3177
  try {
2991
3178
  await ensureCodexLoginViaConfig(request, target);
2992
3179
  } catch (error) {
2993
3180
  await target.cleanup().catch(() => void 0);
2994
3181
  throw error;
2995
3182
  }
2996
- const { artifacts: skillArtifacts, installCommands } = await prepareSkillArtifacts(provider, options.skills, target.layout);
2997
- const { artifacts: configArtifacts } = buildArtifactsFor(target);
2998
- await applyDifferentialSetup(
2999
- target,
3000
- [...skillArtifacts, ...configArtifacts],
3001
- installCommands
3002
- );
3183
+ await applyDifferentialSetup(target, allArtifacts, installCommands);
3184
+ await markSetupComplete(target, setupId);
3003
3185
  }
3004
3186
  async function createRuntime(request, inputParts) {
3005
3187
  const options = request.options;
@@ -3383,7 +3565,7 @@ var CodexAgentAdapter = class {
3383
3565
  Date.now() - executeStartedAt,
3384
3566
  text?.length ?? 0
3385
3567
  );
3386
- sink.complete({ text, costData: extractCodexCostData(rawPayloads) });
3568
+ sink.complete({ costData: extractCodexCostData(rawPayloads) });
3387
3569
  }
3388
3570
  }
3389
3571
  } finally {
@@ -3481,73 +3663,6 @@ function toRawEvent3(runId, payload, type) {
3481
3663
  payload
3482
3664
  };
3483
3665
  }
3484
- function extractText(value) {
3485
- if (!value) {
3486
- return "";
3487
- }
3488
- if (typeof value === "string") {
3489
- return value;
3490
- }
3491
- if (Array.isArray(value)) {
3492
- return value.map(extractText).filter(Boolean).join("");
3493
- }
3494
- if (typeof value === "object") {
3495
- const record = value;
3496
- if (record.type === "text" && typeof record.text === "string") {
3497
- return record.text;
3498
- }
3499
- if (record.type === "reasoning") {
3500
- return "";
3501
- }
3502
- if (record.message) {
3503
- return extractText(record.message);
3504
- }
3505
- if (record.content) {
3506
- return extractText(record.content);
3507
- }
3508
- if (record.parts) {
3509
- return extractText(record.parts);
3510
- }
3511
- if (record.text) {
3512
- return extractText(record.text);
3513
- }
3514
- }
3515
- return "";
3516
- }
3517
- function extractAssistantMessageId(response) {
3518
- if (!response || typeof response !== "object") {
3519
- return void 0;
3520
- }
3521
- const record = response;
3522
- const info = record.info && typeof record.info === "object" ? record.info : record.message && typeof record.message === "object" ? record.message : void 0;
3523
- const id = info?.id ?? record.id;
3524
- return typeof id === "string" ? id : void 0;
3525
- }
3526
- function extractReasoning(value) {
3527
- if (!value) {
3528
- return "";
3529
- }
3530
- if (Array.isArray(value)) {
3531
- return value.map(extractReasoning).filter(Boolean).join("");
3532
- }
3533
- if (typeof value === "object") {
3534
- const record = value;
3535
- if (record.type === "reasoning") {
3536
- if (typeof record.text === "string") {
3537
- return record.text;
3538
- }
3539
- if (typeof record.reasoning === "string") {
3540
- return record.reasoning;
3541
- }
3542
- }
3543
- return [
3544
- extractReasoning(record.message),
3545
- extractReasoning(record.content),
3546
- extractReasoning(record.parts)
3547
- ].filter(Boolean).join("");
3548
- }
3549
- return "";
3550
- }
3551
3666
  function toOpenCodeModel(model) {
3552
3667
  if (!model) {
3553
3668
  return void 0;
@@ -3609,12 +3724,19 @@ var OPEN_CODE_REASONING_LEVELS = ["low", "medium", "high", "xhigh"];
3609
3724
  function openCodeAgentSlug(reasoning) {
3610
3725
  return reasoning ? `agentbox-${reasoning}` : "agentbox";
3611
3726
  }
3612
- function buildOpenCodeConfig(options, systemPrompt, interactiveApproval) {
3727
+ function buildOpenCodeConfig(options, interactiveApproval) {
3613
3728
  const mcpConfig = buildOpenCodeMcpConfig(options.mcps);
3614
3729
  const commandsConfig = buildOpenCodeCommandsConfig(options.commands);
3615
3730
  const baseAgent = {
3616
3731
  mode: "primary",
3617
- prompt: systemPrompt,
3732
+ // Suppress opencode's default provider system prompt (e.g.
3733
+ // PROMPT_ANTHROPIC's "You are OpenCode...") so request.run.systemPrompt
3734
+ // is the dominant identity statement the model sees. opencode's
3735
+ // session/llm.ts uses agent.prompt instead of SystemPrompt.provider(model)
3736
+ // when this field is truthy — codex already takes that branch via a
3737
+ // separate options.instructions channel, so this brings anthropic et al.
3738
+ // in line. Constant value, so setupId stays stable.
3739
+ prompt: "You are an AI coding assistant. Follow the user's instructions.",
3618
3740
  permission: buildOpenCodePermissionConfig(interactiveApproval),
3619
3741
  tools: {
3620
3742
  write: true,
@@ -3651,19 +3773,6 @@ async function ensureSandboxOpenCodeServer(request) {
3651
3773
  const sandbox = request.options.sandbox;
3652
3774
  const options = request.options;
3653
3775
  const port = SANDBOX_OPENCODE_PORT;
3654
- const healthCheck = await time(
3655
- debugOpencode,
3656
- "health probe (warm path)",
3657
- () => sandbox.run(
3658
- `curl -fsS http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
3659
- { cwd: options.cwd, timeoutMs: 5e3 }
3660
- )
3661
- );
3662
- if (healthCheck.exitCode === 0) {
3663
- debugOpencode("opencode server already running \u2014 reusing");
3664
- return;
3665
- }
3666
- debugOpencode("opencode server not running \u2014 cold-spawning");
3667
3776
  const plugins = assertHooksSupported(request.provider, options);
3668
3777
  assertCommandsSupported(request.provider, options.commands);
3669
3778
  const interactiveApproval = isInteractiveApproval(options);
@@ -3684,26 +3793,32 @@ async function ensureSandboxOpenCodeServer(request) {
3684
3793
  const configPath = path10.join(target.layout.opencodeDir, "agentbox.json");
3685
3794
  const openCodeConfig = buildOpenCodeConfig(
3686
3795
  options,
3687
- request.config.systemPrompt ?? "",
3688
3796
  interactiveApproval
3689
3797
  );
3798
+ const allArtifacts = [
3799
+ ...skillArtifacts,
3800
+ ...pluginArtifacts,
3801
+ {
3802
+ path: configPath,
3803
+ content: JSON.stringify(openCodeConfig, null, 2)
3804
+ }
3805
+ ];
3806
+ const daemonInfo = { port, healthPath: "/global/health" };
3807
+ const setupId = computeSetupId({
3808
+ artifacts: allArtifacts,
3809
+ installCommands,
3810
+ daemon: daemonInfo
3811
+ });
3812
+ if (await preflightSetup(target, setupId, daemonInfo)) {
3813
+ debugOpencode("opencode setup() preflight hit \u2014 skipping");
3814
+ return;
3815
+ }
3690
3816
  const commonEnv = {
3691
3817
  OPENCODE_CONFIG: configPath,
3692
3818
  OPENCODE_CONFIG_DIR: target.layout.opencodeDir,
3693
3819
  OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
3694
3820
  };
3695
- await applyDifferentialSetup(
3696
- target,
3697
- [
3698
- ...skillArtifacts,
3699
- ...pluginArtifacts,
3700
- {
3701
- path: configPath,
3702
- content: JSON.stringify(openCodeConfig, null, 2)
3703
- }
3704
- ],
3705
- installCommands
3706
- );
3821
+ await applyDifferentialSetup(target, allArtifacts, installCommands);
3707
3822
  const binary = options.provider?.binary ?? "opencode";
3708
3823
  const pidFilePath = path10.posix.join(
3709
3824
  target.layout.rootDir,
@@ -3766,6 +3881,7 @@ async function ensureSandboxOpenCodeServer(request) {
3766
3881
  `OpenCode server did not become ready within ${SANDBOX_OPENCODE_READY_TIMEOUT_MS}ms.`
3767
3882
  );
3768
3883
  });
3884
+ await markSetupComplete(target, setupId);
3769
3885
  });
3770
3886
  }
3771
3887
  async function ensureLocalOpenCodeServer(request) {
@@ -3798,28 +3914,28 @@ async function ensureLocalOpenCodeServer(request) {
3798
3914
  target.layout.opencodeDir
3799
3915
  );
3800
3916
  const configPath = path10.join(target.layout.opencodeDir, "agentbox.json");
3801
- const openCodeConfig = buildOpenCodeConfig(
3802
- options,
3803
- request.config.systemPrompt ?? "",
3804
- interactiveApproval
3805
- );
3917
+ const openCodeConfig = buildOpenCodeConfig(options, interactiveApproval);
3806
3918
  const commonEnv = {
3807
3919
  OPENCODE_CONFIG: configPath,
3808
3920
  OPENCODE_CONFIG_DIR: target.layout.opencodeDir,
3809
3921
  OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
3810
3922
  };
3811
- await applyDifferentialSetup(
3812
- target,
3813
- [
3814
- ...skillArtifacts,
3815
- ...pluginArtifacts,
3816
- {
3817
- path: configPath,
3818
- content: JSON.stringify(openCodeConfig, null, 2)
3819
- }
3820
- ],
3923
+ const allArtifacts = [
3924
+ ...skillArtifacts,
3925
+ ...pluginArtifacts,
3926
+ {
3927
+ path: configPath,
3928
+ content: JSON.stringify(openCodeConfig, null, 2)
3929
+ }
3930
+ ];
3931
+ const setupId = computeSetupId({
3932
+ artifacts: allArtifacts,
3821
3933
  installCommands
3822
- );
3934
+ });
3935
+ const preflightHit = await preflightSetup(target, setupId);
3936
+ if (!preflightHit) {
3937
+ await applyDifferentialSetup(target, allArtifacts, installCommands);
3938
+ }
3823
3939
  spawnCommand({
3824
3940
  command: options.provider?.binary ?? "opencode",
3825
3941
  args: [
@@ -3841,6 +3957,7 @@ async function ensureLocalOpenCodeServer(request) {
3841
3957
  `http://127.0.0.1:${LOCAL_OPENCODE_PORT}/global/health`,
3842
3958
  { timeoutMs: LOCAL_OPENCODE_READY_TIMEOUT_MS }
3843
3959
  );
3960
+ await markSetupComplete(target, setupId);
3844
3961
  }
3845
3962
  async function setupOpenCode(request) {
3846
3963
  if (request.options.sandbox) {
@@ -3878,25 +3995,14 @@ var OpenCodeAgentAdapter = class {
3878
3995
  "validateProviderUserInput",
3879
3996
  () => validateProviderUserInput(request.provider, request.run.input)
3880
3997
  );
3881
- let pendingMessages = 0;
3882
- let finalText = "";
3883
3998
  let streamedTextFromSse = "";
3884
- const settledMessageIds = /* @__PURE__ */ new Set();
3999
+ const assistantTextByMessageId = /* @__PURE__ */ new Map();
4000
+ const announcedAssistantCompletions = /* @__PURE__ */ new Set();
3885
4001
  let dispatchError;
3886
4002
  let firstSseEventLogged = false;
3887
- let resolveAllDone;
3888
- const allDone = new Promise((resolve) => {
3889
- resolveAllDone = resolve;
3890
- });
3891
- const checkDone = () => {
3892
- if (pendingMessages === 0) {
3893
- resolveAllDone();
3894
- }
3895
- };
3896
4003
  let sendToSession;
3897
4004
  const queuedParts = [];
3898
4005
  sink.onMessage(async (content) => {
3899
- pendingMessages++;
3900
4006
  try {
3901
4007
  const parts = await validateProviderUserInput(
3902
4008
  request.provider,
@@ -3909,11 +4015,10 @@ var OpenCodeAgentAdapter = class {
3909
4015
  queuedParts.push(mapped);
3910
4016
  }
3911
4017
  } catch (error) {
3912
- pendingMessages--;
3913
4018
  if (!dispatchError) {
3914
4019
  dispatchError = error;
3915
4020
  }
3916
- checkDone();
4021
+ resolveSessionTerminal();
3917
4022
  throw error;
3918
4023
  }
3919
4024
  });
@@ -3932,10 +4037,15 @@ var OpenCodeAgentAdapter = class {
3932
4037
  const rawPayloads = [];
3933
4038
  const sseAbort = new AbortController();
3934
4039
  let sseTask;
3935
- const dispatchAbort = new AbortController();
3936
4040
  let capturedSessionId;
3937
4041
  let sessionErrorFromSse;
3938
4042
  let sessionAbortedFromSse = false;
4043
+ let sessionIdleFromSse = false;
4044
+ let resolveSessionTerminal;
4045
+ const sessionTerminal = new Promise((resolve) => {
4046
+ resolveSessionTerminal = resolve;
4047
+ });
4048
+ let lastSseActivityAt = Date.now();
3939
4049
  let userAbortRequested = false;
3940
4050
  sink.setAbort(async () => {
3941
4051
  userAbortRequested = true;
@@ -3963,7 +4073,7 @@ var OpenCodeAgentAdapter = class {
3963
4073
  } catch {
3964
4074
  }
3965
4075
  }
3966
- dispatchAbort.abort();
4076
+ resolveSessionTerminal();
3967
4077
  });
3968
4078
  try {
3969
4079
  const interactiveApproval = isInteractiveApproval(request.options);
@@ -4004,10 +4114,14 @@ var OpenCodeAgentAdapter = class {
4004
4114
  const foreignMessageIds = /* @__PURE__ */ new Set();
4005
4115
  sseTask = (async () => {
4006
4116
  try {
4007
- for await (const event of streamSse(`${runtime.baseUrl}/event`, {
4008
- headers: runtime.previewHeaders,
4009
- signal: sseAbort.signal
4010
- })) {
4117
+ for await (const event of streamSseResilient(
4118
+ `${runtime.baseUrl}/event`,
4119
+ {
4120
+ headers: runtime.previewHeaders,
4121
+ signal: sseAbort.signal
4122
+ }
4123
+ )) {
4124
+ lastSseActivityAt = Date.now();
4011
4125
  if (!firstSseEventLogged) {
4012
4126
  firstSseEventLogged = true;
4013
4127
  debugOpencode(
@@ -4050,6 +4164,22 @@ var OpenCodeAgentAdapter = class {
4050
4164
  { messageId: info.id }
4051
4165
  )
4052
4166
  );
4167
+ } else if (info.role === "assistant" && !announcedAssistantCompletions.has(info.id)) {
4168
+ const time2 = info.time;
4169
+ if (typeof time2?.completed === "number") {
4170
+ announcedAssistantCompletions.add(info.id);
4171
+ sink.emitEvent(
4172
+ createNormalizedEvent(
4173
+ "message.completed",
4174
+ {
4175
+ provider: request.provider,
4176
+ runId: request.runId,
4177
+ raw
4178
+ },
4179
+ { text: assistantTextByMessageId.get(info.id) ?? "" }
4180
+ )
4181
+ );
4182
+ }
4053
4183
  }
4054
4184
  }
4055
4185
  }
@@ -4082,7 +4212,7 @@ var OpenCodeAgentAdapter = class {
4082
4212
  continue;
4083
4213
  }
4084
4214
  const payloadRecord = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
4085
- if ((payloadRecord?.type === "session.idle" || payloadRecord?.type === "session.error") && !dispatchAbort.signal.aborted) {
4215
+ if (payloadRecord?.type === "session.idle" || payloadRecord?.type === "session.error") {
4086
4216
  const properties = payloadRecord.properties;
4087
4217
  const eventSessionId = typeof properties?.sessionID === "string" ? properties.sessionID : void 0;
4088
4218
  if (!eventSessionId || eventSessionId === sessionId) {
@@ -4094,13 +4224,28 @@ var OpenCodeAgentAdapter = class {
4094
4224
  const errMsg = typeof errData?.data?.message === "string" ? errData.data.message : typeof errData?.message === "string" ? errData.message : "OpenCode session error";
4095
4225
  sessionErrorFromSse = new Error(errMsg);
4096
4226
  }
4227
+ } else {
4228
+ sessionIdleFromSse = true;
4097
4229
  }
4098
4230
  debugOpencode(
4099
- "\u2605 %s for session=%s \u2014 aborting in-flight dispatch",
4231
+ "\u2605 %s for session=%s",
4100
4232
  payloadRecord.type,
4101
4233
  sessionId
4102
4234
  );
4103
- dispatchAbort.abort();
4235
+ resolveSessionTerminal();
4236
+ }
4237
+ }
4238
+ if (payloadRecord?.type === "session.status") {
4239
+ const properties = payloadRecord.properties;
4240
+ const status = properties?.status;
4241
+ const eventSessionId = typeof properties?.sessionID === "string" ? properties.sessionID : void 0;
4242
+ if ((!eventSessionId || eventSessionId === sessionId) && status?.type === "idle") {
4243
+ sessionIdleFromSse = true;
4244
+ debugOpencode(
4245
+ "\u2605 session.status{idle} for session=%s",
4246
+ sessionId
4247
+ );
4248
+ resolveSessionTerminal();
4104
4249
  }
4105
4250
  }
4106
4251
  if (payloadRecord?.type === "message.part.delta") {
@@ -4111,12 +4256,15 @@ var OpenCodeAgentAdapter = class {
4111
4256
  if (isForeignSession) {
4112
4257
  continue;
4113
4258
  }
4114
- if (eventMessageId !== void 0 && settledMessageIds.has(eventMessageId)) {
4115
- continue;
4116
- }
4117
4259
  const delta = typeof properties?.delta === "string" ? properties.delta : "";
4118
4260
  if (delta && properties?.field === "text") {
4119
4261
  streamedTextFromSse += delta;
4262
+ if (eventMessageId) {
4263
+ assistantTextByMessageId.set(
4264
+ eventMessageId,
4265
+ (assistantTextByMessageId.get(eventMessageId) ?? "") + delta
4266
+ );
4267
+ }
4120
4268
  sink.emitEvent(
4121
4269
  createNormalizedEvent(
4122
4270
  "text.delta",
@@ -4169,126 +4317,131 @@ var OpenCodeAgentAdapter = class {
4169
4317
  })
4170
4318
  );
4171
4319
  const agentSlug = openCodeAgentSlug(request.run.reasoning);
4172
- const dispatchMessage = async (parts) => {
4173
- const sseTextLengthBeforeDispatch = streamedTextFromSse.length;
4174
- try {
4175
- const response = await fetchJson(
4176
- `${runtime.baseUrl}/session/${sessionId}/message`,
4177
- {
4178
- method: "POST",
4179
- signal: dispatchAbort.signal,
4180
- headers: {
4181
- "content-type": "application/json",
4182
- ...runtime.previewHeaders
4183
- },
4184
- body: JSON.stringify({
4185
- ...request.run.model ? { model: toOpenCodeModel(request.run.model) } : {},
4186
- agent: agentSlug,
4187
- parts
4188
- })
4320
+ const dispatchPrompt = async (parts) => {
4321
+ const body = JSON.stringify({
4322
+ ...request.run.model ? { model: toOpenCodeModel(request.run.model) } : {},
4323
+ // Per-message system prompt — keeps systemPrompt out of
4324
+ // the on-disk agent config so changing it doesn't
4325
+ // invalidate setupId. Sent on every dispatch (the field
4326
+ // is per-message, not session-sticky).
4327
+ ...request.run.systemPrompt ? { system: request.run.systemPrompt } : {},
4328
+ agent: agentSlug,
4329
+ parts
4330
+ });
4331
+ const url = `${runtime.baseUrl}/session/${sessionId}/prompt_async`;
4332
+ const attempt = async () => {
4333
+ return fetch(url, {
4334
+ method: "POST",
4335
+ headers: {
4336
+ "content-type": "application/json",
4337
+ ...runtime.previewHeaders
4338
+ },
4339
+ body
4340
+ });
4341
+ };
4342
+ let lastError;
4343
+ for (let i = 0; i < 2; i++) {
4344
+ try {
4345
+ const response = await attempt();
4346
+ if (response.ok || response.status === 204) {
4347
+ return;
4189
4348
  }
4190
- );
4191
- const rawResponse = toRawEvent3(
4192
- request.runId,
4193
- response,
4194
- "message.response"
4195
- );
4196
- if (response && typeof response === "object" && !Array.isArray(response)) {
4197
- rawPayloads.push(response);
4198
- }
4199
- sink.emitRaw(rawResponse);
4200
- for (const event of normalizeRawAgentEvent(rawResponse)) {
4201
- sink.emitEvent(event);
4202
- }
4203
- const reasoning = extractReasoning(response);
4204
- if (reasoning) {
4205
- sink.emitEvent(
4206
- createNormalizedEvent(
4207
- "reasoning.delta",
4208
- {
4209
- provider: request.provider,
4210
- runId: request.runId,
4211
- raw: rawResponse
4212
- },
4213
- { delta: reasoning }
4214
- )
4349
+ lastError = new Error(
4350
+ `POST ${url} returned ${response.status}`
4215
4351
  );
4352
+ } catch (error) {
4353
+ lastError = error;
4216
4354
  }
4217
- const text = extractText(response);
4218
- if (text) {
4219
- finalText = text;
4220
- const sseTextForThisDispatch = streamedTextFromSse.slice(
4221
- sseTextLengthBeforeDispatch
4355
+ if (i === 0) {
4356
+ debugOpencode(
4357
+ "prompt_async dispatch attempt %d failed (%s); retrying once",
4358
+ i + 1,
4359
+ lastError?.message ?? String(lastError)
4222
4360
  );
4223
- let missing;
4224
- if (text.startsWith(sseTextForThisDispatch)) {
4225
- missing = text.slice(sseTextForThisDispatch.length);
4226
- } else if (sseTextForThisDispatch.length === 0) {
4227
- missing = text;
4228
- } else {
4229
- missing = "";
4230
- }
4231
- if (missing.length > 0) {
4232
- streamedTextFromSse += missing;
4233
- sink.emitEvent(
4234
- createNormalizedEvent(
4235
- "text.delta",
4236
- {
4237
- provider: request.provider,
4238
- runId: request.runId
4239
- },
4240
- { delta: missing }
4241
- )
4242
- );
4243
- }
4244
- const assistantMessageId = extractAssistantMessageId(response);
4245
- if (assistantMessageId) {
4246
- settledMessageIds.add(assistantMessageId);
4247
- }
4361
+ await sleep(500);
4248
4362
  }
4249
- } catch (error) {
4250
- if (!dispatchError) {
4251
- dispatchError = error;
4252
- }
4253
- } finally {
4254
- pendingMessages--;
4255
- checkDone();
4256
4363
  }
4364
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
4257
4365
  };
4258
4366
  sendToSession = (parts) => {
4259
- void dispatchMessage(parts);
4367
+ void (async () => {
4368
+ try {
4369
+ await dispatchPrompt(parts);
4370
+ } catch (error) {
4371
+ if (!dispatchError) {
4372
+ dispatchError = error;
4373
+ }
4374
+ resolveSessionTerminal();
4375
+ }
4376
+ })();
4260
4377
  };
4261
4378
  for (const queued of queuedParts.splice(0)) {
4262
4379
  sendToSession(queued);
4263
4380
  }
4264
- pendingMessages++;
4265
- void dispatchMessage(mapToOpenCodeParts(inputParts));
4266
- await allDone;
4381
+ try {
4382
+ await dispatchPrompt(mapToOpenCodeParts(inputParts));
4383
+ } catch (error) {
4384
+ if (!dispatchError) {
4385
+ dispatchError = error;
4386
+ }
4387
+ resolveSessionTerminal();
4388
+ }
4389
+ const SSE_SILENCE_THRESHOLD_MS = 18e4;
4390
+ const SSE_POLL_INTERVAL_MS = 5e3;
4391
+ lastSseActivityAt = Date.now();
4392
+ let sseSilent = false;
4393
+ while (!sessionIdleFromSse && !sessionErrorFromSse && !sessionAbortedFromSse && !userAbortRequested && !dispatchError) {
4394
+ const silence = Date.now() - lastSseActivityAt;
4395
+ if (silence > SSE_SILENCE_THRESHOLD_MS) {
4396
+ sseSilent = true;
4397
+ debugOpencode("SSE went silent (%dms) \u2014 giving up", silence);
4398
+ break;
4399
+ }
4400
+ await Promise.race([
4401
+ sessionTerminal,
4402
+ new Promise(
4403
+ (resolve) => setTimeout(resolve, SSE_POLL_INTERVAL_MS)
4404
+ )
4405
+ ]);
4406
+ }
4407
+ sseAbort.abort();
4408
+ await sseTask;
4267
4409
  if (userAbortRequested || sessionAbortedFromSse) {
4268
4410
  debugOpencode(
4269
4411
  "\u2605 run.cancelled (%dms since execute start)",
4270
4412
  Date.now() - executeStartedAt
4271
4413
  );
4272
- sseAbort.abort();
4273
- await sseTask;
4274
4414
  sink.cancel({
4275
4415
  text: streamedTextFromSse || void 0,
4276
4416
  costData: extractOpenCodeCostData(rawPayloads)
4277
4417
  });
4278
4418
  } else if (sessionErrorFromSse) {
4279
- sseAbort.abort();
4280
- await sseTask;
4281
4419
  sink.fail(sessionErrorFromSse);
4282
- } else if (dispatchError && !(dispatchAbort.signal.aborted && dispatchError?.name === "AbortError")) {
4283
- sseAbort.abort();
4284
- await sseTask;
4420
+ } else if (dispatchError) {
4285
4421
  sink.fail(dispatchError);
4286
- } else {
4422
+ } else if (sessionIdleFromSse) {
4287
4423
  debugOpencode(
4288
4424
  "\u2605 run.completed (%dms since execute start) chars=%d",
4289
4425
  Date.now() - executeStartedAt,
4290
- finalText.length
4426
+ streamedTextFromSse.length
4291
4427
  );
4428
+ let lastAssistantText = "";
4429
+ for (const [messageId, text] of assistantTextByMessageId) {
4430
+ lastAssistantText = text;
4431
+ if (!announcedAssistantCompletions.has(messageId)) {
4432
+ announcedAssistantCompletions.add(messageId);
4433
+ sink.emitEvent(
4434
+ createNormalizedEvent(
4435
+ "message.completed",
4436
+ {
4437
+ provider: request.provider,
4438
+ runId: request.runId
4439
+ },
4440
+ { text }
4441
+ )
4442
+ );
4443
+ }
4444
+ }
4292
4445
  sink.emitEvent(
4293
4446
  createNormalizedEvent(
4294
4447
  "run.completed",
@@ -4296,15 +4449,23 @@ var OpenCodeAgentAdapter = class {
4296
4449
  provider: request.provider,
4297
4450
  runId: request.runId
4298
4451
  },
4299
- { text: finalText }
4452
+ { text: lastAssistantText }
4300
4453
  )
4301
4454
  );
4302
- sseAbort.abort();
4303
- await sseTask;
4304
4455
  sink.complete({
4305
- text: finalText,
4456
+ text: lastAssistantText,
4306
4457
  costData: extractOpenCodeCostData(rawPayloads)
4307
4458
  });
4459
+ } else if (sseSilent) {
4460
+ sink.fail(
4461
+ new Error(
4462
+ "opencode SSE went silent before the session reached idle"
4463
+ )
4464
+ );
4465
+ } else {
4466
+ sink.fail(
4467
+ new Error("opencode run ended without a terminal signal")
4468
+ );
4308
4469
  }
4309
4470
  } finally {
4310
4471
  sseAbort.abort();
@@ -4350,10 +4511,10 @@ var OpenCodeAgentAdapter = class {
4350
4511
  }
4351
4512
  }
4352
4513
  /**
4353
- * Stateless message injection. POST a fresh user message to
4354
- * `/session/:id/message` with `agent` defaulting to the build agent
4355
- * opencode appends it to the running session and the originating
4356
- * instance picks up the new turn through its existing SSE stream.
4514
+ * Stateless message injection. Fire-and-forget POST to
4515
+ * `/session/:id/prompt_async` (returns 204) opencode appends the
4516
+ * message to the running session and the originating instance picks
4517
+ * up the new turn through its existing SSE stream.
4357
4518
  */
4358
4519
  async attachSendMessage(request, content) {
4359
4520
  if (!request.sessionId) {
@@ -4367,20 +4528,21 @@ var OpenCodeAgentAdapter = class {
4367
4528
  content
4368
4529
  );
4369
4530
  const parts = mapToOpenCodeParts(inputParts);
4370
- await fetchJson(
4371
- `${baseUrl}/session/${request.sessionId}/message`,
4372
- {
4373
- method: "POST",
4374
- headers: {
4375
- "content-type": "application/json",
4376
- ...request.sandbox.previewHeaders
4377
- },
4378
- body: JSON.stringify({
4379
- agent: openCodeAgentSlug(void 0),
4380
- parts
4381
- })
4382
- }
4383
- );
4531
+ const url = `${baseUrl}/session/${request.sessionId}/prompt_async`;
4532
+ const response = await fetch(url, {
4533
+ method: "POST",
4534
+ headers: {
4535
+ "content-type": "application/json",
4536
+ ...request.sandbox.previewHeaders
4537
+ },
4538
+ body: JSON.stringify({
4539
+ agent: openCodeAgentSlug(void 0),
4540
+ parts
4541
+ })
4542
+ });
4543
+ if (!response.ok && response.status !== 204) {
4544
+ throw new Error(`POST ${url} returned ${response.status}`);
4545
+ }
4384
4546
  }
4385
4547
  };
4386
4548
 
@@ -4463,6 +4625,7 @@ var AgentRunController = class {
4463
4625
  pendingPermissions = /* @__PURE__ */ new Map();
4464
4626
  messageHandler;
4465
4627
  text = "";
4628
+ observedMessageCompleted = false;
4466
4629
  costData = null;
4467
4630
  settled = false;
4468
4631
  finished;
@@ -4513,6 +4676,9 @@ var AgentRunController = class {
4513
4676
  this.text += event.delta;
4514
4677
  } else if ((event.type === "message.completed" || event.type === "run.completed") && event.text) {
4515
4678
  this.text = event.text;
4679
+ if (event.type === "message.completed") {
4680
+ this.observedMessageCompleted = true;
4681
+ }
4516
4682
  }
4517
4683
  this.eventQueue.push(event);
4518
4684
  }
@@ -4610,7 +4776,7 @@ var AgentRunController = class {
4610
4776
  "Agent run completed before pending permission requests resolved."
4611
4777
  )
4612
4778
  );
4613
- if (result?.text) {
4779
+ if (result?.text && !this.observedMessageCompleted) {
4614
4780
  this.text = result.text;
4615
4781
  }
4616
4782
  if (result && "costData" in result) {
@@ -4651,9 +4817,11 @@ var AgentRunController = class {
4651
4817
  }
4652
4818
  this.settled = true;
4653
4819
  this.clearPendingPermissions(
4654
- new Error("Agent run was cancelled before pending permission requests resolved.")
4820
+ new Error(
4821
+ "Agent run was cancelled before pending permission requests resolved."
4822
+ )
4655
4823
  );
4656
- if (result?.text) {
4824
+ if (result?.text && !this.observedMessageCompleted) {
4657
4825
  this.text = result.text;
4658
4826
  }
4659
4827
  if (result && "costData" in result) {
@@ -4768,7 +4936,7 @@ var Agent = class {
4768
4936
  * second `setup()` against the same sandbox does ~one round-trip of
4769
4937
  * work.
4770
4938
  */
4771
- async setup(config = {}) {
4939
+ async setup() {
4772
4940
  if (this.setupPromise) {
4773
4941
  await this.setupPromise;
4774
4942
  return;
@@ -4778,8 +4946,7 @@ var Agent = class {
4778
4946
  this.setupPromise = (async () => {
4779
4947
  await this.adapter.setup({
4780
4948
  provider: this.provider,
4781
- options: this.options,
4782
- config
4949
+ options: this.options
4783
4950
  });
4784
4951
  debugAgent(
4785
4952
  "setup() returned provider=%s after %dms",
@@ -4801,14 +4968,10 @@ var Agent = class {
4801
4968
  );
4802
4969
  }
4803
4970
  if (runConfig.forkSessionId && !runConfig.forkAtMessageId) {
4804
- throw new Error(
4805
- "AgentRunConfig.forkSessionId requires forkAtMessageId."
4806
- );
4971
+ throw new Error("AgentRunConfig.forkSessionId requires forkAtMessageId.");
4807
4972
  }
4808
4973
  if (runConfig.forkAtMessageId && !runConfig.forkSessionId) {
4809
- throw new Error(
4810
- "AgentRunConfig.forkAtMessageId requires forkSessionId."
4811
- );
4974
+ throw new Error("AgentRunConfig.forkAtMessageId requires forkSessionId.");
4812
4975
  }
4813
4976
  const runId = runConfig.runId ?? randomUUID2();
4814
4977
  const streamCalledAt = Date.now();