@coreplane/switchboard 1.235.0 → 1.237.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/assets/Dockerfile +11 -0
- package/dist/assets/config/config.example.yaml +9 -4
- package/dist/assets/deploy/cloudflare-memory/worker.ts +12 -0
- package/dist/assets/deploy/cloudflare-resident/Dockerfile +18 -0
- package/dist/assets/deploy/cloudflare-resident/worker.ts +290 -118
- package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +11 -0
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +104 -17
- package/dist/assets/package-lock.json +212 -3
- package/dist/assets/package.json +4 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/authz/authorize.ts +25 -6
- package/dist/assets/src/core/authz/policy.ts +9 -0
- package/dist/assets/src/core/authz/types.ts +15 -1
- package/dist/assets/src/core/coordinator/contract.ts +24 -4
- package/dist/assets/src/core/coordinator/driver.ts +36 -4
- package/dist/assets/src/core/runEvents.ts +46 -7
- package/dist/assets/src/core/runFriction.ts +1 -0
- package/dist/assets/src/core/ship/contract.ts +3 -1
- package/dist/assets/src/core/ship/coordinator.ts +236 -30
- package/dist/assets/src/core/trace/types.ts +5 -0
- package/dist/assets/src/execution/residentDepsStore.ts +14 -2
- package/dist/assets/src/execution/residentDisk.ts +21 -10
- package/dist/assets/src/execution/residentDiskBudget.ts +45 -11
- package/dist/assets/src/execution/residentHead.ts +34 -8
- package/dist/assets/src/execution/residentRebind.ts +88 -31
- package/dist/assets/src/execution/sandboxErrors.ts +72 -0
- package/dist/assets/src/execution/sandboxLifecycle.ts +33 -0
- package/dist/assets/src/execution/sandboxStart.ts +90 -0
- package/dist/assets/web/dist/.vite/manifest.json +437 -423
- package/dist/assets/web/dist/assets/AppShell-MYHNxJks.js +1 -0
- package/dist/assets/web/dist/assets/{CostsPage-D-v8am88.js → CostsPage-DKk9OjB0.js} +1 -1
- package/dist/assets/web/dist/assets/{DeliveryPage-CvlWP7Eq.js → DeliveryPage-Kz6js49i.js} +1 -1
- package/dist/assets/web/dist/assets/{NotFoundPage-BzWS4Ca1.js → NotFoundPage-BXLrQQzy.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentDetailPage-Dc23v_8v.js +1 -0
- package/dist/assets/web/dist/assets/ResidentsIndexPage-TR5D1TRR.js +1 -0
- package/dist/assets/web/dist/assets/RunFoldRow-mkUj7WEN.js +9 -0
- package/dist/assets/web/dist/assets/{RunRoutePage-oN9GkVEA.js → RunRoutePage-CQI17__g.js} +3 -3
- package/dist/assets/web/dist/assets/RunsIndexPage-yIP4PlKP.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-DEICQ4FY.js → RunsTabs-XpCqeWu4.js} +1 -1
- package/dist/assets/web/dist/assets/ScheduledPage-CdCVyNbh.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-DuA8JpLU.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-D14iJMy7.js → StatusDot-DmNHX6am.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-B0Ob5MQ4.js → Tooltip-qVnJTgSt.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-BcTeH7m2.js +1 -0
- package/dist/assets/web/dist/assets/{angular-html-Cw130Zyi.js → angular-html-DYtV9uIN.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-DY1m4C2s.js → angular-ts-DF2P25Zp.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-W6Ty7v05.js → apl-Ff96wxko.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-DlLVHwVk.js → astro-LbBnZna-.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-Bco3tDh4.js → blade-DltuxeBW.js} +1 -1
- package/dist/assets/web/dist/assets/{c-1LaFY_cj.js → c-DBg7yuPx.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-B-OvrOL5.js → chapel-C83hUAds.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-Ch3EPlHu.js → cobol-DB0yV_2D.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-hZuITnto.js → coffee-Bpih6Kzl.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-B8iRVo8m.js → cpp-CU00ZeRn.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-DKveI7lr.js → crystal-f8ImJkux.js} +1 -1
- package/dist/assets/web/dist/assets/{css-_2_CD-rr.js → css-4VrC4ISo.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-pOdUzj6D.js → dist-TAGawIfM.js} +2 -2
- package/dist/assets/web/dist/assets/durationTone-CYMux25J.js +1 -0
- package/dist/assets/web/dist/assets/{edge-BenMDPct.js → edge-BwFDzfS_.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-CBbdEqG6.js → elixir-wwIYIpJm.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-DMub_2lY.js → elm-DyDuM3v3.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-CI9He_Up.js → erb-DFJBEVrh.js} +1 -1
- package/dist/assets/web/dist/assets/{favicon-BQsePYv5.js → favicon-CSt-qDvc.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-BBfhnyF8.js → git-rebase-CuDv-44X.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-CR9Fxrau.js → glimmer-js-ZFnrm4J3.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-CwFwX_XS.js → glimmer-ts-XY4Yqshp.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-Daiodp1r.js → glsl-xnR8efx0.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-DZvrsEkB.js → graphql-D-gO5tnP.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-BaXOzdg7.js → hack-CSxbW4ax.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-CMskq_VA.js → haml-D4ar42QT.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-Dhw_f7jD.js → handlebars-CMLbDE7o.js} +1 -1
- package/dist/assets/web/dist/assets/{html-C9k99z0z.js → html-CVekU4Fs.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-VckPItXN.js → html-derivative-St3HHw_N.js} +1 -1
- package/dist/assets/web/dist/assets/{http-DXU_3h9v.js → http-Mon0DWHS.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-KU24JNdp.js → hurl-Cbl5IvBs.js} +1 -1
- package/dist/assets/web/dist/assets/indexRow-FLJu5mR9.js +1 -0
- package/dist/assets/web/dist/assets/{java-BbZjTNgf.js → java-Bprck1l-.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-DXCdqcZZ.js → javascript-C73G1-UR.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-pDlsjTVp.js → jinja-C4DuRxYJ.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-DsXDk66e.js → jison-B2U03GEC.js} +1 -1
- package/dist/assets/web/dist/assets/{json-tCxXfRgG.js → json-CvTZUDzx.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-B0ZYkCmi.js → jsx-DMWKCzwo.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-BLzzeI6x.js → julia-Czn7bV_3.js} +1 -1
- package/dist/assets/web/dist/assets/{just-DvD2_60c.js → just-BBxpNfvC.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-511zn3h7.js → latex-Bw9yg5mr.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-BD0BkMox.js → liquid-BRCjaydE.js} +1 -1
- package/dist/assets/web/dist/assets/{indexFormat-B-pd4r7Y.js → localIso-CNA-bhS-.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-NFicKm6N.js → lua-DUu2KIC0.js} +1 -1
- package/dist/assets/web/dist/assets/main-csihqEj5.css +1 -0
- package/dist/assets/web/dist/assets/main-mUd_TJjH.js +28 -0
- package/dist/assets/web/dist/assets/{marko-1-G_nEXK.js → marko-CZJZIlSQ.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-DUe3AQLp.js → mdc-BmClq6fd.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-Dg379_bA.js → nginx-Bmt3xSEm.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-BnFq97ZO.js → nim-Da-i0utK.js} +1 -1
- package/dist/assets/web/dist/assets/{org-F0uiwGvq.js → org-BvAre2qP.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-B0euczkl.js → perl-v9WwFFtB.js} +1 -1
- package/dist/assets/web/dist/assets/{php-DkL3k_n7.js → php-DTU4Zyze.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-L_OjjZP4.js → pug-DhTwEqTO.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-BMjB00Zz.js → qml-BUD9AC_c.js} +1 -1
- package/dist/assets/web/dist/assets/{r-DoeLdnqR.js → r-CAnu56n7.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-BzYNkAWy.js → razor-Dn0P4gAq.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-CPmElMk3.js → regexp-CZyBRaVW.js} +1 -1
- package/dist/assets/web/dist/assets/residentsModel-By1fThvk.js +1 -0
- package/dist/assets/web/dist/assets/{rst-DrsjgfI7.js → rst-Ckyd5UsY.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-cB42ppvx.js → ruby-BBQ62fsx.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-R5Y3NM_I.js → sas-CW35hMxG.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-C-zWxV9s.js → scss-CpV8pF4J.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-oJF96aAU.js → shellscript-CRJvsmE1.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-CznECKyi.js → shellsession-CyriUZxf.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-wjHLSRag.js → soy-D5QzYUeg.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-C_BM8IOW.js → sql-CUBf_Mse.js} +1 -1
- package/dist/assets/web/dist/assets/{stata-D31HO8aQ.js → stata-B-4n4ITz.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-CKCLyplA.js → surrealql-RuWZBLtr.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-3geWk2iu.js → svelte-CO5YzCsK.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-DiJr_hTD.js → templ-DbkWVaBF.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-BEJGWyeF.js → tex-IuxoEcId.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-C8Mzdpxx.js → ts-tags-g6dGQPjd.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-UQiSm_p3.js → tsx-WnXwiyH2.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-_Pvzkeo9.js → twig-Lx1G7PnY.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BWMkINKK.js → typescript-BQd7biLF.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-Bdg9m-e7.js → typst-D2lXPHK7.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-CDZrrAZ8.js → vue-4wVQD9y9.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-tAlpNhfN.js → vue-html-DZPaRte4.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-C27UF_rh.js → vue-vine-DbmZCmu-.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-CxeDr9Zh.js → xml-DV16szyo.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-Bpi0uUnM.js → xsl-eo5-1sk0.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BtUgZ1GN.js → yaml-BXdX8HZf.js} +1 -1
- package/dist/cli.js +3329 -1804
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/AppShell-lYVcj-k7.js +0 -1
- package/dist/assets/web/dist/assets/ResidentDetailPage-DSpsye5e.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CT8fI1M3.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-Cqh8pTXC.js +0 -9
- package/dist/assets/web/dist/assets/RunsIndexPage-sjXr8iCV.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-Tz__zLzi.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-CbKCL58v.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-Bnj883ii.js +0 -1
- package/dist/assets/web/dist/assets/localIso-L06jV29p.js +0 -1
- package/dist/assets/web/dist/assets/main-D3lG-yQ9.js +0 -28
- package/dist/assets/web/dist/assets/main-Dm11o0hc.css +0 -1
- package/dist/assets/web/dist/assets/residentDiskBudget-BMBKlYRH.js +0 -1
|
@@ -176,6 +176,17 @@ RUN npm install -g @earendil-works/pi-coding-agent@0.85.1 \
|
|
|
176
176
|
&& pi --version | grep -qx '0.85.1' \
|
|
177
177
|
&& pi --help | grep -q -- '--mode <mode>'
|
|
178
178
|
|
|
179
|
+
# OpenCode, the second harness (docs/reference/specs/harness.md): `@opencode/cli`
|
|
180
|
+
# at the EXACT pin held by src/deploy/imageOpenCodeHarness.test.ts, beside pi.
|
|
181
|
+
# The wrapper's postinstall hard-links the platform binary into place, so it
|
|
182
|
+
# is the one install script allowed by name; the grep proves the binary on
|
|
183
|
+
# PATH prints the pin's version text (`opencode v2.0.3`, exactly); the npm
|
|
184
|
+
# cache (a 200 MB tarball) is dropped in the same layer. Dark until a
|
|
185
|
+
# deployment names it: nothing here starts it on its own.
|
|
186
|
+
RUN npm install -g --allow-scripts=@opencode/cli @opencode/cli@2.0.3 \
|
|
187
|
+
&& npm cache clean --force \
|
|
188
|
+
&& opencode --version | grep -qx 'opencode v2.0.3'
|
|
189
|
+
|
|
179
190
|
# The container outlives a crash of the SDK's container server (the
|
|
180
191
|
# supervisor's header says why). The base image's ENTRYPOINT is tini running
|
|
181
192
|
# the server, so one uncaught exception there ended the container; here tini
|
|
@@ -41,9 +41,13 @@ import {
|
|
|
41
41
|
SANDBOX_SLEEP_AFTER,
|
|
42
42
|
isRecycleError,
|
|
43
43
|
recycledMidCommandMessage,
|
|
44
|
+
DETACH_HINT,
|
|
45
|
+
OUTPUT_AFTER_EXIT_MS,
|
|
46
|
+
heldOutputNote,
|
|
44
47
|
} from "../../src/execution/sandboxLifecycle.js";
|
|
45
48
|
import { envFromRequest } from "../../src/execution/sandboxEnv.js";
|
|
46
49
|
import { IdleGuard, type IdleGuardHost } from "../../src/execution/sandboxIdle.js";
|
|
50
|
+
import { StartGate, type StartGateHost, type StartingCause } from "../../src/execution/sandboxStart.js";
|
|
47
51
|
import { RUNTIME_REPLACEMENT_WORDING, isRuntimeUnreachableSignal } from "../../src/execution/residentRefresh.js";
|
|
48
52
|
import {
|
|
49
53
|
fleetBusyAnswer,
|
|
@@ -53,6 +57,8 @@ import {
|
|
|
53
57
|
runtimeUnreachableAnswer,
|
|
54
58
|
runtimeUnreachableExecAnswer,
|
|
55
59
|
SandboxRuntimeUnreachableError,
|
|
60
|
+
sandboxStartingAnswer,
|
|
61
|
+
sandboxStartingExecAnswer,
|
|
56
62
|
thrownShape,
|
|
57
63
|
thrownText,
|
|
58
64
|
} from "../../src/execution/sandboxErrors.js";
|
|
@@ -85,10 +91,17 @@ const EXEC_TIMEOUT_SECS = 280;
|
|
|
85
91
|
* the shell-level 124 is always the deadline a command meets first. */
|
|
86
92
|
const SDK_BACKSTOP_MARGIN_MS = 40_000;
|
|
87
93
|
|
|
88
|
-
/** How long the Durable Object waits for the process's
|
|
89
|
-
* own
|
|
90
|
-
*
|
|
91
|
-
|
|
94
|
+
/** How long the Durable Object waits for the process's output to END past
|
|
95
|
+
* the command's own deadline before it asks the runtime what became of the
|
|
96
|
+
* process (docs/reference/specs/execution.md item 24). The runtime ends the
|
|
97
|
+
* output stream only when every holder of the command's stdout and stderr is
|
|
98
|
+
* gone; a process that exited while a detached child holds them is answered
|
|
99
|
+
* with its exit code and a note, a process still running is killed and
|
|
100
|
+
* answered as the shell-level timeout. The wait is `OUTPUT_AFTER_EXIT_MS`
|
|
101
|
+
* (20 s), inside the executor's per-send margin, so this answer always
|
|
102
|
+
* reaches the bot before it gives the command up — the SDK's own limit
|
|
103
|
+
* (`SDK_BACKSTOP_MARGIN_MS`) stays above it as the last resort. */
|
|
104
|
+
const OUTPUT_WAIT_AFTER_DEADLINE_MS = OUTPUT_AFTER_EXIT_MS;
|
|
92
105
|
|
|
93
106
|
/** The interruption reasons that mean the container's runtime is not the one
|
|
94
107
|
* the command started on: the process, if it started, is gone with its
|
|
@@ -167,6 +180,12 @@ interface FileRefusal {
|
|
|
167
180
|
reason?: string;
|
|
168
181
|
}
|
|
169
182
|
|
|
183
|
+
/** The SDK's own limit on the warm-up command (`true`): generous, since the
|
|
184
|
+
* SDK's start — an instance grant up to 30 s, the runtime's port up to 90 s
|
|
185
|
+
* by its defaults — happens inside this one call. The executor's wait for
|
|
186
|
+
* the `sandbox-starting` token is bounded on its own side. */
|
|
187
|
+
const WARM_UP_BACKSTOP_MS = 4 * 60_000;
|
|
188
|
+
|
|
170
189
|
/** Durable Object storage key of the idle ledger's served-time. */
|
|
171
190
|
const IDLE_LEDGER_KEY = "switchboard.idle.lastServedAt";
|
|
172
191
|
/** The scheduled-callback name of the idle sweep (a method below). */
|
|
@@ -185,10 +204,12 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
185
204
|
sleepAfter = SANDBOX_SLEEP_AFTER;
|
|
186
205
|
|
|
187
206
|
private readonly idle: IdleGuard;
|
|
207
|
+
private readonly gate: StartGate;
|
|
188
208
|
|
|
189
209
|
constructor(...args: ConstructorParameters<typeof Sandbox<Env>>) {
|
|
190
210
|
super(...args);
|
|
191
211
|
this.idle = new IdleGuard(this.idleHost());
|
|
212
|
+
this.gate = new StartGate(this.startHost());
|
|
192
213
|
// Every wake, including the one the SDK's own alarm causes on a leaked
|
|
193
214
|
// container: the baseline is read and a sweep armed before any request.
|
|
194
215
|
this.ctx.blockConcurrencyWhile(() => this.idle.wake());
|
|
@@ -215,6 +236,25 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
215
236
|
};
|
|
216
237
|
}
|
|
217
238
|
|
|
239
|
+
/** The start gate's view of this object (docs/reference/specs/execution.md
|
|
240
|
+
* item 23): the platform's running flag, and a warm-up that is one trivial
|
|
241
|
+
* command through the SDK — which does the start (instance grant, image
|
|
242
|
+
* pull, boot, the runtime's port) and answers when the runtime does. The
|
|
243
|
+
* warm-up runs inside the idle ledger so the guard sees a request in flight
|
|
244
|
+
* for as long as the start takes. */
|
|
245
|
+
private startHost(): StartGateHost {
|
|
246
|
+
return {
|
|
247
|
+
now: systemClock,
|
|
248
|
+
containerRunning: () => this.ctx.container?.running,
|
|
249
|
+
warmUp: () =>
|
|
250
|
+
this.idle.served(async () => {
|
|
251
|
+
const proc = await createExtensionProcessSandbox(this).exec(["true"], { timeout: WARM_UP_BACKSTOP_MS });
|
|
252
|
+
await proc.output({ encoding: "utf8", timeout: WARM_UP_BACKSTOP_MS });
|
|
253
|
+
}),
|
|
254
|
+
log: (event) => console.log(JSON.stringify({ ...event, thread: this.ctx.id.name ?? this.ctx.id.toString() })),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
218
258
|
/** The idle sweep, scheduled by the guard through the SDK's schedule table
|
|
219
259
|
* and re-armed by it while a container is running. */
|
|
220
260
|
async idleSweep(): Promise<void> {
|
|
@@ -240,7 +280,19 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
240
280
|
execTimeoutSecs: number,
|
|
241
281
|
envVars: Record<string, string>,
|
|
242
282
|
): Promise<ExecAnswer | ExecFailure> {
|
|
243
|
-
|
|
283
|
+
const startedAt = systemClock();
|
|
284
|
+
return this.idle.served(async () => {
|
|
285
|
+
try {
|
|
286
|
+
return await this.gate.through(
|
|
287
|
+
() => this.execute(command, execTimeoutSecs, envVars),
|
|
288
|
+
(cause) => sandboxStartingExecAnswer(cause),
|
|
289
|
+
);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
// The warm-up's own failure, handed on by the gate: a full fleet or a
|
|
292
|
+
// silent control port keeps its name; anything else propagates.
|
|
293
|
+
return this.execFailure(err, startedAt);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
244
296
|
}
|
|
245
297
|
|
|
246
298
|
/** `runCommand` without the ledger entry: the body, and the internal caller
|
|
@@ -261,7 +313,10 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
261
313
|
return this.execFailure(err, startedAt);
|
|
262
314
|
}
|
|
263
315
|
try {
|
|
264
|
-
const out = await proc.output({
|
|
316
|
+
const out = await proc.output({
|
|
317
|
+
encoding: "utf8",
|
|
318
|
+
timeout: execTimeoutSecs * 1000 + OUTPUT_WAIT_AFTER_DEADLINE_MS,
|
|
319
|
+
});
|
|
265
320
|
// coreutils `timeout` exits 124 when the deadline killed the command
|
|
266
321
|
// (137 when the follow-up SIGKILL had to); the SDK's own limit, if it
|
|
267
322
|
// ever wins, reports `timedOut`. All three are the one story.
|
|
@@ -279,9 +334,21 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
279
334
|
};
|
|
280
335
|
} catch (err) {
|
|
281
336
|
if (err instanceof ProcessWaitTimeoutError) {
|
|
282
|
-
// The
|
|
283
|
-
//
|
|
284
|
-
//
|
|
337
|
+
// The output never ended inside the command's deadline plus the
|
|
338
|
+
// margin. Two stories, told apart by asking the runtime (item 24):
|
|
339
|
+
// the process EXITED and a detached child holds its output open —
|
|
340
|
+
// the answer is its exit code and the note, the child runs on; or
|
|
341
|
+
// the process is still RUNNING past its own `timeout` — end it, and
|
|
342
|
+
// report the shell-level timeout it exceeded.
|
|
343
|
+
const status = await proc.status().catch(() => null);
|
|
344
|
+
if (status?.state === "exited") {
|
|
345
|
+
return {
|
|
346
|
+
stdout: "",
|
|
347
|
+
stderr: heldOutputNote(status.exit.code),
|
|
348
|
+
exitCode: status.exit.code,
|
|
349
|
+
durationMs: systemClock() - startedAt,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
285
352
|
await proc.kill(9).catch(() => {});
|
|
286
353
|
return {
|
|
287
354
|
stdout: "",
|
|
@@ -337,9 +404,19 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
337
404
|
}
|
|
338
405
|
}
|
|
339
406
|
|
|
407
|
+
/** A file route's answer while the container starts: HTTP 503 with the
|
|
408
|
+
* token, like a full fleet's. */
|
|
409
|
+
private startingRefusal(cause: StartingCause): FileRefusal {
|
|
410
|
+
const { error, reason } = sandboxStartingAnswer(cause);
|
|
411
|
+
return { error, status: 503, reason };
|
|
412
|
+
}
|
|
413
|
+
|
|
340
414
|
async readText(path: string): Promise<{ content: string } | FileRefusal> {
|
|
341
415
|
return this.idle.served(() =>
|
|
342
|
-
this.
|
|
416
|
+
this.gate.through(
|
|
417
|
+
() => this.fileOp(async () => ({ content: (await this.readFile(path, { encoding: "utf-8" })).content })),
|
|
418
|
+
(cause) => this.startingRefusal(cause),
|
|
419
|
+
),
|
|
343
420
|
);
|
|
344
421
|
}
|
|
345
422
|
|
|
@@ -348,7 +425,12 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
348
425
|
* decoded bytes to it — an SDK read that came back short would otherwise
|
|
349
426
|
* pass as the file. A file over the cap is refused by name inside a 200. */
|
|
350
427
|
async readBase64(path: string): Promise<Base64ReadAnswer | FileRefusal> {
|
|
351
|
-
return this.idle.served(() =>
|
|
428
|
+
return this.idle.served(() =>
|
|
429
|
+
this.gate.through(
|
|
430
|
+
() => this.readBase64Now(path),
|
|
431
|
+
(cause) => this.startingRefusal(cause),
|
|
432
|
+
),
|
|
433
|
+
);
|
|
352
434
|
}
|
|
353
435
|
|
|
354
436
|
private async readBase64Now(path: string): Promise<Base64ReadAnswer | FileRefusal> {
|
|
@@ -377,10 +459,14 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
377
459
|
|
|
378
460
|
async write(path: string, content: string): Promise<{ ok: true } | FileRefusal> {
|
|
379
461
|
return this.idle.served(() =>
|
|
380
|
-
this.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
462
|
+
this.gate.through(
|
|
463
|
+
() =>
|
|
464
|
+
this.fileOp(async () => {
|
|
465
|
+
await this.writeFile(path, content);
|
|
466
|
+
return { ok: true as const };
|
|
467
|
+
}),
|
|
468
|
+
(cause) => this.startingRefusal(cause),
|
|
469
|
+
),
|
|
384
470
|
);
|
|
385
471
|
}
|
|
386
472
|
}
|
|
@@ -388,11 +474,12 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
388
474
|
/** The stderr line an exit 124 carries: the limit, the knob, and the way to
|
|
389
475
|
* outlive a command (`setsid -f`: every /exec runs under `timeout … bash -c`,
|
|
390
476
|
* whose process group is reaped when the command returns, so a plain
|
|
391
|
-
* background job dies with it
|
|
477
|
+
* background job dies with it — and with the wrapper's stdio redirected, so
|
|
478
|
+
* the command's output ends when the command does, item 24). */
|
|
392
479
|
function timeoutNote(execTimeoutSecs: number): string {
|
|
393
480
|
return (
|
|
394
481
|
`command timed out in the sandbox after ${execTimeoutSecs}s (pass the bash tool's timeoutMs for longer commands, max ${BASH_TIMEOUT_MAX_MS} ms); ` +
|
|
395
|
-
|
|
482
|
+
`re-run as smaller/faster steps, or start it detached with \`${DETACH_HINT}\` and poll the log on later calls`
|
|
396
483
|
);
|
|
397
484
|
}
|
|
398
485
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.237.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.237.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
36
36
|
"@eslint/js": "^10.0.1",
|
|
37
|
+
"@opencode/protocol": "2.0.3",
|
|
38
|
+
"@opencode/schema": "2.0.3",
|
|
37
39
|
"@types/node": "^24.0.0",
|
|
40
|
+
"effect": "4.0.0-rc.112",
|
|
38
41
|
"eslint": "^10.10.0",
|
|
39
42
|
"eslint-config-prettier": "^10.1.8",
|
|
40
43
|
"eslint-plugin-vue": "^10.11.0",
|
|
@@ -5863,6 +5866,90 @@
|
|
|
5863
5866
|
"@chevrotain/types": "~11.1.2"
|
|
5864
5867
|
}
|
|
5865
5868
|
},
|
|
5869
|
+
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
|
5870
|
+
"version": "3.0.4",
|
|
5871
|
+
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz",
|
|
5872
|
+
"integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==",
|
|
5873
|
+
"cpu": [
|
|
5874
|
+
"arm64"
|
|
5875
|
+
],
|
|
5876
|
+
"dev": true,
|
|
5877
|
+
"license": "MIT",
|
|
5878
|
+
"optional": true,
|
|
5879
|
+
"os": [
|
|
5880
|
+
"darwin"
|
|
5881
|
+
]
|
|
5882
|
+
},
|
|
5883
|
+
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
|
5884
|
+
"version": "3.0.4",
|
|
5885
|
+
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz",
|
|
5886
|
+
"integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==",
|
|
5887
|
+
"cpu": [
|
|
5888
|
+
"x64"
|
|
5889
|
+
],
|
|
5890
|
+
"dev": true,
|
|
5891
|
+
"license": "MIT",
|
|
5892
|
+
"optional": true,
|
|
5893
|
+
"os": [
|
|
5894
|
+
"darwin"
|
|
5895
|
+
]
|
|
5896
|
+
},
|
|
5897
|
+
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
|
5898
|
+
"version": "3.0.4",
|
|
5899
|
+
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz",
|
|
5900
|
+
"integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==",
|
|
5901
|
+
"cpu": [
|
|
5902
|
+
"arm"
|
|
5903
|
+
],
|
|
5904
|
+
"dev": true,
|
|
5905
|
+
"license": "MIT",
|
|
5906
|
+
"optional": true,
|
|
5907
|
+
"os": [
|
|
5908
|
+
"linux"
|
|
5909
|
+
]
|
|
5910
|
+
},
|
|
5911
|
+
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
|
5912
|
+
"version": "3.0.4",
|
|
5913
|
+
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz",
|
|
5914
|
+
"integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==",
|
|
5915
|
+
"cpu": [
|
|
5916
|
+
"arm64"
|
|
5917
|
+
],
|
|
5918
|
+
"dev": true,
|
|
5919
|
+
"license": "MIT",
|
|
5920
|
+
"optional": true,
|
|
5921
|
+
"os": [
|
|
5922
|
+
"linux"
|
|
5923
|
+
]
|
|
5924
|
+
},
|
|
5925
|
+
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
|
5926
|
+
"version": "3.0.4",
|
|
5927
|
+
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz",
|
|
5928
|
+
"integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==",
|
|
5929
|
+
"cpu": [
|
|
5930
|
+
"x64"
|
|
5931
|
+
],
|
|
5932
|
+
"dev": true,
|
|
5933
|
+
"license": "MIT",
|
|
5934
|
+
"optional": true,
|
|
5935
|
+
"os": [
|
|
5936
|
+
"linux"
|
|
5937
|
+
]
|
|
5938
|
+
},
|
|
5939
|
+
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
|
5940
|
+
"version": "3.0.4",
|
|
5941
|
+
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz",
|
|
5942
|
+
"integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==",
|
|
5943
|
+
"cpu": [
|
|
5944
|
+
"x64"
|
|
5945
|
+
],
|
|
5946
|
+
"dev": true,
|
|
5947
|
+
"license": "MIT",
|
|
5948
|
+
"optional": true,
|
|
5949
|
+
"os": [
|
|
5950
|
+
"win32"
|
|
5951
|
+
]
|
|
5952
|
+
},
|
|
5866
5953
|
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
|
|
5867
5954
|
"version": "1.5.1",
|
|
5868
5955
|
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
|
|
@@ -6155,6 +6242,28 @@
|
|
|
6155
6242
|
"dev": true,
|
|
6156
6243
|
"license": "MIT"
|
|
6157
6244
|
},
|
|
6245
|
+
"node_modules/@opencode/protocol": {
|
|
6246
|
+
"version": "2.0.3",
|
|
6247
|
+
"resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.3.tgz",
|
|
6248
|
+
"integrity": "sha512-DNHbkLDTuAyMsmVdnjuKulcfoDbllW2dxt4W19r2aJEYvswJpEO9168CSK6Sjbx1FOh/B9yL7SQgoDRMd4K5Vw==",
|
|
6249
|
+
"dev": true,
|
|
6250
|
+
"license": "MIT",
|
|
6251
|
+
"dependencies": {
|
|
6252
|
+
"@opencode/schema": "2.0.3",
|
|
6253
|
+
"effect": "4.0.0-rc.112"
|
|
6254
|
+
}
|
|
6255
|
+
},
|
|
6256
|
+
"node_modules/@opencode/schema": {
|
|
6257
|
+
"version": "2.0.3",
|
|
6258
|
+
"resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.3.tgz",
|
|
6259
|
+
"integrity": "sha512-thPeBbqw4+SkZ/0LOuqfahSRGuWogRa/wwfIVyiQYsoE0vzLx8XUpHQ+FSUWkdz6llDtP0+ZsyBqXUMpXmPgbg==",
|
|
6260
|
+
"dev": true,
|
|
6261
|
+
"license": "MIT",
|
|
6262
|
+
"dependencies": {
|
|
6263
|
+
"@standard-schema/spec": "1.1.0",
|
|
6264
|
+
"effect": "4.0.0-rc.112"
|
|
6265
|
+
}
|
|
6266
|
+
},
|
|
6158
6267
|
"node_modules/@oxc-project/types": {
|
|
6159
6268
|
"version": "0.148.0",
|
|
6160
6269
|
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz",
|
|
@@ -11840,6 +11949,17 @@
|
|
|
11840
11949
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
|
11841
11950
|
"license": "MIT"
|
|
11842
11951
|
},
|
|
11952
|
+
"node_modules/effect": {
|
|
11953
|
+
"version": "4.0.0-rc.112",
|
|
11954
|
+
"resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-rc.112.tgz",
|
|
11955
|
+
"integrity": "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A==",
|
|
11956
|
+
"dev": true,
|
|
11957
|
+
"license": "MIT",
|
|
11958
|
+
"dependencies": {
|
|
11959
|
+
"fast-check": "^4.9.0",
|
|
11960
|
+
"msgpackr": "^2.0.5"
|
|
11961
|
+
}
|
|
11962
|
+
},
|
|
11843
11963
|
"node_modules/embla-carousel": {
|
|
11844
11964
|
"version": "8.6.0",
|
|
11845
11965
|
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
|
@@ -12447,6 +12567,29 @@
|
|
|
12447
12567
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
|
12448
12568
|
"license": "MIT"
|
|
12449
12569
|
},
|
|
12570
|
+
"node_modules/fast-check": {
|
|
12571
|
+
"version": "4.10.1",
|
|
12572
|
+
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.10.1.tgz",
|
|
12573
|
+
"integrity": "sha512-sB5Vghiu8MyCyToHoBVGsT0baZg3sZWNIY+a6Ct2EDrQJlT4YdH6MC1BSLNe3kX1k5i5g0q1O52XFgcKK/rGHg==",
|
|
12574
|
+
"dev": true,
|
|
12575
|
+
"funding": [
|
|
12576
|
+
{
|
|
12577
|
+
"type": "individual",
|
|
12578
|
+
"url": "https://github.com/sponsors/dubzzz"
|
|
12579
|
+
},
|
|
12580
|
+
{
|
|
12581
|
+
"type": "opencollective",
|
|
12582
|
+
"url": "https://opencollective.com/fast-check"
|
|
12583
|
+
}
|
|
12584
|
+
],
|
|
12585
|
+
"license": "MIT",
|
|
12586
|
+
"dependencies": {
|
|
12587
|
+
"pure-rand": "^8.0.0"
|
|
12588
|
+
},
|
|
12589
|
+
"engines": {
|
|
12590
|
+
"node": ">=12.17.0"
|
|
12591
|
+
}
|
|
12592
|
+
},
|
|
12450
12593
|
"node_modules/fast-deep-equal": {
|
|
12451
12594
|
"version": "3.1.3",
|
|
12452
12595
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
@@ -15349,6 +15492,39 @@
|
|
|
15349
15492
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
|
15350
15493
|
"license": "MIT"
|
|
15351
15494
|
},
|
|
15495
|
+
"node_modules/msgpackr": {
|
|
15496
|
+
"version": "2.1.0",
|
|
15497
|
+
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.1.0.tgz",
|
|
15498
|
+
"integrity": "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ==",
|
|
15499
|
+
"dev": true,
|
|
15500
|
+
"license": "MIT",
|
|
15501
|
+
"optionalDependencies": {
|
|
15502
|
+
"msgpackr-extract": "^3.0.4"
|
|
15503
|
+
}
|
|
15504
|
+
},
|
|
15505
|
+
"node_modules/msgpackr-extract": {
|
|
15506
|
+
"version": "3.0.4",
|
|
15507
|
+
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz",
|
|
15508
|
+
"integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==",
|
|
15509
|
+
"dev": true,
|
|
15510
|
+
"hasInstallScript": true,
|
|
15511
|
+
"license": "MIT",
|
|
15512
|
+
"optional": true,
|
|
15513
|
+
"dependencies": {
|
|
15514
|
+
"node-gyp-build-optional-packages": "5.2.2"
|
|
15515
|
+
},
|
|
15516
|
+
"bin": {
|
|
15517
|
+
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
|
|
15518
|
+
},
|
|
15519
|
+
"optionalDependencies": {
|
|
15520
|
+
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4",
|
|
15521
|
+
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4",
|
|
15522
|
+
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4",
|
|
15523
|
+
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4",
|
|
15524
|
+
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4",
|
|
15525
|
+
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4"
|
|
15526
|
+
}
|
|
15527
|
+
},
|
|
15352
15528
|
"node_modules/muggle-string": {
|
|
15353
15529
|
"version": "0.4.1",
|
|
15354
15530
|
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
|
@@ -15453,6 +15629,22 @@
|
|
|
15453
15629
|
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
|
15454
15630
|
"license": "MIT"
|
|
15455
15631
|
},
|
|
15632
|
+
"node_modules/node-gyp-build-optional-packages": {
|
|
15633
|
+
"version": "5.2.2",
|
|
15634
|
+
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
|
15635
|
+
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
|
|
15636
|
+
"dev": true,
|
|
15637
|
+
"license": "MIT",
|
|
15638
|
+
"optional": true,
|
|
15639
|
+
"dependencies": {
|
|
15640
|
+
"detect-libc": "^2.0.1"
|
|
15641
|
+
},
|
|
15642
|
+
"bin": {
|
|
15643
|
+
"node-gyp-build-optional-packages": "bin.js",
|
|
15644
|
+
"node-gyp-build-optional-packages-optional": "optional.js",
|
|
15645
|
+
"node-gyp-build-optional-packages-test": "build-test.js"
|
|
15646
|
+
}
|
|
15647
|
+
},
|
|
15456
15648
|
"node_modules/node-mock-http": {
|
|
15457
15649
|
"version": "1.0.5",
|
|
15458
15650
|
"resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz",
|
|
@@ -16269,6 +16461,23 @@
|
|
|
16269
16461
|
"node": ">=6"
|
|
16270
16462
|
}
|
|
16271
16463
|
},
|
|
16464
|
+
"node_modules/pure-rand": {
|
|
16465
|
+
"version": "8.4.2",
|
|
16466
|
+
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.2.tgz",
|
|
16467
|
+
"integrity": "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==",
|
|
16468
|
+
"dev": true,
|
|
16469
|
+
"funding": [
|
|
16470
|
+
{
|
|
16471
|
+
"type": "individual",
|
|
16472
|
+
"url": "https://github.com/sponsors/dubzzz"
|
|
16473
|
+
},
|
|
16474
|
+
{
|
|
16475
|
+
"type": "opencollective",
|
|
16476
|
+
"url": "https://opencollective.com/fast-check"
|
|
16477
|
+
}
|
|
16478
|
+
],
|
|
16479
|
+
"license": "MIT"
|
|
16480
|
+
},
|
|
16272
16481
|
"node_modules/qified": {
|
|
16273
16482
|
"version": "0.10.1",
|
|
16274
16483
|
"resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz",
|
|
@@ -20032,7 +20241,7 @@
|
|
|
20032
20241
|
},
|
|
20033
20242
|
"packages/switchboard": {
|
|
20034
20243
|
"name": "@coreplane/switchboard",
|
|
20035
|
-
"version": "1.
|
|
20244
|
+
"version": "1.237.0",
|
|
20036
20245
|
"license": "Apache-2.0",
|
|
20037
20246
|
"dependencies": {
|
|
20038
20247
|
"@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.
|
|
3
|
+
"version": "1.237.0",
|
|
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",
|
|
@@ -91,7 +91,10 @@
|
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
93
93
|
"@eslint/js": "^10.0.1",
|
|
94
|
+
"@opencode/protocol": "2.0.3",
|
|
95
|
+
"@opencode/schema": "2.0.3",
|
|
94
96
|
"@types/node": "^24.0.0",
|
|
97
|
+
"effect": "4.0.0-rc.112",
|
|
95
98
|
"eslint": "^10.10.0",
|
|
96
99
|
"eslint-config-prettier": "^10.1.8",
|
|
97
100
|
"eslint-plugin-vue": "^10.11.0",
|
package/dist/assets/source.json
CHANGED
|
@@ -63,6 +63,21 @@ export function principalOf(actor: Actor): Actor {
|
|
|
63
63
|
return current;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** The ids that mean "me" for a decision (record 0042): the root principal's
|
|
67
|
+
* `self` when it carries one — its own id and the person a dashboard session
|
|
68
|
+
* is linked to — else its id alone. Never read by a grant check. */
|
|
69
|
+
export function selfIdsOf(actor: Actor): readonly string[] {
|
|
70
|
+
const principal = principalOf(actor);
|
|
71
|
+
return principal.self && principal.self.length > 0 ? principal.self : [principal.id];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Whether a self set names a chat identity — the `acts-as-person` condition:
|
|
75
|
+
* every chat actor, a dashboard session linked to its person; never an
|
|
76
|
+
* unlinked browser session or a credential. */
|
|
77
|
+
export function actsAsPerson(selfIds: readonly string[]): boolean {
|
|
78
|
+
return selfIds.some((id) => id.startsWith("slack:"));
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
// ── decision ─────────────────────────────────────────────────────────────────
|
|
67
82
|
|
|
68
83
|
const ALLOW: Decision = Object.freeze({ allow: true });
|
|
@@ -76,11 +91,13 @@ export type DenyReason =
|
|
|
76
91
|
| "missing-grant"
|
|
77
92
|
| "not-member"
|
|
78
93
|
| "not-self"
|
|
94
|
+
| "not-person"
|
|
79
95
|
| "not-owner"
|
|
80
96
|
| "not-all-channels";
|
|
81
97
|
|
|
82
98
|
const FAILURE_REASON: Readonly<Record<Condition["kind"], DenyReason>> = {
|
|
83
99
|
"has-grant": "missing-grant",
|
|
100
|
+
"acts-as-person": "not-person",
|
|
84
101
|
"member-of": "not-member",
|
|
85
102
|
"is-self": "not-self",
|
|
86
103
|
"owner-of": "not-owner",
|
|
@@ -98,7 +115,7 @@ export function isKnownActorKind(kind: string): boolean {
|
|
|
98
115
|
export function evaluateCondition(
|
|
99
116
|
condition: Condition,
|
|
100
117
|
grants: Grants,
|
|
101
|
-
|
|
118
|
+
selfIds: readonly string[],
|
|
102
119
|
attributes: ResourceAttributes,
|
|
103
120
|
): boolean {
|
|
104
121
|
switch (condition.kind) {
|
|
@@ -115,7 +132,9 @@ export function evaluateCondition(
|
|
|
115
132
|
attributes.channelVisibility === "public"
|
|
116
133
|
);
|
|
117
134
|
case "is-self":
|
|
118
|
-
return attributes.userId !== undefined && attributes.userId
|
|
135
|
+
return attributes.userId !== undefined && selfIds.includes(attributes.userId);
|
|
136
|
+
case "acts-as-person":
|
|
137
|
+
return actsAsPerson(selfIds);
|
|
119
138
|
case "owner-of":
|
|
120
139
|
return attributes.repo !== undefined && holds(grants.repos, attributes.repo);
|
|
121
140
|
case "all-channels":
|
|
@@ -134,8 +153,8 @@ export function evaluateRule(rule: Rule, actor: Actor, resource: Resource): bool
|
|
|
134
153
|
const attributes = attributesOf(resource);
|
|
135
154
|
if (rule.originVisibility && !rule.originVisibility.includes(attributes.visibility)) return false;
|
|
136
155
|
const grants = effectiveGrants(actor);
|
|
137
|
-
const
|
|
138
|
-
return rule.when.every((condition) => evaluateCondition(condition, grants,
|
|
156
|
+
const selfIds = selfIdsOf(actor);
|
|
157
|
+
return rule.when.every((condition) => evaluateCondition(condition, grants, selfIds, attributes));
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
/** `authorize` over an explicit (validated) table. Tests use it to drive
|
|
@@ -153,10 +172,10 @@ export function authorizeWith(rules: readonly Rule[], actor: Actor, action: Acti
|
|
|
153
172
|
);
|
|
154
173
|
if (forOrigin.length === 0) return deny("origin-visibility");
|
|
155
174
|
const grants = effectiveGrants(actor);
|
|
156
|
-
const
|
|
175
|
+
const selfIds = selfIdsOf(actor);
|
|
157
176
|
let reason: DenyReason | undefined;
|
|
158
177
|
for (const rule of forOrigin) {
|
|
159
|
-
const failed = rule.when.find((condition) => !evaluateCondition(condition, grants,
|
|
178
|
+
const failed = rule.when.find((condition) => !evaluateCondition(condition, grants, selfIds, attributes));
|
|
160
179
|
if (!failed) return ALLOW;
|
|
161
180
|
reason ??= FAILURE_REASON[failed.kind];
|
|
162
181
|
}
|
|
@@ -41,6 +41,7 @@ import type { ActorKind, Condition, Rule } from "./types.js";
|
|
|
41
41
|
const grant = (g: string): Condition => ({ kind: "has-grant", grant: g });
|
|
42
42
|
const MEMBER_OF: Condition = { kind: "member-of" };
|
|
43
43
|
const IS_SELF: Condition = { kind: "is-self" };
|
|
44
|
+
const ACTS_AS_PERSON: Condition = { kind: "acts-as-person" };
|
|
44
45
|
const OWNER_OF: Condition = { kind: "owner-of" };
|
|
45
46
|
const ALL_CHANNELS: Condition = { kind: "all-channels" };
|
|
46
47
|
|
|
@@ -144,6 +145,13 @@ export const POLICY: readonly Rule[] = [
|
|
|
144
145
|
// ── mcp (external MCP servers live in the three config tiers) ────────────
|
|
145
146
|
{ action: "mcp:read", resource: "command", when: [grant("mcp:read")] },
|
|
146
147
|
{ action: "mcp:write", resource: "command", when: [grant("mcp:write")] },
|
|
148
|
+
// A person always has their own tier to write (`mcp add|connect|remove`
|
|
149
|
+
// for themselves), as `config set me` already is theirs: the row admits an
|
|
150
|
+
// actor whose `self` names a chat identity — every chat user (who holds
|
|
151
|
+
// `mcp:write` by baseline anyway) and a dashboard session linked to its
|
|
152
|
+
// person (record 0042) — never an unlinked browser session or a credential.
|
|
153
|
+
// Which tier the write reaches stays the handler's question below.
|
|
154
|
+
{ action: "mcp:write", resource: "command", actorKinds: ["user"], when: [ACTS_AS_PERSON] },
|
|
147
155
|
// A CHANNEL's servers: the channel-config right for a person; a credential
|
|
148
156
|
// an admin minted with `mcp:write` manages any tier it can name.
|
|
149
157
|
{ action: "mcp:write", resource: "config-scope", resourceKind: "channel", when: [grant("config:write")] },
|
|
@@ -214,6 +222,7 @@ export const CONDITION_KINDS: readonly Condition["kind"][] = [
|
|
|
214
222
|
"has-grant",
|
|
215
223
|
"member-of",
|
|
216
224
|
"is-self",
|
|
225
|
+
"acts-as-person",
|
|
217
226
|
"owner-of",
|
|
218
227
|
"all-channels",
|
|
219
228
|
];
|