@clawos-dev/clawd 0.2.632 → 0.2.634

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.
@@ -32607,8 +32607,10 @@ var DashboardOpenUrlResultSchema = external_exports.object({
32607
32607
  }).strict();
32608
32608
 
32609
32609
  // ../protocol/src/persona-schemas.ts
32610
- var PERSONA_SIZES = ["tiny", "small", "medium", "large"];
32610
+ var PERSONA_SIZES = ["tiny", "small", "medium", "large", "xlarge"];
32611
32611
  var PersonaSizeSchema = external_exports.enum(PERSONA_SIZES);
32612
+ var PERSONA_CPUS = [0.5, 1, 2];
32613
+ var PersonaCpuSchema = external_exports.number().refine((cpu) => PERSONA_CPUS.includes(cpu), "Unsupported CPU");
32612
32614
  var PersonaFileSchema = external_exports.object({
32613
32615
  personaId: external_exports.string(),
32614
32616
  label: external_exports.string(),
@@ -32625,6 +32627,7 @@ var PersonaFileSchema = external_exports.object({
32625
32627
  tool: external_exports.string().optional(),
32626
32628
  /** Cloud resource size is string-compatible in stored files and narrowed at update/deploy boundaries; local execution ignores it. */
32627
32629
  size: external_exports.string().optional(),
32630
+ cpu: external_exports.number().finite().optional(),
32628
32631
  /** Cloud task-Pod concurrency, validated at release. Local execution ignores it; changes apply on the next publication. */
32629
32632
  taskConcurrency: external_exports.number().int().positive().optional(),
32630
32633
  createdAt: external_exports.number(),
@@ -32722,6 +32725,7 @@ var PersonaUpdateArgsSchema = external_exports.object({
32722
32725
  effort: external_exports.string().optional(),
32723
32726
  tool: external_exports.string().optional(),
32724
32727
  size: PersonaSizeSchema.optional(),
32728
+ cpu: PersonaCpuSchema.optional(),
32725
32729
  taskConcurrency: PersonaFileSchema.shape.taskConcurrency,
32726
32730
  personality: external_exports.string().optional(),
32727
32731
  // 人格补充层正文(见 PersonaInfoResponseSchema.personalityLocal)。与 personality 各写
@@ -34468,6 +34472,7 @@ function renderDeployWorkflow(config2) {
34468
34472
  " --model '${{ steps.plan.outputs.model }}' \\",
34469
34473
  " --effort '${{ steps.plan.outputs.effort }}' \\",
34470
34474
  " --size '${{ steps.plan.outputs.size }}' \\",
34475
+ " --cpu '${{ steps.plan.outputs.cpu }}' \\",
34471
34476
  " --task-concurrency '${{ steps.plan.outputs.task_concurrency }}' \\",
34472
34477
  " --engine '${{ steps.plan.outputs.engine }}' \\",
34473
34478
  " --connectors '${{ steps.plan.outputs.connectors }}' \\",
package/dist/cli.cjs CHANGED
@@ -7690,14 +7690,16 @@ var init_dashboard_schemas = __esm({
7690
7690
  });
7691
7691
 
7692
7692
  // ../protocol/src/persona-schemas.ts
7693
- var PERSONA_SIZES, PersonaSizeSchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, CodexSandboxSettingsSchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema;
7693
+ var PERSONA_SIZES, PersonaSizeSchema, PERSONA_CPUS, PersonaCpuSchema, PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, CodexSandboxSettingsSchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema;
7694
7694
  var init_persona_schemas = __esm({
7695
7695
  "../protocol/src/persona-schemas.ts"() {
7696
7696
  "use strict";
7697
7697
  init_zod();
7698
7698
  init_release_schemas();
7699
- PERSONA_SIZES = ["tiny", "small", "medium", "large"];
7699
+ PERSONA_SIZES = ["tiny", "small", "medium", "large", "xlarge"];
7700
7700
  PersonaSizeSchema = external_exports.enum(PERSONA_SIZES);
7701
+ PERSONA_CPUS = [0.5, 1, 2];
7702
+ PersonaCpuSchema = external_exports.number().refine((cpu) => PERSONA_CPUS.includes(cpu), "Unsupported CPU");
7701
7703
  PersonaFileSchema = external_exports.object({
7702
7704
  personaId: external_exports.string(),
7703
7705
  label: external_exports.string(),
@@ -7714,6 +7716,7 @@ var init_persona_schemas = __esm({
7714
7716
  tool: external_exports.string().optional(),
7715
7717
  /** Cloud resource size is string-compatible in stored files and narrowed at update/deploy boundaries; local execution ignores it. */
7716
7718
  size: external_exports.string().optional(),
7719
+ cpu: external_exports.number().finite().optional(),
7717
7720
  /** Cloud task-Pod concurrency, validated at release. Local execution ignores it; changes apply on the next publication. */
7718
7721
  taskConcurrency: external_exports.number().int().positive().optional(),
7719
7722
  createdAt: external_exports.number(),
@@ -7811,6 +7814,7 @@ var init_persona_schemas = __esm({
7811
7814
  effort: external_exports.string().optional(),
7812
7815
  tool: external_exports.string().optional(),
7813
7816
  size: PersonaSizeSchema.optional(),
7817
+ cpu: PersonaCpuSchema.optional(),
7814
7818
  taskConcurrency: PersonaFileSchema.shape.taskConcurrency,
7815
7819
  personality: external_exports.string().optional(),
7816
7820
  // 人格补充层正文(见 PersonaInfoResponseSchema.personalityLocal)。与 personality 各写
@@ -45000,7 +45004,7 @@ var CodexHistoryReader = class {
45000
45004
  }
45001
45005
  async read(args) {
45002
45006
  const locate = createCodexRolloutLocator(this.deps.roots);
45003
- const segments = this.parseThreadSegments(locate, args.toolSessionId);
45007
+ const segments = parseCodexThreadSegments(locate, args.toolSessionId);
45004
45008
  const messages = [];
45005
45009
  for (const segment of segments) {
45006
45010
  messages.push(...await withSubagentResults(
@@ -45020,7 +45024,7 @@ var CodexHistoryReader = class {
45020
45024
  }
45021
45025
  async readSubagent(args) {
45022
45026
  const locate = createCodexRolloutLocator(this.deps.roots);
45023
- const owner2 = this.parseThreadSegments(locate, args.toolSessionId).find((segment) => segment.parsed.subagents.some((sub) => sub.agentId === args.subagentId));
45027
+ const owner2 = parseCodexThreadSegments(locate, args.toolSessionId).find((segment) => segment.parsed.subagents.some((sub) => sub.agentId === args.subagentId));
45024
45028
  const child = this.parseThread(locate, args.subagentId);
45025
45029
  if (!owner2 || child.meta?.threadId !== args.subagentId || child.meta.parentThreadId !== owner2.threadId) {
45026
45030
  throw new Error(
@@ -45036,7 +45040,7 @@ var CodexHistoryReader = class {
45036
45040
  return [];
45037
45041
  }
45038
45042
  parseThread(locate, threadId) {
45039
- const segments = this.parseThreadSegments(locate, threadId);
45043
+ const segments = parseCodexThreadSegments(locate, threadId);
45040
45044
  const current = segments[segments.length - 1].parsed;
45041
45045
  return {
45042
45046
  ...current,
@@ -45045,30 +45049,30 @@ var CodexHistoryReader = class {
45045
45049
  turns: segments.flatMap((s2) => s2.parsed.turns)
45046
45050
  };
45047
45051
  }
45048
- parseThreadSegments(locate, threadId, endByteOffset, seen = /* @__PURE__ */ new Set()) {
45049
- const key = threadId.toLowerCase();
45050
- if (seen.has(key)) throw new Error(`Codex fork history cycle at thread ${threadId}`);
45051
- seen.add(key);
45052
- try {
45053
- const file = locate(threadId);
45054
- if (!file) throw new Error(`Codex rollout not found for thread ${threadId}`);
45055
- const bytes = import_node_fs3.default.readFileSync(file);
45056
- if (endByteOffset !== void 0 && endByteOffset > bytes.length) {
45057
- throw new Error(`Codex fork history_base exceeds source rollout ${threadId}`);
45058
- }
45059
- const text = (endByteOffset === void 0 ? bytes : bytes.subarray(0, endByteOffset)).toString("utf8");
45060
- const parsed = parseCodexRollout(text);
45061
- if (parsed.meta?.threadId.toLowerCase() !== key) {
45062
- throw new Error(`Codex rollout metadata does not match thread ${threadId}`);
45063
- }
45064
- const base = historyBase(text);
45065
- const inherited = base ? this.parseThreadSegments(locate, base.threadId, base.endByteOffset, seen) : [];
45066
- return [...inherited, { threadId, parsed }];
45067
- } finally {
45068
- seen.delete(key);
45069
- }
45070
- }
45071
45052
  };
45053
+ function parseCodexThreadSegments(locate, threadId, endByteOffset, seen = /* @__PURE__ */ new Set()) {
45054
+ const key = threadId.toLowerCase();
45055
+ if (seen.has(key)) throw new Error(`Codex fork history cycle at thread ${threadId}`);
45056
+ seen.add(key);
45057
+ try {
45058
+ const file = locate(threadId);
45059
+ if (!file) throw new Error(`Codex rollout not found for thread ${threadId}`);
45060
+ const bytes = import_node_fs3.default.readFileSync(file);
45061
+ if (endByteOffset !== void 0 && endByteOffset > bytes.length) {
45062
+ throw new Error(`Codex fork history_base exceeds source rollout ${threadId}`);
45063
+ }
45064
+ const text = (endByteOffset === void 0 ? bytes : bytes.subarray(0, endByteOffset)).toString("utf8");
45065
+ const parsed = parseCodexRollout(text);
45066
+ if (parsed.meta?.threadId.toLowerCase() !== key) {
45067
+ throw new Error(`Codex rollout metadata does not match thread ${threadId}`);
45068
+ }
45069
+ const base = historyBase(text);
45070
+ const inherited = base ? parseCodexThreadSegments(locate, base.threadId, base.endByteOffset, seen) : [];
45071
+ return [...inherited, { threadId, parsed }];
45072
+ } finally {
45073
+ seen.delete(key);
45074
+ }
45075
+ }
45072
45076
 
45073
45077
  // src/tools/cloud-history-pages.ts
45074
45078
  var import_node_crypto = require("crypto");
@@ -45983,6 +45987,37 @@ var SessionStore = class {
45983
45987
  filePath(sessionId) {
45984
45988
  return import_node_path6.default.join(this.root, `${safeFileName(sessionId)}.json`);
45985
45989
  }
45990
+ // Exact prepared cloud turn bodies (images inlined as base64) live beside the session file, never in it, so they are
45991
+ // not persisted with every write or broadcast; restart recovery replays them byte-for-byte (the gateway rejects a
45992
+ // changed body for the same turn ID). Each write prunes payloads of turns no longer in cloudPendingTurns.
45993
+ cloudPayloadDir(sessionId) {
45994
+ return import_node_path6.default.join(this.root, `${safeFileName(sessionId)}.cloud-payloads`);
45995
+ }
45996
+ writeCloudPayload(sessionId, turnId, text) {
45997
+ const dir = this.cloudPayloadDir(sessionId);
45998
+ import_node_fs7.default.mkdirSync(dir, { recursive: true });
45999
+ const target = import_node_path6.default.join(dir, safeFileName(turnId));
46000
+ const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
46001
+ import_node_fs7.default.writeFileSync(tmp, text, { encoding: "utf8", mode: 384 });
46002
+ import_node_fs7.default.renameSync(tmp, target);
46003
+ }
46004
+ readCloudPayload(sessionId, turnId) {
46005
+ try {
46006
+ return import_node_fs7.default.readFileSync(import_node_path6.default.join(this.cloudPayloadDir(sessionId), safeFileName(turnId)), "utf8");
46007
+ } catch {
46008
+ return null;
46009
+ }
46010
+ }
46011
+ pruneCloudPayloads(file) {
46012
+ const dir = this.cloudPayloadDir(file.sessionId);
46013
+ if (!import_node_fs7.default.existsSync(dir)) return;
46014
+ const keep = new Set((file.cloudPendingTurns ?? []).map((turn) => safeFileName(turn.id)));
46015
+ if (keep.size === 0) {
46016
+ import_node_fs7.default.rmSync(dir, { recursive: true, force: true });
46017
+ return;
46018
+ }
46019
+ for (const name of import_node_fs7.default.readdirSync(dir)) if (!keep.has(name)) import_node_fs7.default.rmSync(import_node_path6.default.join(dir, name), { force: true });
46020
+ }
45986
46021
  ensureDir() {
45987
46022
  import_node_fs7.default.mkdirSync(this.root, { recursive: true });
45988
46023
  }
@@ -46004,9 +46039,14 @@ var SessionStore = class {
46004
46039
  const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
46005
46040
  import_node_fs7.default.writeFileSync(tmp, JSON.stringify(validated, null, 2), { encoding: "utf8", mode: 384 });
46006
46041
  import_node_fs7.default.renameSync(tmp, target);
46042
+ try {
46043
+ this.pruneCloudPayloads(validated);
46044
+ } catch {
46045
+ }
46007
46046
  return validated;
46008
46047
  }
46009
46048
  delete(sessionId) {
46049
+ import_node_fs7.default.rmSync(this.cloudPayloadDir(sessionId), { recursive: true, force: true });
46010
46050
  try {
46011
46051
  import_node_fs7.default.unlinkSync(this.filePath(sessionId));
46012
46052
  return true;
@@ -47402,14 +47442,6 @@ function reduceSessionInner(state, input, deps) {
47402
47442
  case "inject-events": {
47403
47443
  return applyEventBatch(state, input.events, deps);
47404
47444
  }
47405
- case "cloud-command-prepared": {
47406
- const pending = state.file.cloudPendingCommand;
47407
- const turn = state.file.cloudPendingTurns?.find((item) => item.id === input.commandId);
47408
- if ((!pending || pending.id !== input.commandId) && !turn) return { state, effects: [] };
47409
- const next = cloneState(state);
47410
- next.file = turn ? { ...next.file, cloudPendingTurns: next.file.cloudPendingTurns?.map((item) => item.id === input.commandId ? { ...item, text: input.text, prepared: true } : item) } : { ...next.file, cloudPendingCommand: { id: pending.id, text: input.text, prepared: true } };
47411
- return { state: next, effects: [{ kind: "persist-file", file: next.file }] };
47412
- }
47413
47445
  case "cloud-turn-terminal": {
47414
47446
  if (!state.file.cloudPendingTurns?.some((item) => item.id === input.turnId)) return { state, effects: [] };
47415
47447
  const next = cloneState(state);
@@ -47419,9 +47451,13 @@ function reduceSessionInner(state, input, deps) {
47419
47451
  case "proc-exit": {
47420
47452
  const next = cloneState(state);
47421
47453
  next.procAlive = false;
47454
+ if (isCloudTool(next.file.tool) && next.file.cloudPendingCommand && !next.file.cloudPendingTurns) {
47455
+ next.file = { ...next.file, cloudPendingCommand: void 0 };
47456
+ }
47422
47457
  if (isCloudTool(next.file.tool) && !next.file.cloudPendingCommand && next.file.cloudGatewaySessionId) {
47423
47458
  next.file = { ...next.file, cloudGatewaySessionId: void 0 };
47424
47459
  }
47460
+ const cloudFileChanged = next.file !== state.file;
47425
47461
  next.status = "stopped";
47426
47462
  next.stdinClosed = false;
47427
47463
  next.pendingBgTasks = [];
@@ -47444,7 +47480,7 @@ function reduceSessionInner(state, input, deps) {
47444
47480
  return {
47445
47481
  state: next,
47446
47482
  effects: [
47447
- ...isCloudTool(state.file.tool) && state.file.cloudGatewaySessionId && !next.file.cloudGatewaySessionId ? [{ kind: "persist-file", file: next.file }] : [],
47483
+ ...cloudFileChanged ? [{ kind: "persist-file", file: next.file }] : [],
47448
47484
  ...interruptEffects,
47449
47485
  {
47450
47486
  kind: "emit-frame",
@@ -47901,7 +47937,10 @@ var SessionRunner = class {
47901
47937
  this.input({ kind: "command", command: { kind: "spawn-if-needed" } });
47902
47938
  const pending = this.state.file.cloudPendingCommand;
47903
47939
  if (pending) this.session?.startTurn(pending.text, pending.id, pending.prepared === true);
47904
- for (const turn of this.state.file.cloudPendingTurns ?? []) this.session?.startTurn(turn.text, turn.id, turn.prepared === true);
47940
+ for (const turn of this.state.file.cloudPendingTurns ?? []) {
47941
+ const payload = turn.prepared ? null : this.hooks.store.readCloudPayload(this.state.file.sessionId, turn.id);
47942
+ this.session?.startTurn(payload ?? turn.text, turn.id, payload !== null || turn.prepared === true);
47943
+ }
47905
47944
  } finally {
47906
47945
  this.recoveringCloud = false;
47907
47946
  }
@@ -48224,10 +48263,9 @@ var SessionRunner = class {
48224
48263
  }, "broadcast"),
48225
48264
  onCommandPrepared: (commandId, text, prepared) => {
48226
48265
  const pending = this.state.file.cloudPendingTurns?.find((turn) => turn.id === commandId) ?? this.state.file.cloudPendingCommand;
48227
- if (!pending || pending.id !== commandId || prepared && pending.text !== text) return false;
48228
- if (!prepared) {
48229
- this.hooks.store.write(this.state.file.cloudPendingTurns?.some((turn) => turn.id === commandId) ? { ...this.state.file, cloudPendingTurns: this.state.file.cloudPendingTurns.map((turn) => turn.id === commandId ? { ...turn, text, prepared: true } : turn) } : { ...this.state.file, cloudPendingCommand: { id: commandId, text, prepared: true } });
48230
- this.input({ kind: "cloud-command-prepared", commandId, text });
48266
+ if (!pending || pending.id !== commandId) return false;
48267
+ if (!prepared && pending !== this.state.file.cloudPendingCommand) {
48268
+ this.hooks.store.writeCloudPayload(this.state.file.sessionId, commandId, text);
48231
48269
  }
48232
48270
  return true;
48233
48271
  },
@@ -48420,6 +48458,24 @@ var SessionManager = class {
48420
48458
  setCloudCanceller(cancel) {
48421
48459
  this.cloudCancel = cancel;
48422
48460
  }
48461
+ /**
48462
+ * Cancel only an in-flight cloud turn before a local stop / delete / reset. Idle conversations need no gateway
48463
+ * call, and a failed cancel must not block the local action (offline / logged out / gateway errors).
48464
+ */
48465
+ async cancelCloudTurns(file, runner) {
48466
+ const inFlight = (file.cloudConversationId || file.cloudGatewaySessionId) && (file.cloudPendingCommand || file.cloudPendingTurns?.length);
48467
+ if (!inFlight) {
48468
+ runner?.confirmCloudCancelled();
48469
+ return;
48470
+ }
48471
+ if (!this.cloudCancel) throw new Error("cloud canceller is not configured");
48472
+ try {
48473
+ await this.cloudCancel(file, runner);
48474
+ } catch (err) {
48475
+ this.deps.logger?.warn("cloud cancel failed; continuing locally", { sessionId: file.sessionId, message: err.message });
48476
+ runner?.confirmCloudCancelled();
48477
+ }
48478
+ }
48423
48479
  // sessionId → SessionRunner;在 send / ensureSession 时按需创建
48424
48480
  runners = /* @__PURE__ */ new Map();
48425
48481
  // 标题生成 in-flight 防重(进程内);跨重启防重靠 SessionFile.aiLabelGeneratedAt
@@ -49178,12 +49234,7 @@ var SessionManager = class {
49178
49234
  const file = this.findOwnedSession(args.sessionId);
49179
49235
  if (!file) throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, args.sessionId);
49180
49236
  const runner = this.runners.get(args.sessionId) ?? null;
49181
- if (isCloudTool(file.tool)) {
49182
- if (file.cloudConversationId || file.cloudGatewaySessionId) {
49183
- if (!this.cloudCancel) throw new Error("cloud canceller is not configured");
49184
- await this.cloudCancel(file, runner);
49185
- } else runner?.confirmCloudCancelled();
49186
- }
49237
+ if (isCloudTool(file.tool)) await this.cancelCloudTurns(file, runner);
49187
49238
  return this.delete(args);
49188
49239
  }
49189
49240
  send(args, senderPrincipal) {
@@ -49327,12 +49378,7 @@ var SessionManager = class {
49327
49378
  async stop(args) {
49328
49379
  const file = this.findOwnedSession(args.sessionId);
49329
49380
  const runner = this.runners.get(args.sessionId);
49330
- if (file && isCloudTool(file.tool)) {
49331
- if (file.cloudConversationId || file.cloudGatewaySessionId) {
49332
- if (!this.cloudCancel) throw new Error("cloud canceller is not configured");
49333
- await this.cloudCancel(file, runner ?? null);
49334
- } else runner?.confirmCloudCancelled();
49335
- }
49381
+ if (file && isCloudTool(file.tool)) await this.cancelCloudTurns(file, runner ?? null);
49336
49382
  if (!runner) {
49337
49383
  if (file && isCloudTool(file.tool) && (file.cloudPendingCommand || file.cloudPendingTurns?.length)) this.writeOwned({ ...file, cloudPendingCommand: void 0, cloudPendingTurns: void 0 });
49338
49384
  return { response: { ok: true }, broadcast: [] };
@@ -49528,11 +49574,7 @@ var SessionManager = class {
49528
49574
  async newSessionWithCloudCancel(args) {
49529
49575
  const file = this.getFile(args.sessionId);
49530
49576
  if (isCloudTool(file.tool)) {
49531
- const runner = this.runners.get(args.sessionId) ?? null;
49532
- if (file.cloudConversationId || file.cloudGatewaySessionId) {
49533
- if (!this.cloudCancel) throw new Error("cloud canceller is not configured");
49534
- await this.cloudCancel(file, runner);
49535
- } else runner?.confirmCloudCancelled();
49577
+ await this.cancelCloudTurns(file, this.runners.get(args.sessionId) ?? null);
49536
49578
  return this.newSession(args);
49537
49579
  }
49538
49580
  return this.newSession(args);
@@ -56771,6 +56813,9 @@ var CloudGatewayClient = class {
56771
56813
  return CloudVolumeUsageSchema.parse(body.volume);
56772
56814
  }
56773
56815
  };
56816
+ var TURN_RETRY_BASE_MS = 250;
56817
+ var TURN_RETRY_MAX_MS = 3e4;
56818
+ var TURN_RETRY_MAX_FAILURES = 12;
56774
56819
  function openHttpTurn(opts) {
56775
56820
  const { fetchImpl, gatewayUrl, apiKey, personaId: personaId2, conversationId, turnId, text, expectedEngine, engineSessionId, instructions, effort, personaMode, purpose, logger } = opts;
56776
56821
  const body = JSON.stringify({
@@ -56814,8 +56859,9 @@ function openHttpTurn(opts) {
56814
56859
  if (!gapCbs.length) earlyGap = true;
56815
56860
  for (const cb of gapCbs) cb();
56816
56861
  };
56817
- const retryWait = () => new Promise((resolve9) => {
56818
- const timer = setTimeout(resolve9, 250);
56862
+ let failures = 0;
56863
+ const retryWait = (ms) => new Promise((resolve9) => {
56864
+ const timer = setTimeout(resolve9, ms);
56819
56865
  timer.unref?.();
56820
56866
  abort.signal.addEventListener("abort", () => {
56821
56867
  clearTimeout(timer);
@@ -56857,6 +56903,7 @@ function openHttpTurn(opts) {
56857
56903
  for (; ; ) {
56858
56904
  const { value, done } = await reader.read();
56859
56905
  if (done || closed) break;
56906
+ if (value.byteLength) failures = 0;
56860
56907
  pending += decoder.decode(value, { stream: true });
56861
56908
  let delimiter2;
56862
56909
  while ((delimiter2 = /\r?\n\r?\n/.exec(pending)) !== null) {
@@ -56893,7 +56940,12 @@ function openHttpTurn(opts) {
56893
56940
  }
56894
56941
  logger?.warn("cloud turn SSE disconnected; reconnecting", { message: error.message, turnId });
56895
56942
  }
56896
- if (!closed) await retryWait();
56943
+ if (closed) return;
56944
+ if (++failures > TURN_RETRY_MAX_FAILURES) {
56945
+ state({ conversationId, turnId, status: "failed", errorCode: "GATEWAY_UNREACHABLE" });
56946
+ return;
56947
+ }
56948
+ await retryWait(Math.min(TURN_RETRY_BASE_MS * 2 ** (failures - 1), TURN_RETRY_MAX_MS));
56897
56949
  }
56898
56950
  })();
56899
56951
  return {
@@ -61158,6 +61210,7 @@ function renderDeployWorkflow(config) {
61158
61210
  " --model '${{ steps.plan.outputs.model }}' \\",
61159
61211
  " --effort '${{ steps.plan.outputs.effort }}' \\",
61160
61212
  " --size '${{ steps.plan.outputs.size }}' \\",
61213
+ " --cpu '${{ steps.plan.outputs.cpu }}' \\",
61161
61214
  " --task-concurrency '${{ steps.plan.outputs.task_concurrency }}' \\",
61162
61215
  " --engine '${{ steps.plan.outputs.engine }}' \\",
61163
61216
  " --connectors '${{ steps.plan.outputs.connectors }}' \\",
@@ -70967,10 +71020,15 @@ async function forkCodexThread(input, deps = {}) {
70967
71020
  if (rollout.meta?.threadId.toLowerCase() !== input.sourceThreadId.toLowerCase()) {
70968
71021
  throw new Error(`codex fork: source thread metadata does not match ${input.sourceThreadId}`);
70969
71022
  }
70970
- if (!rollout.turns.some((turn) => turn.turnId === input.turnId && turn.status === "completed")) {
70971
- throw new Error(`codex fork: completed turn not found: ${input.turnId}`);
71023
+ const completed = (turns) => turns.some((turn) => turn.turnId === input.turnId && turn.status === "completed");
71024
+ let owner2 = { threadId: input.sourceThreadId, home: source.home };
71025
+ if (!completed(rollout.turns)) {
71026
+ const segment = parseCodexThreadSegments(createCodexRolloutLocator(input.roots), input.sourceThreadId).find((item) => completed(item.parsed.turns));
71027
+ const home = segment && locateCodexThread(input.roots, segment.threadId)?.home;
71028
+ if (!segment || !home) throw new Error(`codex fork: completed turn not found: ${input.turnId}`);
71029
+ owner2 = { threadId: segment.threadId, home };
70972
71030
  }
70973
- const connection = (deps.open ?? openCodexAppServer)(source.home, input.targetCwd);
71031
+ const connection = (deps.open ?? openCodexAppServer)(owner2.home, input.targetCwd);
70974
71032
  const timeoutMs = deps.timeoutMs ?? DEFAULT_CODEX_QUERY_TIMEOUT_MS;
70975
71033
  let timer;
70976
71034
  const timeout = new Promise((_3, reject) => {
@@ -70985,7 +71043,7 @@ async function forkCodexThread(input, deps = {}) {
70985
71043
  });
70986
71044
  client.notify("initialized", {});
70987
71045
  const result = await client.request("thread/fork", {
70988
- threadId: input.sourceThreadId,
71046
+ threadId: owner2.threadId,
70989
71047
  lastTurnId: input.turnId,
70990
71048
  cwd: input.targetCwd
70991
71049
  });
@@ -70993,7 +71051,7 @@ async function forkCodexThread(input, deps = {}) {
70993
71051
  if (typeof forkedToolSessionId !== "string" || !forkedToolSessionId) {
70994
71052
  throw new Error("codex fork: app-server returned no thread id");
70995
71053
  }
70996
- const forked = locateCodexThread([import_node_path66.default.join(source.home, "sessions")], forkedToolSessionId);
71054
+ const forked = locateCodexThread([import_node_path66.default.join(owner2.home, "sessions")], forkedToolSessionId);
70997
71055
  if (!forked) throw new Error(`codex fork: rollout not found for forked thread ${forkedToolSessionId}`);
70998
71056
  return { forkedToolSessionId, forkedFilePath: forked.file };
70999
71057
  })()]);
@@ -73672,7 +73730,7 @@ function computeMethodAccess(args) {
73672
73730
  }
73673
73731
 
73674
73732
  // src/version.ts
73675
- var version = "0.2.632".length > 0 ? "0.2.632" : "dev";
73733
+ var version = "0.2.634".length > 0 ? "0.2.634" : "dev";
73676
73734
 
73677
73735
  // src/cli-probe/probe.ts
73678
73736
  var fs84 = __toESM(require("fs"), 1);