@coreplane/switchboard 0.0.0 → 1.18.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.
Files changed (131) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +18 -1
  3. package/dist/assets/.dockerignore +27 -0
  4. package/dist/assets/.env.example +33 -0
  5. package/dist/assets/Dockerfile +111 -0
  6. package/dist/assets/config/config.example.yaml +359 -0
  7. package/dist/assets/deploy/bin/build-stamp.d.mts +15 -0
  8. package/dist/assets/deploy/bin/build-stamp.mjs +98 -0
  9. package/dist/assets/deploy/bin/cf-logs +32 -0
  10. package/dist/assets/deploy/cloudflare/package.json +29 -0
  11. package/dist/assets/deploy/cloudflare/preflight.mjs +243 -0
  12. package/dist/assets/deploy/cloudflare/tsconfig.json +18 -0
  13. package/dist/assets/deploy/cloudflare/worker.ts +382 -0
  14. package/dist/assets/deploy/cloudflare/wrangler.template.jsonc +67 -0
  15. package/dist/assets/deploy/cloudflare/write-build.d.mts +7 -0
  16. package/dist/assets/deploy/cloudflare/write-build.mjs +53 -0
  17. package/dist/assets/deploy/cloudflare-docs/package.json +18 -0
  18. package/dist/assets/deploy/cloudflare-docs/wrangler.template.jsonc +30 -0
  19. package/dist/assets/deploy/cloudflare-memory/package.json +25 -0
  20. package/dist/assets/deploy/cloudflare-memory/tsconfig.json +17 -0
  21. package/dist/assets/deploy/cloudflare-memory/worker.ts +2635 -0
  22. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +50 -0
  23. package/dist/assets/deploy/cloudflare-resident/Dockerfile +91 -0
  24. package/dist/assets/deploy/cloudflare-resident/gc.ts +287 -0
  25. package/dist/assets/deploy/cloudflare-resident/node-async-hooks.d.ts +11 -0
  26. package/dist/assets/deploy/cloudflare-resident/package.json +29 -0
  27. package/dist/assets/deploy/cloudflare-resident/preflight.mjs +224 -0
  28. package/dist/assets/deploy/cloudflare-resident/tsconfig.json +19 -0
  29. package/dist/assets/deploy/cloudflare-resident/worker.ts +6637 -0
  30. package/dist/assets/deploy/cloudflare-resident/wrangler.template.jsonc +120 -0
  31. package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +67 -0
  32. package/dist/assets/deploy/cloudflare-sandbox/docker-wrapper.sh +37 -0
  33. package/dist/assets/deploy/cloudflare-sandbox/package.json +26 -0
  34. package/dist/assets/deploy/cloudflare-sandbox/tsconfig.json +20 -0
  35. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +410 -0
  36. package/dist/assets/deploy/cloudflare-sandbox/wrangler.template.jsonc +67 -0
  37. package/dist/assets/deploy/profile.example.json +13 -0
  38. package/dist/assets/deploy/secrets.manifest.json +108 -0
  39. package/dist/assets/docker-entrypoint.sh +15 -0
  40. package/dist/assets/package-lock.json +18407 -0
  41. package/dist/assets/package.json +104 -0
  42. package/dist/assets/project.json +219 -0
  43. package/dist/assets/source.json +5 -0
  44. package/dist/assets/src/core/authz/actor.ts +100 -0
  45. package/dist/assets/src/core/authz/authorize.ts +169 -0
  46. package/dist/assets/src/core/authz/grants.ts +347 -0
  47. package/dist/assets/src/core/authz/policy.ts +281 -0
  48. package/dist/assets/src/core/authz/resource.ts +147 -0
  49. package/dist/assets/src/core/authz/types.ts +164 -0
  50. package/dist/assets/src/core/drain.ts +54 -0
  51. package/dist/assets/src/core/ingressTokens.ts +64 -0
  52. package/dist/assets/src/core/memory/engine.ts +115 -0
  53. package/dist/assets/src/core/memory/scorer.ts +147 -0
  54. package/dist/assets/src/core/memory/types.ts +120 -0
  55. package/dist/assets/src/core/normalizeSpans.ts +299 -0
  56. package/dist/assets/src/core/prDescriptionTypes.ts +54 -0
  57. package/dist/assets/src/core/redact.ts +113 -0
  58. package/dist/assets/src/core/runEvents.ts +537 -0
  59. package/dist/assets/src/core/runFriction.ts +665 -0
  60. package/dist/assets/src/core/runLedger/decisions.ts +126 -0
  61. package/dist/assets/src/core/runLedger/types.ts +177 -0
  62. package/dist/assets/src/core/runRecord.ts +627 -0
  63. package/dist/assets/src/core/runShape.ts +61 -0
  64. package/dist/assets/src/core/schedules.ts +452 -0
  65. package/dist/assets/src/core/time/formatDuration.ts +61 -0
  66. package/dist/assets/src/core/trace/attrs.ts +203 -0
  67. package/dist/assets/src/core/trace/classify.ts +49 -0
  68. package/dist/assets/src/core/trace/clock.ts +6 -0
  69. package/dist/assets/src/core/trace/context.ts +9 -0
  70. package/dist/assets/src/core/trace/ids.ts +23 -0
  71. package/dist/assets/src/core/trace/partition.ts +235 -0
  72. package/dist/assets/src/core/trace/sinks.ts +68 -0
  73. package/dist/assets/src/core/trace/streamSpans.ts +163 -0
  74. package/dist/assets/src/core/trace/traceparent.ts +29 -0
  75. package/dist/assets/src/core/trace/tracer.ts +247 -0
  76. package/dist/assets/src/core/trace/types.ts +125 -0
  77. package/dist/assets/src/core/trace/workerTrace.ts +97 -0
  78. package/dist/assets/src/deploy/buildStamp.ts +93 -0
  79. package/dist/assets/src/deploy/liveGate.ts +203 -0
  80. package/dist/assets/src/deploy/profile.ts +162 -0
  81. package/dist/assets/src/deploy/restart.ts +393 -0
  82. package/dist/assets/src/effort.ts +17 -0
  83. package/dist/assets/src/execution/bashTimeout.ts +78 -0
  84. package/dist/assets/src/execution/bindingPurge.ts +43 -0
  85. package/dist/assets/src/execution/residentBackupTransfer.ts +50 -0
  86. package/dist/assets/src/execution/residentCleanliness.ts +95 -0
  87. package/dist/assets/src/execution/residentCredentials.ts +81 -0
  88. package/dist/assets/src/execution/residentDepCache.ts +321 -0
  89. package/dist/assets/src/execution/residentDepsStore.ts +326 -0
  90. package/dist/assets/src/execution/residentDetach.ts +48 -0
  91. package/dist/assets/src/execution/residentDisk.ts +107 -0
  92. package/dist/assets/src/execution/residentDiskBudget.ts +448 -0
  93. package/dist/assets/src/execution/residentExecWrap.ts +100 -0
  94. package/dist/assets/src/execution/residentHead.ts +85 -0
  95. package/dist/assets/src/execution/residentReadonly.ts +72 -0
  96. package/dist/assets/src/execution/residentRefresh.ts +429 -0
  97. package/dist/assets/src/execution/residentRestoreExtract.ts +130 -0
  98. package/dist/assets/src/execution/residentState.ts +47 -0
  99. package/dist/assets/src/execution/residentStepReport.ts +98 -0
  100. package/dist/assets/src/execution/residentStepTrace.ts +97 -0
  101. package/dist/assets/src/execution/residentSteps.ts +99 -0
  102. package/dist/assets/src/execution/residentText.ts +83 -0
  103. package/dist/assets/src/execution/residentTrace.ts +119 -0
  104. package/dist/assets/src/execution/sandboxEnv.ts +42 -0
  105. package/dist/assets/src/execution/sandboxErrors.ts +159 -0
  106. package/dist/assets/src/execution/sandboxKeepalive.ts +118 -0
  107. package/dist/assets/src/execution/shellQuote.ts +8 -0
  108. package/dist/assets/src/mcp/registry.ts +242 -0
  109. package/dist/assets/src/providers/types.ts +152 -0
  110. package/dist/assets/web/dist/.vite/manifest.json +176 -0
  111. package/dist/assets/web/dist/assets/AppShell-Bk2gbvet.js +1 -0
  112. package/dist/assets/web/dist/assets/CostsPage-CTZcMYYx.js +1 -0
  113. package/dist/assets/web/dist/assets/NotFoundPage-C-BuaSm8.js +1 -0
  114. package/dist/assets/web/dist/assets/ResidentDetailPage-D3shEnzl.js +1 -0
  115. package/dist/assets/web/dist/assets/ResidentsIndexPage-DWIubQ05.js +1 -0
  116. package/dist/assets/web/dist/assets/RunRoutePage-BMjuE-oX.js +126 -0
  117. package/dist/assets/web/dist/assets/RunRoutePage-XVFj0XDc.css +1 -0
  118. package/dist/assets/web/dist/assets/RunsIndexPage-C3_jYIo0.js +1 -0
  119. package/dist/assets/web/dist/assets/RunsTabs-C4krAL9o.js +1 -0
  120. package/dist/assets/web/dist/assets/ScheduledPage-g1W58mtN.js +1 -0
  121. package/dist/assets/web/dist/assets/StatusDot-DcPRw3zu.js +1 -0
  122. package/dist/assets/web/dist/assets/Tooltip-DJUkMYjo.js +1 -0
  123. package/dist/assets/web/dist/assets/favicon-DL1rdWJt.js +1 -0
  124. package/dist/assets/web/dist/assets/localIso-L06jV29p.js +1 -0
  125. package/dist/assets/web/dist/assets/main-BsBGUyMH.css +2 -0
  126. package/dist/assets/web/dist/assets/main-CyM5f4JC.js +28 -0
  127. package/dist/assets/web/dist/assets/residentDiskBudget-BMBKlYRH.js +1 -0
  128. package/dist/assets/web/dist/assets/seed-BglCRKLA.js +6 -0
  129. package/dist/assets/web/dist/assets/wallClock-Ckv3sKoR.js +1 -0
  130. package/dist/cli.js +34494 -0
  131. package/package.json +43 -10
@@ -0,0 +1,67 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ "name": "{{script}}",
4
+ "main": "worker.ts",
5
+ "compatibility_date": "2026-08-01",
6
+ // The installation's Cloudflare account (deploy/profile.json `account`) — every Worker deploys to it.
7
+ "account_id": "{{account}}",
8
+ // Custom domain on the installation's zone (profile `zone`; the state,
9
+ // resident and sandbox Workers sit beside it). Slack traffic is the
10
+ // container's outbound websocket; the inbound HTTP surface here is the cron
11
+ // keep-alive + the authed ingress/MCP endpoints + the live-view dashboard
12
+ // (/runs). workers.dev is DISABLED: the /runs dashboard must sit behind
13
+ // Cloudflare Access (SSO), and Access can only front a real zone domain —
14
+ // leaving the workers.dev URL up would expose an un-SSO'd copy of /runs.
15
+ "workers_dev": false,
16
+ "routes": [{ "pattern": "{{hostname}}", "custom_domain": true }],
17
+ // Non-secret, deploy-constant config for the container. PUBLIC_BASE_URL: the
18
+ // live-view link base — the dispatcher (in the container) reads
19
+ // process.env.PUBLIC_BASE_URL to put the /runs/<id>?t=… link on the status
20
+ // card; worker.ts forwards it into envVars. ACCESS_*: the Cloudflare Access
21
+ // app this bot sits behind (profile `access`) — the app-layer gate
22
+ // (src/channels/accessAuth.ts) verifies the Cf-Access-Jwt-Assertion JWT
23
+ // against this team's keys and AUD, fail-closed, so /runs is denied without
24
+ // a valid Access identity. Neither ACCESS_ value is a secret (forging a JWT
25
+ // needs Cloudflare's private key, not these). A profile without `access`
26
+ // renders neither var, and the gate then treats Access as not configured:
27
+ // every dashboard request is denied.
28
+ "vars": {
29
+ "PUBLIC_BASE_URL": "{{urls.publicBaseUrl}}",
30
+ // {{#if access}}
31
+ "ACCESS_TEAM_DOMAIN": "{{access.teamDomain}}",
32
+ "ACCESS_AUD": "{{access.aud}}",
33
+ // {{/if}}
34
+ // The state Worker (deploy/cloudflare-memory/): where worker.ts records each
35
+ // scheduled firing for the /runs Scheduled panel, with MEMORY_TOKEN.
36
+ // A profile without a memory Worker renders no var: firings go unrecorded.
37
+ // {{#if urls.stateWorkerUrl}}
38
+ "STATE_WORKER_URL": "{{urls.stateWorkerUrl}}"
39
+ // {{/if}}
40
+ },
41
+ "observability": { "enabled": true, "traces": { "enabled": true } },
42
+ "containers": [
43
+ {
44
+ "class_name": "SwitchboardServer",
45
+ // The profile's image mode decides (src/deploy/images.ts): `build` renders
46
+ // ../../Dockerfile (the same image docker-compose uses), built by wrangler
47
+ // at deploy time; `registry` renders the release's published image as
48
+ // copied into this account's registry by `deploy images`.
49
+ "image": "{{image}}",
50
+ "instance_type": "basic",
51
+ "max_instances": 1
52
+ }
53
+ ],
54
+ "durable_objects": {
55
+ "bindings": [{ "name": "SWITCHBOARD", "class_name": "SwitchboardServer" }]
56
+ },
57
+ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["SwitchboardServer"] }],
58
+ // Every cron here IS a schedule in src/core/schedules.ts (the registry; a unit
59
+ // test fails when the two sets differ) and worker.ts looks each firing up there.
60
+ // "* * * * *": keep-alive (/healthz) — also what RESTARTS the container after a
61
+ // deploy rollout, a `deploy restart` (POST /admin/restart → DO stop()), or
62
+ // platform maintenance; the interval bounds the deaf window
63
+ // (Socket Mode events during downtime are lost, never redelivered). Not a run.
64
+ // "0 14 * * 1": the weekly self-improvement pass — POST /ingress as the
65
+ // `cron` identity with `friction propose`; an ordinary run on /runs.
66
+ "triggers": { "crons": ["* * * * *", "0 14 * * 1"] }
67
+ }
@@ -0,0 +1,7 @@
1
+ // Types for write-build.mjs (plain-Node ESM so the bot's `npm run deploy` needs
2
+ // no build step). The file it writes is what `src/index.ts` reads as `BuildInfo`.
3
+ export type BuildInfo = { commit: string; builtAt: string };
4
+ export const COMMIT_ENV: "SWITCHBOARD_BUILD_COMMIT";
5
+ export function buildInfo(input: { commit: string; dirty: boolean; now?: Date }): BuildInfo;
6
+ export function readBuildInfo(env?: Record<string, string | undefined>, git?: (args: string[]) => string): BuildInfo;
7
+ export function main(): number;
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ // Stamp the build identity into the bot image (docs/reference/specs/slack-channel.md item 8).
3
+ //
4
+ // `wrangler deploy` builds ../../Dockerfile from the CURRENT tree and exposes
5
+ // no build args, so `npm run deploy` writes `<repo>/build.json` first and the
6
+ // Dockerfile COPYs it (`COPY package.json build.jso[n] ./` — the glob makes the
7
+ // file optional, so a bare `wrangler deploy` or docker-compose still builds and
8
+ // the bot then reports `commit: "unknown"`). The bot reads it once at startup and
9
+ // serves it on `GET /healthz` as `build: { commit, builtAt }`; `deploy:all`'s
10
+ // live gate compares that commit to the one it deployed, so "deployed" is never
11
+ // confused with "live". The file is gitignored: the tree stays clean.
12
+ //
13
+ // Dependency-free Node (child_process + fs). `buildInfo()` is pure given its
14
+ // inputs; `main()` does the I/O.
15
+ import { execFileSync } from "node:child_process";
16
+ import { writeFileSync } from "node:fs";
17
+ import { dirname, join } from "node:path";
18
+ import { fileURLToPath, pathToFileURL } from "node:url";
19
+
20
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
21
+
22
+ /** `{ commit, builtAt }` — `commit` gets a `-dirty` suffix when the tree has uncommitted changes. */
23
+ export function buildInfo({ commit, dirty, now = new Date() }) {
24
+ return { commit: `${commit}${dirty ? "-dirty" : ""}`, builtAt: now.toISOString() };
25
+ }
26
+
27
+ /** The env var that names the commit when there is no tree to read — a deploy from the published npm
28
+ * package (src/deploy/run.ts sets it; `../bin/build-stamp.mjs` reads the same variable). */
29
+ export const COMMIT_ENV = "SWITCHBOARD_BUILD_COMMIT";
30
+
31
+ /** The identity: the environment's commit when it names one (never `-dirty` — nothing was read from a tree), else git's. */
32
+ export function readBuildInfo(
33
+ env = process.env,
34
+ git = (args) => execFileSync("git", args, { cwd: REPO_ROOT, encoding: "utf8" }).trim(),
35
+ ) {
36
+ const given = env[COMMIT_ENV]?.trim();
37
+ if (given) return buildInfo({ commit: given, dirty: false });
38
+ return buildInfo({ commit: git(["rev-parse", "HEAD"]), dirty: git(["status", "--porcelain"]) !== "" });
39
+ }
40
+
41
+ export function main() {
42
+ const info = readBuildInfo();
43
+ const path = join(REPO_ROOT, "build.json");
44
+ writeFileSync(path, `${JSON.stringify(info)}\n`);
45
+ console.log(`[build] ${path}: ${info.commit} @ ${info.builtAt}`);
46
+ return 0;
47
+ }
48
+
49
+ // Run only when executed directly (`node write-build.mjs`), not when imported.
50
+ // pathToFileURL, not `file://${argv[1]}`, so the guard also holds on Windows paths.
51
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
52
+ process.exitCode = main();
53
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "switchboard-docs-worker",
3
+ "private": true,
4
+ "type": "module",
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
8
+ "scripts": {
9
+ "build": "npm --prefix ../../docs run build",
10
+ "deploy": "npm --prefix ../.. run --silent deploy:gen && npm run build && wrangler deploy",
11
+ "verify": "npm --prefix ../.. run --silent deploy:gen && wrangler deploy --dry-run",
12
+ "dev": "wrangler dev",
13
+ "tail": "wrangler tail"
14
+ },
15
+ "devDependencies": {
16
+ "wrangler": "^4.129.0"
17
+ }
18
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ // THE PROJECT'S WEBSITE, not a Worker of an installation: the script name is
4
+ // `<name>-docs` and the hostname the host of `docs`, both from project.json
5
+ // (src/deploy/wranglerTemplate.ts `siteView`) — an installation never deploys
6
+ // a copy of the site, and the bot's /docs sends everyone to the published one.
7
+ "name": "{{script}}",
8
+ "compatibility_date": "2026-08-01",
9
+ // The account the project's own CI deploys the site to: the one value the
10
+ // deployment profile (deploy/profile.json `account`) contributes here.
11
+ "account_id": "{{account}}",
12
+ // ASSETS ONLY: no `main`, so there is no Worker script, no Durable Object,
13
+ // and no container. Nothing here can be mid-rollout or draining, which is
14
+ // exactly why CI may deploy it on every docs change, while the four runtime
15
+ // Workers deploy on the release, preflighted and in order (AGENTS.md,
16
+ // docs/reference/specs/docs-site.md, docs/reference/specs/release-and-deploy.md).
17
+ "assets": {
18
+ "directory": "../../docs/.vitepress/dist",
19
+ // VitePress emits clean URLs (`/reference/cli.html`), so /reference/cli must
20
+ // resolve to it; a miss serves the built 404 page, not an empty 404.
21
+ "html_handling": "auto-trailing-slash",
22
+ "not_found_handling": "404-page"
23
+ },
24
+ // workers.dev is DISABLED so the site has exactly one hostname, the one
25
+ // project.json publishes. The site is public: no Access application fronts
26
+ // it, and nothing here is an installation's to gate.
27
+ "workers_dev": false,
28
+ "routes": [{ "pattern": "{{hostname}}", "custom_domain": true }],
29
+ "observability": { "enabled": true }
30
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "switchboard-memory-worker",
3
+ "private": true,
4
+ "type": "module",
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
8
+ "scripts": {
9
+ "deploy": "node ../bin/build-stamp.mjs",
10
+ "dev": "wrangler dev",
11
+ "typecheck": "tsc --noEmit -p tsconfig.json",
12
+ "test": "vitest run",
13
+ "verify": "npm --prefix ../.. run --silent deploy:gen && npm run typecheck && npm test",
14
+ "typegen": "wrangler types",
15
+ "tail": "wrangler tail",
16
+ "secrets": "npm --prefix ../.. run --silent cli -- deploy secrets memory"
17
+ },
18
+ "devDependencies": {
19
+ "@cloudflare/vitest-pool-workers": "^0.22.0",
20
+ "@cloudflare/workers-types": "^5.20260904.1",
21
+ "typescript": "^5.9.3",
22
+ "vitest": "^4.1.11",
23
+ "wrangler": "^4.129.0"
24
+ }
25
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ // Scoped config so `npm run typecheck` here checks worker.ts (and the shared
3
+ // src/core/memory engine it imports by relative path) — without it, tsc
4
+ // silently falls back to the repo-root tsconfig, which excludes this dir.
5
+ "compilerOptions": {
6
+ "target": "esnext",
7
+ "module": "esnext",
8
+ "moduleResolution": "bundler",
9
+ "lib": ["esnext"],
10
+ "types": ["@cloudflare/workers-types/experimental", "@cloudflare/vitest-pool-workers/types"],
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "skipLibCheck": true,
14
+ "allowImportingTsExtensions": true
15
+ },
16
+ "include": ["worker.ts", "worker.test.ts", "friction.test.ts", "schedules.test.ts", "test-env.d.ts"]
17
+ }