@clawos-dev/clawd 0.2.632 → 0.2.636

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;
@@ -46614,6 +46654,7 @@ function cloneState(s2) {
46614
46654
  bufferStartSeq: s2.bufferStartSeq,
46615
46655
  turnOpen: s2.turnOpen,
46616
46656
  turnStartedAt: s2.turnStartedAt,
46657
+ turnSelfWoke: s2.turnSelfWoke,
46617
46658
  pendingPermissions: s2.pendingPermissions,
46618
46659
  pendingQuestions: s2.pendingQuestions,
46619
46660
  freshSpawn: s2.freshSpawn,
@@ -47009,7 +47050,7 @@ function pushEventToBufferInPlace(next, event, deps) {
47009
47050
  next.pendingBgTasks = [...pending, bg.agentId];
47010
47051
  }
47011
47052
  }
47012
- if (isTurnEnd && next.turnOpen && isLateTurnEnd(withSeq, next.turnStartedAt, deps.now())) {
47053
+ if (isTurnEnd && next.turnOpen && !next.turnSelfWoke && isLateTurnEnd(withSeq, next.turnStartedAt, deps.now())) {
47013
47054
  return [emitSessionEvent(next.file.sessionId, withSeq)];
47014
47055
  }
47015
47056
  if (isTurnEnd) {
@@ -47022,6 +47063,7 @@ function pushEventToBufferInPlace(next, event, deps) {
47022
47063
  } else if (!isBackgroundEvent && !(isSessionInit && next.status === "running-idle") && !next.turnOpen) {
47023
47064
  next.turnOpen = true;
47024
47065
  next.turnStartedAt = deps.now();
47066
+ next.turnSelfWoke = next.status === "running-idle";
47025
47067
  }
47026
47068
  if (!next.turnOpen && next.buffer.length > deps.bufferCap) {
47027
47069
  const remove = next.buffer.length - deps.bufferCap;
@@ -47402,14 +47444,6 @@ function reduceSessionInner(state, input, deps) {
47402
47444
  case "inject-events": {
47403
47445
  return applyEventBatch(state, input.events, deps);
47404
47446
  }
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
47447
  case "cloud-turn-terminal": {
47414
47448
  if (!state.file.cloudPendingTurns?.some((item) => item.id === input.turnId)) return { state, effects: [] };
47415
47449
  const next = cloneState(state);
@@ -47419,9 +47453,13 @@ function reduceSessionInner(state, input, deps) {
47419
47453
  case "proc-exit": {
47420
47454
  const next = cloneState(state);
47421
47455
  next.procAlive = false;
47456
+ if (isCloudTool(next.file.tool) && next.file.cloudPendingCommand && !next.file.cloudPendingTurns) {
47457
+ next.file = { ...next.file, cloudPendingCommand: void 0 };
47458
+ }
47422
47459
  if (isCloudTool(next.file.tool) && !next.file.cloudPendingCommand && next.file.cloudGatewaySessionId) {
47423
47460
  next.file = { ...next.file, cloudGatewaySessionId: void 0 };
47424
47461
  }
47462
+ const cloudFileChanged = next.file !== state.file;
47425
47463
  next.status = "stopped";
47426
47464
  next.stdinClosed = false;
47427
47465
  next.pendingBgTasks = [];
@@ -47444,7 +47482,7 @@ function reduceSessionInner(state, input, deps) {
47444
47482
  return {
47445
47483
  state: next,
47446
47484
  effects: [
47447
- ...isCloudTool(state.file.tool) && state.file.cloudGatewaySessionId && !next.file.cloudGatewaySessionId ? [{ kind: "persist-file", file: next.file }] : [],
47485
+ ...cloudFileChanged ? [{ kind: "persist-file", file: next.file }] : [],
47448
47486
  ...interruptEffects,
47449
47487
  {
47450
47488
  kind: "emit-frame",
@@ -47901,7 +47939,10 @@ var SessionRunner = class {
47901
47939
  this.input({ kind: "command", command: { kind: "spawn-if-needed" } });
47902
47940
  const pending = this.state.file.cloudPendingCommand;
47903
47941
  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);
47942
+ for (const turn of this.state.file.cloudPendingTurns ?? []) {
47943
+ const payload = turn.prepared ? null : this.hooks.store.readCloudPayload(this.state.file.sessionId, turn.id);
47944
+ this.session?.startTurn(payload ?? turn.text, turn.id, payload !== null || turn.prepared === true);
47945
+ }
47905
47946
  } finally {
47906
47947
  this.recoveringCloud = false;
47907
47948
  }
@@ -48224,10 +48265,9 @@ var SessionRunner = class {
48224
48265
  }, "broadcast"),
48225
48266
  onCommandPrepared: (commandId, text, prepared) => {
48226
48267
  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 });
48268
+ if (!pending || pending.id !== commandId) return false;
48269
+ if (!prepared && pending !== this.state.file.cloudPendingCommand) {
48270
+ this.hooks.store.writeCloudPayload(this.state.file.sessionId, commandId, text);
48231
48271
  }
48232
48272
  return true;
48233
48273
  },
@@ -48420,6 +48460,24 @@ var SessionManager = class {
48420
48460
  setCloudCanceller(cancel) {
48421
48461
  this.cloudCancel = cancel;
48422
48462
  }
48463
+ /**
48464
+ * Cancel only an in-flight cloud turn before a local stop / delete / reset. Idle conversations need no gateway
48465
+ * call, and a failed cancel must not block the local action (offline / logged out / gateway errors).
48466
+ */
48467
+ async cancelCloudTurns(file, runner) {
48468
+ const inFlight = (file.cloudConversationId || file.cloudGatewaySessionId) && (file.cloudPendingCommand || file.cloudPendingTurns?.length);
48469
+ if (!inFlight) {
48470
+ runner?.confirmCloudCancelled();
48471
+ return;
48472
+ }
48473
+ if (!this.cloudCancel) throw new Error("cloud canceller is not configured");
48474
+ try {
48475
+ await this.cloudCancel(file, runner);
48476
+ } catch (err) {
48477
+ this.deps.logger?.warn("cloud cancel failed; continuing locally", { sessionId: file.sessionId, message: err.message });
48478
+ runner?.confirmCloudCancelled();
48479
+ }
48480
+ }
48423
48481
  // sessionId → SessionRunner;在 send / ensureSession 时按需创建
48424
48482
  runners = /* @__PURE__ */ new Map();
48425
48483
  // 标题生成 in-flight 防重(进程内);跨重启防重靠 SessionFile.aiLabelGeneratedAt
@@ -49178,12 +49236,7 @@ var SessionManager = class {
49178
49236
  const file = this.findOwnedSession(args.sessionId);
49179
49237
  if (!file) throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, args.sessionId);
49180
49238
  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
- }
49239
+ if (isCloudTool(file.tool)) await this.cancelCloudTurns(file, runner);
49187
49240
  return this.delete(args);
49188
49241
  }
49189
49242
  send(args, senderPrincipal) {
@@ -49327,12 +49380,7 @@ var SessionManager = class {
49327
49380
  async stop(args) {
49328
49381
  const file = this.findOwnedSession(args.sessionId);
49329
49382
  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
- }
49383
+ if (file && isCloudTool(file.tool)) await this.cancelCloudTurns(file, runner ?? null);
49336
49384
  if (!runner) {
49337
49385
  if (file && isCloudTool(file.tool) && (file.cloudPendingCommand || file.cloudPendingTurns?.length)) this.writeOwned({ ...file, cloudPendingCommand: void 0, cloudPendingTurns: void 0 });
49338
49386
  return { response: { ok: true }, broadcast: [] };
@@ -49528,11 +49576,7 @@ var SessionManager = class {
49528
49576
  async newSessionWithCloudCancel(args) {
49529
49577
  const file = this.getFile(args.sessionId);
49530
49578
  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();
49579
+ await this.cancelCloudTurns(file, this.runners.get(args.sessionId) ?? null);
49536
49580
  return this.newSession(args);
49537
49581
  }
49538
49582
  return this.newSession(args);
@@ -56771,6 +56815,9 @@ var CloudGatewayClient = class {
56771
56815
  return CloudVolumeUsageSchema.parse(body.volume);
56772
56816
  }
56773
56817
  };
56818
+ var TURN_RETRY_BASE_MS = 250;
56819
+ var TURN_RETRY_MAX_MS = 3e4;
56820
+ var TURN_RETRY_MAX_FAILURES = 12;
56774
56821
  function openHttpTurn(opts) {
56775
56822
  const { fetchImpl, gatewayUrl, apiKey, personaId: personaId2, conversationId, turnId, text, expectedEngine, engineSessionId, instructions, effort, personaMode, purpose, logger } = opts;
56776
56823
  const body = JSON.stringify({
@@ -56814,8 +56861,9 @@ function openHttpTurn(opts) {
56814
56861
  if (!gapCbs.length) earlyGap = true;
56815
56862
  for (const cb of gapCbs) cb();
56816
56863
  };
56817
- const retryWait = () => new Promise((resolve9) => {
56818
- const timer = setTimeout(resolve9, 250);
56864
+ let failures = 0;
56865
+ const retryWait = (ms) => new Promise((resolve9) => {
56866
+ const timer = setTimeout(resolve9, ms);
56819
56867
  timer.unref?.();
56820
56868
  abort.signal.addEventListener("abort", () => {
56821
56869
  clearTimeout(timer);
@@ -56857,6 +56905,7 @@ function openHttpTurn(opts) {
56857
56905
  for (; ; ) {
56858
56906
  const { value, done } = await reader.read();
56859
56907
  if (done || closed) break;
56908
+ if (value.byteLength) failures = 0;
56860
56909
  pending += decoder.decode(value, { stream: true });
56861
56910
  let delimiter2;
56862
56911
  while ((delimiter2 = /\r?\n\r?\n/.exec(pending)) !== null) {
@@ -56893,7 +56942,12 @@ function openHttpTurn(opts) {
56893
56942
  }
56894
56943
  logger?.warn("cloud turn SSE disconnected; reconnecting", { message: error.message, turnId });
56895
56944
  }
56896
- if (!closed) await retryWait();
56945
+ if (closed) return;
56946
+ if (++failures > TURN_RETRY_MAX_FAILURES) {
56947
+ state({ conversationId, turnId, status: "failed", errorCode: "GATEWAY_UNREACHABLE" });
56948
+ return;
56949
+ }
56950
+ await retryWait(Math.min(TURN_RETRY_BASE_MS * 2 ** (failures - 1), TURN_RETRY_MAX_MS));
56897
56951
  }
56898
56952
  })();
56899
56953
  return {
@@ -61158,6 +61212,7 @@ function renderDeployWorkflow(config) {
61158
61212
  " --model '${{ steps.plan.outputs.model }}' \\",
61159
61213
  " --effort '${{ steps.plan.outputs.effort }}' \\",
61160
61214
  " --size '${{ steps.plan.outputs.size }}' \\",
61215
+ " --cpu '${{ steps.plan.outputs.cpu }}' \\",
61161
61216
  " --task-concurrency '${{ steps.plan.outputs.task_concurrency }}' \\",
61162
61217
  " --engine '${{ steps.plan.outputs.engine }}' \\",
61163
61218
  " --connectors '${{ steps.plan.outputs.connectors }}' \\",
@@ -70967,10 +71022,15 @@ async function forkCodexThread(input, deps = {}) {
70967
71022
  if (rollout.meta?.threadId.toLowerCase() !== input.sourceThreadId.toLowerCase()) {
70968
71023
  throw new Error(`codex fork: source thread metadata does not match ${input.sourceThreadId}`);
70969
71024
  }
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}`);
71025
+ const completed = (turns) => turns.some((turn) => turn.turnId === input.turnId && turn.status === "completed");
71026
+ let owner2 = { threadId: input.sourceThreadId, home: source.home };
71027
+ if (!completed(rollout.turns)) {
71028
+ const segment = parseCodexThreadSegments(createCodexRolloutLocator(input.roots), input.sourceThreadId).find((item) => completed(item.parsed.turns));
71029
+ const home = segment && locateCodexThread(input.roots, segment.threadId)?.home;
71030
+ if (!segment || !home) throw new Error(`codex fork: completed turn not found: ${input.turnId}`);
71031
+ owner2 = { threadId: segment.threadId, home };
70972
71032
  }
70973
- const connection = (deps.open ?? openCodexAppServer)(source.home, input.targetCwd);
71033
+ const connection = (deps.open ?? openCodexAppServer)(owner2.home, input.targetCwd);
70974
71034
  const timeoutMs = deps.timeoutMs ?? DEFAULT_CODEX_QUERY_TIMEOUT_MS;
70975
71035
  let timer;
70976
71036
  const timeout = new Promise((_3, reject) => {
@@ -70985,7 +71045,7 @@ async function forkCodexThread(input, deps = {}) {
70985
71045
  });
70986
71046
  client.notify("initialized", {});
70987
71047
  const result = await client.request("thread/fork", {
70988
- threadId: input.sourceThreadId,
71048
+ threadId: owner2.threadId,
70989
71049
  lastTurnId: input.turnId,
70990
71050
  cwd: input.targetCwd
70991
71051
  });
@@ -70993,7 +71053,7 @@ async function forkCodexThread(input, deps = {}) {
70993
71053
  if (typeof forkedToolSessionId !== "string" || !forkedToolSessionId) {
70994
71054
  throw new Error("codex fork: app-server returned no thread id");
70995
71055
  }
70996
- const forked = locateCodexThread([import_node_path66.default.join(source.home, "sessions")], forkedToolSessionId);
71056
+ const forked = locateCodexThread([import_node_path66.default.join(owner2.home, "sessions")], forkedToolSessionId);
70997
71057
  if (!forked) throw new Error(`codex fork: rollout not found for forked thread ${forkedToolSessionId}`);
70998
71058
  return { forkedToolSessionId, forkedFilePath: forked.file };
70999
71059
  })()]);
@@ -73672,7 +73732,7 @@ function computeMethodAccess(args) {
73672
73732
  }
73673
73733
 
73674
73734
  // src/version.ts
73675
- var version = "0.2.632".length > 0 ? "0.2.632" : "dev";
73735
+ var version = "0.2.636".length > 0 ? "0.2.636" : "dev";
73676
73736
 
73677
73737
  // src/cli-probe/probe.ts
73678
73738
  var fs84 = __toESM(require("fs"), 1);