@bridge4dev/runner 0.47.0 → 0.49.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/adapters/claude.js +60 -0
- package/dist/adapters/codex-protocol.d.ts +13 -0
- package/dist/adapters/codex-protocol.js +17 -1
- package/dist/adapters/codex.js +129 -16
- package/dist/adapters/types.d.ts +46 -2
- package/dist/levels.d.ts +49 -0
- package/dist/levels.js +51 -0
- package/dist/stop-cycle.d.ts +148 -0
- package/dist/stop-cycle.js +86 -0
- package/dist/supervisor.d.ts +123 -0
- package/dist/supervisor.js +619 -20
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { AgentEvent, AgentSession, InterruptOutcome } from './adapters/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* One managed stop of one agent process (#373).
|
|
4
|
+
*
|
|
5
|
+
* The defect this exists for: stopping a turn used to be a boolean and a hope.
|
|
6
|
+
* The Claude adapter armed a single `aborting` flag, EVERY result read and
|
|
7
|
+
* cleared it, and the second result of the same stop therefore arrived looking
|
|
8
|
+
* like a failure — `turn_end{ok:false}` → `settleTurnStatus` → FAILED, which is
|
|
9
|
+
* terminal. A session that had merely run out of plan limit was buried with a
|
|
10
|
+
* live clock still ticking on it, and nothing would ever wake it again.
|
|
11
|
+
*
|
|
12
|
+
* A cycle is bound to ONE process instance and to one reason. Everything that
|
|
13
|
+
* arrives from that instance while the cycle runs — a late result, the tail of
|
|
14
|
+
* a tool the CLI had already started, the ending our own `close()` produces —
|
|
15
|
+
* belongs to the stop and is not news about the session's health. Everything
|
|
16
|
+
* from any OTHER instance (the process we start after the pause is lifted) is
|
|
17
|
+
* outside it, which is what keeps a real failure after a resume visible.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately a small module of plain rules: the decisions below are the ones
|
|
20
|
+
* a mistake is expensive in, and they are worth being able to test without a
|
|
21
|
+
* supervisor, a fake CLI and a socket.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* How far the stop has got.
|
|
25
|
+
*
|
|
26
|
+
* - `interrupting` — the request is out; the process is still ours to lose.
|
|
27
|
+
* - `parking` — the turn has ended (or will not end), and we are closing
|
|
28
|
+
* the process ourselves. From here on its noises are ours.
|
|
29
|
+
* - `parked` — the event stream ended. Nothing else can arrive.
|
|
30
|
+
*/
|
|
31
|
+
export type StopPhase = 'interrupting' | 'parking' | 'parked';
|
|
32
|
+
/**
|
|
33
|
+
* Who asked.
|
|
34
|
+
*
|
|
35
|
+
* `user` keeps the old contract exactly: one Stop, one ending, and the NEXT
|
|
36
|
+
* genuine failure still reaches the person. `pause` is the one that closes the
|
|
37
|
+
* process and therefore has to own what closing it produces.
|
|
38
|
+
*/
|
|
39
|
+
export type StopReason = 'user' | 'pause';
|
|
40
|
+
export interface StopCycle {
|
|
41
|
+
/** The adapter session this cycle stops. Identity only — never called. */
|
|
42
|
+
readonly session: AgentSession;
|
|
43
|
+
/**
|
|
44
|
+
* Who asked — and it can be RAISED while the stop is in flight.
|
|
45
|
+
*
|
|
46
|
+
* A clock landing on a Stop the person pressed a second earlier is the same
|
|
47
|
+
* stop, not a second one: the CLI has one outstanding interrupt and will
|
|
48
|
+
* answer it once. But it is now a PAUSE, and a pause closes the process and
|
|
49
|
+
* owns what closing it produces. Left as `user` the cycle would step aside at
|
|
50
|
+
* the first ending and the second result would file the session FAILED —
|
|
51
|
+
* the incident, reached through a door the fix did not cover.
|
|
52
|
+
*/
|
|
53
|
+
reason: StopReason;
|
|
54
|
+
/**
|
|
55
|
+
* Which clock this stop belongs to, for the log.
|
|
56
|
+
*
|
|
57
|
+
* Deliberately NOT a guard: what decides whether two stops are the same one
|
|
58
|
+
* is the process instance, and «a clock lifted and set again» is already
|
|
59
|
+
* answered by the release dropping a cycle that closed nothing. The number is
|
|
60
|
+
* here so a line in journald can be tied to the pause that caused it —
|
|
61
|
+
* reading two stops in one session and not knowing whether they answered one
|
|
62
|
+
* clock or two is how the incident took a day to reconstruct.
|
|
63
|
+
*/
|
|
64
|
+
readonly epoch: number;
|
|
65
|
+
phase: StopPhase;
|
|
66
|
+
/** The single interrupt round-trip. Every later request joins this one. */
|
|
67
|
+
inFlight: Promise<void> | null;
|
|
68
|
+
/** What the CLI said about the request, once it said anything. */
|
|
69
|
+
outcome: InterruptOutcome | null;
|
|
70
|
+
/** The control channel refused while the process was still running. */
|
|
71
|
+
refused: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* We closed this process ourselves.
|
|
74
|
+
*
|
|
75
|
+
* The line invariant 7 is drawn on. Everything a process says after we have
|
|
76
|
+
* closed it is the closing; everything it says before is still news about the
|
|
77
|
+
* session. `phase` alone cannot answer this — a stop that is refused parking
|
|
78
|
+
* (an open permission card) reaches `parking` with the process untouched.
|
|
79
|
+
*/
|
|
80
|
+
closed: boolean;
|
|
81
|
+
/** The one `turn_end` this cycle publishes has gone out. */
|
|
82
|
+
turnEndSent: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* The stop has run its course.
|
|
85
|
+
*
|
|
86
|
+
* It still OWNS this process's endings — a late result after a refused park
|
|
87
|
+
* is exactly the incident's second result — but it no longer stands in the way
|
|
88
|
+
* of a new stop: a turn that starts minutes later, from inside a CLI nobody
|
|
89
|
+
* closed, has to be stoppable in its own right.
|
|
90
|
+
*/
|
|
91
|
+
settled: boolean;
|
|
92
|
+
/** There was a turn to stop — so an ending is owed to the feed. */
|
|
93
|
+
readonly hadTurn: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Tool calls already in flight when the stop began.
|
|
96
|
+
*
|
|
97
|
+
* The reason the incident fired a SECOND interrupt: the result of the
|
|
98
|
+
* question this runner had just withdrawn came back as an ordinary tool
|
|
99
|
+
* event, and «the agent is producing output» could not tell it from a turn
|
|
100
|
+
* starting up. Ownership answers it — a result for one of these ids is the
|
|
101
|
+
* tail of the work being stopped, not new work.
|
|
102
|
+
*/
|
|
103
|
+
readonly toolsAtStop: ReadonlySet<string>;
|
|
104
|
+
/** Waiting for the result that should follow an accepted interrupt. */
|
|
105
|
+
settleTimer?: ReturnType<typeof setTimeout>;
|
|
106
|
+
}
|
|
107
|
+
/** Is this cycle the one that owns `session`, and is it still live? */
|
|
108
|
+
export declare function ownsProcess(cycle: StopCycle | undefined, session: AgentSession | null): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* What to do with a `turn_end` that arrived inside a stop cycle.
|
|
111
|
+
*
|
|
112
|
+
* - `ordinary` — nothing special: a manual Stop keeps the contract it has had
|
|
113
|
+
* since QA-120, including the rule that the next real failure is not hidden.
|
|
114
|
+
* - `stopped` — the one ending this cycle publishes: `ok`, `aborted`, and the
|
|
115
|
+
* facts the turn actually carried.
|
|
116
|
+
* - `tail` — a second (or third) ending of a turn that has already been
|
|
117
|
+
* closed out. The incident's `error_during_execution`, 1.2 s late.
|
|
118
|
+
*/
|
|
119
|
+
export declare function turnEndVerdict(cycle: StopCycle): 'ordinary' | 'stopped' | 'tail';
|
|
120
|
+
/**
|
|
121
|
+
* What to do with an `error` that arrived inside a stop cycle.
|
|
122
|
+
*
|
|
123
|
+
* The one place the difference between «the process fell over» and «the process
|
|
124
|
+
* was closed» has to be paid attention to, and the only honest source for it is
|
|
125
|
+
* the adapter's own `processGone` — never the sentence in the message, which is
|
|
126
|
+
* written by the CLI and changes with it.
|
|
127
|
+
*/
|
|
128
|
+
export declare function errorVerdict(cycle: StopCycle, processGone: boolean): 'failure' | 'tail';
|
|
129
|
+
/**
|
|
130
|
+
* Is this event work STARTING, rather than the tail of what is being stopped?
|
|
131
|
+
*
|
|
132
|
+
* Only a genuinely new turn deserves a second stop. A tool result belonging to
|
|
133
|
+
* a call that was already running when the stop began is the first stop still
|
|
134
|
+
* finishing — firing another interrupt at it is what gave the incident two
|
|
135
|
+
* interrupts, two `aborting` arms and one result too few to spend them.
|
|
136
|
+
*/
|
|
137
|
+
export declare function isNewWork(event: AgentEvent, cycle: StopCycle | undefined): boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Is this the result of a tool call that was already running when we stopped?
|
|
140
|
+
*
|
|
141
|
+
* The incident's `tool_result` at seq 832 was exactly this: the withdrawn
|
|
142
|
+
* question's own refusal, arriving three seconds after the stop. Kept apart
|
|
143
|
+
* from «a result we never saw start» rather than lumping every result together
|
|
144
|
+
* — the two say different things about the process, and the diagnostic log has
|
|
145
|
+
* to be able to say which one happened.
|
|
146
|
+
*/
|
|
147
|
+
export declare function isStoppedToolTail(cycle: StopCycle, event: AgentEvent): boolean;
|
|
148
|
+
//# sourceMappingURL=stop-cycle.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Is this cycle the one that owns `session`, and is it still live? */
|
|
2
|
+
export function ownsProcess(cycle, session) {
|
|
3
|
+
return cycle !== undefined && session !== null && cycle.session === session;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* What to do with a `turn_end` that arrived inside a stop cycle.
|
|
7
|
+
*
|
|
8
|
+
* - `ordinary` — nothing special: a manual Stop keeps the contract it has had
|
|
9
|
+
* since QA-120, including the rule that the next real failure is not hidden.
|
|
10
|
+
* - `stopped` — the one ending this cycle publishes: `ok`, `aborted`, and the
|
|
11
|
+
* facts the turn actually carried.
|
|
12
|
+
* - `tail` — a second (or third) ending of a turn that has already been
|
|
13
|
+
* closed out. The incident's `error_during_execution`, 1.2 s late.
|
|
14
|
+
*/
|
|
15
|
+
export function turnEndVerdict(cycle) {
|
|
16
|
+
if (cycle.reason === 'user')
|
|
17
|
+
return 'ordinary';
|
|
18
|
+
if (cycle.turnEndSent)
|
|
19
|
+
return 'tail';
|
|
20
|
+
return 'stopped';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What to do with an `error` that arrived inside a stop cycle.
|
|
24
|
+
*
|
|
25
|
+
* The one place the difference between «the process fell over» and «the process
|
|
26
|
+
* was closed» has to be paid attention to, and the only honest source for it is
|
|
27
|
+
* the adapter's own `processGone` — never the sentence in the message, which is
|
|
28
|
+
* written by the CLI and changes with it.
|
|
29
|
+
*/
|
|
30
|
+
export function errorVerdict(cycle, processGone) {
|
|
31
|
+
// A manual Stop does not close anything, so nothing here is ours.
|
|
32
|
+
if (cycle.reason === 'user')
|
|
33
|
+
return 'failure';
|
|
34
|
+
// We have not closed it: a process dying on its own is a real failure, and
|
|
35
|
+
// burying it under the pause would be the original defect wearing the fix's
|
|
36
|
+
// clothes. Everything else from a process we asked to stop is the stopping.
|
|
37
|
+
if (!cycle.closed)
|
|
38
|
+
return processGone ? 'failure' : 'tail';
|
|
39
|
+
// We closed it. Whatever it says on the way out is the closing.
|
|
40
|
+
return 'tail';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Is this event work STARTING, rather than the tail of what is being stopped?
|
|
44
|
+
*
|
|
45
|
+
* Only a genuinely new turn deserves a second stop. A tool result belonging to
|
|
46
|
+
* a call that was already running when the stop began is the first stop still
|
|
47
|
+
* finishing — firing another interrupt at it is what gave the incident two
|
|
48
|
+
* interrupts, two `aborting` arms and one result too few to spend them.
|
|
49
|
+
*/
|
|
50
|
+
export function isNewWork(event, cycle) {
|
|
51
|
+
if (event.type !== 'tool')
|
|
52
|
+
return true;
|
|
53
|
+
if (event.phase === 'use')
|
|
54
|
+
return true;
|
|
55
|
+
// A result is always the tail of something, and the only question is whose.
|
|
56
|
+
// The tail of a call that was ALREADY RUNNING when we stopped is the stop
|
|
57
|
+
// finishing — the incident's seq 832, which fired a second interrupt. A
|
|
58
|
+
// result for a call we did not see start, on a process we asked to stop, is
|
|
59
|
+
// the opposite: something began after the stop and has now finished.
|
|
60
|
+
//
|
|
61
|
+
// Without a cycle there is no ownership to read, and a result on its own is
|
|
62
|
+
// no evidence of a turn beginning — a turn begins with a call, a thought or a
|
|
63
|
+
// sentence. Excluding every result regardless of whose it is is what the plan
|
|
64
|
+
// forbids, and this is the distinction it asks for.
|
|
65
|
+
if (!cycle)
|
|
66
|
+
return false;
|
|
67
|
+
if (event.toolUseId === undefined)
|
|
68
|
+
return false;
|
|
69
|
+
return !cycle.toolsAtStop.has(event.toolUseId);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Is this the result of a tool call that was already running when we stopped?
|
|
73
|
+
*
|
|
74
|
+
* The incident's `tool_result` at seq 832 was exactly this: the withdrawn
|
|
75
|
+
* question's own refusal, arriving three seconds after the stop. Kept apart
|
|
76
|
+
* from «a result we never saw start» rather than lumping every result together
|
|
77
|
+
* — the two say different things about the process, and the diagnostic log has
|
|
78
|
+
* to be able to say which one happened.
|
|
79
|
+
*/
|
|
80
|
+
export function isStoppedToolTail(cycle, event) {
|
|
81
|
+
return (event.type === 'tool' &&
|
|
82
|
+
event.phase === 'result' &&
|
|
83
|
+
event.toolUseId !== undefined &&
|
|
84
|
+
cycle.toolsAtStop.has(event.toolUseId));
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=stop-cycle.js.map
|
package/dist/supervisor.d.ts
CHANGED
|
@@ -109,6 +109,19 @@ export interface SupervisorOptions {
|
|
|
109
109
|
* firing over a working agent (QA-2026-08-16 M-4). Never set in production.
|
|
110
110
|
*/
|
|
111
111
|
emptyTurnSettleMs?: number;
|
|
112
|
+
/**
|
|
113
|
+
* How long an unchanged plan-usage snapshot is held before it is re-sent
|
|
114
|
+
* (#366) – a test seam over `RATE_LIMITS_RESEND_INTERVAL_MS`, for the same
|
|
115
|
+
* reason `emptyTurnSettleMs` exists: the real floor is three minutes and this
|
|
116
|
+
* suite runs on real timers. Never set in production.
|
|
117
|
+
*/
|
|
118
|
+
rateLimitsResendMs?: number;
|
|
119
|
+
/**
|
|
120
|
+
* How long a stop waits for the ending it asked for (#373) – a test seam over
|
|
121
|
+
* `STOP_SETTLE_MS`, for the same reason `emptyTurnSettleMs` exists. Never set
|
|
122
|
+
* in production.
|
|
123
|
+
*/
|
|
124
|
+
stopSettleMs?: number;
|
|
112
125
|
}
|
|
113
126
|
export declare class Supervisor {
|
|
114
127
|
private readonly ws;
|
|
@@ -136,6 +149,9 @@ export declare class Supervisor {
|
|
|
136
149
|
private static readonly EMPTY_TURN_SETTLE_MS;
|
|
137
150
|
/** The window actually used — the constant, or a test's own shorter one. */
|
|
138
151
|
private readonly emptyTurnSettleMs;
|
|
152
|
+
private readonly rateLimitsResendMs;
|
|
153
|
+
/** The stop-settle window actually used — the constant, or a test's own. */
|
|
154
|
+
private readonly stopSettleMs;
|
|
139
155
|
/** A finished session's journal is kept this long for a late reconnect. */
|
|
140
156
|
private static readonly JOURNAL_TTL_MS;
|
|
141
157
|
/** Backstop: events the API will never accept must not pile up forever. */
|
|
@@ -525,6 +541,29 @@ export declare class Supervisor {
|
|
|
525
541
|
* its timer runs out, and it must end in exactly the way it would have
|
|
526
542
|
* ended immediately. A copy would be two behaviours one edit apart.
|
|
527
543
|
*/
|
|
544
|
+
/**
|
|
545
|
+
* The level gate for the context meter (#366).
|
|
546
|
+
*
|
|
547
|
+
* While a turn is open, a frame goes out only when the meter moved by at
|
|
548
|
+
* least one of the shared thresholds or the window itself changed; the
|
|
549
|
+
* newest held-back value is flushed right before `turn_end`, so the ring at
|
|
550
|
+
* rest is exact. Outside a turn every frame passes – there are only a handful
|
|
551
|
+
* (process start, a model change, and the Claude adapter's own measurement,
|
|
552
|
+
* which resolves AFTER `turn_end`), and each one is news.
|
|
553
|
+
*/
|
|
554
|
+
private forwardContextUsage;
|
|
555
|
+
/** Send the value the gate was holding, if any – strictly before `turn_end`. */
|
|
556
|
+
private flushHeldContextUsage;
|
|
557
|
+
/**
|
|
558
|
+
* The level gate for plan usage (#366).
|
|
559
|
+
*
|
|
560
|
+
* A snapshot goes out when it is the first, when anything but `measuredAt`
|
|
561
|
+
* changed, when it carries a refusal (#258 – always, even twice in a row), or
|
|
562
|
+
* when the one last sent is older than the resend floor. The fingerprint is
|
|
563
|
+
* the whole payload minus the clock, so a field an adapter adds tomorrow is
|
|
564
|
+
* part of it without anybody remembering to list it here.
|
|
565
|
+
*/
|
|
566
|
+
private forwardRateLimits;
|
|
528
567
|
private completeTurn;
|
|
529
568
|
/**
|
|
530
569
|
* Move the session to the status a finished turn leaves it in.
|
|
@@ -729,6 +768,90 @@ export declare class Supervisor {
|
|
|
729
768
|
* turn it stops would bury the feed under a fact nobody asked about.
|
|
730
769
|
*/
|
|
731
770
|
private interruptSession;
|
|
771
|
+
/**
|
|
772
|
+
* How long a stop waits for the ending that should follow it (#373).
|
|
773
|
+
*
|
|
774
|
+
* The transition out of `interrupting` is the first result, not the
|
|
775
|
+
* acknowledgement — an ACK only says the request was heard, and the incident
|
|
776
|
+
* shows an ACK and a result are different events that arrive in either order.
|
|
777
|
+
* This is the backstop for the case where no result comes at all: an idle
|
|
778
|
+
* session had no turn to end, and a CLI can always simply not answer.
|
|
779
|
+
*/
|
|
780
|
+
private static readonly STOP_SETTLE_MS;
|
|
781
|
+
/**
|
|
782
|
+
* How long after one stop the next output is still read as its tail (#196).
|
|
783
|
+
*
|
|
784
|
+
* The floor under the stop cycle rather than a substitute for it: while a
|
|
785
|
+
* cycle is open every event joins it, and this is what keeps a burst from
|
|
786
|
+
* opening a NEW cycle per line in the window after one closed without closing
|
|
787
|
+
* its process. Far shorter than the gap before a genuinely new turn — a
|
|
788
|
+
* background subagent finishing — which still gets its own stop.
|
|
789
|
+
*/
|
|
790
|
+
private static readonly PAUSE_BURST_MS;
|
|
791
|
+
/**
|
|
792
|
+
* Stop one agent process once, and own what stopping it produces (#373).
|
|
793
|
+
*
|
|
794
|
+
* The shape is deliberate: the interrupt is a round trip that can take half a
|
|
795
|
+
* minute (a CLI sitting out a provider retry answers only when it comes back
|
|
796
|
+
* — 28.5 seconds, measured in #357), so nothing here may be on the path the
|
|
797
|
+
* event pump takes. The cycle is stored before the request goes out, which is
|
|
798
|
+
* what makes every later request join this one instead of starting a second.
|
|
799
|
+
*/
|
|
800
|
+
private beginStopCycle;
|
|
801
|
+
/**
|
|
802
|
+
* Wait a bounded time for the ending an accepted interrupt should produce.
|
|
803
|
+
*
|
|
804
|
+
* «Bounded» rather than «for ever» because two real cases produce no ending
|
|
805
|
+
* at all: a pause landing between turns (there was nothing to stop), and a
|
|
806
|
+
* CLI that takes the request and then says nothing. Leaving the cycle open
|
|
807
|
+
* there would hold the seat and the clock indefinitely.
|
|
808
|
+
*/
|
|
809
|
+
private armStopSettle;
|
|
810
|
+
private clearStopSettleTimer;
|
|
811
|
+
/**
|
|
812
|
+
* The turn is over — close the process, keeping the session resumable.
|
|
813
|
+
*
|
|
814
|
+
* This is the change the whole plan is about. A pause used to leave a live
|
|
815
|
+
* CLI standing for up to five hours, which is where the second result came
|
|
816
|
+
* from; now the runner takes the ending it asked for and parks, and the next
|
|
817
|
+
* message (or the clock's own «continue») starts a process that resumes the
|
|
818
|
+
* same conversation.
|
|
819
|
+
*/
|
|
820
|
+
private settleStopCycle;
|
|
821
|
+
/**
|
|
822
|
+
* The agent never said yes — say so, and say only what actually happened.
|
|
823
|
+
*
|
|
824
|
+
* Invariant 5: a stop the CLI refused must not read as a clean cancellation.
|
|
825
|
+
* Which sentence is true depends on what came next, so it is written here and
|
|
826
|
+
* not where the refusal was noticed — a line promising «its process was
|
|
827
|
+
* closed» over a manual Stop, which closes nothing, is the same class of lie
|
|
828
|
+
* as the one this ticket is about (gotcha 324).
|
|
829
|
+
*/
|
|
830
|
+
private sayIfTheStopWasRefused;
|
|
831
|
+
/**
|
|
832
|
+
* Why this paused process must NOT be closed, or null when it may be.
|
|
833
|
+
*
|
|
834
|
+
* Invariant 8 lives here. `park()` calls the adapter's `stop()`, and `stop()`
|
|
835
|
+
* answers every pending permission «denied» in the RUNNER's name — a decision
|
|
836
|
+
* nobody made, in a place that keeps decisions for ninety days. Worse, the API
|
|
837
|
+
* sends nothing when the clock is lifted over an open card
|
|
838
|
+
* (`pauseInterruptedTurn` is false while `openAsks > 0`), so the session would
|
|
839
|
+
* simply stand there, silent, with the card gone.
|
|
840
|
+
*
|
|
841
|
+
* A conversation the CLI has not named yet is the other refusal: parking
|
|
842
|
+
* promises the context is saved, and there is nothing to resume from.
|
|
843
|
+
*/
|
|
844
|
+
private parkRefusalUnderPause;
|
|
845
|
+
/**
|
|
846
|
+
* The one ending a stop cycle publishes (#373).
|
|
847
|
+
*
|
|
848
|
+
* `ok` unconditionally: a turn this runner stopped is not a turn that failed,
|
|
849
|
+
* and `turn_end{ok:false}` is what put a paused session into the terminal
|
|
850
|
+
* status it could never be woken out of. The facts the turn actually carried
|
|
851
|
+
* ride along — `limitBlocked` because the API counts refusals with it, and
|
|
852
|
+
* `produced` because it is what keeps the refusal counter honest (#300).
|
|
853
|
+
*/
|
|
854
|
+
private publishStoppedTurn;
|
|
732
855
|
/** Is this session held under a clock right now (ticket #196)? */
|
|
733
856
|
private static isPaused;
|
|
734
857
|
/**
|