@bridge4dev/runner 0.31.0 → 0.33.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.
@@ -38,26 +38,52 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
38
38
  * scratch anyway; the bounds here only keep nonsense off the wire.
39
39
  */
40
40
  agentPromptPath: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
41
+ /**
42
+ * Session 18: the project's git policy for agents.
43
+ *
44
+ * `.optional().catch(undefined)` for the QA-100 MAJOR-1 reason above — this
45
+ * rides inside `hello_ack` with every session on the server, so a malformed
46
+ * value must cost its own session's policy at most, never the frame.
47
+ *
48
+ * «Costs its own session's policy» is safe here BECAUSE of the polarity in
49
+ * `policy.ts`: `agentPushBan` resolves `undefined` to «refuse», so a value
50
+ * this schema drops leaves the session strictly more restricted, never less.
51
+ * That property is the whole reason `.catch(undefined)` is acceptable on a
52
+ * security field, and it is why the resolution lives in exactly one
53
+ * function rather than at each point of use.
54
+ */
55
+ agentPushBan: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
56
+ agentProtectedBranches: z.ZodCatch<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
57
+ agentAllowForcePush: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
58
+ agentAllowDestructiveGit: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
41
59
  budgetUsd: z.ZodNullable<z.ZodNumber>;
42
60
  budgetMinutes: z.ZodNullable<z.ZodNumber>;
43
61
  }, "strip", z.ZodTypeAny, {
62
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
44
63
  id: string;
45
64
  path: string;
46
65
  projectId: string;
47
- trustMode: "STRICT" | "NORMAL" | "AUTO";
48
66
  budgetUsd: number | null;
49
67
  budgetMinutes: number | null;
50
68
  agentAutoCommit?: boolean | undefined;
69
+ agentPushBan?: boolean | undefined;
51
70
  agentPromptPath?: string | null | undefined;
71
+ agentProtectedBranches?: string[] | undefined;
72
+ agentAllowForcePush?: boolean | undefined;
73
+ agentAllowDestructiveGit?: boolean | undefined;
52
74
  }, {
75
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
53
76
  id: string;
54
77
  path: string;
55
78
  projectId: string;
56
- trustMode: "STRICT" | "NORMAL" | "AUTO";
57
79
  budgetUsd: number | null;
58
80
  budgetMinutes: number | null;
59
81
  agentAutoCommit?: boolean | undefined;
82
+ agentPushBan?: unknown;
60
83
  agentPromptPath?: unknown;
84
+ agentProtectedBranches?: unknown;
85
+ agentAllowForcePush?: unknown;
86
+ agentAllowDestructiveGit?: unknown;
61
87
  }>;
62
88
  /**
63
89
  * «Run this one without the project's agent prompt.» Absent means no — both
@@ -153,14 +179,18 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
153
179
  activeMsBase: number;
154
180
  extraBudgetMinutes: number | null;
155
181
  workspace: {
182
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
156
183
  id: string;
157
184
  path: string;
158
185
  projectId: string;
159
- trustMode: "STRICT" | "NORMAL" | "AUTO";
160
186
  budgetUsd: number | null;
161
187
  budgetMinutes: number | null;
162
188
  agentAutoCommit?: boolean | undefined;
189
+ agentPushBan?: boolean | undefined;
163
190
  agentPromptPath?: string | null | undefined;
191
+ agentProtectedBranches?: string[] | undefined;
192
+ agentAllowForcePush?: boolean | undefined;
193
+ agentAllowDestructiveGit?: boolean | undefined;
164
194
  };
165
195
  tickets: {
166
196
  number: number;
@@ -188,14 +218,18 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
188
218
  prompt: string;
189
219
  providerSessionId: string | null;
190
220
  workspace: {
221
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
191
222
  id: string;
192
223
  path: string;
193
224
  projectId: string;
194
- trustMode: "STRICT" | "NORMAL" | "AUTO";
195
225
  budgetUsd: number | null;
196
226
  budgetMinutes: number | null;
197
227
  agentAutoCommit?: boolean | undefined;
228
+ agentPushBan?: unknown;
198
229
  agentPromptPath?: unknown;
230
+ agentProtectedBranches?: unknown;
231
+ agentAllowForcePush?: unknown;
232
+ agentAllowDestructiveGit?: unknown;
199
233
  };
200
234
  tickets: {
201
235
  number: number;
@@ -264,26 +298,52 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
264
298
  * scratch anyway; the bounds here only keep nonsense off the wire.
265
299
  */
266
300
  agentPromptPath: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
301
+ /**
302
+ * Session 18: the project's git policy for agents.
303
+ *
304
+ * `.optional().catch(undefined)` for the QA-100 MAJOR-1 reason above — this
305
+ * rides inside `hello_ack` with every session on the server, so a malformed
306
+ * value must cost its own session's policy at most, never the frame.
307
+ *
308
+ * «Costs its own session's policy» is safe here BECAUSE of the polarity in
309
+ * `policy.ts`: `agentPushBan` resolves `undefined` to «refuse», so a value
310
+ * this schema drops leaves the session strictly more restricted, never less.
311
+ * That property is the whole reason `.catch(undefined)` is acceptable on a
312
+ * security field, and it is why the resolution lives in exactly one
313
+ * function rather than at each point of use.
314
+ */
315
+ agentPushBan: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
316
+ agentProtectedBranches: z.ZodCatch<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
317
+ agentAllowForcePush: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
318
+ agentAllowDestructiveGit: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
267
319
  budgetUsd: z.ZodNullable<z.ZodNumber>;
268
320
  budgetMinutes: z.ZodNullable<z.ZodNumber>;
269
321
  }, "strip", z.ZodTypeAny, {
322
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
270
323
  id: string;
271
324
  path: string;
272
325
  projectId: string;
273
- trustMode: "STRICT" | "NORMAL" | "AUTO";
274
326
  budgetUsd: number | null;
275
327
  budgetMinutes: number | null;
276
328
  agentAutoCommit?: boolean | undefined;
329
+ agentPushBan?: boolean | undefined;
277
330
  agentPromptPath?: string | null | undefined;
331
+ agentProtectedBranches?: string[] | undefined;
332
+ agentAllowForcePush?: boolean | undefined;
333
+ agentAllowDestructiveGit?: boolean | undefined;
278
334
  }, {
335
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
279
336
  id: string;
280
337
  path: string;
281
338
  projectId: string;
282
- trustMode: "STRICT" | "NORMAL" | "AUTO";
283
339
  budgetUsd: number | null;
284
340
  budgetMinutes: number | null;
285
341
  agentAutoCommit?: boolean | undefined;
342
+ agentPushBan?: unknown;
286
343
  agentPromptPath?: unknown;
344
+ agentProtectedBranches?: unknown;
345
+ agentAllowForcePush?: unknown;
346
+ agentAllowDestructiveGit?: unknown;
287
347
  }>;
288
348
  /**
289
349
  * «Run this one without the project's agent prompt.» Absent means no — both
@@ -379,14 +439,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
379
439
  activeMsBase: number;
380
440
  extraBudgetMinutes: number | null;
381
441
  workspace: {
442
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
382
443
  id: string;
383
444
  path: string;
384
445
  projectId: string;
385
- trustMode: "STRICT" | "NORMAL" | "AUTO";
386
446
  budgetUsd: number | null;
387
447
  budgetMinutes: number | null;
388
448
  agentAutoCommit?: boolean | undefined;
449
+ agentPushBan?: boolean | undefined;
389
450
  agentPromptPath?: string | null | undefined;
451
+ agentProtectedBranches?: string[] | undefined;
452
+ agentAllowForcePush?: boolean | undefined;
453
+ agentAllowDestructiveGit?: boolean | undefined;
390
454
  };
391
455
  tickets: {
392
456
  number: number;
@@ -414,14 +478,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
414
478
  prompt: string;
415
479
  providerSessionId: string | null;
416
480
  workspace: {
481
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
417
482
  id: string;
418
483
  path: string;
419
484
  projectId: string;
420
- trustMode: "STRICT" | "NORMAL" | "AUTO";
421
485
  budgetUsd: number | null;
422
486
  budgetMinutes: number | null;
423
487
  agentAutoCommit?: boolean | undefined;
488
+ agentPushBan?: unknown;
424
489
  agentPromptPath?: unknown;
490
+ agentProtectedBranches?: unknown;
491
+ agentAllowForcePush?: unknown;
492
+ agentAllowDestructiveGit?: unknown;
425
493
  };
426
494
  tickets: {
427
495
  number: number;
@@ -465,14 +533,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
465
533
  activeMsBase: number;
466
534
  extraBudgetMinutes: number | null;
467
535
  workspace: {
536
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
468
537
  id: string;
469
538
  path: string;
470
539
  projectId: string;
471
- trustMode: "STRICT" | "NORMAL" | "AUTO";
472
540
  budgetUsd: number | null;
473
541
  budgetMinutes: number | null;
474
542
  agentAutoCommit?: boolean | undefined;
543
+ agentPushBan?: boolean | undefined;
475
544
  agentPromptPath?: string | null | undefined;
545
+ agentProtectedBranches?: string[] | undefined;
546
+ agentAllowForcePush?: boolean | undefined;
547
+ agentAllowDestructiveGit?: boolean | undefined;
476
548
  };
477
549
  tickets: {
478
550
  number: number;
@@ -506,14 +578,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
506
578
  prompt: string;
507
579
  providerSessionId: string | null;
508
580
  workspace: {
581
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
509
582
  id: string;
510
583
  path: string;
511
584
  projectId: string;
512
- trustMode: "STRICT" | "NORMAL" | "AUTO";
513
585
  budgetUsd: number | null;
514
586
  budgetMinutes: number | null;
515
587
  agentAutoCommit?: boolean | undefined;
588
+ agentPushBan?: unknown;
516
589
  agentPromptPath?: unknown;
590
+ agentProtectedBranches?: unknown;
591
+ agentAllowForcePush?: unknown;
592
+ agentAllowDestructiveGit?: unknown;
517
593
  };
518
594
  tickets: {
519
595
  number: number;
@@ -606,26 +682,52 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
606
682
  * scratch anyway; the bounds here only keep nonsense off the wire.
607
683
  */
608
684
  agentPromptPath: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
685
+ /**
686
+ * Session 18: the project's git policy for agents.
687
+ *
688
+ * `.optional().catch(undefined)` for the QA-100 MAJOR-1 reason above — this
689
+ * rides inside `hello_ack` with every session on the server, so a malformed
690
+ * value must cost its own session's policy at most, never the frame.
691
+ *
692
+ * «Costs its own session's policy» is safe here BECAUSE of the polarity in
693
+ * `policy.ts`: `agentPushBan` resolves `undefined` to «refuse», so a value
694
+ * this schema drops leaves the session strictly more restricted, never less.
695
+ * That property is the whole reason `.catch(undefined)` is acceptable on a
696
+ * security field, and it is why the resolution lives in exactly one
697
+ * function rather than at each point of use.
698
+ */
699
+ agentPushBan: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
700
+ agentProtectedBranches: z.ZodCatch<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
701
+ agentAllowForcePush: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
702
+ agentAllowDestructiveGit: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
609
703
  budgetUsd: z.ZodNullable<z.ZodNumber>;
610
704
  budgetMinutes: z.ZodNullable<z.ZodNumber>;
611
705
  }, "strip", z.ZodTypeAny, {
706
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
612
707
  id: string;
613
708
  path: string;
614
709
  projectId: string;
615
- trustMode: "STRICT" | "NORMAL" | "AUTO";
616
710
  budgetUsd: number | null;
617
711
  budgetMinutes: number | null;
618
712
  agentAutoCommit?: boolean | undefined;
713
+ agentPushBan?: boolean | undefined;
619
714
  agentPromptPath?: string | null | undefined;
715
+ agentProtectedBranches?: string[] | undefined;
716
+ agentAllowForcePush?: boolean | undefined;
717
+ agentAllowDestructiveGit?: boolean | undefined;
620
718
  }, {
719
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
621
720
  id: string;
622
721
  path: string;
623
722
  projectId: string;
624
- trustMode: "STRICT" | "NORMAL" | "AUTO";
625
723
  budgetUsd: number | null;
626
724
  budgetMinutes: number | null;
627
725
  agentAutoCommit?: boolean | undefined;
726
+ agentPushBan?: unknown;
628
727
  agentPromptPath?: unknown;
728
+ agentProtectedBranches?: unknown;
729
+ agentAllowForcePush?: unknown;
730
+ agentAllowDestructiveGit?: unknown;
629
731
  }>;
630
732
  /**
631
733
  * «Run this one without the project's agent prompt.» Absent means no — both
@@ -721,14 +823,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
721
823
  activeMsBase: number;
722
824
  extraBudgetMinutes: number | null;
723
825
  workspace: {
826
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
724
827
  id: string;
725
828
  path: string;
726
829
  projectId: string;
727
- trustMode: "STRICT" | "NORMAL" | "AUTO";
728
830
  budgetUsd: number | null;
729
831
  budgetMinutes: number | null;
730
832
  agentAutoCommit?: boolean | undefined;
833
+ agentPushBan?: boolean | undefined;
731
834
  agentPromptPath?: string | null | undefined;
835
+ agentProtectedBranches?: string[] | undefined;
836
+ agentAllowForcePush?: boolean | undefined;
837
+ agentAllowDestructiveGit?: boolean | undefined;
732
838
  };
733
839
  tickets: {
734
840
  number: number;
@@ -756,14 +862,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
756
862
  prompt: string;
757
863
  providerSessionId: string | null;
758
864
  workspace: {
865
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
759
866
  id: string;
760
867
  path: string;
761
868
  projectId: string;
762
- trustMode: "STRICT" | "NORMAL" | "AUTO";
763
869
  budgetUsd: number | null;
764
870
  budgetMinutes: number | null;
765
871
  agentAutoCommit?: boolean | undefined;
872
+ agentPushBan?: unknown;
766
873
  agentPromptPath?: unknown;
874
+ agentProtectedBranches?: unknown;
875
+ agentAllowForcePush?: unknown;
876
+ agentAllowDestructiveGit?: unknown;
767
877
  };
768
878
  tickets: {
769
879
  number: number;
@@ -805,14 +915,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
805
915
  activeMsBase: number;
806
916
  extraBudgetMinutes: number | null;
807
917
  workspace: {
918
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
808
919
  id: string;
809
920
  path: string;
810
921
  projectId: string;
811
- trustMode: "STRICT" | "NORMAL" | "AUTO";
812
922
  budgetUsd: number | null;
813
923
  budgetMinutes: number | null;
814
924
  agentAutoCommit?: boolean | undefined;
925
+ agentPushBan?: boolean | undefined;
815
926
  agentPromptPath?: string | null | undefined;
927
+ agentProtectedBranches?: string[] | undefined;
928
+ agentAllowForcePush?: boolean | undefined;
929
+ agentAllowDestructiveGit?: boolean | undefined;
816
930
  };
817
931
  tickets: {
818
932
  number: number;
@@ -843,14 +957,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
843
957
  prompt: string;
844
958
  providerSessionId: string | null;
845
959
  workspace: {
960
+ trustMode: "STRICT" | "NORMAL" | "AUTO";
846
961
  id: string;
847
962
  path: string;
848
963
  projectId: string;
849
- trustMode: "STRICT" | "NORMAL" | "AUTO";
850
964
  budgetUsd: number | null;
851
965
  budgetMinutes: number | null;
852
966
  agentAutoCommit?: boolean | undefined;
967
+ agentPushBan?: unknown;
853
968
  agentPromptPath?: unknown;
969
+ agentProtectedBranches?: unknown;
970
+ agentAllowForcePush?: unknown;
971
+ agentAllowDestructiveGit?: unknown;
854
972
  };
855
973
  tickets: {
856
974
  number: number;
@@ -1005,16 +1123,28 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1005
1123
  workspaceId: z.ZodString;
1006
1124
  trustMode: z.ZodOptional<z.ZodEnum<["STRICT", "NORMAL", "AUTO"]>>;
1007
1125
  agentAutoCommit: z.ZodOptional<z.ZodBoolean>;
1126
+ agentPushBan: z.ZodOptional<z.ZodBoolean>;
1127
+ agentProtectedBranches: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1128
+ agentAllowForcePush: z.ZodOptional<z.ZodBoolean>;
1129
+ agentAllowDestructiveGit: z.ZodOptional<z.ZodBoolean>;
1008
1130
  }, "strip", z.ZodTypeAny, {
1009
1131
  type: "workspace_settings";
1010
1132
  workspaceId: string;
1011
- agentAutoCommit?: boolean | undefined;
1012
1133
  trustMode?: "STRICT" | "NORMAL" | "AUTO" | undefined;
1134
+ agentAutoCommit?: boolean | undefined;
1135
+ agentPushBan?: boolean | undefined;
1136
+ agentProtectedBranches?: string[] | undefined;
1137
+ agentAllowForcePush?: boolean | undefined;
1138
+ agentAllowDestructiveGit?: boolean | undefined;
1013
1139
  }, {
1014
1140
  type: "workspace_settings";
1015
1141
  workspaceId: string;
1016
- agentAutoCommit?: boolean | undefined;
1017
1142
  trustMode?: "STRICT" | "NORMAL" | "AUTO" | undefined;
1143
+ agentAutoCommit?: boolean | undefined;
1144
+ agentPushBan?: boolean | undefined;
1145
+ agentProtectedBranches?: string[] | undefined;
1146
+ agentAllowForcePush?: boolean | undefined;
1147
+ agentAllowDestructiveGit?: boolean | undefined;
1018
1148
  }>, z.ZodObject<{
1019
1149
  type: z.ZodLiteral<"session_settings">;
1020
1150
  sessionId: z.ZodString;
package/dist/protocol.js CHANGED
@@ -64,6 +64,31 @@ export const SessionDescriptorSchema = z.object({
64
64
  * scratch anyway; the bounds here only keep nonsense off the wire.
65
65
  */
66
66
  agentPromptPath: z.string().max(300).nullable().optional().catch(undefined),
67
+ /**
68
+ * Session 18: the project's git policy for agents.
69
+ *
70
+ * `.optional().catch(undefined)` for the QA-100 MAJOR-1 reason above — this
71
+ * rides inside `hello_ack` with every session on the server, so a malformed
72
+ * value must cost its own session's policy at most, never the frame.
73
+ *
74
+ * «Costs its own session's policy» is safe here BECAUSE of the polarity in
75
+ * `policy.ts`: `agentPushBan` resolves `undefined` to «refuse», so a value
76
+ * this schema drops leaves the session strictly more restricted, never less.
77
+ * That property is the whole reason `.catch(undefined)` is acceptable on a
78
+ * security field, and it is why the resolution lives in exactly one
79
+ * function rather than at each point of use.
80
+ */
81
+ agentPushBan: z.boolean().optional().catch(undefined),
82
+ agentProtectedBranches: z
83
+ .array(z
84
+ .string()
85
+ .max(200)
86
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._\-/]*$/))
87
+ .max(20)
88
+ .optional()
89
+ .catch(undefined),
90
+ agentAllowForcePush: z.boolean().optional().catch(undefined),
91
+ agentAllowDestructiveGit: z.boolean().optional().catch(undefined),
67
92
  budgetUsd: z.number().nullable(),
68
93
  budgetMinutes: z.number().nullable(),
69
94
  }),
@@ -230,6 +255,19 @@ export const GatewayFrameSchema = z.discriminatedUnion('type', [
230
255
  workspaceId: z.string().min(1).max(64),
231
256
  trustMode: z.enum(['STRICT', 'NORMAL', 'AUTO']).optional(),
232
257
  agentAutoCommit: z.boolean().optional(),
258
+ // Session 18 — the git policy, for the same reason: read on every tool
259
+ // call, so switching «Принудительно запретить push» back on must not
260
+ // require stopping the agent first.
261
+ agentPushBan: z.boolean().optional(),
262
+ agentProtectedBranches: z
263
+ .array(z
264
+ .string()
265
+ .max(200)
266
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._\-/]*$/))
267
+ .max(20)
268
+ .optional(),
269
+ agentAllowForcePush: z.boolean().optional(),
270
+ agentAllowDestructiveGit: z.boolean().optional(),
233
271
  }),
234
272
  z.object({
235
273
  type: z.literal('session_settings'),
@@ -22,6 +22,28 @@ import { availableModes, MODE_REFUSED_TEXT } from './adapters/types.js';
22
22
  /** Refusals shared by every checkpoint command (ticket #126). */
23
23
  const CHECKPOINTS_OFF = 'Restore points are switched off on this server ([checkpoints] enabled = false)';
24
24
  const AGENT_BUSY = 'The agent is still working — stop the turn first';
25
+ /**
26
+ * The project's git policy, lifted out of a session descriptor (session 18).
27
+ *
28
+ * One function so that «which descriptor fields are the git policy» is answered
29
+ * once. Absent fields are passed through as absent — `resolveGitPolicy` in
30
+ * `policy.ts` is the single place that decides what absent MEANS, and inventing
31
+ * a default here would put a second such place in the codebase, facing the
32
+ * other way.
33
+ */
34
+ function gitPolicyOf(descriptor) {
35
+ const w = descriptor.workspace;
36
+ return {
37
+ ...(w.agentPushBan !== undefined ? { agentPushBan: w.agentPushBan } : {}),
38
+ ...(w.agentProtectedBranches !== undefined
39
+ ? { agentProtectedBranches: w.agentProtectedBranches }
40
+ : {}),
41
+ ...(w.agentAllowForcePush !== undefined ? { agentAllowForcePush: w.agentAllowForcePush } : {}),
42
+ ...(w.agentAllowDestructiveGit !== undefined
43
+ ? { agentAllowDestructiveGit: w.agentAllowDestructiveGit }
44
+ : {}),
45
+ };
46
+ }
25
47
  export class Supervisor {
26
48
  ws;
27
49
  opts;
@@ -131,11 +153,43 @@ export class Supervisor {
131
153
  if (frame.agentAutoCommit !== undefined) {
132
154
  running.descriptor.workspace.agentAutoCommit = frame.agentAutoCommit;
133
155
  }
156
+ /**
157
+ * Session 18: the git policy, applied live for the same reason as the
158
+ * two above — it is read on every tool call, so «switch push back
159
+ * off» must not mean «stop the agent first».
160
+ *
161
+ * The four fields move together, and the API sends all four in every
162
+ * frame that touches any of them, so a partial update cannot leave
163
+ * the runner with «push is allowed now» beside a protected list from
164
+ * before the change. `!== undefined` on the FRAME is what tells a
165
+ * policy change apart from a trust-only frame; inside the object,
166
+ * `undefined` keeps its restrictive meaning as everywhere else.
167
+ */
168
+ const policyChanged = frame.agentPushBan !== undefined ||
169
+ frame.agentProtectedBranches !== undefined ||
170
+ frame.agentAllowForcePush !== undefined ||
171
+ frame.agentAllowDestructiveGit !== undefined;
172
+ if (policyChanged) {
173
+ const next = {
174
+ ...(frame.agentPushBan !== undefined ? { agentPushBan: frame.agentPushBan } : {}),
175
+ ...(frame.agentProtectedBranches !== undefined
176
+ ? { agentProtectedBranches: frame.agentProtectedBranches }
177
+ : {}),
178
+ ...(frame.agentAllowForcePush !== undefined
179
+ ? { agentAllowForcePush: frame.agentAllowForcePush }
180
+ : {}),
181
+ ...(frame.agentAllowDestructiveGit !== undefined
182
+ ? { agentAllowDestructiveGit: frame.agentAllowDestructiveGit }
183
+ : {}),
184
+ };
185
+ Object.assign(running.descriptor.workspace, next);
186
+ }
134
187
  running.session?.setWorkspacePolicy({
135
188
  ...(frame.trustMode !== undefined ? { trustMode: frame.trustMode } : {}),
136
189
  ...(frame.agentAutoCommit !== undefined
137
190
  ? { agentAutoCommit: frame.agentAutoCommit }
138
191
  : {}),
192
+ ...(policyChanged ? { gitPolicy: gitPolicyOf(running.descriptor) } : {}),
139
193
  });
140
194
  }
141
195
  break;
@@ -406,6 +460,11 @@ export class Supervisor {
406
460
  ...(descriptor.workspace.agentAutoCommit === undefined
407
461
  ? {}
408
462
  : { agentAutoCommit: descriptor.workspace.agentAutoCommit }),
463
+ // Session 18. Always present, even when every field inside it is absent:
464
+ // an absent OBJECT and an object of absent fields resolve identically
465
+ // (`resolveGitPolicy` gives both the restrictive reading), and passing it
466
+ // unconditionally keeps one code path instead of two.
467
+ gitPolicy: gitPolicyOf(descriptor),
409
468
  mode: running.mode,
410
469
  ...(running.model ? { model: running.model } : {}),
411
470
  ...(running.effort ? { effort: running.effort } : {}),
@@ -3358,16 +3417,58 @@ export function composeInitialPrompt(descriptor) {
3358
3417
  export function composeWorkspaceContext(descriptor, agentPrompt) {
3359
3418
  const sections = [];
3360
3419
  const plan = descriptor.branchPlan;
3420
+ /**
3421
+ * What this session may do about push, in one sentence (session 18).
3422
+ *
3423
+ * `!== false` — silence refuses, exactly as `resolveGitPolicy` reads it. This
3424
+ * text and the rule in `policy.ts` have to agree; an agent told it may push
3425
+ * and then refused has been lied to, and an agent told it may not while the
3426
+ * project allows it wastes the freedom the owner paid for.
3427
+ */
3428
+ const pushLine = (() => {
3429
+ // `?.` and not `.`: this function is exported and called with descriptors
3430
+ // assembled by hand in tests and by older code paths. A missing `workspace`
3431
+ // must land on the refusing branch like every other unknown here, not throw
3432
+ // and take the whole session start with it.
3433
+ if (descriptor.workspace?.agentPushBan !== false) {
3434
+ return '- You cannot push: `git push` is refused for this project. A human presses «Push» in DevBridge when they want the branch on the remote.';
3435
+ }
3436
+ const guarded = descriptor.workspace?.agentProtectedBranches ?? ['main', 'master'];
3437
+ return guarded.length > 0
3438
+ ? `- You may push, except to these protected branches: ${guarded.join(', ')}. Name the branch explicitly — \`git push <remote> <branch>\`.`
3439
+ : '- You may push.';
3440
+ })();
3361
3441
  if (plan) {
3362
3442
  const forked = plan.baseBranch ? ` It was branched from \`${plan.baseBranch}\`.` : '';
3363
3443
  sections.push([
3364
3444
  'Git in this session:',
3365
3445
  `- You are in a dedicated worktree on branch \`${plan.branch}\`.${forked}`,
3366
3446
  '- Commit to this branch. Do not switch branches and do not merge into the base branch yourself — a human presses «Apply» in DevBridge, which squash-merges your branch for them.',
3367
- '- You cannot push: `git push` is refused. A human presses «Push» when they want the branch on the remote.',
3447
+ pushLine,
3368
3448
  `- Put a plan or working notes in \`docs/devbridge/${plan.branch.replace(/\//g, '-')}.md\`, unless this repository already has its own convention for where such documents live — if it does, follow that.`,
3369
3449
  ].join('\n'));
3370
3450
  }
3451
+ else {
3452
+ /**
3453
+ * A DIRECT session got NO «Git in this session» block at all, and that is
3454
+ * the older half of this bug (session 16 shipped DIRECT as the default;
3455
+ * this block stayed gated on `branchPlan`, which only a BRANCH session
3456
+ * has). So the arrangement that writes into the person's OWN project
3457
+ * folder was the one told nothing about where it was — while the adapter's
3458
+ * static line went on claiming «a dedicated git worktree on a session
3459
+ * branch», which in DIRECT is simply false.
3460
+ *
3461
+ * Being in somebody's working copy is the case that needs saying MORE, not
3462
+ * less: there is no «Apply» to undo it, and uncommitted changes in that
3463
+ * folder may not be the agent's.
3464
+ */
3465
+ sections.push([
3466
+ 'Git in this session:',
3467
+ '- You are working in the PROJECT FOLDER ITSELF, on the branch it is already checked out on — not in a worktree of your own. There is no session branch and nothing to «apply» afterwards: your commits are immediately on the branch the person works on.',
3468
+ '- Uncommitted changes you find here may be a human’s, or another session’s. Never discard or reset what you did not write.',
3469
+ pushLine,
3470
+ ].join('\n'));
3471
+ }
3371
3472
  // The ticket convention lives HERE, not in the first chat message (session
3372
3473
  // 16). It is a standing rule of the project — true on the twentieth turn as
3373
3474
  // much as the first — and putting it in the visible prompt only trained the
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const RUNNER_VERSION = "0.31.0";
1
+ export declare const RUNNER_VERSION = "0.33.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in sync with package.json by the release script (manual for now).
2
- export const RUNNER_VERSION = '0.31.0';
2
+ export const RUNNER_VERSION = '0.33.0';
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge4dev/runner",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "description": "DevBridge dev runner — connects a dev server to DevBridge and runs agent sessions (Claude Code / Codex)",
5
5
  "homepage": "https://bridge4.dev",
6
6
  "license": "MIT",