@cabane/companion 0.6.189 → 0.6.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5972,6 +5972,10 @@ var init_transport2 = __esm({
5972
5972
  });
5973
5973
 
5974
5974
  // packages/agent-runtime/src/codex/index.ts
5975
+ function isMissingRollout(err) {
5976
+ const message2 = err instanceof Error ? err.message : String(err);
5977
+ return /no rollout found for thread id/i.test(message2);
5978
+ }
5975
5979
  function createCodexAdapter(deps = {}) {
5976
5980
  const transport = deps.transport ?? (deps.enabled ? createSdkCodexTransport({ ...deps.apiKey ? { apiKey: deps.apiKey } : {} }) : null);
5977
5981
  return {
@@ -5990,8 +5994,8 @@ function createCodexAdapter(deps = {}) {
5990
5994
  }
5991
5995
  const directory = req.local.cwd ?? "";
5992
5996
  const decision = decideResume3(req.session, directory);
5993
- const resumeThreadId = "resume" in decision ? decision.resume : null;
5994
- const threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
5997
+ let resumeThreadId = "resume" in decision ? decision.resume : null;
5998
+ let threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
5995
5999
  if ("fresh" in decision && decision.reason === "cwd_mismatch") {
5996
6000
  deps.onWarn?.(
5997
6001
  "codex adapter: stored session directory no longer matches the current environment \u2014 starting a fresh thread (SJ527-analogue guard)",
@@ -6056,38 +6060,69 @@ function createCodexAdapter(deps = {}) {
6056
6060
  }
6057
6061
  if (signal.aborted) return;
6058
6062
  }
6059
- const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
6060
- if (spec.model === null) {
6061
- deps.onWarn?.("Codex is using its default model because no model was resolved", {
6062
- model: req.config.model
6063
- });
6063
+ for (; ; ) {
6064
+ const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
6065
+ if (spec.model === null) {
6066
+ deps.onWarn?.("Codex is using its default model because no model was resolved", {
6067
+ model: req.config.model
6068
+ });
6069
+ }
6070
+ let emitted = false;
6071
+ try {
6072
+ const result = await transport.run(spec, signal);
6073
+ if (signal.aborted) return;
6074
+ for await (const event of decodeCodexStream(result.events, {
6075
+ signal,
6076
+ cwd: req.local.cwd,
6077
+ resumedThreadId: resumeThreadId,
6078
+ degraded,
6079
+ // Which segments the thread carries once this turn lands. A segment sent is that one —
6080
+ // the later copy is what the model reads. One withheld is what the thread had. Read off
6081
+ // the spec, so the record can't disagree with what was sent.
6082
+ promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
6083
+ conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
6084
+ compactions,
6085
+ // What the thread held before this turn, for the write at `thread.started`.
6086
+ heldStanding: {
6087
+ promptFingerprint: threadStanding?.core ?? null,
6088
+ conversationFingerprint: threadStanding?.conversation ?? null,
6089
+ compactions: threadStanding ? compactions : null
6090
+ },
6091
+ storedState: req.session,
6092
+ usageBaseline: threadUsageBaseline,
6093
+ // Codex reports no model in-stream, so record what the thread was started with (null on the
6094
+ // default token). Same for the reasoning effort.
6095
+ resolvedModel: spec.model,
6096
+ resolvedReasoningEffort: spec.modelReasoningEffort ?? null
6097
+ })) {
6098
+ emitted = true;
6099
+ yield event;
6100
+ }
6101
+ return;
6102
+ } catch (err) {
6103
+ if (resumeThreadId === null || emitted || signal.aborted || !isMissingRollout(err)) {
6104
+ throw err;
6105
+ }
6106
+ deps.onWarn?.(
6107
+ "codex adapter: the stored thread has no rollout on this host \u2014 starting a fresh thread",
6108
+ { threadId: resumeThreadId }
6109
+ );
6110
+ resumeThreadId = null;
6111
+ threadUsageBaseline = null;
6112
+ threadStanding = null;
6113
+ compactions = 0;
6114
+ req = { ...req, session: null };
6115
+ degraded = true;
6116
+ if (hooks?.fresh) {
6117
+ const recomposed = await hooks.fresh("session_start_failed");
6118
+ if (signal.aborted) return;
6119
+ if (recomposed) {
6120
+ req = { ...req, ...recomposed, session: null };
6121
+ degraded = false;
6122
+ }
6123
+ }
6124
+ }
6064
6125
  }
6065
- const result = await transport.run(spec, signal);
6066
- if (signal.aborted) return;
6067
- yield* decodeCodexStream(result.events, {
6068
- signal,
6069
- cwd: req.local.cwd,
6070
- resumedThreadId: resumeThreadId,
6071
- degraded,
6072
- // Which segments the thread carries once this turn lands. A segment sent is that one —
6073
- // the later copy is what the model reads. One withheld is what the thread had. Read off
6074
- // the spec, so the record can't disagree with what was sent.
6075
- promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
6076
- conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
6077
- compactions,
6078
- // What the thread held before this turn, for the write at `thread.started`.
6079
- heldStanding: {
6080
- promptFingerprint: threadStanding?.core ?? null,
6081
- conversationFingerprint: threadStanding?.conversation ?? null,
6082
- compactions: threadStanding ? compactions : null
6083
- },
6084
- storedState: req.session,
6085
- usageBaseline: threadUsageBaseline,
6086
- // Codex reports no model in-stream, so record what the thread was started with (null on the
6087
- // default token). Same for the reasoning effort.
6088
- resolvedModel: spec.model,
6089
- resolvedReasoningEffort: spec.modelReasoningEffort ?? null
6090
- });
6091
6126
  } finally {
6092
6127
  if (images) {
6093
6128
  try {
package/dist/runtime.js CHANGED
@@ -6158,6 +6158,10 @@ function createSdkCodexTransport(opts = {}) {
6158
6158
  }
6159
6159
 
6160
6160
  // packages/agent-runtime/src/codex/index.ts
6161
+ function isMissingRollout(err) {
6162
+ const message = err instanceof Error ? err.message : String(err);
6163
+ return /no rollout found for thread id/i.test(message);
6164
+ }
6161
6165
  function createCodexAdapter(deps = {}) {
6162
6166
  const transport = deps.transport ?? (deps.enabled ? createSdkCodexTransport({ ...deps.apiKey ? { apiKey: deps.apiKey } : {} }) : null);
6163
6167
  return {
@@ -6176,8 +6180,8 @@ function createCodexAdapter(deps = {}) {
6176
6180
  }
6177
6181
  const directory = req.local.cwd ?? "";
6178
6182
  const decision = decideResume3(req.session, directory);
6179
- const resumeThreadId = "resume" in decision ? decision.resume : null;
6180
- const threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
6183
+ let resumeThreadId = "resume" in decision ? decision.resume : null;
6184
+ let threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
6181
6185
  if ("fresh" in decision && decision.reason === "cwd_mismatch") {
6182
6186
  deps.onWarn?.(
6183
6187
  "codex adapter: stored session directory no longer matches the current environment \u2014 starting a fresh thread (SJ527-analogue guard)",
@@ -6242,38 +6246,69 @@ function createCodexAdapter(deps = {}) {
6242
6246
  }
6243
6247
  if (signal.aborted) return;
6244
6248
  }
6245
- const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
6246
- if (spec.model === null) {
6247
- deps.onWarn?.("Codex is using its default model because no model was resolved", {
6248
- model: req.config.model
6249
- });
6249
+ for (; ; ) {
6250
+ const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
6251
+ if (spec.model === null) {
6252
+ deps.onWarn?.("Codex is using its default model because no model was resolved", {
6253
+ model: req.config.model
6254
+ });
6255
+ }
6256
+ let emitted = false;
6257
+ try {
6258
+ const result = await transport.run(spec, signal);
6259
+ if (signal.aborted) return;
6260
+ for await (const event of decodeCodexStream(result.events, {
6261
+ signal,
6262
+ cwd: req.local.cwd,
6263
+ resumedThreadId: resumeThreadId,
6264
+ degraded,
6265
+ // Which segments the thread carries once this turn lands. A segment sent is that one —
6266
+ // the later copy is what the model reads. One withheld is what the thread had. Read off
6267
+ // the spec, so the record can't disagree with what was sent.
6268
+ promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
6269
+ conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
6270
+ compactions,
6271
+ // What the thread held before this turn, for the write at `thread.started`.
6272
+ heldStanding: {
6273
+ promptFingerprint: threadStanding?.core ?? null,
6274
+ conversationFingerprint: threadStanding?.conversation ?? null,
6275
+ compactions: threadStanding ? compactions : null
6276
+ },
6277
+ storedState: req.session,
6278
+ usageBaseline: threadUsageBaseline,
6279
+ // Codex reports no model in-stream, so record what the thread was started with (null on the
6280
+ // default token). Same for the reasoning effort.
6281
+ resolvedModel: spec.model,
6282
+ resolvedReasoningEffort: spec.modelReasoningEffort ?? null
6283
+ })) {
6284
+ emitted = true;
6285
+ yield event;
6286
+ }
6287
+ return;
6288
+ } catch (err) {
6289
+ if (resumeThreadId === null || emitted || signal.aborted || !isMissingRollout(err)) {
6290
+ throw err;
6291
+ }
6292
+ deps.onWarn?.(
6293
+ "codex adapter: the stored thread has no rollout on this host \u2014 starting a fresh thread",
6294
+ { threadId: resumeThreadId }
6295
+ );
6296
+ resumeThreadId = null;
6297
+ threadUsageBaseline = null;
6298
+ threadStanding = null;
6299
+ compactions = 0;
6300
+ req = { ...req, session: null };
6301
+ degraded = true;
6302
+ if (hooks?.fresh) {
6303
+ const recomposed = await hooks.fresh("session_start_failed");
6304
+ if (signal.aborted) return;
6305
+ if (recomposed) {
6306
+ req = { ...req, ...recomposed, session: null };
6307
+ degraded = false;
6308
+ }
6309
+ }
6310
+ }
6250
6311
  }
6251
- const result = await transport.run(spec, signal);
6252
- if (signal.aborted) return;
6253
- yield* decodeCodexStream(result.events, {
6254
- signal,
6255
- cwd: req.local.cwd,
6256
- resumedThreadId: resumeThreadId,
6257
- degraded,
6258
- // Which segments the thread carries once this turn lands. A segment sent is that one —
6259
- // the later copy is what the model reads. One withheld is what the thread had. Read off
6260
- // the spec, so the record can't disagree with what was sent.
6261
- promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
6262
- conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
6263
- compactions,
6264
- // What the thread held before this turn, for the write at `thread.started`.
6265
- heldStanding: {
6266
- promptFingerprint: threadStanding?.core ?? null,
6267
- conversationFingerprint: threadStanding?.conversation ?? null,
6268
- compactions: threadStanding ? compactions : null
6269
- },
6270
- storedState: req.session,
6271
- usageBaseline: threadUsageBaseline,
6272
- // Codex reports no model in-stream, so record what the thread was started with (null on the
6273
- // default token). Same for the reasoning effort.
6274
- resolvedModel: spec.model,
6275
- resolvedReasoningEffort: spec.modelReasoningEffort ?? null
6276
- });
6277
6312
  } finally {
6278
6313
  if (images) {
6279
6314
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.189",
3
+ "version": "0.6.190",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect an agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
6
  "license": "UNLICENSED",