@cabane/companion 0.6.30 → 0.6.32

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
@@ -5514,9 +5514,13 @@ function sealHeld2(held, terminal) {
5514
5514
  import { z as z11 } from "zod";
5515
5515
  function codexToolPolicy(policy) {
5516
5516
  return policy.hostFs ? {
5517
- permissionProfile: "cabane-coding",
5517
+ sandboxMode: "danger-full-access",
5518
+ // Headless: nothing to approve, because nothing is denied.
5518
5519
  approvalPolicy: "never",
5519
- networkAccessEnabled: policy.web
5520
+ // Full access means the command network is open regardless of the grant —
5521
+ // recorded as `true` so the value never claims a restriction that isn't
5522
+ // enforced.
5523
+ networkAccessEnabled: true
5520
5524
  } : {
5521
5525
  sandboxMode: "read-only",
5522
5526
  // Headless: the sandbox is the boundary; never pause for a human.
@@ -5636,7 +5640,6 @@ function buildConfig(req, baseInstructionsFile) {
5636
5640
  default_tools_approval_mode: "approve"
5637
5641
  };
5638
5642
  }
5639
- const policy = codexToolPolicy(req.policy);
5640
5643
  const tmpDir = req.local.env?.TMPDIR;
5641
5644
  const shellEnv = {};
5642
5645
  if (tmpDir) shellEnv.TMPDIR = tmpDir;
@@ -5644,7 +5647,6 @@ function buildConfig(req, baseInstructionsFile) {
5644
5647
  const value = req.local.env?.[key];
5645
5648
  if (value !== void 0) shellEnv[key] = value;
5646
5649
  }
5647
- const gateLockDir = req.local.env?.CABANE_GATE_LOCK_DIR;
5648
5650
  return {
5649
5651
  mcp_servers,
5650
5652
  experimental_use_rmcp_client: true,
@@ -5652,28 +5654,7 @@ function buildConfig(req, baseInstructionsFile) {
5652
5654
  // becomes Codex's base instructions, and Codex's own environment preamble goes
5653
5655
  // with the harness prompt it belonged to.
5654
5656
  ...baseInstructionsFile ? { model_instructions_file: baseInstructionsFile, include_environment_context: false } : {},
5655
- ...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {},
5656
- ...policy.permissionProfile ? {
5657
- // CT733: named permission profiles are Codex's split-filesystem path.
5658
- // `:root = read` preserves coding-mode host reads; the one explicit
5659
- // workspace-root write grants the checkout, and the more-specific
5660
- // `.git` write reopens the metadata Codex protects by default. Neither
5661
- // rule grants an adjacent directory. Do not combine this with legacy `sandbox_mode` /
5662
- // `sandbox_workspace_write`, which would restore the `.git` carve-out.
5663
- approval_policy: policy.approvalPolicy,
5664
- default_permissions: policy.permissionProfile,
5665
- permissions: {
5666
- [policy.permissionProfile]: {
5667
- filesystem: {
5668
- ":root": "read",
5669
- ":workspace_roots": { ".": "write", ".git": "write" },
5670
- // The one absolute-path grant — see the CT612-fallout note above.
5671
- ...gateLockDir ? { [gateLockDir]: "write" } : {}
5672
- },
5673
- network: { enabled: policy.networkAccessEnabled, mode: "full" }
5674
- }
5675
- }
5676
- } : {}
5657
+ ...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {}
5677
5658
  };
5678
5659
  }
5679
5660
  function isStringRecord2(v) {
@@ -5687,11 +5668,10 @@ import {
5687
5668
  function buildSdkThreadOptions(spec) {
5688
5669
  return {
5689
5670
  ...spec.model ? { model: spec.model } : {},
5690
- ...spec.policy.sandboxMode ? { sandboxMode: spec.policy.sandboxMode } : {},
5671
+ sandboxMode: spec.policy.sandboxMode,
5691
5672
  workingDirectory: spec.directory,
5692
5673
  skipGitRepoCheck: spec.skipGitRepoCheck,
5693
- ...spec.policy.sandboxMode ? { approvalPolicy: spec.policy.approvalPolicy } : {},
5694
- ...spec.policy.sandboxMode === "workspace-write" ? { networkAccessEnabled: spec.policy.networkAccessEnabled } : {},
5674
+ approvalPolicy: spec.policy.approvalPolicy,
5695
5675
  ...spec.modelReasoningEffort ? { modelReasoningEffort: spec.modelReasoningEffort } : {}
5696
5676
  };
5697
5677
  }
@@ -6475,7 +6455,7 @@ var ConnectorHealthStore = class {
6475
6455
 
6476
6456
  // src/dispatcher.ts
6477
6457
  import { randomUUID } from "crypto";
6478
- import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync11, statSync } from "fs";
6458
+ import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync11, readdirSync as readdirSync2, statSync } from "fs";
6479
6459
  import { join as join15 } from "path";
6480
6460
 
6481
6461
  // src/summon.ts
@@ -7371,10 +7351,19 @@ var DEFAULT_PREPARING_ROW_DELAY_MS = 1500;
7371
7351
  var DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 6e4;
7372
7352
  var DEFAULT_AGENT_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
7373
7353
  function checkoutState(cwd) {
7374
- if (!cwd) return { ok: false, reason: "no checkout was resolved for this turn" };
7375
- if (!existsSync11(cwd)) return { ok: false, reason: `the checkout directory is gone (${cwd})` };
7354
+ if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
7355
+ if (!existsSync11(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
7356
+ let entries;
7357
+ try {
7358
+ entries = readdirSync2(cwd);
7359
+ } catch (error) {
7360
+ return { ok: false, reason: `${cwd} is unreadable (${error.message})` };
7361
+ }
7362
+ if (entries.length === 0) {
7363
+ return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
7364
+ }
7376
7365
  const gitPath = join15(cwd, ".git");
7377
- if (!existsSync11(gitPath)) return { ok: false, reason: `${cwd} holds no git metadata` };
7366
+ if (!existsSync11(gitPath)) return { ok: true, reason: "usable" };
7378
7367
  let stat;
7379
7368
  try {
7380
7369
  stat = statSync(gitPath);
@@ -7708,22 +7697,7 @@ ${reason}`,
7708
7697
  }
7709
7698
  }
7710
7699
  }
7711
- let turnEnv = hookEnv;
7712
- if (effectiveCwd && turnContext.runtime === "codex") {
7713
- const tmpDir = join15(effectiveCwd, "node_modules", ".cache", "cabane-tmp", turnId);
7714
- try {
7715
- mkdirSync11(tmpDir, { recursive: true });
7716
- turnEnv = { ...hookEnv, TMPDIR: tmpDir };
7717
- } catch (err) {
7718
- turnLog.warn(
7719
- { err: err instanceof Error ? err.message : String(err), tmpDir },
7720
- "dispatcher: failed to create per-turn TMPDIR \u2014 proceeding with the inherited temp dir"
7721
- );
7722
- }
7723
- }
7724
- if (turnContext.runtime === "codex" && process.env.CABANE_GATE_LOCK_DIR) {
7725
- turnEnv = { ...turnEnv, CABANE_GATE_LOCK_DIR: process.env.CABANE_GATE_LOCK_DIR };
7726
- }
7700
+ const turnEnv = hookEnv;
7727
7701
  const key = runKey(payload.conversationId, payload.agentId);
7728
7702
  const abortController = new AbortController();
7729
7703
  this.aborts.set(key, abortController);
@@ -8588,7 +8562,7 @@ async function enumerateOpencodeModels(serverUrl, fetchImpl = fetch) {
8588
8562
  import {
8589
8563
  existsSync as existsSync12,
8590
8564
  mkdirSync as mkdirSync12,
8591
- readdirSync as readdirSync2,
8565
+ readdirSync as readdirSync3,
8592
8566
  readFileSync as readFileSync9,
8593
8567
  renameSync as renameSync3,
8594
8568
  rmSync as rmSync7,
@@ -8645,7 +8619,7 @@ var Outbox = class {
8645
8619
  if (!existsSync12(dir2)) return [];
8646
8620
  let names;
8647
8621
  try {
8648
- names = readdirSync2(dir2);
8622
+ names = readdirSync3(dir2);
8649
8623
  } catch {
8650
8624
  return [];
8651
8625
  }
@@ -8680,7 +8654,7 @@ var Outbox = class {
8680
8654
  const dir2 = this.dir();
8681
8655
  if (!existsSync12(dir2)) return 0;
8682
8656
  try {
8683
- return readdirSync2(dir2).filter((n) => n.endsWith(".json")).length;
8657
+ return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
8684
8658
  } catch {
8685
8659
  return 0;
8686
8660
  }
@@ -10058,7 +10032,7 @@ companion: deploy drain requested (${graceMs}ms grace)\u2026
10058
10032
  }
10059
10033
 
10060
10034
  // src/commands/status.ts
10061
- import { readdirSync as readdirSync3 } from "fs";
10035
+ import { readdirSync as readdirSync4 } from "fs";
10062
10036
  async function status(deps = {}) {
10063
10037
  const readService = deps.service ?? serviceStatus;
10064
10038
  const cfg = loadConfig();
@@ -10129,7 +10103,7 @@ function transcriptsLine() {
10129
10103
  let count = 0;
10130
10104
  let newest;
10131
10105
  try {
10132
- const files = readdirSync3(dir2).filter((f) => f.endsWith(".jsonl")).sort();
10106
+ const files = readdirSync4(dir2).filter((f) => f.endsWith(".jsonl")).sort();
10133
10107
  count = files.length;
10134
10108
  newest = files[files.length - 1];
10135
10109
  } catch {
@@ -10229,7 +10203,7 @@ function isAlive(kill, pid) {
10229
10203
  }
10230
10204
 
10231
10205
  // src/commands/transcript.ts
10232
- import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync4 } from "fs";
10206
+ import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync5 } from "fs";
10233
10207
  import { isAbsolute, join as join18 } from "path";
10234
10208
  async function transcript(opts = {}) {
10235
10209
  const dir2 = transcriptsDir();
@@ -10355,7 +10329,7 @@ async function followTranscripts(dir2) {
10355
10329
  }
10356
10330
  function listFiles(dir2) {
10357
10331
  try {
10358
- return readdirSync4(dir2).filter((f) => f.endsWith(".jsonl")).sort().reverse();
10332
+ return readdirSync5(dir2).filter((f) => f.endsWith(".jsonl")).sort().reverse();
10359
10333
  } catch {
10360
10334
  return [];
10361
10335
  }
package/dist/runtime.js CHANGED
@@ -4626,9 +4626,13 @@ function sealHeld2(held, terminal) {
4626
4626
  import { z as z11 } from "zod";
4627
4627
  function codexToolPolicy(policy) {
4628
4628
  return policy.hostFs ? {
4629
- permissionProfile: "cabane-coding",
4629
+ sandboxMode: "danger-full-access",
4630
+ // Headless: nothing to approve, because nothing is denied.
4630
4631
  approvalPolicy: "never",
4631
- networkAccessEnabled: policy.web
4632
+ // Full access means the command network is open regardless of the grant —
4633
+ // recorded as `true` so the value never claims a restriction that isn't
4634
+ // enforced.
4635
+ networkAccessEnabled: true
4632
4636
  } : {
4633
4637
  sandboxMode: "read-only",
4634
4638
  // Headless: the sandbox is the boundary; never pause for a human.
@@ -4748,7 +4752,6 @@ function buildConfig(req, baseInstructionsFile) {
4748
4752
  default_tools_approval_mode: "approve"
4749
4753
  };
4750
4754
  }
4751
- const policy = codexToolPolicy(req.policy);
4752
4755
  const tmpDir = req.local.env?.TMPDIR;
4753
4756
  const shellEnv = {};
4754
4757
  if (tmpDir) shellEnv.TMPDIR = tmpDir;
@@ -4756,7 +4759,6 @@ function buildConfig(req, baseInstructionsFile) {
4756
4759
  const value = req.local.env?.[key];
4757
4760
  if (value !== void 0) shellEnv[key] = value;
4758
4761
  }
4759
- const gateLockDir = req.local.env?.CABANE_GATE_LOCK_DIR;
4760
4762
  return {
4761
4763
  mcp_servers,
4762
4764
  experimental_use_rmcp_client: true,
@@ -4764,28 +4766,7 @@ function buildConfig(req, baseInstructionsFile) {
4764
4766
  // becomes Codex's base instructions, and Codex's own environment preamble goes
4765
4767
  // with the harness prompt it belonged to.
4766
4768
  ...baseInstructionsFile ? { model_instructions_file: baseInstructionsFile, include_environment_context: false } : {},
4767
- ...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {},
4768
- ...policy.permissionProfile ? {
4769
- // CT733: named permission profiles are Codex's split-filesystem path.
4770
- // `:root = read` preserves coding-mode host reads; the one explicit
4771
- // workspace-root write grants the checkout, and the more-specific
4772
- // `.git` write reopens the metadata Codex protects by default. Neither
4773
- // rule grants an adjacent directory. Do not combine this with legacy `sandbox_mode` /
4774
- // `sandbox_workspace_write`, which would restore the `.git` carve-out.
4775
- approval_policy: policy.approvalPolicy,
4776
- default_permissions: policy.permissionProfile,
4777
- permissions: {
4778
- [policy.permissionProfile]: {
4779
- filesystem: {
4780
- ":root": "read",
4781
- ":workspace_roots": { ".": "write", ".git": "write" },
4782
- // The one absolute-path grant — see the CT612-fallout note above.
4783
- ...gateLockDir ? { [gateLockDir]: "write" } : {}
4784
- },
4785
- network: { enabled: policy.networkAccessEnabled, mode: "full" }
4786
- }
4787
- }
4788
- } : {}
4769
+ ...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {}
4789
4770
  };
4790
4771
  }
4791
4772
  function isStringRecord2(v) {
@@ -4799,11 +4780,10 @@ import {
4799
4780
  function buildSdkThreadOptions(spec) {
4800
4781
  return {
4801
4782
  ...spec.model ? { model: spec.model } : {},
4802
- ...spec.policy.sandboxMode ? { sandboxMode: spec.policy.sandboxMode } : {},
4783
+ sandboxMode: spec.policy.sandboxMode,
4803
4784
  workingDirectory: spec.directory,
4804
4785
  skipGitRepoCheck: spec.skipGitRepoCheck,
4805
- ...spec.policy.sandboxMode ? { approvalPolicy: spec.policy.approvalPolicy } : {},
4806
- ...spec.policy.sandboxMode === "workspace-write" ? { networkAccessEnabled: spec.policy.networkAccessEnabled } : {},
4786
+ approvalPolicy: spec.policy.approvalPolicy,
4807
4787
  ...spec.modelReasoningEffort ? { modelReasoningEffort: spec.modelReasoningEffort } : {}
4808
4788
  };
4809
4789
  }
@@ -5587,7 +5567,7 @@ var ConnectorHealthStore = class {
5587
5567
 
5588
5568
  // src/dispatcher.ts
5589
5569
  import { randomUUID } from "crypto";
5590
- import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, statSync } from "fs";
5570
+ import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync9, readdirSync as readdirSync2, statSync } from "fs";
5591
5571
  import { join as join13 } from "path";
5592
5572
 
5593
5573
  // src/summon.ts
@@ -6483,10 +6463,19 @@ var DEFAULT_PREPARING_ROW_DELAY_MS = 1500;
6483
6463
  var DEFAULT_AGENT_IDLE_TIMEOUT_MS = 10 * 6e4;
6484
6464
  var DEFAULT_AGENT_TOTAL_TIMEOUT_MS = 6 * 60 * 6e4;
6485
6465
  function checkoutState(cwd) {
6486
- if (!cwd) return { ok: false, reason: "no checkout was resolved for this turn" };
6487
- if (!existsSync9(cwd)) return { ok: false, reason: `the checkout directory is gone (${cwd})` };
6466
+ if (!cwd) return { ok: false, reason: "no working directory was resolved for this turn" };
6467
+ if (!existsSync9(cwd)) return { ok: false, reason: `the working directory is gone (${cwd})` };
6468
+ let entries;
6469
+ try {
6470
+ entries = readdirSync2(cwd);
6471
+ } catch (error) {
6472
+ return { ok: false, reason: `${cwd} is unreadable (${error.message})` };
6473
+ }
6474
+ if (entries.length === 0) {
6475
+ return { ok: false, reason: `${cwd} is empty \u2014 a recreated shell, not a prepared directory` };
6476
+ }
6488
6477
  const gitPath = join13(cwd, ".git");
6489
- if (!existsSync9(gitPath)) return { ok: false, reason: `${cwd} holds no git metadata` };
6478
+ if (!existsSync9(gitPath)) return { ok: true, reason: "usable" };
6490
6479
  let stat;
6491
6480
  try {
6492
6481
  stat = statSync(gitPath);
@@ -6820,22 +6809,7 @@ ${reason}`,
6820
6809
  }
6821
6810
  }
6822
6811
  }
6823
- let turnEnv = hookEnv;
6824
- if (effectiveCwd && turnContext.runtime === "codex") {
6825
- const tmpDir = join13(effectiveCwd, "node_modules", ".cache", "cabane-tmp", turnId);
6826
- try {
6827
- mkdirSync9(tmpDir, { recursive: true });
6828
- turnEnv = { ...hookEnv, TMPDIR: tmpDir };
6829
- } catch (err) {
6830
- turnLog.warn(
6831
- { err: err instanceof Error ? err.message : String(err), tmpDir },
6832
- "dispatcher: failed to create per-turn TMPDIR \u2014 proceeding with the inherited temp dir"
6833
- );
6834
- }
6835
- }
6836
- if (turnContext.runtime === "codex" && process.env.CABANE_GATE_LOCK_DIR) {
6837
- turnEnv = { ...turnEnv, CABANE_GATE_LOCK_DIR: process.env.CABANE_GATE_LOCK_DIR };
6838
- }
6812
+ const turnEnv = hookEnv;
6839
6813
  const key = runKey(payload.conversationId, payload.agentId);
6840
6814
  const abortController = new AbortController();
6841
6815
  this.aborts.set(key, abortController);
@@ -7700,7 +7674,7 @@ async function enumerateOpencodeModels(serverUrl, fetchImpl = fetch) {
7700
7674
  import {
7701
7675
  existsSync as existsSync10,
7702
7676
  mkdirSync as mkdirSync10,
7703
- readdirSync as readdirSync2,
7677
+ readdirSync as readdirSync3,
7704
7678
  readFileSync as readFileSync8,
7705
7679
  renameSync as renameSync3,
7706
7680
  rmSync as rmSync6,
@@ -7757,7 +7731,7 @@ var Outbox = class {
7757
7731
  if (!existsSync10(dir2)) return [];
7758
7732
  let names;
7759
7733
  try {
7760
- names = readdirSync2(dir2);
7734
+ names = readdirSync3(dir2);
7761
7735
  } catch {
7762
7736
  return [];
7763
7737
  }
@@ -7792,7 +7766,7 @@ var Outbox = class {
7792
7766
  const dir2 = this.dir();
7793
7767
  if (!existsSync10(dir2)) return 0;
7794
7768
  try {
7795
- return readdirSync2(dir2).filter((n) => n.endsWith(".json")).length;
7769
+ return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
7796
7770
  } catch {
7797
7771
  return 0;
7798
7772
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.30",
3
+ "version": "0.6.32",
4
4
  "type": "module",
5
5
  "description": "The Cabane Companion (headless): connect a coding 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",