@bridge4dev/runner 0.52.0 → 0.54.0

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
@@ -517,6 +517,189 @@ function parsePush(segment) {
517
517
  remoteExec,
518
518
  };
519
519
  }
520
+ const GIT_CHECKOUT = String.raw `\bgit\s+${FLAGS}(?:checkout|switch)\b`;
521
+ /** A word whose meaning only the shell knows — `$BRANCH`, `` `cat .b` ``, `\x`. */
522
+ const SHELL_UNRESOLVED = /[$`\\]/;
523
+ /**
524
+ * A REVISION expression — `main~1`, `v2^`, `abc123^{commit}`.
525
+ *
526
+ * Not a branch name (git's own ref grammar forbids `~` and `^` in one) and
527
+ * emphatically not a pathspec: checking one out leaves the folder on no branch
528
+ * at all. Grouping it with the pathspecs below, on the grounds that it «cannot
529
+ * be a branch», was exactly backwards — `git checkout main~1` detaches the
530
+ * shared folder for everybody in it, silently.
531
+ */
532
+ const REVISION_EXPRESSION = /[~^]/;
533
+ /**
534
+ * An operand git could never read as a branch name — because it is a PATH.
535
+ *
536
+ * Not a guess about intent, a fact about the ref grammar: `*`, `?`, `[` and
537
+ * spaces are forbidden in a branch name, a leading `:` is git's own magic
538
+ * pathspec prefix, and `.`, `./x`, `../x`, `/x`, `x/` are paths by shape.
539
+ * A closing `]` is deliberately NOT here: `git check-ref-format --branch 'wip]1'`
540
+ * accepts it, so calling it impossible-in-a-branch would have waved through a
541
+ * real branch switch (QA #255).
542
+ * Everything else stays ambiguous, and git resolves ambiguity in favour of the
543
+ * BRANCH — which is why an ambiguous word asks rather than being waved through.
544
+ *
545
+ * Characters that are merely UNUSUAL in a branch name (a backslash, a control
546
+ * character, `@{`) are deliberately absent: they end up in the ambiguous pile,
547
+ * which asks. Every gap here has to fall on that side.
548
+ */
549
+ function cannotBeBranch(operand) {
550
+ if (operand === '.' || operand === '..')
551
+ return true;
552
+ if (/^\.{1,2}\//.test(operand) || operand.startsWith('/'))
553
+ return true;
554
+ if (operand.startsWith(':'))
555
+ return true;
556
+ if (operand.endsWith('/'))
557
+ return true;
558
+ return /[*?[ ]/.test(operand);
559
+ }
560
+ /**
561
+ * The agent chose this word, so it is quoted back at a length WE choose.
562
+ *
563
+ * This string becomes the description of a permission card and travels in an
564
+ * event payload the gateway caps; a single implausibly long operand should cost
565
+ * a few characters of the sentence, never the card.
566
+ */
567
+ function shortOperand(operand) {
568
+ return operand.length > 60 ? `${operand.slice(0, 60)}…` : operand;
569
+ }
570
+ const RESTORE_A_FILE = 'To put a file back use `git checkout -- <path>` or `git restore <path>`: those touch files only.';
571
+ const MOVES_THE_FOLDER = 'this session works in the project folder itself, so switching branches there changes the files under every other session and person working in it';
572
+ /**
573
+ * Does this segment move the shared folder onto another branch? (#361 п. 5)
574
+ *
575
+ * Parsed rather than pattern matched, for the same reason as `parsePush`: the
576
+ * difference between «restore this file» and «move everybody to another
577
+ * branch» is one `--` in the middle of the arguments, and no regex answers
578
+ * that. Synchronous and free of child processes — gotcha 196: this runs on
579
+ * every Bash call of every session on the machine.
580
+ *
581
+ * Returns null when the segment is not a checkout at all, or is one of the
582
+ * forms that provably only touches files.
583
+ */
584
+ function parseCheckout(segment) {
585
+ if (!new RegExp(GIT_CHECKOUT).test(segment))
586
+ return null;
587
+ const tokens = words(segment);
588
+ const at = tokens.findIndex((token) => token === 'checkout' || token === 'switch');
589
+ if (at === -1) {
590
+ // The regex matched but the verb is hidden by a form this tokenizer does
591
+ // not model. Report the moving reading — «I could not parse it» must never
592
+ // become «go ahead».
593
+ return {
594
+ reason: `This git command could not be read well enough to tell whether it moves the folder onto another branch, and ${MOVES_THE_FOLDER}. ${RESTORE_A_FILE}`,
595
+ };
596
+ }
597
+ if (tokens[at] === 'switch') {
598
+ // `git switch` has no file form at all — it exists to move a branch.
599
+ return {
600
+ reason: `\`git switch\` moves the folder onto another branch, and ${MOVES_THE_FOLDER}. ${RESTORE_A_FILE}`,
601
+ };
602
+ }
603
+ const rest = tokens.slice(at + 1);
604
+ /**
605
+ * `--` names paths only when there ARE paths after it.
606
+ *
607
+ * The obvious reading — «a `--` anywhere means this is a file command» — is
608
+ * wrong, and wrong in the direction that matters. Verified on git 2.43:
609
+ * `git checkout feature --` prints «Switched to branch 'feature'», and
610
+ * `git checkout -b tmp --` creates the branch and moves onto it. Git's own
611
+ * parser treats a trailing `--` as «the word before me is definitely a ref»,
612
+ * which is the opposite of what it looks like. So the separator is only a
613
+ * separator when it separates something.
614
+ */
615
+ const dashDash = rest.indexOf('--');
616
+ if (dashDash !== -1 && dashDash < rest.length - 1)
617
+ return null;
618
+ const head = dashDash === -1 ? rest : rest.slice(0, dashDash);
619
+ let fileOnly = false;
620
+ let movingFlag = null;
621
+ const operands = [];
622
+ for (const token of head) {
623
+ if (token.startsWith('--')) {
624
+ const name = token.split('=')[0] ?? token;
625
+ if (name === '--ours' ||
626
+ name === '--theirs' ||
627
+ name === '--patch' ||
628
+ name === '--pathspec-from-file' ||
629
+ name === '--pathspec-file-nul') {
630
+ fileOnly = true;
631
+ }
632
+ else if (name === '--orphan' ||
633
+ name === '--detach' ||
634
+ name === '--track' ||
635
+ name === '--merge' ||
636
+ name === '--force') {
637
+ movingFlag ??= name;
638
+ }
639
+ continue;
640
+ }
641
+ // A lone `-` is an operand: «the branch I was on before».
642
+ if (token.startsWith('-') && token.length > 1) {
643
+ for (const letter of token.slice(1)) {
644
+ if (letter === 'p')
645
+ fileOnly = true;
646
+ else if ('bBtmf'.includes(letter))
647
+ movingFlag ??= `-${letter}`;
648
+ }
649
+ continue;
650
+ }
651
+ operands.push(token);
652
+ }
653
+ if (fileOnly)
654
+ return null;
655
+ if (movingFlag) {
656
+ return {
657
+ reason: `\`git checkout ${shortOperand(movingFlag)}\` moves the folder onto another branch, and ${MOVES_THE_FOLDER}. ${RESTORE_A_FILE}`,
658
+ };
659
+ }
660
+ // `git checkout` on its own restores nothing and moves nothing — but only
661
+ // when the arguments really are all of them. `echo feature | xargs git
662
+ // checkout` is a checkout whose operand is handed to it by the program in
663
+ // front of it, and this parser cannot see it: the segment ends with a bare
664
+ // `git checkout` and looks harmless. So «no operands» is only an answer when
665
+ // git is the thing being run, not an argument to something else.
666
+ if (operands.length === 0) {
667
+ const first = tokens.find((token) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(token));
668
+ if (first === 'git')
669
+ return null;
670
+ return {
671
+ reason: `This \`git checkout\` gets its arguments from another program, so the runner cannot tell whether it moves the folder onto another branch, and ${MOVES_THE_FOLDER}. ${RESTORE_A_FILE}`,
672
+ };
673
+ }
674
+ for (const operand of operands) {
675
+ if (SHELL_UNRESOLVED.test(operand)) {
676
+ return {
677
+ reason: `This \`git checkout\` names its target through the shell, so the runner cannot tell whether it is a file or a branch — and ${MOVES_THE_FOLDER}. ${RESTORE_A_FILE}`,
678
+ };
679
+ }
680
+ if (operand === '-') {
681
+ return {
682
+ reason: `\`git checkout -\` moves the folder back onto the previous branch, and ${MOVES_THE_FOLDER}. ${RESTORE_A_FILE}`,
683
+ };
684
+ }
685
+ if (/^(HEAD|@)([~^].*)?$/.test(operand) ||
686
+ /^[0-9a-f]{7,40}$/i.test(operand) ||
687
+ REVISION_EXPRESSION.test(operand)) {
688
+ return {
689
+ reason: `\`git checkout ${shortOperand(operand)}\` leaves the folder on no branch at all (detached HEAD), and ${MOVES_THE_FOLDER}. ${RESTORE_A_FILE}`,
690
+ };
691
+ }
692
+ if (cannotBeBranch(operand))
693
+ continue;
694
+ // A bare word. Git reads it as a BRANCH first and only falls back to a
695
+ // path, so «I meant the file» is not something this can assume — and the
696
+ // unambiguous spelling costs the agent three characters.
697
+ return {
698
+ reason: `\`git checkout ${shortOperand(operand)}\` is read by git as a branch first, and ${MOVES_THE_FOLDER}. ${RESTORE_A_FILE}`,
699
+ };
700
+ }
701
+ return null;
702
+ }
520
703
  /**
521
704
  * The project's git rules, applied to one Bash command.
522
705
  *
@@ -525,9 +708,18 @@ function parsePush(segment) {
525
708
  * DELIBERATELY switched something on — with the shipped defaults the first
526
709
  * check refuses every push and the rest never run, which is byte for byte the
527
710
  * behaviour of the four regexes it replaces.
711
+ *
712
+ * **A `deny` anywhere beats an `ask` anywhere.** Until #361 every decision here
713
+ * was a refusal, so returning the first one found in the first segment was the
714
+ * same thing as returning the worst one. With an `ask` in the set that stopped
715
+ * being true: `git checkout main && git push origin main` would have shown a
716
+ * card about the checkout, and confirming it would have run the push this
717
+ * function exists to refuse. So `deny` returns at once, `ask` is remembered,
718
+ * and the loop reads to the end.
528
719
  */
529
720
  export function evaluateGitPolicy(command, ctx) {
530
721
  const policy = resolveGitPolicy(ctx);
722
+ let asked = null;
531
723
  for (const segment of commandSegments(command)) {
532
724
  if (!policy.allowDestructiveGit) {
533
725
  /**
@@ -539,10 +731,14 @@ export function evaluateGitPolicy(command, ctx) {
539
731
  * that went that way on 2026-07-28 existed nowhere else — and
540
732
  * `git reset --hard` throws away every uncommitted edit in the tree.
541
733
  *
542
- * Denied rather than turned into a permission card, because under AUTO
543
- * trust there is no card: Bash is allowed before the safe list is
544
- * consulted. An agent that needs to remove build output can name the
545
- * paths; one that wants to undo its own work has `git checkout -- <file>`.
734
+ * Denied rather than turned into a permission card because there is no
735
+ * safe version of them, not because a card was impossible: this whole
736
+ * function runs BEFORE the trust branches, so an `ask` returned from here
737
+ * does reach the person even under AUTO (that is how #361 п. 5 works two
738
+ * rules below). What is true is that the safe LIST never sees them — Bash
739
+ * is allowed before it is consulted. An agent that needs to remove build
740
+ * output can name the paths; one that wants to undo its own work has
741
+ * `git checkout -- <file>`.
546
742
  */
547
743
  if (new RegExp(String.raw `\bgit\s+${FLAGS}clean\b`).test(segment)) {
548
744
  return {
@@ -557,6 +753,16 @@ export function evaluateGitPolicy(command, ctx) {
557
753
  };
558
754
  }
559
755
  }
756
+ // #361 п. 5 — only where the folder is shared. In a BRANCH session the
757
+ // worktree belongs to this session alone and moving it costs nobody
758
+ // anything. See `PolicyContext.workMode` for why silence means «ask».
759
+ if (ctx.workMode !== 'BRANCH') {
760
+ const move = parseCheckout(segment);
761
+ // `reason` stays a LABEL — it goes to logs and, on a denial, to the feed.
762
+ // The sentence a person reads on the card travels in `explain`.
763
+ if (move)
764
+ asked ??= { decision: 'ask', reason: 'moves the shared folder', explain: move.reason };
765
+ }
560
766
  const push = parsePush(segment);
561
767
  if (!push)
562
768
  continue;
@@ -648,7 +854,7 @@ export function evaluateGitPolicy(command, ctx) {
648
854
  }
649
855
  }
650
856
  }
651
- return null;
857
+ return asked;
652
858
  }
653
859
  // Commands considered safe enough to run without asking under NORMAL trust.
654
860
  // Interpreters (node/python/…) and find (-exec) are NOT here: they execute
@@ -829,7 +1035,14 @@ export function evaluateRecipeCommand(command, ctx = {}) {
829
1035
  * approval screen, not a side effect of this one.
830
1036
  */
831
1037
  const git = evaluateGitPolicy(variant, { trustMode: 'STRICT', worktreePath: '' });
832
- if (git) {
1038
+ // Only a REFUSAL stops a recipe. Since #361 this function can also return
1039
+ // «ask», and the first line of this doc comment is the answer to it: an
1040
+ // approved recipe does not ask, because a human already read this exact
1041
+ // command on the approval screen. Turning that ask into `{allowed: false}`
1042
+ // would hard-refuse every build script that starts with `git checkout main`
1043
+ // — a refusal invented by a rule about AGENTS, applied to a text a person
1044
+ // wrote and approved.
1045
+ if (git?.decision === 'deny') {
833
1046
  // The sentence is rewritten, not passed through (QA-134 MINOR-2). The
834
1047
  // generic one says «this project has «Принудительно запретить push»
835
1048
  // switched on» — and a project that switched it OFF would be sent to look
@@ -1001,11 +1214,19 @@ export function evaluateToolUse(toolName, input, ctx) {
1001
1214
  // and nothing after this line is consulted. That is exactly how «agents
1002
1215
  // never push» quietly failed for a whole release (session 13), and it is
1003
1216
  // the reason these rules did not simply move onto the safe list.
1217
+ // Both spellings are read to the end before anything is returned: a `deny`
1218
+ // in the dequoted form must beat an `ask` in the raw one, for the same
1219
+ // reason a later segment beats an earlier one (see `evaluateGitPolicy`).
1220
+ let gitAsk = null;
1004
1221
  for (const variant of [command, dequote(command)]) {
1005
1222
  const git = evaluateGitPolicy(variant, ctx);
1006
- if (git)
1223
+ if (git?.decision === 'deny')
1007
1224
  return git;
1225
+ if (git)
1226
+ gitAsk ??= git;
1008
1227
  }
1228
+ if (gitAsk)
1229
+ return gitAsk;
1009
1230
  if (trust === 'STRICT')
1010
1231
  return { decision: 'ask', reason: 'strict mode' };
1011
1232
  if (trust === 'AUTO')
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Everything an agent starts runs at a lower CPU priority than the daemon that
3
+ * supervises it.
4
+ *
5
+ * The failure this exists for is the runner starving itself with its own
6
+ * children (16.08): a session's build saturated the box, the daemon lost four
7
+ * heartbeats in a row, the server went Offline and every session on that
8
+ * machine answered 504 — while the work it was doing was fine. The daemon's job
9
+ * during a heavy turn is a few milliseconds of socket traffic; it should not
10
+ * have to queue behind a `tsc` it launched itself.
11
+ *
12
+ * **What this gives.** Inside the service's own cgroup the daemon (nice 0) gets
13
+ * the processor before the agents (nice 10); neighbouring sessions, all at 10,
14
+ * still share it evenly between themselves. Children inherit the nice value at
15
+ * fork, so a `vitest` the agent starts through Bash — several levels down from
16
+ * the process we renice — is covered without us knowing about it.
17
+ *
18
+ * Inheritance is why every call site is the line straight after its `spawn`:
19
+ * measured here, a grandchild forked AFTER the call comes up at 10 and one
20
+ * forked in the microseconds before it stays at 0. An agent needs hundreds of
21
+ * milliseconds to boot before it forks anything, so that window is empty in
22
+ * practice — but it is a window, and it only grows if the call drifts down the
23
+ * function.
24
+ *
25
+ * **What it does NOT give.** Against processes OUTSIDE the cgroup (production
26
+ * in `system.slice`) it does nothing at all: across cgroups the split is
27
+ * decided by `cpu.weight`, and nice only orders tasks within one. It does not
28
+ * touch memory, which is the mechanism behind three of the four incidents on
29
+ * other people's machines. This is the approach to stage 2 (a scope per session
30
+ * with a memory ceiling) and the fallback for it on cgroup v1, where stage 2
31
+ * cannot work — not a replacement for it.
32
+ *
33
+ * Priority is a convenience, not correctness: nothing here throws. A session
34
+ * that runs at the wrong priority is a slower machine; a session that fails to
35
+ * start because renicing failed is a broken product.
36
+ */
37
+ /**
38
+ * The nice value every process the runner spawns for an agent gets.
39
+ *
40
+ * 10 rather than 19: the point is to lose to the daemon and to anything the
41
+ * owner is doing by hand, not to be scheduled last behind every background cron
42
+ * on the box. The scheduler's weight table gives nice 10 about a ninth of the
43
+ * share of nice 0 under contention (1024 → 110), which is all the room the
44
+ * heartbeat needs — 19 would buy an order of magnitude more and cost a session
45
+ * its throughput whenever anything else on the machine woke up.
46
+ */
47
+ export declare const NICE = 10;
48
+ /**
49
+ * Push one spawned process down to {@link NICE}. Never throws.
50
+ *
51
+ * Takes `number | undefined` because that is exactly what `child.pid` is: a
52
+ * spawn that failed has none, and the caller should not have to ask.
53
+ */
54
+ export declare function lowerPriority(pid: number | undefined): void;
55
+ //# sourceMappingURL=process-priority.d.ts.map
@@ -0,0 +1,99 @@
1
+ import os from 'node:os';
2
+ import { log } from './log.js';
3
+ /**
4
+ * Everything an agent starts runs at a lower CPU priority than the daemon that
5
+ * supervises it.
6
+ *
7
+ * The failure this exists for is the runner starving itself with its own
8
+ * children (16.08): a session's build saturated the box, the daemon lost four
9
+ * heartbeats in a row, the server went Offline and every session on that
10
+ * machine answered 504 — while the work it was doing was fine. The daemon's job
11
+ * during a heavy turn is a few milliseconds of socket traffic; it should not
12
+ * have to queue behind a `tsc` it launched itself.
13
+ *
14
+ * **What this gives.** Inside the service's own cgroup the daemon (nice 0) gets
15
+ * the processor before the agents (nice 10); neighbouring sessions, all at 10,
16
+ * still share it evenly between themselves. Children inherit the nice value at
17
+ * fork, so a `vitest` the agent starts through Bash — several levels down from
18
+ * the process we renice — is covered without us knowing about it.
19
+ *
20
+ * Inheritance is why every call site is the line straight after its `spawn`:
21
+ * measured here, a grandchild forked AFTER the call comes up at 10 and one
22
+ * forked in the microseconds before it stays at 0. An agent needs hundreds of
23
+ * milliseconds to boot before it forks anything, so that window is empty in
24
+ * practice — but it is a window, and it only grows if the call drifts down the
25
+ * function.
26
+ *
27
+ * **What it does NOT give.** Against processes OUTSIDE the cgroup (production
28
+ * in `system.slice`) it does nothing at all: across cgroups the split is
29
+ * decided by `cpu.weight`, and nice only orders tasks within one. It does not
30
+ * touch memory, which is the mechanism behind three of the four incidents on
31
+ * other people's machines. This is the approach to stage 2 (a scope per session
32
+ * with a memory ceiling) and the fallback for it on cgroup v1, where stage 2
33
+ * cannot work — not a replacement for it.
34
+ *
35
+ * Priority is a convenience, not correctness: nothing here throws. A session
36
+ * that runs at the wrong priority is a slower machine; a session that fails to
37
+ * start because renicing failed is a broken product.
38
+ */
39
+ /**
40
+ * The nice value every process the runner spawns for an agent gets.
41
+ *
42
+ * 10 rather than 19: the point is to lose to the daemon and to anything the
43
+ * owner is doing by hand, not to be scheduled last behind every background cron
44
+ * on the box. The scheduler's weight table gives nice 10 about a ninth of the
45
+ * share of nice 0 under contention (1024 → 110), which is all the room the
46
+ * heartbeat needs — 19 would buy an order of magnitude more and cost a session
47
+ * its throughput whenever anything else on the machine woke up.
48
+ */
49
+ export const NICE = 10;
50
+ /**
51
+ * EPERM is a property of the machine, not of the process — it means this kernel
52
+ * or container will not let us renice at all, and it will mean that for every
53
+ * spawn afterwards. Said once; the alternative is one warning per agent process
54
+ * for the life of the daemon, which is how a real line gets buried.
55
+ */
56
+ let permissionWarned = false;
57
+ function errorCode(error) {
58
+ if (typeof error === 'object' && error !== null && 'code' in error) {
59
+ return String(error.code);
60
+ }
61
+ return '';
62
+ }
63
+ /**
64
+ * Push one spawned process down to {@link NICE}. Never throws.
65
+ *
66
+ * Takes `number | undefined` because that is exactly what `child.pid` is: a
67
+ * spawn that failed has none, and the caller should not have to ask.
68
+ */
69
+ export function lowerPriority(pid) {
70
+ if (pid === undefined)
71
+ return;
72
+ try {
73
+ os.setPriority(pid, NICE);
74
+ }
75
+ catch (error) {
76
+ const code = errorCode(error);
77
+ // ESRCH: the child was already gone — a binary that is not there exits
78
+ // before we get to it. Nothing happened and nothing is wrong, so nothing
79
+ // is said; the spawn failure itself is reported by whoever spawned it.
80
+ if (code === 'ESRCH')
81
+ return;
82
+ if (code === 'EPERM') {
83
+ if (permissionWarned)
84
+ return;
85
+ permissionWarned = true;
86
+ log.warn('priority: not allowed to renice agent processes on this machine', {
87
+ nice: NICE,
88
+ error: String(error),
89
+ });
90
+ return;
91
+ }
92
+ log.warn('priority: could not lower a spawned process', {
93
+ pid,
94
+ nice: NICE,
95
+ error: String(error),
96
+ });
97
+ }
98
+ }
99
+ //# sourceMappingURL=process-priority.js.map
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import type { HostLoadFrame } from './host-load.js';
2
3
  export declare const SessionDescriptorSchema: z.ZodObject<{
3
4
  id: z.ZodString;
4
5
  kind: z.ZodEnum<["TICKET", "CHAT"]>;
@@ -242,6 +243,7 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
242
243
  url: string;
243
244
  token: string;
244
245
  } | undefined;
246
+ workMode?: "DIRECT" | "BRANCH" | undefined;
245
247
  pausedUntil?: string | null | undefined;
246
248
  agentLatestVersion?: string | undefined;
247
249
  skipAgentPrompt?: boolean | undefined;
@@ -252,7 +254,6 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
252
254
  baseBranch?: string | undefined;
253
255
  baseSha?: string | undefined;
254
256
  } | undefined;
255
- workMode?: "DIRECT" | "BRANCH" | undefined;
256
257
  }, {
257
258
  agent: "CLAUDE" | "CODEX";
258
259
  id: string;
@@ -284,6 +285,7 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
284
285
  token: string;
285
286
  } | undefined;
286
287
  mode?: "ask" | "plan" | "auto" | "full" | undefined;
288
+ workMode?: unknown;
287
289
  model?: string | null | undefined;
288
290
  effort?: string | null | undefined;
289
291
  epoch?: number | undefined;
@@ -296,7 +298,6 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
296
298
  skipAgentPrompt?: unknown;
297
299
  branchHint?: unknown;
298
300
  branchPlan?: unknown;
299
- workMode?: unknown;
300
301
  }>;
301
302
  export type SessionDescriptor = z.infer<typeof SessionDescriptorSchema>;
302
303
  /**
@@ -660,6 +661,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
660
661
  url: string;
661
662
  token: string;
662
663
  } | undefined;
664
+ workMode?: "DIRECT" | "BRANCH" | undefined;
663
665
  pausedUntil?: string | null | undefined;
664
666
  agentLatestVersion?: string | undefined;
665
667
  skipAgentPrompt?: boolean | undefined;
@@ -670,7 +672,6 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
670
672
  baseBranch?: string | undefined;
671
673
  baseSha?: string | undefined;
672
674
  } | undefined;
673
- workMode?: "DIRECT" | "BRANCH" | undefined;
674
675
  }, {
675
676
  agent: "CLAUDE" | "CODEX";
676
677
  id: string;
@@ -702,6 +703,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
702
703
  token: string;
703
704
  } | undefined;
704
705
  mode?: "ask" | "plan" | "auto" | "full" | undefined;
706
+ workMode?: unknown;
705
707
  model?: string | null | undefined;
706
708
  effort?: string | null | undefined;
707
709
  epoch?: number | undefined;
@@ -714,12 +716,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
714
716
  skipAgentPrompt?: unknown;
715
717
  branchHint?: unknown;
716
718
  branchPlan?: unknown;
717
- workMode?: unknown;
718
719
  }>, "many">;
719
720
  }, "strip", z.ZodTypeAny, {
720
- type: "hello_ack";
721
- serverName: string;
722
- serverId: string;
723
721
  sessions: {
724
722
  mode: "ask" | "plan" | "auto" | "full";
725
723
  agent: "CLAUDE" | "CODEX";
@@ -758,6 +756,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
758
756
  url: string;
759
757
  token: string;
760
758
  } | undefined;
759
+ workMode?: "DIRECT" | "BRANCH" | undefined;
761
760
  pausedUntil?: string | null | undefined;
762
761
  agentLatestVersion?: string | undefined;
763
762
  skipAgentPrompt?: boolean | undefined;
@@ -768,13 +767,12 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
768
767
  baseBranch?: string | undefined;
769
768
  baseSha?: string | undefined;
770
769
  } | undefined;
771
- workMode?: "DIRECT" | "BRANCH" | undefined;
772
770
  }[];
773
- maxSessions?: number | undefined;
774
- }, {
775
771
  type: "hello_ack";
776
772
  serverName: string;
777
773
  serverId: string;
774
+ maxSessions?: number | undefined;
775
+ }, {
778
776
  sessions: {
779
777
  agent: "CLAUDE" | "CODEX";
780
778
  id: string;
@@ -806,6 +804,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
806
804
  token: string;
807
805
  } | undefined;
808
806
  mode?: "ask" | "plan" | "auto" | "full" | undefined;
807
+ workMode?: unknown;
809
808
  model?: string | null | undefined;
810
809
  effort?: string | null | undefined;
811
810
  epoch?: number | undefined;
@@ -818,8 +817,10 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
818
817
  skipAgentPrompt?: unknown;
819
818
  branchHint?: unknown;
820
819
  branchPlan?: unknown;
821
- workMode?: unknown;
822
820
  }[];
821
+ type: "hello_ack";
822
+ serverName: string;
823
+ serverId: string;
823
824
  maxSessions?: unknown;
824
825
  }>, z.ZodObject<{
825
826
  type: z.ZodLiteral<"event_ack">;
@@ -1093,6 +1094,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1093
1094
  url: string;
1094
1095
  token: string;
1095
1096
  } | undefined;
1097
+ workMode?: "DIRECT" | "BRANCH" | undefined;
1096
1098
  pausedUntil?: string | null | undefined;
1097
1099
  agentLatestVersion?: string | undefined;
1098
1100
  skipAgentPrompt?: boolean | undefined;
@@ -1103,7 +1105,6 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1103
1105
  baseBranch?: string | undefined;
1104
1106
  baseSha?: string | undefined;
1105
1107
  } | undefined;
1106
- workMode?: "DIRECT" | "BRANCH" | undefined;
1107
1108
  }, {
1108
1109
  agent: "CLAUDE" | "CODEX";
1109
1110
  id: string;
@@ -1135,6 +1136,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1135
1136
  token: string;
1136
1137
  } | undefined;
1137
1138
  mode?: "ask" | "plan" | "auto" | "full" | undefined;
1139
+ workMode?: unknown;
1138
1140
  model?: string | null | undefined;
1139
1141
  effort?: string | null | undefined;
1140
1142
  epoch?: number | undefined;
@@ -1147,10 +1149,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1147
1149
  skipAgentPrompt?: unknown;
1148
1150
  branchHint?: unknown;
1149
1151
  branchPlan?: unknown;
1150
- workMode?: unknown;
1151
1152
  }>;
1152
1153
  }, "strip", z.ZodTypeAny, {
1153
- type: "session_start";
1154
1154
  session: {
1155
1155
  mode: "ask" | "plan" | "auto" | "full";
1156
1156
  agent: "CLAUDE" | "CODEX";
@@ -1189,6 +1189,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1189
1189
  url: string;
1190
1190
  token: string;
1191
1191
  } | undefined;
1192
+ workMode?: "DIRECT" | "BRANCH" | undefined;
1192
1193
  pausedUntil?: string | null | undefined;
1193
1194
  agentLatestVersion?: string | undefined;
1194
1195
  skipAgentPrompt?: boolean | undefined;
@@ -1199,10 +1200,9 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1199
1200
  baseBranch?: string | undefined;
1200
1201
  baseSha?: string | undefined;
1201
1202
  } | undefined;
1202
- workMode?: "DIRECT" | "BRANCH" | undefined;
1203
1203
  };
1204
- }, {
1205
1204
  type: "session_start";
1205
+ }, {
1206
1206
  session: {
1207
1207
  agent: "CLAUDE" | "CODEX";
1208
1208
  id: string;
@@ -1234,6 +1234,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1234
1234
  token: string;
1235
1235
  } | undefined;
1236
1236
  mode?: "ask" | "plan" | "auto" | "full" | undefined;
1237
+ workMode?: unknown;
1237
1238
  model?: string | null | undefined;
1238
1239
  effort?: string | null | undefined;
1239
1240
  epoch?: number | undefined;
@@ -1246,8 +1247,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1246
1247
  skipAgentPrompt?: unknown;
1247
1248
  branchHint?: unknown;
1248
1249
  branchPlan?: unknown;
1249
- workMode?: unknown;
1250
1250
  };
1251
+ type: "session_start";
1251
1252
  }>, z.ZodObject<{
1252
1253
  type: z.ZodLiteral<"session_message">;
1253
1254
  sessionId: z.ZodString;
@@ -1406,8 +1407,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1406
1407
  }, "strip", z.ZodTypeAny, {
1407
1408
  type: "workspace_settings";
1408
1409
  workspaceId: string;
1409
- trustMode?: "STRICT" | "NORMAL" | "AUTO" | undefined;
1410
1410
  agentAutoCommit?: boolean | undefined;
1411
+ trustMode?: "STRICT" | "NORMAL" | "AUTO" | undefined;
1411
1412
  agentPushBan?: boolean | undefined;
1412
1413
  agentProtectedBranches?: string[] | undefined;
1413
1414
  agentAllowForcePush?: boolean | undefined;
@@ -1415,8 +1416,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1415
1416
  }, {
1416
1417
  type: "workspace_settings";
1417
1418
  workspaceId: string;
1418
- trustMode?: "STRICT" | "NORMAL" | "AUTO" | undefined;
1419
1419
  agentAutoCommit?: boolean | undefined;
1420
+ trustMode?: "STRICT" | "NORMAL" | "AUTO" | undefined;
1420
1421
  agentPushBan?: boolean | undefined;
1421
1422
  agentProtectedBranches?: string[] | undefined;
1422
1423
  agentAllowForcePush?: boolean | undefined;
@@ -1651,7 +1652,28 @@ export type RunnerFrame = {
1651
1652
  from: string | null;
1652
1653
  to: string;
1653
1654
  };
1654
- } | {
1655
+ }
1656
+ /**
1657
+ * What this machine's own load looks like, right now (plan §5.3).
1658
+ *
1659
+ * A frame of its own rather than a field of `hello`, for the same reason
1660
+ * `agent_versions` is one: `hello` is composed once per process and replayed
1661
+ * on every reconnect, so a load put there would be frozen at daemon start —
1662
+ * a number that is always wrong except in the first second of the machine's
1663
+ * life. This one is measured on a timer and sent only when it moved.
1664
+ *
1665
+ * Nothing static travels here. `machine: {cpuCount, memTotalBytes,
1666
+ * memAvailableBytes}` is already in `hello`, and repeating facts is how two
1667
+ * sources of one truth start disagreeing. `cpuCount` is the single exception
1668
+ * and it earns its place: load1 without it cannot be read as a ratio, and a
1669
+ * consumer joining two frames to find out would eventually paint one
1670
+ * machine's load against another's core count.
1671
+ *
1672
+ * Fields and thresholds mirror `@devbridge/shared` — see `host-load.ts`.
1673
+ */
1674
+ | ({
1675
+ type: 'host_load';
1676
+ } & HostLoadFrame) | {
1655
1677
  type: 'pong';
1656
1678
  };
1657
1679
  //# sourceMappingURL=protocol.d.ts.map