@coreplane/switchboard 1.249.0 → 1.249.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/config/config.example.yaml +1 -1
- package/dist/assets/deploy/cloudflare-resident/refresh.ts +5 -3
- package/dist/assets/deploy/cloudflare-resident/worker.ts +62 -4
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/ship/coordinator.ts +9 -1
- package/dist/assets/src/execution/residentRefresh.ts +26 -1
- package/dist/assets/src/execution/sandboxErrors.ts +8 -0
- package/dist/cli.js +24 -12
- package/package.json +1 -1
|
@@ -262,7 +262,7 @@ execution:
|
|
|
262
262
|
# baseUrl: https://switchboard-resident.<account>.workers.dev
|
|
263
263
|
# tokenEnv: RESIDENT_OPERATOR_TOKEN # operator bearer secret (default)
|
|
264
264
|
# adminTokenEnv: RESIDENT_ADMIN_TOKEN # admin bearer for `repo ...` commands (default)
|
|
265
|
-
# probeTimeoutMs:
|
|
265
|
+
# probeTimeoutMs: 8000 # /status probe deadline; a miss = not warm for that dispatch, never an outage
|
|
266
266
|
|
|
267
267
|
# Where per-conversation agent workspaces are created (local execution only).
|
|
268
268
|
# Under docker compose this directory is a named volume, so checkouts survive a restart.
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "../../src/execution/residentInstanceId.js";
|
|
22
22
|
import { RESTORE_MAX_MS, type RefreshPlan } from "../../src/execution/residentRefresh.js";
|
|
23
23
|
import { residentText } from "../../src/execution/residentText.js";
|
|
24
|
+
import { RUNTIME_BUSY_REASON } from "../../src/execution/sandboxErrors.js";
|
|
24
25
|
import { graftResidentSteps } from "../../src/execution/residentTrace.js";
|
|
25
26
|
import {
|
|
26
27
|
DEFAULT_EXEC_TIMEOUT_MS,
|
|
@@ -288,9 +289,10 @@ export class ResidentRefresh extends WorkflowEntrypoint<Env, RefreshInstancePara
|
|
|
288
289
|
}
|
|
289
290
|
// Housekeeping rides on every instance whatever the cycle's verdict — a
|
|
290
291
|
// parked resident still releases its idle bindings and re-measures, and
|
|
291
|
-
// neither step wakes a slept container — except an offboarded one
|
|
292
|
-
// nothing is left to keep
|
|
293
|
-
|
|
292
|
+
// neither step wakes a slept container — except an offboarded one
|
|
293
|
+
// (nothing is left to keep) and one whose container turned the cycle
|
|
294
|
+
// away (item 68: the sweep and the measure exec on that same container).
|
|
295
|
+
if (cycle.outcome !== "offboarded" && cycle.outcome !== RUNTIME_BUSY_REASON) {
|
|
294
296
|
const swept = await step.do("sweep", { retries, timeout: stepTimeoutMs(REFRESH_SWEEP_STEP_BUDGET_MS) }, () =>
|
|
295
297
|
stub.refreshInstanceSweep({ resource, instance }),
|
|
296
298
|
);
|
|
@@ -159,7 +159,11 @@ import {
|
|
|
159
159
|
} from "../../src/core/schedules.js";
|
|
160
160
|
import type { ResidentLifecycleState } from "../../src/execution/residentState.js";
|
|
161
161
|
import { RestoreWaiters } from "../../src/execution/restoreWaiters.js";
|
|
162
|
-
import {
|
|
162
|
+
import {
|
|
163
|
+
isRuntimeBusySignal,
|
|
164
|
+
RUNTIME_BUSY_REASON,
|
|
165
|
+
SandboxRuntimeBusyError,
|
|
166
|
+
} from "../../src/execution/sandboxErrors.js";
|
|
163
167
|
import {
|
|
164
168
|
decisivePull,
|
|
165
169
|
effectiveLimits,
|
|
@@ -613,6 +617,12 @@ const LIFECYCLE_KEY = "resident:lifecycle";
|
|
|
613
617
|
* resident, with the step it last reported and the cycle lease it holds, and
|
|
614
618
|
* the last bucket the cron skipped (a live cycle, a duplicate id). */
|
|
615
619
|
const REFRESH_INSTANCE_KEY = "resident:refreshInstance";
|
|
620
|
+
/** The settled state a cycle found before it wrote `refreshing` (item 68):
|
|
621
|
+
* what a cycle that yields to a busy container puts back. Rewritten by every
|
|
622
|
+
* cycle right before its `refreshing` write, so it always names the state
|
|
623
|
+
* under the current marker and nothing older. */
|
|
624
|
+
const REFRESHING_FROM_KEY = "resident:refreshingFrom";
|
|
625
|
+
type RefreshingFrom = ResidentStatus;
|
|
616
626
|
/** A `du` over a multi-GB checkout plus every live tree is seconds warm, tens
|
|
617
627
|
* of seconds on a cold page cache — the same class as a git network step. */
|
|
618
628
|
const DU_TIMEOUT_MS = GIT_NETWORK_TIMEOUT_MS;
|
|
@@ -3358,6 +3368,9 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
3358
3368
|
}
|
|
3359
3369
|
}
|
|
3360
3370
|
|
|
3371
|
+
// Item 68: remember what `refreshing` covers, so a cycle the busy container
|
|
3372
|
+
// turns away can put it back — the last snapshot never stopped serving.
|
|
3373
|
+
await this.ctx.storage.put(REFRESHING_FROM_KEY, (await this.getStatus()) satisfies RefreshingFrom);
|
|
3361
3374
|
await this.setResidentState("refreshing");
|
|
3362
3375
|
let sha: string;
|
|
3363
3376
|
try {
|
|
@@ -3380,6 +3393,9 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
3380
3393
|
// and die at git-setup). Name the disk instead: not
|
|
3381
3394
|
// serviceable, and the recovery below can free it.
|
|
3382
3395
|
const failure = await this.classifyFailure("fetch", message);
|
|
3396
|
+
// Item 68: the container did not accept the connect — a run's command
|
|
3397
|
+
// has its cores. Not GitHub, not the mirror: the instance step yields.
|
|
3398
|
+
if (failure.busy) throw err;
|
|
3383
3399
|
if (failure.diskFull) {
|
|
3384
3400
|
await this.setResidentState("degraded", failure.reason);
|
|
3385
3401
|
await this.recoverFromDiskFull(failure.reason, selfInFlight);
|
|
@@ -3533,7 +3549,9 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
3533
3549
|
* error can surface between steps — with the generic "refresh" step, whose
|
|
3534
3550
|
* failure reason is the `refresh-failed: …` shape. A full disk is a third
|
|
3535
3551
|
* class: `disk-full: …`, never serviceable, and the one failure the
|
|
3536
|
-
* resident can act on itself (recoverFromDiskFull).
|
|
3552
|
+
* resident can act on itself (recoverFromDiskFull). A container that did
|
|
3553
|
+
* not accept the connect (`runtime-busy`, item 68) is a fourth: `busy`,
|
|
3554
|
+
* and the cycle yields to the run that holds it. */
|
|
3537
3555
|
private async classifyCycleError(err: unknown): Promise<RefreshFailure> {
|
|
3538
3556
|
// The control port never answered (item 64): the count decides, and a disk
|
|
3539
3557
|
// probe would only cost another 30 s abort against the same silent port.
|
|
@@ -3911,7 +3929,10 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
3911
3929
|
* on purpose (`CycleRestartError`), is thrown to the engine, whose retry
|
|
3912
3930
|
* re-enters the same idempotent method — the row stays `refreshing`, never
|
|
3913
3931
|
* `degraded`, and a `refreshing` younger than the stale bound keeps the
|
|
3914
|
-
* cron from creating a second instance meanwhile. A
|
|
3932
|
+
* cron from creating a second instance meanwhile. A container that turned
|
|
3933
|
+
* the step's connect away (`runtime-busy`, item 68) ends the cycle as
|
|
3934
|
+
* `stopped` with nothing recorded — `yieldCycle` puts back the state the
|
|
3935
|
+
* cycle found. A failure of the repo's
|
|
3915
3936
|
* own is recorded — `degraded` with the reason, the last snapshot still
|
|
3916
3937
|
* serving — and answered `failed`, which ends the cycle; the next cron
|
|
3917
3938
|
* firing starts the next one from that state. */
|
|
@@ -3963,6 +3984,21 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
3963
3984
|
return { ...result, startedAt, trace: trace.steps() };
|
|
3964
3985
|
}
|
|
3965
3986
|
const failure = await this.classifyCycleError(err);
|
|
3987
|
+
if (failure.busy) {
|
|
3988
|
+
// Item 68: the container did not accept the cycle's connect — a run's
|
|
3989
|
+
// command has its cores. Nothing ran and nothing about the repository
|
|
3990
|
+
// is known: the cycle yields, the state it found goes back, and no
|
|
3991
|
+
// failure is recorded — not `degraded`, not a rung of item 67's ladder
|
|
3992
|
+
// (three cycles of it used to destroy the container under the run).
|
|
3993
|
+
// The next cron firing tries again; the engine is not asked to retry
|
|
3994
|
+
// into the same busy container.
|
|
3995
|
+
outcome = `yielded (${failure.reason})`;
|
|
3996
|
+
console.log(
|
|
3997
|
+
`refresh instance ${instance}: ${step} yielded — ${failure.reason.slice(0, 400)}; the next cycle retries`,
|
|
3998
|
+
);
|
|
3999
|
+
await this.yieldCycle(instance);
|
|
4000
|
+
return { status: "stopped", why: RUNTIME_BUSY_REASON, startedAt, trace: trace.steps() };
|
|
4001
|
+
}
|
|
3966
4002
|
if (failure.interrupted) {
|
|
3967
4003
|
outcome = `interrupted (${failure.reason}) — the engine retries`;
|
|
3968
4004
|
console.log(`refresh instance ${instance}: ${step} interrupted — ${failure.reason.slice(0, 400)}; retrying`);
|
|
@@ -3984,6 +4020,27 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
3984
4020
|
}
|
|
3985
4021
|
}
|
|
3986
4022
|
|
|
4023
|
+
/** A cycle that met the busy container ends here (item 68): the lease it
|
|
4024
|
+
* holds goes back, and the `refreshing` it wrote is undone to the settled
|
|
4025
|
+
* state it found, so the row says what the last snapshot still is — warm,
|
|
4026
|
+
* or the degraded an earlier cycle earned — never a `degraded` of this
|
|
4027
|
+
* cycle's own. A `refreshing` this cycle did not write (a step past the
|
|
4028
|
+
* fetch found the marker of a cycle that died mid-flight) is left to the
|
|
4029
|
+
* watchdog, which normalizes it. */
|
|
4030
|
+
private async yieldCycle(instance: string): Promise<void> {
|
|
4031
|
+
await this.clearInstanceLease(instance);
|
|
4032
|
+
const status = await this.getStatus();
|
|
4033
|
+
if (status.state !== "refreshing") return;
|
|
4034
|
+
const from = await this.ctx.storage.get<RefreshingFrom>(REFRESHING_FROM_KEY);
|
|
4035
|
+
if (!from || (from.state !== "warm" && from.state !== "degraded")) {
|
|
4036
|
+
console.log(
|
|
4037
|
+
`refresh instance ${instance}: yielded from \`refreshing\` over ${from?.state ?? "no recorded state"}; left for the watchdog`,
|
|
4038
|
+
);
|
|
4039
|
+
return;
|
|
4040
|
+
}
|
|
4041
|
+
await this.setResidentState(from.state, from.reason);
|
|
4042
|
+
}
|
|
4043
|
+
|
|
3987
4044
|
/** Step `fetch`: the gates, the cycle lease, the fetch and the plan. The
|
|
3988
4045
|
* instance id is the cycle `fetchMirror` records, so a retry of this step
|
|
3989
4046
|
* finds its fetch done. A rebuild's markers come off here, once per cycle,
|
|
@@ -4227,7 +4284,8 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
4227
4284
|
* errno, so a full-disk attach reads like a lock bug without the probe. */
|
|
4228
4285
|
private async classifyFailure(step: string, message: string): Promise<RefreshFailure> {
|
|
4229
4286
|
const direct = classifyRefreshFailure({ step, message });
|
|
4230
|
-
|
|
4287
|
+
// A busy container (item 68) would only refuse the disk probe's exec too.
|
|
4288
|
+
if (direct.diskFull || direct.interrupted || direct.busy) return direct;
|
|
4231
4289
|
return classifyRefreshFailure({ step, message, freeKiB: await this.freeKiB() });
|
|
4232
4290
|
}
|
|
4233
4291
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.249.
|
|
3
|
+
"version": "1.249.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.249.
|
|
9
|
+
"version": "1.249.1",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -20445,7 +20445,7 @@
|
|
|
20445
20445
|
},
|
|
20446
20446
|
"packages/switchboard": {
|
|
20447
20447
|
"name": "@coreplane/switchboard",
|
|
20448
|
-
"version": "1.249.
|
|
20448
|
+
"version": "1.249.1",
|
|
20449
20449
|
"license": "Apache-2.0",
|
|
20450
20450
|
"dependencies": {
|
|
20451
20451
|
"@earendil-works/pi-ai": "0.85.1",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.249.
|
|
3
|
+
"version": "1.249.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/dist/assets/source.json
CHANGED
|
@@ -1781,9 +1781,17 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1781
1781
|
: "Remaining gate: a person's merge — the runner merges only when the instance's `merge` field says runner, and ship never approves.",
|
|
1782
1782
|
].join("\n");
|
|
1783
1783
|
case "merge_refused":
|
|
1784
|
+
// The approved work is on the branch, so the remedy is a person's hand
|
|
1785
|
+
// merge, never a re-run: a seeded plan re-issued afterwards finds the
|
|
1786
|
+
// merged pull request (the pre-check's `merged` by other, or
|
|
1787
|
+
// `already_landed`) and moves on to the dependents. The generated
|
|
1788
|
+
// plan's line already says to re-issue with the PR URL, which takes the
|
|
1789
|
+
// same recognition path.
|
|
1784
1790
|
return join([
|
|
1785
1791
|
`⚠️ The review approved ${e.pr.url} but the runner did not merge it: ${e.reason}. A person decides what becomes of the pull request.`,
|
|
1786
|
-
|
|
1792
|
+
s.input.generated
|
|
1793
|
+
? reissue
|
|
1794
|
+
: `The approved work is on the branch: rebase or fix it, push, and merge it by hand. Then re-issue the plan naming the remaining units — a unit whose pull request has merged is recognized and not run again, and its dependents start from there.`,
|
|
1787
1795
|
]);
|
|
1788
1796
|
case "round_cap":
|
|
1789
1797
|
return join([
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* interrupted one stopped instead of redoing the whole rebuild. */
|
|
23
23
|
|
|
24
24
|
import { DISK_FULL_FREE_KIB, diskFullReason, isDiskFullMessage } from "./residentDisk.js";
|
|
25
|
+
import { carriesRuntimeBusyToken } from "./sandboxErrors.js";
|
|
25
26
|
|
|
26
27
|
export interface RefreshDisk {
|
|
27
28
|
/** `git rev-parse HEAD` of the warm checkout; null when unreadable. */
|
|
@@ -121,6 +122,9 @@ export function checkoutUpdateCommand(sha: string, clean: CleanScope): string {
|
|
|
121
122
|
* repository: the container's control port never answered the SDK's connect
|
|
122
123
|
* (`runtimeUnreachableReason`), so no command ran at all — the resident
|
|
123
124
|
* escalates on its persisted count instead of recording a command failure.
|
|
125
|
+
* `busy` is the third: the container did not accept the cycle's connect
|
|
126
|
+
* inside the platform's allowance (`runtime-busy`, resident-repos item 68) —
|
|
127
|
+
* a run's command has its cores; nothing ran, and the cycle yields to it.
|
|
124
128
|
* Everything else is the repo's own build failing. */
|
|
125
129
|
export interface RefreshFailure {
|
|
126
130
|
/** The reason. An interruption's is prefixed `refresh-interrupted:` and is
|
|
@@ -130,7 +134,8 @@ export interface RefreshFailure {
|
|
|
130
134
|
* `disk-full:` (`residentDisk.ts` — the cycle's entry gate and the recycle
|
|
131
135
|
* decision key on it); an unanswered control port is `runtime-unreachable:`
|
|
132
136
|
* with the attempt count (the ladder below decides what the resident does
|
|
133
|
-
* about it);
|
|
137
|
+
* about it); a busy container's is `refresh-yielded:` (the cycle ends
|
|
138
|
+
* `stopped`, the state it found put back); real failures keep `<step>-failed:`. */
|
|
134
139
|
reason: string;
|
|
135
140
|
/** The step that failed, as the caller named it (`refresh` for a failure
|
|
136
141
|
* between steps): item 67 reads whether it ran the repository's own command
|
|
@@ -139,6 +144,7 @@ export interface RefreshFailure {
|
|
|
139
144
|
interrupted: boolean;
|
|
140
145
|
diskFull: boolean;
|
|
141
146
|
runtimeUnreachable: boolean;
|
|
147
|
+
busy: boolean;
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
/** Root argv that kills every process the build user still owns and waits
|
|
@@ -290,15 +296,31 @@ export function classifyRefreshFailure(input: {
|
|
|
290
296
|
interrupted: false,
|
|
291
297
|
diskFull: false,
|
|
292
298
|
runtimeUnreachable: true,
|
|
299
|
+
busy: false,
|
|
293
300
|
reason: runtimeUnreachableReason(input.runtimeUnreachable.count),
|
|
294
301
|
};
|
|
295
302
|
}
|
|
303
|
+
// Item 68: the token the Worker's `run()` named the refused connect with,
|
|
304
|
+
// anywhere in the text — a wrapper (a StepError, the fetch's mint prefix)
|
|
305
|
+
// keeps it. Decided before the disk inference: no command ran, so a low
|
|
306
|
+
// free-disk reading says nothing about this failure.
|
|
307
|
+
if (carriesRuntimeBusyToken(message)) {
|
|
308
|
+
return {
|
|
309
|
+
step,
|
|
310
|
+
interrupted: false,
|
|
311
|
+
diskFull: false,
|
|
312
|
+
runtimeUnreachable: false,
|
|
313
|
+
busy: true,
|
|
314
|
+
reason: `refresh-yielded: ${step} ${message}`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
296
317
|
if (isDiskFullMessage(message)) {
|
|
297
318
|
return {
|
|
298
319
|
step,
|
|
299
320
|
interrupted: false,
|
|
300
321
|
diskFull: true,
|
|
301
322
|
runtimeUnreachable: false,
|
|
323
|
+
busy: false,
|
|
302
324
|
reason: diskFullReason({ step, message, freeKiB: input.freeKiB ?? null }),
|
|
303
325
|
};
|
|
304
326
|
}
|
|
@@ -309,6 +331,7 @@ export function classifyRefreshFailure(input: {
|
|
|
309
331
|
interrupted: true,
|
|
310
332
|
diskFull: false,
|
|
311
333
|
runtimeUnreachable: false,
|
|
334
|
+
busy: false,
|
|
312
335
|
reason: `refresh-interrupted: ${step} ${message}`,
|
|
313
336
|
};
|
|
314
337
|
}
|
|
@@ -318,6 +341,7 @@ export function classifyRefreshFailure(input: {
|
|
|
318
341
|
interrupted: false,
|
|
319
342
|
diskFull: true,
|
|
320
343
|
runtimeUnreachable: false,
|
|
344
|
+
busy: false,
|
|
321
345
|
reason: diskFullReason({ step, message, freeKiB: input.freeKiB }),
|
|
322
346
|
};
|
|
323
347
|
}
|
|
@@ -326,6 +350,7 @@ export function classifyRefreshFailure(input: {
|
|
|
326
350
|
interrupted: false,
|
|
327
351
|
diskFull: false,
|
|
328
352
|
runtimeUnreachable: false,
|
|
353
|
+
busy: false,
|
|
329
354
|
reason: `${step}-failed: ${message}`,
|
|
330
355
|
};
|
|
331
356
|
}
|
|
@@ -403,6 +403,14 @@ export function isRuntimeBusyError(err: unknown): boolean {
|
|
|
403
403
|
return s.name === RUNTIME_BUSY_ERROR_NAME || !!s.message?.startsWith(`${RUNTIME_BUSY_REASON}:`);
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
/** Does a failure's text carry the token anywhere — the typed error's own
|
|
407
|
+
* message, or that message behind a wrapper's prefix (a `StepError` built
|
|
408
|
+
* from it, the fetch step's mint prefix)? The refresh cycle's classifier
|
|
409
|
+
* reads this: the token decides, never the platform's words. */
|
|
410
|
+
export function carriesRuntimeBusyToken(message: string): boolean {
|
|
411
|
+
return new RegExp(`(?:^|[\\s;(])${RUNTIME_BUSY_REASON}: `).test(message);
|
|
412
|
+
}
|
|
413
|
+
|
|
406
414
|
/** The `/read` and `/write` answer (sent as HTTP 503): the token and the text. */
|
|
407
415
|
export function runtimeBusyAnswer(message: string): { error: string; reason: typeof RUNTIME_BUSY_REASON } {
|
|
408
416
|
return { error: message, reason: RUNTIME_BUSY_REASON };
|
package/dist/cli.js
CHANGED
|
@@ -10136,6 +10136,9 @@ function execDeadline(timeoutMs, signal2) {
|
|
|
10136
10136
|
signal2.addEventListener("abort", onStop, { once: true });
|
|
10137
10137
|
return AbortSignal.any([deadline.signal, signal2]);
|
|
10138
10138
|
}
|
|
10139
|
+
function isDeadlineMiss(err2) {
|
|
10140
|
+
return err2 instanceof Error && err2.name === "TimeoutError";
|
|
10141
|
+
}
|
|
10139
10142
|
function isRunStopError(err2) {
|
|
10140
10143
|
return err2 instanceof ExecInfraError && err2.reason === "aborted";
|
|
10141
10144
|
}
|
|
@@ -11121,6 +11124,7 @@ var init_residentRefresh = __esm({
|
|
|
11121
11124
|
"../../src/execution/residentRefresh.ts"() {
|
|
11122
11125
|
"use strict";
|
|
11123
11126
|
init_residentDisk();
|
|
11127
|
+
init_sandboxErrors();
|
|
11124
11128
|
RUNTIME_MOVED_WORDING = /previous runtime incarnation|interrupted because the runtime changed|runtime identity is no longer active|sandbox lifetime is no longer current|platform was updating the sandbox runtime|no longer identifies pid/i;
|
|
11125
11129
|
STOPPED_CONTAINER_WORDING = /process supervisor is closed|container is not running, consider calling start|the container just exited/i;
|
|
11126
11130
|
RUNTIME_REPLACEMENT_WORDING = new RegExp(
|
|
@@ -11498,6 +11502,7 @@ var init_resident = __esm({
|
|
|
11498
11502
|
init_residentDiskBudget();
|
|
11499
11503
|
init_sandboxErrors();
|
|
11500
11504
|
init_executor();
|
|
11505
|
+
init_executor();
|
|
11501
11506
|
DETACH_TIMEOUT_MS = 1e4;
|
|
11502
11507
|
DRAIN_POLL_MS = DRAIN.pollMs;
|
|
11503
11508
|
DRAIN_WAIT_MAX_MS = DRAIN.waitMaxMs;
|
|
@@ -11685,7 +11690,9 @@ var init_resident = __esm({
|
|
|
11685
11690
|
await ex.attach();
|
|
11686
11691
|
return ex;
|
|
11687
11692
|
}
|
|
11688
|
-
/** One operator-scope GET /status, bounded by timeoutMs. Never throws.
|
|
11693
|
+
/** One operator-scope GET /status, bounded by timeoutMs. Never throws. A
|
|
11694
|
+
* deadline miss is flagged `timedOut` beside `transport`, so the factory
|
|
11695
|
+
* falls cold for the one dispatch without arming its breaker. */
|
|
11689
11696
|
static async probeStatus(baseUrl, token2, resource, timeoutMs, span, signal2) {
|
|
11690
11697
|
const url = `${baseUrl.replace(/\/$/, "")}/status?resource=${encodeURIComponent(resource)}`;
|
|
11691
11698
|
let res;
|
|
@@ -11697,7 +11704,12 @@ var init_resident = __esm({
|
|
|
11697
11704
|
{ route: "/status" }
|
|
11698
11705
|
);
|
|
11699
11706
|
} catch (err2) {
|
|
11700
|
-
return {
|
|
11707
|
+
return {
|
|
11708
|
+
kind: "unreachable",
|
|
11709
|
+
error: err2 instanceof Error ? err2.message : String(err2),
|
|
11710
|
+
transport: true,
|
|
11711
|
+
...isDeadlineMiss(err2) ? { timedOut: true } : {}
|
|
11712
|
+
};
|
|
11701
11713
|
}
|
|
11702
11714
|
if (res.status === 404) {
|
|
11703
11715
|
return { kind: "status", state: "not-onboarded", reason: "" };
|
|
@@ -12825,13 +12837,10 @@ function residentSlugsLister(cfg, secrets = processSecrets) {
|
|
|
12825
12837
|
try {
|
|
12826
12838
|
res = await fetch(`${cfg.baseUrl.replace(/\/$/, "")}/residents`, {
|
|
12827
12839
|
headers: { authorization: `Bearer ${token2.reveal()}` },
|
|
12828
|
-
signal:
|
|
12840
|
+
signal: execDeadline(cfg.probeTimeoutMs ?? PROBE_TIMEOUT_MS2)
|
|
12829
12841
|
});
|
|
12830
12842
|
} catch (err2) {
|
|
12831
|
-
|
|
12832
|
-
until: systemClock() + PROBE_OUTAGE_WINDOW_MS,
|
|
12833
|
-
error: err2 instanceof Error ? err2.message : String(err2)
|
|
12834
|
-
};
|
|
12843
|
+
if (!isDeadlineMiss(err2)) armOutage(err2 instanceof Error ? err2.message : String(err2));
|
|
12835
12844
|
return void 0;
|
|
12836
12845
|
}
|
|
12837
12846
|
if (!res.ok) return void 0;
|
|
@@ -12856,11 +12865,11 @@ async function probeResident(cfg, token2, resource, span, stop) {
|
|
|
12856
12865
|
cfg.baseUrl,
|
|
12857
12866
|
token2.reveal(),
|
|
12858
12867
|
resource,
|
|
12859
|
-
cfg.probeTimeoutMs ??
|
|
12868
|
+
cfg.probeTimeoutMs ?? PROBE_TIMEOUT_MS2,
|
|
12860
12869
|
span,
|
|
12861
12870
|
stop
|
|
12862
12871
|
);
|
|
12863
|
-
if (probe.kind === "unreachable" && probe.transport && !stop?.aborted) armOutage(probe.error);
|
|
12872
|
+
if (probe.kind === "unreachable" && probe.transport && !probe.timedOut && !stop?.aborted) armOutage(probe.error);
|
|
12864
12873
|
return probe;
|
|
12865
12874
|
}
|
|
12866
12875
|
function armOutage(error) {
|
|
@@ -12968,7 +12977,7 @@ function perThreadBackend(opts) {
|
|
|
12968
12977
|
const type = opts.execution?.type ?? "local";
|
|
12969
12978
|
return type === "e2b" ? "e2b" : type === "cloudflare" ? "sandbox" : "local";
|
|
12970
12979
|
}
|
|
12971
|
-
var BACKENDS, WorkspaceReattachRefusedError, WorkspaceReattachLeaseSpentError, PROBE_OUTAGE_WINDOW_MS, AWAIT_RESTORE_TIMEOUT_MS, probeOutage, NullExecutor;
|
|
12980
|
+
var BACKENDS, WorkspaceReattachRefusedError, WorkspaceReattachLeaseSpentError, PROBE_OUTAGE_WINDOW_MS, PROBE_TIMEOUT_MS2, AWAIT_RESTORE_TIMEOUT_MS, probeOutage, NullExecutor;
|
|
12972
12981
|
var init_factory = __esm({
|
|
12973
12982
|
"../../src/execution/factory.ts"() {
|
|
12974
12983
|
"use strict";
|
|
@@ -13011,6 +13020,7 @@ var init_factory = __esm({
|
|
|
13011
13020
|
note;
|
|
13012
13021
|
};
|
|
13013
13022
|
PROBE_OUTAGE_WINDOW_MS = 3e4;
|
|
13023
|
+
PROBE_TIMEOUT_MS2 = 8e3;
|
|
13014
13024
|
AWAIT_RESTORE_TIMEOUT_MS = 10 * 6e4;
|
|
13015
13025
|
NullExecutor = class {
|
|
13016
13026
|
constructor(agentName) {
|
|
@@ -60469,10 +60479,12 @@ async function merge(body, deps, subject2) {
|
|
|
60469
60479
|
return refused7(
|
|
60470
60480
|
`the head of ${where} moved: \`${facts2.headSha?.slice(0, 7) ?? "?"}\` is not the approved \`${headSha.slice(0, 7)}\``
|
|
60471
60481
|
);
|
|
60472
|
-
if (facts2.mergeableState === "dirty")
|
|
60482
|
+
if (facts2.mergeableState === "dirty") {
|
|
60483
|
+
const base = facts2.baseRef ?? "its base";
|
|
60473
60484
|
return refused7(
|
|
60474
|
-
`${where} conflicts with \`${
|
|
60485
|
+
`${where} conflicts with \`${base}\` at \`${headSha.slice(0, 7)}\` \u2014 rebase onto \`${base}\`, push, and merge it by hand once the checks are green; the approved work stands`
|
|
60475
60486
|
);
|
|
60487
|
+
}
|
|
60476
60488
|
const approved = await reviewPostedAt(deps, pr, "approve", headSha);
|
|
60477
60489
|
if (approved === void 0)
|
|
60478
60490
|
return json2(502, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreplane/switchboard",
|
|
3
|
-
"version": "1.249.
|
|
3
|
+
"version": "1.249.1",
|
|
4
4
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://openswitchboard.dev",
|