@cabane/companion 0.6.191 → 0.6.193

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.
Files changed (3) hide show
  1. package/dist/cli.js +303 -179
  2. package/dist/runtime.js +166 -90
  3. package/package.json +3 -3
package/dist/runtime.js CHANGED
@@ -7296,8 +7296,8 @@ var ConnectorHealthStore = class {
7296
7296
 
7297
7297
  // src/dispatcher.ts
7298
7298
  import { randomUUID as randomUUID4 } from "crypto";
7299
- import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
7300
- import { join as join20 } from "path";
7299
+ import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
7300
+ import { join as join21 } from "path";
7301
7301
 
7302
7302
  // src/runner/launch.ts
7303
7303
  import { createInterface } from "readline";
@@ -7487,7 +7487,8 @@ var dispatchSchema = z14.object({
7487
7487
  });
7488
7488
  var credentialsSchema = z14.object({
7489
7489
  credential: z14.string().startsWith("cabrn_"),
7490
- secrets: z14.record(z14.string(), z14.string()).optional()
7490
+ secrets: z14.record(z14.string(), z14.string()).optional(),
7491
+ grants: z14.record(z14.string(), z14.record(z14.string(), z14.string())).optional()
7491
7492
  });
7492
7493
  function runnerEnv(input) {
7493
7494
  return {
@@ -7501,7 +7502,8 @@ function runnerEnv(input) {
7501
7502
  function runnerStdin(input) {
7502
7503
  return `${JSON.stringify({
7503
7504
  credential: input.credential,
7504
- ...input.secrets ? { secrets: input.secrets } : {}
7505
+ ...input.secrets ? { secrets: input.secrets } : {},
7506
+ ...input.grants ? { grants: input.grants } : {}
7505
7507
  })}
7506
7508
  `;
7507
7509
  }
@@ -7659,7 +7661,12 @@ async function mintForRunner(opts, payload, handleOpts, signal, log) {
7659
7661
  // The one turn the credential will operate: this run's.
7660
7662
  turnId: handleOpts.turnId
7661
7663
  });
7662
- return { ok: true, credential: minted.credential, runnerId: minted.runnerId };
7664
+ return {
7665
+ ok: true,
7666
+ credential: minted.credential,
7667
+ runnerId: minted.runnerId,
7668
+ deviceGrants: minted.deviceGrants ?? []
7669
+ };
7663
7670
  } catch (err) {
7664
7671
  if (signal.aborted) return { ok: false, reason: "cancelled" };
7665
7672
  const status = err instanceof ApiError ? err.status : 0;
@@ -7754,10 +7761,12 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
7754
7761
  try {
7755
7762
  const boxed = launch.boxed === true;
7756
7763
  const env = runnerExecEnv(launch.env ?? process.env, boxed);
7764
+ const grants = minted.deviceGrants.length > 0 ? launch.grants?.(minted.deviceGrants) ?? {} : {};
7757
7765
  const input = {
7758
7766
  baseUrl: opts.baseUrl,
7759
7767
  credential: minted.credential,
7760
7768
  ...boxed ? { secrets: launch.secrets?.() ?? {} } : {},
7769
+ ...Object.keys(grants).length > 0 ? { grants } : {},
7761
7770
  dispatch: payload,
7762
7771
  turnId,
7763
7772
  resumed: handleOpts.resumed,
@@ -7894,13 +7903,72 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
7894
7903
  import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
7895
7904
  import { mkdirSync as mkdirSync9 } from "fs";
7896
7905
 
7906
+ // src/grants.ts
7907
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
7908
+ import { join as join13 } from "path";
7909
+ import { z as z15 } from "zod";
7910
+ var VARIABLE_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
7911
+ function allowedGrantVariable(name) {
7912
+ return name.length <= 100 && VARIABLE_NAME_RE.test(name) && !name.startsWith("CABANE_");
7913
+ }
7914
+ function environmentSafe(value) {
7915
+ return !value.includes("\0");
7916
+ }
7917
+ function grantsPath() {
7918
+ return join13(cabaneDir(), "grants.json");
7919
+ }
7920
+ var grantsFileSchema = z15.record(z15.string(), z15.record(z15.string(), z15.string()));
7921
+ function readGrants(keys, report) {
7922
+ if (keys.length === 0) return {};
7923
+ const file = loadGrantsFile(report);
7924
+ const out = {};
7925
+ for (const key of keys) {
7926
+ if (!allowedGrantVariable(key) || !Object.hasOwn(file, key)) continue;
7927
+ const entry = {};
7928
+ for (const [variable, value] of Object.entries(file[key])) {
7929
+ if (allowedGrantVariable(variable) && environmentSafe(value)) entry[variable] = value;
7930
+ else report.refusedVariable(key, variable);
7931
+ }
7932
+ out[key] = entry;
7933
+ }
7934
+ return out;
7935
+ }
7936
+ function loadGrantsFile(report) {
7937
+ const path = grantsPath();
7938
+ if (!existsSync9(path)) return {};
7939
+ let raw;
7940
+ try {
7941
+ raw = readFileSync7(path, "utf8");
7942
+ } catch (err) {
7943
+ const code = err.code ?? "unknown error";
7944
+ report.unreadable(`couldn't read ${path}: ${code}`);
7945
+ return {};
7946
+ }
7947
+ if (raw.trim().length === 0) return {};
7948
+ let parsed;
7949
+ try {
7950
+ parsed = JSON.parse(raw);
7951
+ } catch {
7952
+ report.unreadable(`${path} is not valid JSON.`);
7953
+ return {};
7954
+ }
7955
+ const result = grantsFileSchema.safeParse(parsed);
7956
+ if (!result.success) {
7957
+ report.unreadable(
7958
+ `${path} must map a secret's variable name to an object of "VARIABLE": "value" pairs (e.g. { "HAS_PROD_ACCESS": { "RENDER_API_KEY": "rnd_\u2026" } }).`
7959
+ );
7960
+ return {};
7961
+ }
7962
+ return result.data;
7963
+ }
7964
+
7897
7965
  // src/work-dir.ts
7898
7966
  import { statSync as statSync3 } from "fs";
7899
- import { join as join13 } from "path";
7967
+ import { join as join14 } from "path";
7900
7968
 
7901
7969
  // src/turn-control-tools.ts
7902
7970
  import { randomUUID as randomUUID2 } from "crypto";
7903
- import { z as z15 } from "zod";
7971
+ import { z as z16 } from "zod";
7904
7972
  var COMPANION_LOCAL_MCP_SERVER = "cabane_companion";
7905
7973
  var COMPANION_LOCAL_TOOL_GLOB = `mcp__${COMPANION_LOCAL_MCP_SERVER}__*`;
7906
7974
  var ASK_TOOL = "ask";
@@ -7915,19 +7983,19 @@ var CANCEL_WAKE_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${CANCEL_WAKE_T
7915
7983
  // src/codex-config.ts
7916
7984
  import { readFile as readFile2 } from "fs/promises";
7917
7985
  import { homedir as homedir6 } from "os";
7918
- import { join as join14 } from "path";
7986
+ import { join as join15 } from "path";
7919
7987
  import { parse } from "smol-toml";
7920
7988
 
7921
7989
  // src/codex-images.ts
7922
7990
  import { mkdtemp, rm, writeFile } from "fs/promises";
7923
7991
  import { tmpdir } from "os";
7924
- import { join as join15 } from "path";
7992
+ import { join as join16 } from "path";
7925
7993
 
7926
7994
  // src/codex-compactions.ts
7927
7995
  import { createReadStream } from "fs";
7928
7996
  import { readdir } from "fs/promises";
7929
7997
  import { homedir as homedir7 } from "os";
7930
- import { join as join16 } from "path";
7998
+ import { join as join17 } from "path";
7931
7999
  import { createInterface as createInterface2 } from "readline";
7932
8000
 
7933
8001
  // src/prepare-failure.ts
@@ -7935,24 +8003,24 @@ import { spawn as spawn3 } from "child_process";
7935
8003
  import { hostname } from "os";
7936
8004
 
7937
8005
  // src/prepared.ts
7938
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "fs";
7939
- import { join as join17 } from "path";
8006
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs";
8007
+ import { join as join18 } from "path";
7940
8008
 
7941
8009
  // src/secrets.ts
7942
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
7943
- import { join as join18 } from "path";
7944
- import { z as z16 } from "zod";
8010
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
8011
+ import { join as join19 } from "path";
8012
+ import { z as z17 } from "zod";
7945
8013
  var PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
7946
8014
  function secretsPath() {
7947
- return join18(cabaneDir(), "secrets.json");
8015
+ return join19(cabaneDir(), "secrets.json");
7948
8016
  }
7949
- var secretStoreSchema = z16.record(z16.string(), z16.string());
8017
+ var secretStoreSchema = z17.record(z17.string(), z17.string());
7950
8018
  function loadSecretValues() {
7951
8019
  const path = secretsPath();
7952
- if (!existsSync10(path)) return {};
8020
+ if (!existsSync11(path)) return {};
7953
8021
  let raw;
7954
8022
  try {
7955
- raw = readFileSync8(path, "utf8");
8023
+ raw = readFileSync9(path, "utf8");
7956
8024
  } catch (err) {
7957
8025
  throw new ConfigError(
7958
8026
  `couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
@@ -8134,9 +8202,9 @@ function runBounded(path, args, env) {
8134
8202
 
8135
8203
  // src/transcript-writer.ts
8136
8204
  import { appendFileSync, chmodSync as chmodSync4, copyFileSync, mkdirSync as mkdirSync8, readdirSync, rmSync as rmSync6 } from "fs";
8137
- import { basename as basename3, dirname as dirname7, join as join19 } from "path";
8205
+ import { basename as basename3, dirname as dirname7, join as join20 } from "path";
8138
8206
  function transcriptsDir() {
8139
- return join19(cabaneDir(), "transcripts");
8207
+ return join20(cabaneDir(), "transcripts");
8140
8208
  }
8141
8209
 
8142
8210
  // src/turn-execution.ts
@@ -8239,7 +8307,7 @@ var Dispatcher = class {
8239
8307
  };
8240
8308
 
8241
8309
  // src/host-report.ts
8242
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
8310
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
8243
8311
  import { userInfo as userInfo2 } from "os";
8244
8312
  function inContainer(signals) {
8245
8313
  if (signals.dockerenv) return true;
@@ -8250,11 +8318,11 @@ function inContainer(signals) {
8250
8318
  function readContainerSignals() {
8251
8319
  let cgroup = null;
8252
8320
  try {
8253
- cgroup = readFileSync9("/proc/1/cgroup", "utf8");
8321
+ cgroup = readFileSync10("/proc/1/cgroup", "utf8");
8254
8322
  } catch {
8255
8323
  cgroup = null;
8256
8324
  }
8257
- return { dockerenv: existsSync12("/.dockerenv"), cgroup, env: process.env };
8325
+ return { dockerenv: existsSync13("/.dockerenv"), cgroup, env: process.env };
8258
8326
  }
8259
8327
  function osUser() {
8260
8328
  try {
@@ -8491,7 +8559,7 @@ function checkReason(check) {
8491
8559
 
8492
8560
  // src/harness-copy.ts
8493
8561
  import { homedir as homedir8 } from "os";
8494
- import { join as join21 } from "path";
8562
+ import { join as join22 } from "path";
8495
8563
  var HARNESS_LABELS = {
8496
8564
  "claude-code": "Claude Code",
8497
8565
  codex: "Codex",
@@ -8505,7 +8573,7 @@ function failureText(runtime, reason) {
8505
8573
  case "spawn":
8506
8574
  return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
8507
8575
  case "config":
8508
- return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join21(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
8576
+ return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join22(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
8509
8577
  case "unreadable":
8510
8578
  return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
8511
8579
  }
@@ -8762,15 +8830,15 @@ var DispatchClaims = class {
8762
8830
 
8763
8831
  // src/outbox.ts
8764
8832
  import {
8765
- existsSync as existsSync13,
8833
+ existsSync as existsSync14,
8766
8834
  mkdirSync as mkdirSync10,
8767
8835
  readdirSync as readdirSync3,
8768
- readFileSync as readFileSync10,
8836
+ readFileSync as readFileSync11,
8769
8837
  renameSync as renameSync5,
8770
8838
  rmSync as rmSync7,
8771
8839
  writeFileSync as writeFileSync6
8772
8840
  } from "fs";
8773
- import { join as join22 } from "path";
8841
+ import { join as join23 } from "path";
8774
8842
  var MAX_ENTRIES = 2e3;
8775
8843
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
8776
8844
  var Outbox = class {
@@ -8783,10 +8851,10 @@ var Outbox = class {
8783
8851
  // Resolved lazily (per call) so tests that swap HOME between
8784
8852
  // cases route writes at the right tmpdir.
8785
8853
  dir() {
8786
- return join22(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
8854
+ return join23(cabaneDir(), "outbox", encodeURIComponent(this.workspaceId));
8787
8855
  }
8788
8856
  fileFor(turnId, seq) {
8789
- return join22(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
8857
+ return join23(this.dir(), `${encodeURIComponent(turnId)}__${seq}.json`);
8790
8858
  }
8791
8859
  // Persist a commit for later draining. Atomic (temp file + rename) so a
8792
8860
  // concurrent `list()` never reads a half-written entry, then enforces the
@@ -8818,7 +8886,7 @@ var Outbox = class {
8818
8886
  // wedging the drain.
8819
8887
  list() {
8820
8888
  const dir2 = this.dir();
8821
- if (!existsSync13(dir2)) return [];
8889
+ if (!existsSync14(dir2)) return [];
8822
8890
  let names;
8823
8891
  try {
8824
8892
  names = readdirSync3(dir2);
@@ -8828,9 +8896,9 @@ var Outbox = class {
8828
8896
  const entries = [];
8829
8897
  for (const name of names) {
8830
8898
  if (!name.endsWith(".json")) continue;
8831
- const full = join22(dir2, name);
8899
+ const full = join23(dir2, name);
8832
8900
  try {
8833
- const parsed = JSON.parse(readFileSync10(full, "utf8"));
8901
+ const parsed = JSON.parse(readFileSync11(full, "utf8"));
8834
8902
  if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
8835
8903
  entries.push(parsed);
8836
8904
  } else {
@@ -8864,7 +8932,7 @@ var Outbox = class {
8864
8932
  // different: it is positive evidence that nothing was ever queued here.
8865
8933
  maxSeqForTurn(turnId) {
8866
8934
  const dir2 = this.dir();
8867
- if (!existsSync13(dir2)) return 0;
8935
+ if (!existsSync14(dir2)) return 0;
8868
8936
  const names = readdirSync3(dir2);
8869
8937
  const prefix = `${encodeURIComponent(turnId)}__`;
8870
8938
  let max = 0;
@@ -8884,7 +8952,7 @@ var Outbox = class {
8884
8952
  }
8885
8953
  size() {
8886
8954
  const dir2 = this.dir();
8887
- if (!existsSync13(dir2)) return 0;
8955
+ if (!existsSync14(dir2)) return 0;
8888
8956
  try {
8889
8957
  return readdirSync3(dir2).filter((n) => n.endsWith(".json")).length;
8890
8958
  } catch {
@@ -9051,20 +9119,20 @@ var SseSubscriber = class {
9051
9119
 
9052
9120
  // src/updater.ts
9053
9121
  import { spawn as spawn6 } from "child_process";
9054
- import { copyFileSync as copyFileSync2, existsSync as existsSync15, mkdirSync as mkdirSync14, renameSync as renameSync9, rmSync as rmSync10 } from "fs";
9055
- import { dirname as dirname11, join as join26 } from "path";
9122
+ import { copyFileSync as copyFileSync2, existsSync as existsSync16, mkdirSync as mkdirSync14, renameSync as renameSync9, rmSync as rmSync10 } from "fs";
9123
+ import { dirname as dirname11, join as join27 } from "path";
9056
9124
 
9057
9125
  // src/boot-rule.ts
9058
9126
  import {
9059
9127
  mkdirSync as mkdirSync11,
9060
- readFileSync as readFileSync11,
9128
+ readFileSync as readFileSync12,
9061
9129
  realpathSync as realpathSync4,
9062
9130
  renameSync as renameSync6,
9063
9131
  rmSync as rmSync8,
9064
9132
  symlinkSync,
9065
9133
  writeFileSync as writeFileSync7
9066
9134
  } from "fs";
9067
- import { dirname as dirname8, join as join23 } from "path";
9135
+ import { dirname as dirname8, join as join24 } from "path";
9068
9136
  var UNCONFIRMED_WINDOW_MS = 10 * 6e4;
9069
9137
  var MAX_STAGE_ATTEMPTS = 3;
9070
9138
  function superseded(pending, ...running) {
@@ -9087,36 +9155,36 @@ function rolledBackState(state, pending, reason, now) {
9087
9155
 
9088
9156
  // src/install-layout.ts
9089
9157
  import {
9090
- existsSync as existsSync14,
9158
+ existsSync as existsSync15,
9091
9159
  lstatSync,
9092
9160
  mkdirSync as mkdirSync12,
9093
- readFileSync as readFileSync12,
9161
+ readFileSync as readFileSync13,
9094
9162
  readdirSync as readdirSync4,
9095
9163
  realpathSync as realpathSync5,
9096
9164
  renameSync as renameSync7,
9097
9165
  rmSync as rmSync9,
9098
9166
  symlinkSync as symlinkSync2
9099
9167
  } from "fs";
9100
- import { dirname as dirname9, join as join24 } from "path";
9168
+ import { dirname as dirname9, join as join25 } from "path";
9101
9169
  import { fileURLToPath as fileURLToPath5 } from "url";
9102
9170
  function versionsDir() {
9103
- return join24(cabaneDir(), "versions");
9171
+ return join25(cabaneDir(), "versions");
9104
9172
  }
9105
9173
  function currentLink() {
9106
- return join24(cabaneDir(), "current");
9174
+ return join25(cabaneDir(), "current");
9107
9175
  }
9108
9176
  function bootGuardPath() {
9109
- return join24(cabaneDir(), "boot-guard.mjs");
9177
+ return join25(cabaneDir(), "boot-guard.mjs");
9110
9178
  }
9111
9179
  function versionPackageRoot(version) {
9112
- return join24(versionsDir(), version, "node_modules", "@cabane", "companion");
9180
+ return join25(versionsDir(), version, "node_modules", "@cabane", "companion");
9113
9181
  }
9114
9182
  function ownPackageRoot(moduleUrl = import.meta.url) {
9115
9183
  return dirname9(dirname9(fileURLToPath5(moduleUrl)));
9116
9184
  }
9117
9185
  function packageVersion(root) {
9118
9186
  try {
9119
- const pkg2 = JSON.parse(readFileSync12(join24(root, "package.json"), "utf8"));
9187
+ const pkg2 = JSON.parse(readFileSync13(join25(root, "package.json"), "utf8"));
9120
9188
  return typeof pkg2.version === "string" ? pkg2.version : null;
9121
9189
  } catch {
9122
9190
  return null;
@@ -9125,7 +9193,7 @@ function packageVersion(root) {
9125
9193
  function currentRoot() {
9126
9194
  try {
9127
9195
  const root = realpathSync5(currentLink());
9128
- return existsSync14(join24(root, "dist", "cli.js")) ? root : null;
9196
+ return existsSync15(join25(root, "dist", "cli.js")) ? root : null;
9129
9197
  } catch {
9130
9198
  return null;
9131
9199
  }
@@ -9144,11 +9212,11 @@ function pointCurrentAt(root) {
9144
9212
  }
9145
9213
  function pruneVersions(keep) {
9146
9214
  const dir2 = versionsDir();
9147
- if (!existsSync14(dir2)) return [];
9215
+ if (!existsSync15(dir2)) return [];
9148
9216
  const removed = [];
9149
9217
  for (const name of readdirSync4(dir2)) {
9150
9218
  if (keep.includes(name)) continue;
9151
- rmSync9(join24(dir2, name), { recursive: true, force: true });
9219
+ rmSync9(join25(dir2, name), { recursive: true, force: true });
9152
9220
  removed.push(name);
9153
9221
  }
9154
9222
  return removed;
@@ -9166,36 +9234,36 @@ function compareVersions(a, b) {
9166
9234
  }
9167
9235
 
9168
9236
  // src/update-state.ts
9169
- import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, renameSync as renameSync8, writeFileSync as writeFileSync8 } from "fs";
9170
- import { dirname as dirname10, join as join25 } from "path";
9171
- import { z as z17 } from "zod";
9172
- var resultSchema = z17.object({
9173
- kind: z17.enum(["updated", "rolled_back", "failed"]),
9174
- from: z17.string(),
9175
- to: z17.string(),
9176
- at: z17.string(),
9177
- reason: z17.string().optional()
9237
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, renameSync as renameSync8, writeFileSync as writeFileSync8 } from "fs";
9238
+ import { dirname as dirname10, join as join26 } from "path";
9239
+ import { z as z18 } from "zod";
9240
+ var resultSchema = z18.object({
9241
+ kind: z18.enum(["updated", "rolled_back", "failed"]),
9242
+ from: z18.string(),
9243
+ to: z18.string(),
9244
+ at: z18.string(),
9245
+ reason: z18.string().optional()
9178
9246
  });
9179
- var pendingSchema = z17.object({
9180
- from: z17.string(),
9247
+ var pendingSchema = z18.object({
9248
+ from: z18.string(),
9181
9249
  // The package root `current` pointed at before the swap, so a rollback can point it back.
9182
- fromRoot: z17.string(),
9183
- to: z17.string(),
9184
- at: z17.string(),
9185
- boots: z17.number().int().nonnegative()
9250
+ fromRoot: z18.string(),
9251
+ to: z18.string(),
9252
+ at: z18.string(),
9253
+ boots: z18.number().int().nonnegative()
9186
9254
  });
9187
- var stateSchema = z17.object({
9188
- server: z17.object({ latest: z17.string(), minimum: z17.string(), at: z17.string() }).optional(),
9255
+ var stateSchema = z18.object({
9256
+ server: z18.object({ latest: z18.string(), minimum: z18.string(), at: z18.string() }).optional(),
9189
9257
  pending: pendingSchema.optional(),
9190
9258
  lastResult: resultSchema.optional(),
9191
- failed: z17.object({ version: z17.string(), attempts: z17.number().int(), at: z17.string() }).optional()
9259
+ failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
9192
9260
  });
9193
9261
  function updateStatePath(dir2 = cabaneDir()) {
9194
- return join25(dir2, "update.json");
9262
+ return join26(dir2, "update.json");
9195
9263
  }
9196
9264
  function readUpdateState(path = updateStatePath()) {
9197
9265
  try {
9198
- const parsed = stateSchema.safeParse(JSON.parse(readFileSync13(path, "utf8")));
9266
+ const parsed = stateSchema.safeParse(JSON.parse(readFileSync14(path, "utf8")));
9199
9267
  return parsed.success ? parsed.data : {};
9200
9268
  } catch {
9201
9269
  return {};
@@ -9236,12 +9304,12 @@ function versionToStage(input) {
9236
9304
  return latest;
9237
9305
  }
9238
9306
  function rollBack(pending, reason, now = /* @__PURE__ */ new Date()) {
9239
- if (existsSync15(pending.fromRoot)) pointCurrentAt(pending.fromRoot);
9307
+ if (existsSync16(pending.fromRoot)) pointCurrentAt(pending.fromRoot);
9240
9308
  return updateUpdateState((state) => rolledBackState(state, pending, reason, now));
9241
9309
  }
9242
9310
  function writeBootGuard(root = ownPackageRoot()) {
9243
- const built = join26(root, "dist", "boot-guard.js");
9244
- if (!existsSync15(built)) return false;
9311
+ const built = join27(root, "dist", "boot-guard.js");
9312
+ if (!existsSync16(built)) return false;
9245
9313
  const target = bootGuardPath();
9246
9314
  mkdirSync14(dirname11(target), { recursive: true });
9247
9315
  const tmp = `${target}.${process.pid}.tmp`;
@@ -9280,17 +9348,17 @@ var runProcess = (command, args, opts = {}) => new Promise((resolve) => {
9280
9348
  async function stageVersion(version, harnesses, deps = {}) {
9281
9349
  const run = deps.run ?? runProcess;
9282
9350
  const node = deps.execPath ?? process.execPath;
9283
- const target = join26(versionsDir(), version);
9351
+ const target = join27(versionsDir(), version);
9284
9352
  const placed = versionPackageRoot(version);
9285
- if (existsSync15(placed)) {
9353
+ if (existsSync16(placed)) {
9286
9354
  if (!await verifyInstall(placed, version, harnesses, { run, execPath: node }))
9287
9355
  return { ok: true, root: placed };
9288
9356
  }
9289
- const npm = join26(dirname11(node), "npm");
9290
- if (!existsSync15(npm))
9357
+ const npm = join27(dirname11(node), "npm");
9358
+ if (!existsSync16(npm))
9291
9359
  return { ok: false, reason: `there is no npm beside Node at ${dirname11(node)}` };
9292
9360
  mkdirSync14(versionsDir(), { recursive: true });
9293
- const staging = join26(versionsDir(), `.${version}.staging-${process.pid}`);
9361
+ const staging = join27(versionsDir(), `.${version}.staging-${process.pid}`);
9294
9362
  rmSync10(staging, { recursive: true, force: true });
9295
9363
  const installed = await run(
9296
9364
  npm,
@@ -9319,7 +9387,7 @@ async function stageVersion(version, harnesses, deps = {}) {
9319
9387
  return { ok: false, reason: `version ${version} isn't published yet`, unpublished: true };
9320
9388
  return { ok: false, reason: `npm couldn't install it: ${lastLine(installed.stderr)}` };
9321
9389
  }
9322
- const root = join26(staging, "node_modules", "@cabane", "companion");
9390
+ const root = join27(staging, "node_modules", "@cabane", "companion");
9323
9391
  const verdict = await verifyInstall(root, version, harnesses, { run, execPath: node });
9324
9392
  if (verdict) {
9325
9393
  rmSync10(staging, { recursive: true, force: true });
@@ -9331,7 +9399,7 @@ async function stageVersion(version, harnesses, deps = {}) {
9331
9399
  }
9332
9400
  async function verifyInstall(root, version, harnesses, deps) {
9333
9401
  if (packageVersion(root) !== version) return `the installed package isn't version ${version}`;
9334
- const answered = await deps.run(deps.execPath, [join26(root, "dist", "bin.js"), "--version"], {
9402
+ const answered = await deps.run(deps.execPath, [join27(root, "dist", "bin.js"), "--version"], {
9335
9403
  timeoutMs: 6e4,
9336
9404
  env: { ...process.env, CABANE_LAUNCH_SELF: "1" }
9337
9405
  });
@@ -9339,7 +9407,7 @@ async function verifyInstall(root, version, harnesses, deps) {
9339
9407
  return `it doesn't start: ${lastLine(answered.stderr) || "no version printed"}`;
9340
9408
  }
9341
9409
  if (harnesses.length === 0) return null;
9342
- const probed = await deps.run(deps.execPath, [join26(root, "dist", "postinstall.js"), "--json"], {
9410
+ const probed = await deps.run(deps.execPath, [join27(root, "dist", "postinstall.js"), "--json"], {
9343
9411
  timeoutMs: 6e4
9344
9412
  });
9345
9413
  let resolutions = {};
@@ -10335,6 +10403,14 @@ var CompanionSupervisor = class {
10335
10403
  (m) => this.log.warn({ err: m }, "Could not read the secrets file for a boxed turn.")
10336
10404
  )
10337
10405
  } : {},
10406
+ // Read per dispatch, for the secret variables its mint named and no others.
10407
+ grants: (keys) => readGrants(keys, {
10408
+ refusedVariable: (key, variable) => this.log.warn(
10409
+ { key, variable },
10410
+ "Skipped a variable in ~/.cabane/grants.json: its name must use capital letters, digits and underscores, not starting with CABANE_, and its value cannot contain a NUL character."
10411
+ ),
10412
+ unreadable: (message) => this.log.warn({ err: message }, "Could not read the grants file.")
10413
+ }),
10338
10414
  ...this.runnerEnv ? { env: this.runnerEnv } : {},
10339
10415
  reportRunnerGone: (report) => this.reportRunnerGone(report),
10340
10416
  connectorHealth: this.connectorHealth,
@@ -11026,10 +11102,10 @@ function handleUncaught(log, err, origin) {
11026
11102
  }
11027
11103
 
11028
11104
  // src/crash-marker.ts
11029
- import { existsSync as existsSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync14, rmSync as rmSync11, writeFileSync as writeFileSync9 } from "fs";
11030
- import { join as join27 } from "path";
11105
+ import { existsSync as existsSync17, mkdirSync as mkdirSync15, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync9 } from "fs";
11106
+ import { join as join28 } from "path";
11031
11107
  function crashMarkerPath() {
11032
- return join27(cabaneDir(), "last-error.json");
11108
+ return join28(cabaneDir(), "last-error.json");
11033
11109
  }
11034
11110
  function recordCrash(rec) {
11035
11111
  try {
@@ -11041,15 +11117,15 @@ function recordCrash(rec) {
11041
11117
  function clearCrash() {
11042
11118
  try {
11043
11119
  const path = crashMarkerPath();
11044
- if (existsSync16(path)) rmSync11(path, { force: true });
11120
+ if (existsSync17(path)) rmSync11(path, { force: true });
11045
11121
  } catch {
11046
11122
  }
11047
11123
  }
11048
11124
 
11049
11125
  // src/stray-companion.ts
11050
11126
  import { spawnSync } from "child_process";
11051
- import { readFileSync as readFileSync15 } from "fs";
11052
- import { join as join28 } from "path";
11127
+ import { readFileSync as readFileSync16 } from "fs";
11128
+ import { join as join29 } from "path";
11053
11129
  var COMPANION_ENTRY = /@cabane\/companion\/|cabane-companion|(^|[\s/])cabane\s|\.cabane\/(versions|current|boot-guard)/;
11054
11130
  var START = /\sstart(\s|$)/;
11055
11131
  function companionStarts(psOutput) {
@@ -11066,7 +11142,7 @@ function companionStarts(psOutput) {
11066
11142
  }
11067
11143
  function stateDirOf(env) {
11068
11144
  const home = env.CABANE_COMPANION_HOME || env.HOME;
11069
- return home ? join28(home, ".cabane") : null;
11145
+ return home ? join29(home, ".cabane") : null;
11070
11146
  }
11071
11147
  function envFromPsE(output, command) {
11072
11148
  const line = output.replace(/\n$/, "");
@@ -11081,7 +11157,7 @@ function envFromPsE(output, command) {
11081
11157
  function envFromProc(pid) {
11082
11158
  try {
11083
11159
  const env = {};
11084
- for (const entry of readFileSync15(`/proc/${pid}/environ`, "utf8").split("\0")) {
11160
+ for (const entry of readFileSync16(`/proc/${pid}/environ`, "utf8").split("\0")) {
11085
11161
  const eq = entry.indexOf("=");
11086
11162
  if (eq > 0) env[entry.slice(0, eq)] = entry.slice(eq + 1);
11087
11163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabane/companion",
3
- "version": "0.6.191",
3
+ "version": "0.6.193",
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",
@@ -30,9 +30,9 @@
30
30
  "prepublishOnly": "pnpm build"
31
31
  },
32
32
  "dependencies": {
33
- "@anthropic-ai/claude-agent-sdk": "0.3.296",
33
+ "@anthropic-ai/claude-agent-sdk": "0.3.303",
34
34
  "@clack/prompts": "1.8.1",
35
- "@openai/codex-sdk": "0.162.1",
35
+ "@openai/codex-sdk": "0.163.0",
36
36
  "@hono/node-server": "1.19.14",
37
37
  "commander": "12.1.0",
38
38
  "eventsource-parser": "3.0.8",