@bridge4dev/runner 0.11.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/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/adapters/claude.d.ts +19 -0
- package/dist/adapters/claude.js +631 -0
- package/dist/adapters/codex-home.d.ts +61 -0
- package/dist/adapters/codex-home.js +234 -0
- package/dist/adapters/codex-protocol.d.ts +59 -0
- package/dist/adapters/codex-protocol.js +204 -0
- package/dist/adapters/codex.d.ts +61 -0
- package/dist/adapters/codex.js +1406 -0
- package/dist/adapters/types.d.ts +183 -0
- package/dist/adapters/types.js +5 -0
- package/dist/async-queue.d.ts +11 -0
- package/dist/async-queue.js +50 -0
- package/dist/attachments.d.ts +72 -0
- package/dist/attachments.js +149 -0
- package/dist/auth-relay.d.ts +57 -0
- package/dist/auth-relay.js +289 -0
- package/dist/config.d.ts +96 -0
- package/dist/config.js +73 -0
- package/dist/fsview.d.ts +20 -0
- package/dist/fsview.js +122 -0
- package/dist/git.d.ts +54 -0
- package/dist/git.js +168 -0
- package/dist/gitops.d.ts +136 -0
- package/dist/gitops.js +596 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +352 -0
- package/dist/journal.d.ts +118 -0
- package/dist/journal.js +300 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +19 -0
- package/dist/paths.d.ts +7 -0
- package/dist/paths.js +33 -0
- package/dist/policy.d.ts +17 -0
- package/dist/policy.js +272 -0
- package/dist/protocol.d.ts +754 -0
- package/dist/protocol.js +154 -0
- package/dist/self-update.d.ts +75 -0
- package/dist/self-update.js +221 -0
- package/dist/status-file.d.ts +14 -0
- package/dist/status-file.js +29 -0
- package/dist/supervisor.d.ts +216 -0
- package/dist/supervisor.js +1648 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/ws-client.d.ts +30 -0
- package/dist/ws-client.js +171 -0
- package/package.json +52 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { JournalStore } from './journal.js';
|
|
2
|
+
import { selfUpdate, type SelfUpdateOutcome } from './self-update.js';
|
|
3
|
+
import type { RunnerWsClient } from './ws-client.js';
|
|
4
|
+
import type { SessionDescriptor } from './protocol.js';
|
|
5
|
+
import type { AgentAdapter } from './adapters/types.js';
|
|
6
|
+
import type { McpConfig } from './adapters/types.js';
|
|
7
|
+
export interface SupervisorOptions {
|
|
8
|
+
adapters: Partial<Record<'CLAUDE' | 'CODEX', AgentAdapter>>;
|
|
9
|
+
mcp?: McpConfig;
|
|
10
|
+
journals?: JournalStore;
|
|
11
|
+
/**
|
|
12
|
+
* The API this runner is paired with — the only origin an update package may
|
|
13
|
+
* come from, and where message attachments are fetched from. Absent means the
|
|
14
|
+
* `self_update` command is refused outright and attachments cannot be pulled.
|
|
15
|
+
*/
|
|
16
|
+
apiUrl?: string;
|
|
17
|
+
/** This server's runner token — used to fetch message attachments. */
|
|
18
|
+
runnerToken?: string;
|
|
19
|
+
/** Test seam for the `self_update` command. */
|
|
20
|
+
selfUpdate?: typeof selfUpdate;
|
|
21
|
+
/**
|
|
22
|
+
* Called after a successful update, once the reply is on the wire. The daemon
|
|
23
|
+
* exits here and systemd starts the new build; without a handler the runner
|
|
24
|
+
* reports the new version only after the next manual restart.
|
|
25
|
+
*/
|
|
26
|
+
onRestartRequested?: (outcome: SelfUpdateOutcome) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Local ceiling on concurrent agent processes, from the runner's own config
|
|
29
|
+
* (layer 1). When set it wins over the API's number if it is stricter — the
|
|
30
|
+
* machine's owner decides how much of their machine an agent fleet may take.
|
|
31
|
+
*/
|
|
32
|
+
maxSessionsLimit?: number;
|
|
33
|
+
}
|
|
34
|
+
export declare class Supervisor {
|
|
35
|
+
private readonly ws;
|
|
36
|
+
private readonly opts;
|
|
37
|
+
private static readonly ORPHAN_MESSAGE_CAP;
|
|
38
|
+
/** A finished session's journal is kept this long for a late reconnect. */
|
|
39
|
+
private static readonly JOURNAL_TTL_MS;
|
|
40
|
+
/** Backstop: events the API will never accept must not pile up forever. */
|
|
41
|
+
private static readonly JOURNAL_HARD_TTL_MS;
|
|
42
|
+
private readonly sessions;
|
|
43
|
+
/**
|
|
44
|
+
* How many agents may run at once, as told by the API (session 8). Starts at
|
|
45
|
+
* 1 so a runner talking to an API that predates the field behaves exactly as
|
|
46
|
+
* it did before: one agent, others parked.
|
|
47
|
+
*/
|
|
48
|
+
private serverMaxSessions;
|
|
49
|
+
/** Messages for sessions this runner does not track yet, keyed by session. */
|
|
50
|
+
private readonly orphanMessages;
|
|
51
|
+
private readonly journals;
|
|
52
|
+
private readonly authRelay;
|
|
53
|
+
/** Serialises repo-mutating git commands per workspace repo (QA-99 MAJOR-1). */
|
|
54
|
+
private readonly repoLocks;
|
|
55
|
+
/** An update is installing right now — a second one would fight it. */
|
|
56
|
+
private selfUpdateInFlight;
|
|
57
|
+
constructor(ws: RunnerWsClient, opts: SupervisorOptions);
|
|
58
|
+
get activeSessionIds(): string[];
|
|
59
|
+
private onFrame;
|
|
60
|
+
private startSession;
|
|
61
|
+
/**
|
|
62
|
+
* Spin the adapter up — for a fresh session, a resume-on-next-message, or a
|
|
63
|
+
* free CHAT session with no prompt at all (the agent boots, reports its
|
|
64
|
+
* capabilities and waits for the first message).
|
|
65
|
+
*
|
|
66
|
+
* Returns whether an agent process actually started: a caller holding a user
|
|
67
|
+
* message needs to know, because a refused launch means the message has to
|
|
68
|
+
* stay queued rather than be marked delivered (session 9).
|
|
69
|
+
*/
|
|
70
|
+
private launchAgent;
|
|
71
|
+
/** Warn the user when this share of the budget is gone. */
|
|
72
|
+
private static readonly BUDGET_WARN_RATIO;
|
|
73
|
+
/** Is the agent actually working right now (i.e. should the clock run)? */
|
|
74
|
+
private static isBillable;
|
|
75
|
+
/**
|
|
76
|
+
* Start or stop the budget clock to match the session's current state.
|
|
77
|
+
*
|
|
78
|
+
* MUST be called from every place that changes `lastReported` — not just
|
|
79
|
+
* reportStatus. `sendEvent` sets WAITING_PERMISSION directly and the API
|
|
80
|
+
* infers WAITING_INPUT from a `question` event, and those two states are
|
|
81
|
+
* exactly where an ask-mode session spends its time waiting for a human.
|
|
82
|
+
*/
|
|
83
|
+
private syncBudgetClock;
|
|
84
|
+
/**
|
|
85
|
+
* Agent time spent so far, INCLUDING the slice currently in flight. Reading
|
|
86
|
+
* `activeMs` alone made every RUNNING→RUNNING re-report (a follow-up message,
|
|
87
|
+
* a reconnect) re-arm the timer from a stale figure, so a busy session could
|
|
88
|
+
* slide past its limit indefinitely.
|
|
89
|
+
*/
|
|
90
|
+
private static spentMs;
|
|
91
|
+
/** Total budget in ms, or null when the workspace has no time limit. */
|
|
92
|
+
private budgetMsFor;
|
|
93
|
+
private armBudgetTimers;
|
|
94
|
+
private clearBudgetTimers;
|
|
95
|
+
/**
|
|
96
|
+
* The agent has worked for as long as it was allowed. Stop it cleanly and
|
|
97
|
+
* keep everything needed to pick it back up — this is NOT a failure, and
|
|
98
|
+
* reporting it as one is what made five of the first twelve prod sessions
|
|
99
|
+
* look broken.
|
|
100
|
+
*/
|
|
101
|
+
private pauseForBudget;
|
|
102
|
+
private pumpEvents;
|
|
103
|
+
/**
|
|
104
|
+
* Deliver messages that were held because every slot was taken.
|
|
105
|
+
*
|
|
106
|
+
* Called whenever an agent process ends. Without it a message typed into a
|
|
107
|
+
* parked session while the server was full would sit in the queue until the
|
|
108
|
+
* next reconnect: the user would see their own bubble in the feed and no
|
|
109
|
+
* answer, which is the worst possible reading of "the server is busy".
|
|
110
|
+
*/
|
|
111
|
+
private drainSessionsWaitingForCapacity;
|
|
112
|
+
/** The ceiling actually in force: the API's number, lowered by local config. */
|
|
113
|
+
get maxSessions(): number;
|
|
114
|
+
/**
|
|
115
|
+
* Adopt a ceiling announced by the API.
|
|
116
|
+
*
|
|
117
|
+
* `undefined` means "this API does not know about the field" — only then do
|
|
118
|
+
* we fall back to the historical single slot. A number that is merely out of
|
|
119
|
+
* range is CLAMPED, never treated as absent: silently collapsing a server to
|
|
120
|
+
* one agent because of a typo in a config field is a much worse failure than
|
|
121
|
+
* capping it (QA-102 MINOR-5).
|
|
122
|
+
*/
|
|
123
|
+
private static readonly MAX_SESSIONS_HARD_CAP;
|
|
124
|
+
private setMaxSessions;
|
|
125
|
+
/**
|
|
126
|
+
* Sessions holding a slot right now. A session already asked to stop or park
|
|
127
|
+
* does not count: its process is winding down and its `session` handle stays
|
|
128
|
+
* set until `pumpEvents` sees the stream end — waiting for that would make
|
|
129
|
+
* parking look like it freed nothing.
|
|
130
|
+
*/
|
|
131
|
+
private liveSessionCount;
|
|
132
|
+
/**
|
|
133
|
+
* Make room for one more agent process.
|
|
134
|
+
*
|
|
135
|
+
* Up to `maxSessions` agents run side by side, each in its own worktree. Over
|
|
136
|
+
* that, idle-but-resumable sessions (REVIEW / WAITING_INPUT) are parked —
|
|
137
|
+
* their provider session survives on disk and relaunches on the next message,
|
|
138
|
+
* so parking costs context nothing. Only mid-turn sessions (RUNNING /
|
|
139
|
+
* WAITING_PERMISSION) genuinely hold a slot; when they fill the ceiling this
|
|
140
|
+
* returns false and the caller tells the user rather than thrashing.
|
|
141
|
+
*/
|
|
142
|
+
private ensureCapacity;
|
|
143
|
+
/** Idle after a finished turn — safe to kill the process and resume later. */
|
|
144
|
+
private isParkable;
|
|
145
|
+
private park;
|
|
146
|
+
private forwardEvent;
|
|
147
|
+
private onUserMessage;
|
|
148
|
+
/**
|
|
149
|
+
* Run delivery work for one session, strictly after whatever is already
|
|
150
|
+
* queued for it. Order is the whole point: two messages typed seconds apart
|
|
151
|
+
* must reach the agent in the order they were typed, however long the first
|
|
152
|
+
* one's attachments take to download.
|
|
153
|
+
*/
|
|
154
|
+
private enqueueDelivery;
|
|
155
|
+
/** Did this session go away (stop, teardown, resume) while we were awaiting? */
|
|
156
|
+
private isStale;
|
|
157
|
+
/**
|
|
158
|
+
* Bring the user's files onto this machine and fold their paths into the
|
|
159
|
+
* prompt. Returns the text unchanged when there is nothing to fetch.
|
|
160
|
+
*
|
|
161
|
+
* A failure here never costs the message: the agent still gets what the user
|
|
162
|
+
* typed, plus a note naming the files that did not make it.
|
|
163
|
+
*/
|
|
164
|
+
private materializeAttachments;
|
|
165
|
+
/**
|
|
166
|
+
* Hand a message to the agent, or hold it if there is nowhere to put it yet.
|
|
167
|
+
*
|
|
168
|
+
* `held` carries the journal records this text was built from, so a delivery
|
|
169
|
+
* resolves them and a refusal puts exactly those records back in the queue —
|
|
170
|
+
* the message is retired from disk only once an agent has it.
|
|
171
|
+
*/
|
|
172
|
+
private deliverMessage;
|
|
173
|
+
/**
|
|
174
|
+
* Deliver messages that raced session start (already journaled).
|
|
175
|
+
*
|
|
176
|
+
* Async since session 10: a held message may carry files, and this is the
|
|
177
|
+
* first point at which the worktree they belong in certainly exists. Callers
|
|
178
|
+
* fire and forget — a failure here is logged, and the records stay on disk.
|
|
179
|
+
*/
|
|
180
|
+
private flushPendingMessages;
|
|
181
|
+
/** Stop the current turn without ending the session (VS-Code-style Stop). */
|
|
182
|
+
private interruptSession;
|
|
183
|
+
/** Live model / interaction-mode switch (persisted for the next relaunch). */
|
|
184
|
+
private applySettings;
|
|
185
|
+
private stopSession;
|
|
186
|
+
private reconcile;
|
|
187
|
+
/**
|
|
188
|
+
* Housekeeping for the journal directory. Safe to call any time: live
|
|
189
|
+
* sessions are skipped and a journal still holding unacked events survives
|
|
190
|
+
* until the hard expiry.
|
|
191
|
+
*/
|
|
192
|
+
pruneJournals(): void;
|
|
193
|
+
private runCommand;
|
|
194
|
+
/**
|
|
195
|
+
* Serialise commands that mutate the shared workspace repo. Two sessions of
|
|
196
|
+
* one workspace can hit «Применить» at the same moment; interleaved
|
|
197
|
+
* `merge --squash` + `commit` would land both change sets in one commit and
|
|
198
|
+
* mark the other session failed (QA-99 MAJOR-1).
|
|
199
|
+
*/
|
|
200
|
+
private withRepoLock;
|
|
201
|
+
/**
|
|
202
|
+
* Same lock, keyed by the shared repository rather than by whichever path the
|
|
203
|
+
* caller happened to have. A worktree commit and a workspace squash-merge
|
|
204
|
+
* touch one repo, so they must take one key (see `repoKeyFor`).
|
|
205
|
+
*/
|
|
206
|
+
private withRepoLockFor;
|
|
207
|
+
/** A session actively mid-turn in this worktree — git writes must wait. */
|
|
208
|
+
private isWorktreeBusy;
|
|
209
|
+
private static readonly EVENT_PAYLOAD_CAP;
|
|
210
|
+
private sendEvent;
|
|
211
|
+
private reportStatus;
|
|
212
|
+
/** Graceful daemon shutdown: kill agents, keep sessions resumable server-side. */
|
|
213
|
+
shutdown(): void;
|
|
214
|
+
}
|
|
215
|
+
export declare function composeInitialPrompt(descriptor: SessionDescriptor): string;
|
|
216
|
+
//# sourceMappingURL=supervisor.d.ts.map
|