@bridge4dev/runner 0.57.0 → 0.58.1

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/policy.js CHANGED
@@ -1173,6 +1173,46 @@ export function effectiveTrust(trustMode, mode) {
1173
1173
  // ask / plan — at most NORMAL, never AUTO.
1174
1174
  return trustMode === 'AUTO' ? 'NORMAL' : trustMode;
1175
1175
  }
1176
+ /**
1177
+ * The largest Node heap this command line asks for, in bytes — or null when it
1178
+ * asks for none.
1179
+ *
1180
+ * Both spellings systemd-era Node accepts (`--max-old-space-size=N` and
1181
+ * `--max_old_space_size N`), anywhere on the line: the flag arrives through
1182
+ * `NODE_OPTIONS=`, through `node --…`, and through a `pnpm` script that passes
1183
+ * it on, and it is the NUMBER that matters, not who wrote it.
1184
+ *
1185
+ * The unit is megabytes, which is V8's own unit for the flag — a plain number,
1186
+ * never a suffix.
1187
+ */
1188
+ /**
1189
+ * Commands that RUN node, as opposed to commands that merely mention it.
1190
+ *
1191
+ * The gate reads the heap flag out of a shell line, and a shell line is not one
1192
+ * command: `sed -i 's/--max-old-space-size=8192/…=3000/' package.json` contains
1193
+ * the flag and asks Node for nothing at all. Denying that was worse than a
1194
+ * nuisance — LOWERING the flag is exactly what the refusal tells the agent to
1195
+ * do, so the gate forbade its own remedy (#398 S7, B10).
1196
+ */
1197
+ const NODE_RUNNERS = /(^|[\s;&|(])(sudo\s+)?(env\s+)?([A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(node|nodejs|npm|npx|pnpm|yarn|bun|deno|tsx|ts-node|vitest|jest)([\s;&|)]|$)/;
1198
+ export function requestedHeapBytes(command) {
1199
+ let largest = null;
1200
+ for (const segment of commandSegments(command)) {
1201
+ // `NODE_OPTIONS=` carries the flag into whatever the segment starts, so it
1202
+ // counts on its own; otherwise the segment has to actually start a runtime.
1203
+ if (!/NODE_OPTIONS\s*=/.test(segment) && !NODE_RUNNERS.test(segment))
1204
+ continue;
1205
+ for (const match of segment.matchAll(/--?max[-_]old[-_]space[-_]size(?:\s*=\s*|\s+)(\d+)/gi)) {
1206
+ const mb = Number(match[1]);
1207
+ if (!Number.isSafeInteger(mb) || mb <= 0)
1208
+ continue;
1209
+ const bytes = mb * 1024 * 1024;
1210
+ if (largest === null || bytes > largest)
1211
+ largest = bytes;
1212
+ }
1213
+ }
1214
+ return largest;
1215
+ }
1176
1216
  export function evaluateToolUse(toolName, input, ctx) {
1177
1217
  // Read ONCE, here, and never `ctx.trustMode` again below: the branches that
1178
1218
  // follow are the whole of layer 1, and a single one still reading the raw
@@ -1227,6 +1267,34 @@ export function evaluateToolUse(toolName, input, ctx) {
1227
1267
  }
1228
1268
  if (gitAsk)
1229
1269
  return gitAsk;
1270
+ /**
1271
+ * A heap the cage cannot honour (#398 S5).
1272
+ *
1273
+ * Same position as everything above it and for the same reason: under AUTO
1274
+ * the Bash branch returns `allow` two lines down, and a rule placed below
1275
+ * that line is dead in the one mode most sessions run in. That mistake cost
1276
+ * a whole release once (session 13).
1277
+ *
1278
+ * From the incident this plan comes from: the agent raised its own heap to
1279
+ * 4 GB, then to 6 GB, against a wall of 4296 MB, and each time made things
1280
+ * worse — the ceiling is enforced OUTSIDE the process, so asking for more
1281
+ * only means being stopped sooner. The reason is written for the model to
1282
+ * read: it names the number and the way out.
1283
+ */
1284
+ if (ctx.sessionMemoryMaxBytes !== undefined && ctx.sessionMemoryMaxBytes > 0) {
1285
+ for (const variant of [command, dequote(command)]) {
1286
+ const asked = requestedHeapBytes(variant);
1287
+ if (asked !== null && asked > ctx.sessionMemoryMaxBytes) {
1288
+ const mb = (bytes) => Math.round(bytes / (1024 * 1024));
1289
+ return {
1290
+ decision: 'deny',
1291
+ reason: `this command asks Node for a ${mb(asked)} MB heap, and this session cannot exceed ` +
1292
+ `${mb(ctx.sessionMemoryMaxBytes)} MB — the ceiling is enforced outside the process, so a ` +
1293
+ 'bigger heap only means being stopped sooner. Lower --max-old-space-size, or split the work up',
1294
+ };
1295
+ }
1296
+ }
1297
+ }
1230
1298
  if (trust === 'STRICT')
1231
1299
  return { decision: 'ask', reason: 'strict mode' };
1232
1300
  if (trust === 'AUTO')
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import type { HostLoadFrame } from './host-load.js';
3
+ import type { SessionLimitsFrame } from './session-limits.js';
3
4
  export declare const SessionDescriptorSchema: z.ZodObject<{
4
5
  id: z.ZodString;
5
6
  kind: z.ZodEnum<["TICKET", "CHAT"]>;
@@ -102,8 +103,8 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
102
103
  budgetMinutes: z.ZodNullable<z.ZodNumber>;
103
104
  }, "strip", z.ZodTypeAny, {
104
105
  path: string;
105
- trustMode: "STRICT" | "NORMAL" | "AUTO";
106
106
  id: string;
107
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
107
108
  projectId: string;
108
109
  budgetUsd: number | null;
109
110
  budgetMinutes: number | null;
@@ -115,8 +116,8 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
115
116
  agentAllowDestructiveGit?: boolean | undefined;
116
117
  }, {
117
118
  path: string;
118
- trustMode: "STRICT" | "NORMAL" | "AUTO";
119
119
  id: string;
120
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
120
121
  projectId: string;
121
122
  budgetUsd: number | null;
122
123
  budgetMinutes: number | null;
@@ -208,8 +209,8 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
208
209
  }, "strip", z.ZodTypeAny, {
209
210
  mode: "ask" | "plan" | "auto" | "full";
210
211
  agent: "CLAUDE" | "CODEX";
211
- id: string;
212
212
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
213
+ id: string;
213
214
  kind: "TICKET" | "CHAT";
214
215
  model: string | null;
215
216
  effort: string | null;
@@ -222,8 +223,8 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
222
223
  extraBudgetMinutes: number | null;
223
224
  workspace: {
224
225
  path: string;
225
- trustMode: "STRICT" | "NORMAL" | "AUTO";
226
226
  id: string;
227
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
227
228
  projectId: string;
228
229
  budgetUsd: number | null;
229
230
  budgetMinutes: number | null;
@@ -256,15 +257,15 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
256
257
  } | undefined;
257
258
  }, {
258
259
  agent: "CLAUDE" | "CODEX";
259
- id: string;
260
260
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
261
+ id: string;
261
262
  kind: "TICKET" | "CHAT";
262
263
  prompt: string;
263
264
  providerSessionId: string | null;
264
265
  workspace: {
265
266
  path: string;
266
- trustMode: "STRICT" | "NORMAL" | "AUTO";
267
267
  id: string;
268
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
268
269
  projectId: string;
269
270
  budgetUsd: number | null;
270
271
  budgetMinutes: number | null;
@@ -520,8 +521,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
520
521
  budgetMinutes: z.ZodNullable<z.ZodNumber>;
521
522
  }, "strip", z.ZodTypeAny, {
522
523
  path: string;
523
- trustMode: "STRICT" | "NORMAL" | "AUTO";
524
524
  id: string;
525
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
525
526
  projectId: string;
526
527
  budgetUsd: number | null;
527
528
  budgetMinutes: number | null;
@@ -533,8 +534,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
533
534
  agentAllowDestructiveGit?: boolean | undefined;
534
535
  }, {
535
536
  path: string;
536
- trustMode: "STRICT" | "NORMAL" | "AUTO";
537
537
  id: string;
538
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
538
539
  projectId: string;
539
540
  budgetUsd: number | null;
540
541
  budgetMinutes: number | null;
@@ -626,8 +627,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
626
627
  }, "strip", z.ZodTypeAny, {
627
628
  mode: "ask" | "plan" | "auto" | "full";
628
629
  agent: "CLAUDE" | "CODEX";
629
- id: string;
630
630
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
631
+ id: string;
631
632
  kind: "TICKET" | "CHAT";
632
633
  model: string | null;
633
634
  effort: string | null;
@@ -640,8 +641,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
640
641
  extraBudgetMinutes: number | null;
641
642
  workspace: {
642
643
  path: string;
643
- trustMode: "STRICT" | "NORMAL" | "AUTO";
644
644
  id: string;
645
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
645
646
  projectId: string;
646
647
  budgetUsd: number | null;
647
648
  budgetMinutes: number | null;
@@ -674,15 +675,15 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
674
675
  } | undefined;
675
676
  }, {
676
677
  agent: "CLAUDE" | "CODEX";
677
- id: string;
678
678
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
679
+ id: string;
679
680
  kind: "TICKET" | "CHAT";
680
681
  prompt: string;
681
682
  providerSessionId: string | null;
682
683
  workspace: {
683
684
  path: string;
684
- trustMode: "STRICT" | "NORMAL" | "AUTO";
685
685
  id: string;
686
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
686
687
  projectId: string;
687
688
  budgetUsd: number | null;
688
689
  budgetMinutes: number | null;
@@ -721,8 +722,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
721
722
  sessions: {
722
723
  mode: "ask" | "plan" | "auto" | "full";
723
724
  agent: "CLAUDE" | "CODEX";
724
- id: string;
725
725
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
726
+ id: string;
726
727
  kind: "TICKET" | "CHAT";
727
728
  model: string | null;
728
729
  effort: string | null;
@@ -735,8 +736,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
735
736
  extraBudgetMinutes: number | null;
736
737
  workspace: {
737
738
  path: string;
738
- trustMode: "STRICT" | "NORMAL" | "AUTO";
739
739
  id: string;
740
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
740
741
  projectId: string;
741
742
  budgetUsd: number | null;
742
743
  budgetMinutes: number | null;
@@ -775,15 +776,15 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
775
776
  }, {
776
777
  sessions: {
777
778
  agent: "CLAUDE" | "CODEX";
778
- id: string;
779
779
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
780
+ id: string;
780
781
  kind: "TICKET" | "CHAT";
781
782
  prompt: string;
782
783
  providerSessionId: string | null;
783
784
  workspace: {
784
785
  path: string;
785
- trustMode: "STRICT" | "NORMAL" | "AUTO";
786
786
  id: string;
787
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
787
788
  projectId: string;
788
789
  budgetUsd: number | null;
789
790
  budgetMinutes: number | null;
@@ -953,8 +954,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
953
954
  budgetMinutes: z.ZodNullable<z.ZodNumber>;
954
955
  }, "strip", z.ZodTypeAny, {
955
956
  path: string;
956
- trustMode: "STRICT" | "NORMAL" | "AUTO";
957
957
  id: string;
958
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
958
959
  projectId: string;
959
960
  budgetUsd: number | null;
960
961
  budgetMinutes: number | null;
@@ -966,8 +967,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
966
967
  agentAllowDestructiveGit?: boolean | undefined;
967
968
  }, {
968
969
  path: string;
969
- trustMode: "STRICT" | "NORMAL" | "AUTO";
970
970
  id: string;
971
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
971
972
  projectId: string;
972
973
  budgetUsd: number | null;
973
974
  budgetMinutes: number | null;
@@ -1059,8 +1060,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1059
1060
  }, "strip", z.ZodTypeAny, {
1060
1061
  mode: "ask" | "plan" | "auto" | "full";
1061
1062
  agent: "CLAUDE" | "CODEX";
1062
- id: string;
1063
1063
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1064
+ id: string;
1064
1065
  kind: "TICKET" | "CHAT";
1065
1066
  model: string | null;
1066
1067
  effort: string | null;
@@ -1073,8 +1074,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1073
1074
  extraBudgetMinutes: number | null;
1074
1075
  workspace: {
1075
1076
  path: string;
1076
- trustMode: "STRICT" | "NORMAL" | "AUTO";
1077
1077
  id: string;
1078
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
1078
1079
  projectId: string;
1079
1080
  budgetUsd: number | null;
1080
1081
  budgetMinutes: number | null;
@@ -1107,15 +1108,15 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1107
1108
  } | undefined;
1108
1109
  }, {
1109
1110
  agent: "CLAUDE" | "CODEX";
1110
- id: string;
1111
1111
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1112
+ id: string;
1112
1113
  kind: "TICKET" | "CHAT";
1113
1114
  prompt: string;
1114
1115
  providerSessionId: string | null;
1115
1116
  workspace: {
1116
1117
  path: string;
1117
- trustMode: "STRICT" | "NORMAL" | "AUTO";
1118
1118
  id: string;
1119
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
1119
1120
  projectId: string;
1120
1121
  budgetUsd: number | null;
1121
1122
  budgetMinutes: number | null;
@@ -1154,8 +1155,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1154
1155
  session: {
1155
1156
  mode: "ask" | "plan" | "auto" | "full";
1156
1157
  agent: "CLAUDE" | "CODEX";
1157
- id: string;
1158
1158
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1159
+ id: string;
1159
1160
  kind: "TICKET" | "CHAT";
1160
1161
  model: string | null;
1161
1162
  effort: string | null;
@@ -1168,8 +1169,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1168
1169
  extraBudgetMinutes: number | null;
1169
1170
  workspace: {
1170
1171
  path: string;
1171
- trustMode: "STRICT" | "NORMAL" | "AUTO";
1172
1172
  id: string;
1173
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
1173
1174
  projectId: string;
1174
1175
  budgetUsd: number | null;
1175
1176
  budgetMinutes: number | null;
@@ -1205,15 +1206,15 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1205
1206
  }, {
1206
1207
  session: {
1207
1208
  agent: "CLAUDE" | "CODEX";
1208
- id: string;
1209
1209
  status: "RUNNING" | "FAILED" | "STARTING" | "WAITING_INPUT" | "WAITING_PERMISSION" | "REVIEW" | "DONE" | "STOPPED";
1210
+ id: string;
1210
1211
  kind: "TICKET" | "CHAT";
1211
1212
  prompt: string;
1212
1213
  providerSessionId: string | null;
1213
1214
  workspace: {
1214
1215
  path: string;
1215
- trustMode: "STRICT" | "NORMAL" | "AUTO";
1216
1216
  id: string;
1217
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
1217
1218
  projectId: string;
1218
1219
  budgetUsd: number | null;
1219
1220
  budgetMinutes: number | null;
@@ -1673,7 +1674,22 @@ export type RunnerFrame = {
1673
1674
  */
1674
1675
  | ({
1675
1676
  type: 'host_load';
1676
- } & HostLoadFrame) | {
1677
+ } & HostLoadFrame)
1678
+ /**
1679
+ * How this machine is dividing its memory between the sessions on it, right
1680
+ * now (#398 S4).
1681
+ *
1682
+ * Machine-scoped with a `sessions` array rather than one frame per session:
1683
+ * half of what has to be shown is about the MACHINE — what is free, what is
1684
+ * held by things that are not DevBridge, how many guarantees fit — and a
1685
+ * machine with no live sessions still has to be able to say it.
1686
+ *
1687
+ * Sent only when it moved, on the same tick that recomputes it. Fields mirror
1688
+ * `@devbridge/shared` — see `session-limits.ts`.
1689
+ */
1690
+ | ({
1691
+ type: 'session_limits';
1692
+ } & SessionLimitsFrame) | {
1677
1693
  type: 'pong';
1678
1694
  };
1679
1695
  //# sourceMappingURL=protocol.d.ts.map
@@ -134,17 +134,17 @@ export declare const ProjectRecipeSchema: z.ZodObject<{
134
134
  env?: Record<string, string> | undefined;
135
135
  timeoutSec?: number | undefined;
136
136
  } | undefined;
137
- verify?: {
137
+ test?: {
138
138
  run: string;
139
139
  env?: Record<string, string> | undefined;
140
140
  timeoutSec?: number | undefined;
141
141
  } | undefined;
142
- build?: {
142
+ verify?: {
143
143
  run: string;
144
144
  env?: Record<string, string> | undefined;
145
145
  timeoutSec?: number | undefined;
146
146
  } | undefined;
147
- test?: {
147
+ build?: {
148
148
  run: string;
149
149
  env?: Record<string, string> | undefined;
150
150
  timeoutSec?: number | undefined;
@@ -160,17 +160,17 @@ export declare const ProjectRecipeSchema: z.ZodObject<{
160
160
  env?: Record<string, string> | undefined;
161
161
  timeoutSec?: number | undefined;
162
162
  } | undefined;
163
- verify?: {
163
+ test?: {
164
164
  run: string;
165
165
  env?: Record<string, string> | undefined;
166
166
  timeoutSec?: number | undefined;
167
167
  } | undefined;
168
- build?: {
168
+ verify?: {
169
169
  run: string;
170
170
  env?: Record<string, string> | undefined;
171
171
  timeoutSec?: number | undefined;
172
172
  } | undefined;
173
- test?: {
173
+ build?: {
174
174
  run: string;
175
175
  env?: Record<string, string> | undefined;
176
176
  timeoutSec?: number | undefined;
@@ -219,17 +219,17 @@ export declare const ProjectRecipeSchema: z.ZodObject<{
219
219
  env?: Record<string, string> | undefined;
220
220
  timeoutSec?: number | undefined;
221
221
  } | undefined;
222
- verify?: {
222
+ test?: {
223
223
  run: string;
224
224
  env?: Record<string, string> | undefined;
225
225
  timeoutSec?: number | undefined;
226
226
  } | undefined;
227
- build?: {
227
+ verify?: {
228
228
  run: string;
229
229
  env?: Record<string, string> | undefined;
230
230
  timeoutSec?: number | undefined;
231
231
  } | undefined;
232
- test?: {
232
+ build?: {
233
233
  run: string;
234
234
  env?: Record<string, string> | undefined;
235
235
  timeoutSec?: number | undefined;
@@ -268,17 +268,17 @@ export declare const ProjectRecipeSchema: z.ZodObject<{
268
268
  env?: Record<string, string> | undefined;
269
269
  timeoutSec?: number | undefined;
270
270
  } | undefined;
271
- verify?: {
271
+ test?: {
272
272
  run: string;
273
273
  env?: Record<string, string> | undefined;
274
274
  timeoutSec?: number | undefined;
275
275
  } | undefined;
276
- build?: {
276
+ verify?: {
277
277
  run: string;
278
278
  env?: Record<string, string> | undefined;
279
279
  timeoutSec?: number | undefined;
280
280
  } | undefined;
281
- test?: {
281
+ build?: {
282
282
  run: string;
283
283
  env?: Record<string, string> | undefined;
284
284
  timeoutSec?: number | undefined;
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { promisify } from 'node:util';
6
6
  import { findClaudeCli, USE_BUNDLED_CLAUDE } from './agent-binary.js';
7
7
  import { systemdUserEnv } from './environment.js';
8
+ import { cageAuthority, CageAuthorityError } from './cage-authority.js';
8
9
  import { log } from './log.js';
9
10
  import { stateDir } from './paths.js';
10
11
  import { RUNNER_VERSION } from './version.js';
@@ -386,8 +387,28 @@ function systemctlEnv() {
386
387
  return env;
387
388
  }
388
389
  export async function selfUpdate(options) {
390
+ /**
391
+ * The default executor installs software on somebody's machine and reloads
392
+ * their systemd, so it asks the same door the cages ask (#403).
393
+ *
394
+ * The check is on the DEFAULT and not on the injected one: a test hands its
395
+ * own executor and is testing the sequence of commands, which is exactly what
396
+ * it should be able to do. What must not happen is a call with no executor
397
+ * from a process that has no business updating anything — `npm install -g`
398
+ * from a vitest worker would be the incident of 10.09.2026 with a much longer
399
+ * tail.
400
+ */
389
401
  const exec = options.exec ??
390
- ((file, args, opts) => execFileAsync(file, args, { timeout: opts.timeout, env: opts.env, maxBuffer: 4_000_000 }));
402
+ ((file, args, opts) => {
403
+ if (cageAuthority() === 'none') {
404
+ throw new CageAuthorityError(`this process may not run «${file}»: updating this machine's runner is the daemon's job`);
405
+ }
406
+ return execFileAsync(file, args, {
407
+ timeout: opts.timeout,
408
+ env: opts.env,
409
+ maxBuffer: 4_000_000,
410
+ });
411
+ });
391
412
  const fromVersion = RUNNER_VERSION;
392
413
  const fail = (detail, extra = {}) => ({
393
414
  ok: false,
@@ -57,7 +57,7 @@ export declare function buildUnit(execStart?: string, nodeBinary?: string): stri
57
57
  * which is the only way to fix the servers that already have the bad numbers
58
58
  * baked in — and it never overwrites a unit the operator edited by hand.
59
59
  */
60
- export declare const LIMITS_VERSION = 5;
60
+ export declare const LIMITS_VERSION = 6;
61
61
  /**
62
62
  * Where the agent sessions live once they have a cage of their own.
63
63
  *
@@ -239,6 +239,19 @@ export declare function managedUsageFloorBytes(facts: MemoryFacts): number;
239
239
  * - cap 85 % of total: on an idle dedicated box `MemAvailable` is nearly the whole
240
240
  * machine, and a ceiling of «everything» is the bug this function exists to fix.
241
241
  */
242
+ /**
243
+ * What the machine keeps for itself, whatever the agents are doing.
244
+ *
245
+ * Enough for sshd, journald, the kernel and some page cache to keep working
246
+ * while our cgroups sit at their ceiling. Proportional on a big machine,
247
+ * absolute on a small one, because 15 % of 4 GB is not enough to stay reachable.
248
+ *
249
+ * Hoisted out of {@link memoryPolicy} for #398 S3: the live allocator has to
250
+ * subtract exactly the same reserve when it decides how much of the machine is
251
+ * free right now, and a second copy of this number would drift from this one on
252
+ * the first machine where it mattered.
253
+ */
254
+ export declare function machineReserveBytes(totalBytes: number): number;
242
255
  export declare function memoryPolicy(facts: MemoryFacts, minCeilingBytes?: number): MemoryPolicy;
243
256
  /** `SwapTotal` of this machine, or null where `/proc/meminfo` will not say. */
244
257
  export declare function readSwapTotalBytes(): number | null;
@@ -418,7 +431,24 @@ export declare function buildLimitsOverride(cpuCount?: number, facts?: MemoryFac
418
431
  * can measure one (the drift check treats a file with no
419
432
  * `MemoryMax` as outdated).
420
433
  */
421
- export declare function buildSessionsSliceOverride(facts?: MemoryFacts | null, sessionsUsageBytes?: number | null): string;
434
+ export declare function buildSessionsSliceOverride(facts?: MemoryFacts | null, sessionsUsageBytes?: number | null, maxSessions?: number): string;
435
+ /**
436
+ * Where the collective brake sits under the collective ceiling.
437
+ *
438
+ * Nine tenths: close enough that the sessions get almost the whole pot before
439
+ * anything is throttled, far enough that the kernel has a tenth of the pot in
440
+ * which to slow everyone down and reclaim, instead of arriving at the hard
441
+ * ceiling and choosing a victim.
442
+ */
443
+ export declare const SLICE_BRAKE_OF_CEILING = 0.9;
444
+ /**
445
+ * Seats assumed when nobody has said otherwise.
446
+ *
447
+ * Three, which is what `DevServer.maxSessions` defaults to in the database. The
448
+ * runner's own config may say fewer; the slice's protection is sized for the
449
+ * seats the machine sells, not for the sessions that happen to be running.
450
+ */
451
+ export declare const DEFAULT_MAX_SESSIONS = 3;
422
452
  /**
423
453
  * The CPU share of everything the agents run, against the daemon's own.
424
454
  *
@@ -453,7 +483,9 @@ export declare function limitsOverrideIsOutdated(readFile?: (p: string) => strin
453
483
  * arrive at the same number — otherwise a write whose floor was binding would be
454
484
  * seen as drifted on the very next call and rewritten forever.
455
485
  */
456
- export declare function writeLimitsOverride(force?: boolean, home?: string, facts?: MemoryFacts | null, sessionsUsageBytes?: number | null): boolean;
486
+ export declare function writeLimitsOverride(force?: boolean, home?: string, facts?: MemoryFacts | null, sessionsUsageBytes?: number | null,
487
+ /** Seats this machine sells — sizes the slice's collective `MemoryLow` (#398 S3). */
488
+ maxSessions?: number): boolean;
457
489
  /**
458
490
  * Does the installed unit point at something that no longer exists?
459
491
  *