@cabane/companion 0.6.189 → 0.6.191

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
@@ -5845,8 +5845,6 @@ var init_run_spec2 = __esm({
5845
5845
  TURN_CONTROL_MCP_SERVER2 = "cabane_companion";
5846
5846
  ACTIVE_CONVERSATION_HEADER3 = "x-cabane-active-conversation";
5847
5847
  ENV_ENVELOPE_KEYS = [
5848
- "CABANE_PLAYGROUND",
5849
- "CABANE_PLAYGROUND_BIN",
5850
5848
  "CABANE_CONVERSATION_ID",
5851
5849
  "CABANE_AGENT_ID",
5852
5850
  "CABANE_CONVERSATION_TITLE"
@@ -5972,6 +5970,10 @@ var init_transport2 = __esm({
5972
5970
  });
5973
5971
 
5974
5972
  // packages/agent-runtime/src/codex/index.ts
5973
+ function isMissingRollout(err) {
5974
+ const message2 = err instanceof Error ? err.message : String(err);
5975
+ return /no rollout found for thread id/i.test(message2);
5976
+ }
5975
5977
  function createCodexAdapter(deps = {}) {
5976
5978
  const transport = deps.transport ?? (deps.enabled ? createSdkCodexTransport({ ...deps.apiKey ? { apiKey: deps.apiKey } : {} }) : null);
5977
5979
  return {
@@ -5990,8 +5992,8 @@ function createCodexAdapter(deps = {}) {
5990
5992
  }
5991
5993
  const directory = req.local.cwd ?? "";
5992
5994
  const decision = decideResume3(req.session, directory);
5993
- const resumeThreadId = "resume" in decision ? decision.resume : null;
5994
- const threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
5995
+ let resumeThreadId = "resume" in decision ? decision.resume : null;
5996
+ let threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
5995
5997
  if ("fresh" in decision && decision.reason === "cwd_mismatch") {
5996
5998
  deps.onWarn?.(
5997
5999
  "codex adapter: stored session directory no longer matches the current environment \u2014 starting a fresh thread (SJ527-analogue guard)",
@@ -6056,38 +6058,69 @@ function createCodexAdapter(deps = {}) {
6056
6058
  }
6057
6059
  if (signal.aborted) return;
6058
6060
  }
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
- });
6061
+ for (; ; ) {
6062
+ const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
6063
+ if (spec.model === null) {
6064
+ deps.onWarn?.("Codex is using its default model because no model was resolved", {
6065
+ model: req.config.model
6066
+ });
6067
+ }
6068
+ let emitted = false;
6069
+ try {
6070
+ const result = await transport.run(spec, signal);
6071
+ if (signal.aborted) return;
6072
+ for await (const event of decodeCodexStream(result.events, {
6073
+ signal,
6074
+ cwd: req.local.cwd,
6075
+ resumedThreadId: resumeThreadId,
6076
+ degraded,
6077
+ // Which segments the thread carries once this turn lands. A segment sent is that one —
6078
+ // the later copy is what the model reads. One withheld is what the thread had. Read off
6079
+ // the spec, so the record can't disagree with what was sent.
6080
+ promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
6081
+ conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
6082
+ compactions,
6083
+ // What the thread held before this turn, for the write at `thread.started`.
6084
+ heldStanding: {
6085
+ promptFingerprint: threadStanding?.core ?? null,
6086
+ conversationFingerprint: threadStanding?.conversation ?? null,
6087
+ compactions: threadStanding ? compactions : null
6088
+ },
6089
+ storedState: req.session,
6090
+ usageBaseline: threadUsageBaseline,
6091
+ // Codex reports no model in-stream, so record what the thread was started with (null on the
6092
+ // default token). Same for the reasoning effort.
6093
+ resolvedModel: spec.model,
6094
+ resolvedReasoningEffort: spec.modelReasoningEffort ?? null
6095
+ })) {
6096
+ emitted = true;
6097
+ yield event;
6098
+ }
6099
+ return;
6100
+ } catch (err) {
6101
+ if (resumeThreadId === null || emitted || signal.aborted || !isMissingRollout(err)) {
6102
+ throw err;
6103
+ }
6104
+ deps.onWarn?.(
6105
+ "codex adapter: the stored thread has no rollout on this host \u2014 starting a fresh thread",
6106
+ { threadId: resumeThreadId }
6107
+ );
6108
+ resumeThreadId = null;
6109
+ threadUsageBaseline = null;
6110
+ threadStanding = null;
6111
+ compactions = 0;
6112
+ req = { ...req, session: null };
6113
+ degraded = true;
6114
+ if (hooks?.fresh) {
6115
+ const recomposed = await hooks.fresh("session_start_failed");
6116
+ if (signal.aborted) return;
6117
+ if (recomposed) {
6118
+ req = { ...req, ...recomposed, session: null };
6119
+ degraded = false;
6120
+ }
6121
+ }
6122
+ }
6064
6123
  }
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
6124
  } finally {
6092
6125
  if (images) {
6093
6126
  try {
@@ -7512,9 +7545,11 @@ function required(env, name) {
7512
7545
  function runnerPlace(env) {
7513
7546
  const workRoot2 = env[PLACE_ENV.workRoot];
7514
7547
  const transcriptDir = env[PLACE_ENV.transcriptDir];
7548
+ const box = env[PLACE_ENV.box];
7515
7549
  return {
7516
7550
  ...workRoot2 ? { workRoot: workRoot2 } : {},
7517
- ...transcriptDir ? { transcriptDir } : {}
7551
+ ...transcriptDir ? { transcriptDir } : {},
7552
+ ...box ? { box } : {}
7518
7553
  };
7519
7554
  }
7520
7555
  function parseRunnerInput(env, stdin) {
@@ -7596,7 +7631,8 @@ var init_protocol = __esm({
7596
7631
  };
7597
7632
  PLACE_ENV = {
7598
7633
  workRoot: "CABANE_WORK_ROOT",
7599
- transcriptDir: "CABANE_TRANSCRIPT_DIR"
7634
+ transcriptDir: "CABANE_TRANSCRIPT_DIR",
7635
+ box: "CABANE_BOX"
7600
7636
  };
7601
7637
  runtimeHealthSchema = z14.object({
7602
7638
  quotaState: z14.enum(["ok", "limited", "auth_expired", "unknown"]),
@@ -7667,8 +7703,7 @@ function runnerExecEnv(base, boxed) {
7667
7703
  const env = {};
7668
7704
  for (const [k, v] of Object.entries(base)) {
7669
7705
  if (v === void 0) continue;
7670
- if (boxed && (BOXED_ENV_DROPPED.includes(k) || k.startsWith(BOXED_ENV_DROPPED_PREFIX)))
7671
- continue;
7706
+ if (boxed && BOXED_ENV_DROPPED.includes(k)) continue;
7672
7707
  env[k] = v;
7673
7708
  }
7674
7709
  return env;
@@ -8006,7 +8041,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
8006
8041
  duties.notifyEnd({ id: payload.messageId, ok: false, durationMs, reason });
8007
8042
  return { ok: false, durationMs, reason };
8008
8043
  }
8009
- var BOXED_ENV_DROPPED, BOXED_ENV_DROPPED_PREFIX, PINO_LEVELS;
8044
+ var BOXED_ENV_DROPPED, PINO_LEVELS;
8010
8045
  var init_launch = __esm({
8011
8046
  "src/runner/launch.ts"() {
8012
8047
  "use strict";
@@ -8026,7 +8061,6 @@ var init_launch = __esm({
8026
8061
  "DBUS_SESSION_BUS_ADDRESS",
8027
8062
  "XDG_RUNTIME_DIR"
8028
8063
  ];
8029
- BOXED_ENV_DROPPED_PREFIX = "CABANE_GATE_";
8030
8064
  PINO_LEVELS = {
8031
8065
  10: "trace",
8032
8066
  20: "debug",
@@ -9547,7 +9581,7 @@ var init_turn_execution = __esm({
9547
9581
  async prepareEnvironment() {
9548
9582
  const { payload, workspaceId, turnId, turnLog } = this;
9549
9583
  const prepareHook = this.opts.local.prepareHook;
9550
- const pairDir = pairDirectory(this.opts.workRoot, {
9584
+ const pairDir = this.opts.boxed ? this.opts.workRoot : pairDirectory(this.opts.workRoot, {
9551
9585
  workspaceSlug: this.opts.workspaceSlug,
9552
9586
  agentUsername: this.opts.agentUsername,
9553
9587
  conversationId: payload.conversationId
@@ -10773,8 +10807,10 @@ function runnerTurnOptions(input, log2, emit, extras = {}) {
10773
10807
  local: spec.local,
10774
10808
  // The place the runner stands in names its own root and transcript
10775
10809
  // directory when it has them (`PLACE_ENV`); the launcher's host-computed
10776
- // values are what a runner on the person's own machine gets.
10810
+ // values are what a runner on the person's own machine gets. A box is one
10811
+ // conversation × agent's, so the turn stands in its root itself.
10777
10812
  workRoot: input.place?.workRoot ?? spec.workRoot,
10813
+ ...input.place?.box ? { boxed: true } : {},
10778
10814
  runConfig: spec.runConfig,
10779
10815
  // The launcher's copy of the device's secrets, when it sent one: a runner
10780
10816
  // in a box has no `secrets.json` to read.
package/dist/runtime.js CHANGED
@@ -5898,8 +5898,6 @@ var CABANE_MCP_SERVER3 = "cabane";
5898
5898
  var TURN_CONTROL_MCP_SERVER2 = "cabane_companion";
5899
5899
  var ACTIVE_CONVERSATION_HEADER3 = "x-cabane-active-conversation";
5900
5900
  var ENV_ENVELOPE_KEYS = [
5901
- "CABANE_PLAYGROUND",
5902
- "CABANE_PLAYGROUND_BIN",
5903
5901
  "CABANE_CONVERSATION_ID",
5904
5902
  "CABANE_AGENT_ID",
5905
5903
  "CABANE_CONVERSATION_TITLE"
@@ -6158,6 +6156,10 @@ function createSdkCodexTransport(opts = {}) {
6158
6156
  }
6159
6157
 
6160
6158
  // packages/agent-runtime/src/codex/index.ts
6159
+ function isMissingRollout(err) {
6160
+ const message = err instanceof Error ? err.message : String(err);
6161
+ return /no rollout found for thread id/i.test(message);
6162
+ }
6161
6163
  function createCodexAdapter(deps = {}) {
6162
6164
  const transport = deps.transport ?? (deps.enabled ? createSdkCodexTransport({ ...deps.apiKey ? { apiKey: deps.apiKey } : {} }) : null);
6163
6165
  return {
@@ -6176,8 +6178,8 @@ function createCodexAdapter(deps = {}) {
6176
6178
  }
6177
6179
  const directory = req.local.cwd ?? "";
6178
6180
  const decision = decideResume3(req.session, directory);
6179
- const resumeThreadId = "resume" in decision ? decision.resume : null;
6180
- const threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
6181
+ let resumeThreadId = "resume" in decision ? decision.resume : null;
6182
+ let threadUsageBaseline = "resume" in decision ? decision.usageBaseline : null;
6181
6183
  if ("fresh" in decision && decision.reason === "cwd_mismatch") {
6182
6184
  deps.onWarn?.(
6183
6185
  "codex adapter: stored session directory no longer matches the current environment \u2014 starting a fresh thread (SJ527-analogue guard)",
@@ -6242,38 +6244,69 @@ function createCodexAdapter(deps = {}) {
6242
6244
  }
6243
6245
  if (signal.aborted) return;
6244
6246
  }
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
- });
6247
+ for (; ; ) {
6248
+ const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
6249
+ if (spec.model === null) {
6250
+ deps.onWarn?.("Codex is using its default model because no model was resolved", {
6251
+ model: req.config.model
6252
+ });
6253
+ }
6254
+ let emitted = false;
6255
+ try {
6256
+ const result = await transport.run(spec, signal);
6257
+ if (signal.aborted) return;
6258
+ for await (const event of decodeCodexStream(result.events, {
6259
+ signal,
6260
+ cwd: req.local.cwd,
6261
+ resumedThreadId: resumeThreadId,
6262
+ degraded,
6263
+ // Which segments the thread carries once this turn lands. A segment sent is that one —
6264
+ // the later copy is what the model reads. One withheld is what the thread had. Read off
6265
+ // the spec, so the record can't disagree with what was sent.
6266
+ promptFingerprint: spec.preamble.core ? spec.fingerprints.core : threadStanding?.core ?? null,
6267
+ conversationFingerprint: spec.preamble.conversation ? spec.fingerprints.conversation : threadStanding?.conversation ?? null,
6268
+ compactions,
6269
+ // What the thread held before this turn, for the write at `thread.started`.
6270
+ heldStanding: {
6271
+ promptFingerprint: threadStanding?.core ?? null,
6272
+ conversationFingerprint: threadStanding?.conversation ?? null,
6273
+ compactions: threadStanding ? compactions : null
6274
+ },
6275
+ storedState: req.session,
6276
+ usageBaseline: threadUsageBaseline,
6277
+ // Codex reports no model in-stream, so record what the thread was started with (null on the
6278
+ // default token). Same for the reasoning effort.
6279
+ resolvedModel: spec.model,
6280
+ resolvedReasoningEffort: spec.modelReasoningEffort ?? null
6281
+ })) {
6282
+ emitted = true;
6283
+ yield event;
6284
+ }
6285
+ return;
6286
+ } catch (err) {
6287
+ if (resumeThreadId === null || emitted || signal.aborted || !isMissingRollout(err)) {
6288
+ throw err;
6289
+ }
6290
+ deps.onWarn?.(
6291
+ "codex adapter: the stored thread has no rollout on this host \u2014 starting a fresh thread",
6292
+ { threadId: resumeThreadId }
6293
+ );
6294
+ resumeThreadId = null;
6295
+ threadUsageBaseline = null;
6296
+ threadStanding = null;
6297
+ compactions = 0;
6298
+ req = { ...req, session: null };
6299
+ degraded = true;
6300
+ if (hooks?.fresh) {
6301
+ const recomposed = await hooks.fresh("session_start_failed");
6302
+ if (signal.aborted) return;
6303
+ if (recomposed) {
6304
+ req = { ...req, ...recomposed, session: null };
6305
+ degraded = false;
6306
+ }
6307
+ }
6308
+ }
6250
6309
  }
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
6310
  } finally {
6278
6311
  if (images) {
6279
6312
  try {
@@ -7506,13 +7539,11 @@ var BOXED_ENV_DROPPED = [
7506
7539
  "DBUS_SESSION_BUS_ADDRESS",
7507
7540
  "XDG_RUNTIME_DIR"
7508
7541
  ];
7509
- var BOXED_ENV_DROPPED_PREFIX = "CABANE_GATE_";
7510
7542
  function runnerExecEnv(base, boxed) {
7511
7543
  const env = {};
7512
7544
  for (const [k, v] of Object.entries(base)) {
7513
7545
  if (v === void 0) continue;
7514
- if (boxed && (BOXED_ENV_DROPPED.includes(k) || k.startsWith(BOXED_ENV_DROPPED_PREFIX)))
7515
- continue;
7546
+ if (boxed && BOXED_ENV_DROPPED.includes(k)) continue;
7516
7547
  env[k] = v;
7517
7548
  }
7518
7549
  return env;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.189",
3
+ "version": "0.6.191",
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",