| author | |
| committer | |
| log | ffceb61fcbbdc7ac5268812a1cc5dfedd7a6f7b9 |
| tree | 0e9dd26b9523d782cf8bd866f276b75ea9049ca8 |
| parent | 0ae1091d019484d474fce0fe0a89195a82134041 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>7 files changed, 168 insertions(+), 35 deletions(-)
.github/ISSUE_TEMPLATE/via-triage.yml+1-1| ... | ... | @@ -37,7 +37,7 @@ body: |
| 37 | 37 | id: version |
| 38 | 38 | attributes: |
| 39 | 39 | label: Version |
| 40 | description: Installed t3 version or commit. | |
| 40 | description: Version the bug happened on (not the `t3 triage` CLI version), or unknown. | |
| 41 | 41 | placeholder: 0.0.33 |
| 42 | 42 | validations: |
| 43 | 43 | required: true |
.github/triage/PLAYBOOK.md+24-8| ... | ... | @@ -6,7 +6,7 @@ crashes, auth failures, broken setups, slow launches, or anything else. Your job |
| 6 | 6 | find out what went wrong, unblock the user if you can, and turn what you learned into |
| 7 | 7 | a well written GitHub issue when one is warranted. |
| 8 | 8 | |
| 9 | A triage context file with machine facts (version, OS, paths, server liveness) was | |
| 9 | A triage context file with machine facts (versions, OS, paths, server liveness) was | |
| 10 | 10 | provided alongside this playbook. Everything machine-specific lives there, not here. |
| 11 | 11 | |
| 12 | 12 | ## 1. Ask what went wrong |
| ... | ... | @@ -18,9 +18,15 @@ valuable thing you can extract from this conversation. |
| 18 | 18 | |
| 19 | 19 | ## 2. Read the machine facts |
| 20 | 20 | |
| 21 | Read the triage context file before investigating. It tells you the installed | |
| 22 | version, the OS, whether the server process is currently running, and the exact | |
| 23 | paths for state, logs, and the database. | |
| 21 | Read the triage context file before investigating. It tells you the OS, whether | |
| 22 | the server process is currently running, and the exact paths for state, logs, and | |
| 23 | the database. | |
| 24 | ||
| 25 | Its triage CLI version (called "Installed version" in older context files) is only | |
| 26 | the version of the `t3` that ran `t3 triage`. It is often not the build the bug | |
| 27 | happened on: `npx t3 triage` runs whatever `t3` npm resolves, while the user may | |
| 28 | run the desktop app, the background service, or a nightly. The server process line | |
| 29 | gives the local server's version when the server recorded one. | |
| 24 | 30 | |
| 25 | 31 | ## 3. Check for a newer playbook |
| 26 | 32 | |
| ... | ... | @@ -30,8 +36,16 @@ instead of this one. The user may be on an old release with an old copy. |
| 30 | 36 | |
| 31 | 37 | ## 4. Get the source |
| 32 | 38 | |
| 33 | Clone the repo at the tag matching the user's installed version, into the source | |
| 34 | cache directory named in the context file, one subdirectory per commit hash: | |
| 39 | Before cloning, ask the user which version, device, and surface the bug happened | |
| 40 | on. The bug may have happened on another machine, a remote server, the mobile app, | |
| 41 | or an older build they have since updated. Check their answer against local facts | |
| 42 | where they apply: the server version in the context file, or `serverVersion` from | |
| 43 | `<origin>/.well-known/t3/environment` when a local server is running. If neither | |
| 44 | the user nor the machine can tell you the version, say it is unknown. Do not guess. | |
| 45 | ||
| 46 | Clone the repo at the release tag for that version (`v` followed by the version, | |
| 47 | such as `v0.0.42`), into the source cache directory named in the context file, | |
| 48 | one subdirectory per commit hash: | |
| 35 | 49 | |
| 36 | 50 | git clone --depth 1 --filter=blob:none --branch <release-tag> \ |
| 37 | 51 | https://github.com/pingdotgg/t3code <source-cache-dir>/<hash> |
| ... | ... | @@ -82,8 +96,8 @@ comes from this repo's `main` branch. |
| 82 | 96 | |
| 83 | 97 | Search existing issues in pingdotgg/t3code (use `gh`, or the public GitHub search |
| 84 | 98 | API if `gh` is missing or not logged in). Then check whether the problem is already |
| 85 | fixed in a release newer than the user's version: compare versions, read release | |
| 86 | notes and recent commits touching the relevant code. | |
| 99 | fixed in a release newer than the version the bug happened on: compare versions, | |
| 100 | read release notes and recent commits touching the relevant code. | |
| 87 | 101 | |
| 88 | 102 | If the user is behind and the fix likely shipped, say so plainly and give them the |
| 89 | 103 | exact update command for how they run the CLI (the context file records how it was |
| ... | ... | @@ -105,6 +119,8 @@ of `main` for that work, never the tag-pinned diagnosis clone. |
| 105 | 119 | - Match the structure of the `via-triage` issue template |
| 106 | 120 | (`.github/ISSUE_TEMPLATE/via-triage.yml` in the repo): what happened, diagnosis, |
| 107 | 121 | repro steps, environment, evidence, related issues. |
| 122 | - Under Version, give the version the bug happened on from step 4, or say it is | |
| 123 | unknown. Name the triage CLI version only as the CLI's. | |
| 108 | 124 | - Label it `via-triage`. Use a plain, specific title with no prefix. |
| 109 | 125 | - Show the user the complete final issue text and get an explicit yes before |
| 110 | 126 | posting. Never post without it. |
apps/server/src/cli/triage.test.ts created+62| ... | ... | @@ -0,0 +1,62 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | |
| 2 | import { assert, it } from "@effect/vitest"; | |
| 3 | import * as Effect from "effect/Effect"; | |
| 4 | import * as FileSystem from "effect/FileSystem"; | |
| 5 | import * as Path from "effect/Path"; | |
| 6 | ||
| 7 | import packageJson from "../../package.json" with { type: "json" }; | |
| 8 | import { | |
| 9 | makePersistedServerRuntimeState, | |
| 10 | persistServerRuntimeState, | |
| 11 | } from "../serverRuntimeState.ts"; | |
| 12 | import { describeServerProcess } from "./triage.ts"; | |
| 13 | ||
| 14 | it.effect("reports the version the running server recorded, not the CLI's", () => | |
| 15 | Effect.gen(function* () { | |
| 16 | const fs = yield* FileSystem.FileSystem; | |
| 17 | const path = yield* Path.Path; | |
| 18 | const statePath = path.join( | |
| 19 | yield* fs.makeTempDirectoryScoped({ prefix: "t3-triage-" }), | |
| 20 | "server-runtime.json", | |
| 21 | ); | |
| 22 | // The test process stands in for the server, so its pid is alive. | |
| 23 | const state = yield* makePersistedServerRuntimeState({ | |
| 24 | config: { host: undefined, devUrl: undefined }, | |
| 25 | port: 3773, | |
| 26 | }); | |
| 27 | yield* persistServerRuntimeState({ path: statePath, state }); | |
| 28 | ||
| 29 | assert.equal( | |
| 30 | yield* describeServerProcess(statePath), | |
| 31 | `running (pid ${String(process.pid)}, http://127.0.0.1:3773, version ${packageJson.version})`, | |
| 32 | ); | |
| 33 | }).pipe(Effect.provide(NodeServices.layer)), | |
| 34 | ); | |
| 35 | ||
| 36 | it.effect("says the server version is unknown when the state file has none", () => | |
| 37 | Effect.gen(function* () { | |
| 38 | const fs = yield* FileSystem.FileSystem; | |
| 39 | const path = yield* Path.Path; | |
| 40 | const statePath = path.join( | |
| 41 | yield* fs.makeTempDirectoryScoped({ prefix: "t3-triage-" }), | |
| 42 | "server-runtime.json", | |
| 43 | ); | |
| 44 | // A server from before versions were recorded, such as the 0.0.43 desktop | |
| 45 | // app in #13337. | |
| 46 | yield* fs.writeFileString( | |
| 47 | statePath, | |
| 48 | JSON.stringify({ | |
| 49 | version: 1, | |
| 50 | pid: process.pid, | |
| 51 | port: 3773, | |
| 52 | origin: "http://127.0.0.1:3773", | |
| 53 | startedAt: "2026-09-24T00:08:56.777Z", | |
| 54 | }), | |
| 55 | ); | |
| 56 | ||
| 57 | assert.equal( | |
| 58 | yield* describeServerProcess(statePath), | |
| 59 | `running (pid ${String(process.pid)}, http://127.0.0.1:3773, version not recorded)`, | |
| 60 | ); | |
| 61 | }).pipe(Effect.provide(NodeServices.layer)), | |
| 62 | ); |
apps/server/src/cli/triage.ts+13-10| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | * `t3 triage` - hand a misbehaving install to the user's own coding agent. |
| 3 | 3 | * |
| 4 | 4 | * The command is deliberately thin: it writes a `context.md` with machine facts |
| 5 | * (version, paths, server liveness), then launches claude or codex | |
| 5 | * (versions, paths, server liveness), then launches claude or codex | |
| 6 | 6 | * interactively, seeded with the playbook from `triagePrompt.ts`. The agent |
| 7 | 7 | * asks the user what went wrong, investigates, and files the issue; the |
| 8 | 8 | * harness's own permission prompts gate anything it wants to run. With no |
| ... | ... | @@ -77,8 +77,11 @@ export class TriageAgentSpawnError extends Schema.TaggedError<TriageAgentSpawnEr |
| 77 | 77 | } |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | /** One human-readable line about the local server, for `context.md`. */ | |
| 81 | const describeServerProcess = Effect.fn("triage.describeServerProcess")(function* ( | |
| 80 | /** | |
| 81 | * One human-readable line about the local server, for `context.md`. Includes | |
| 82 | * the version the server recorded, which can differ from this CLI's version. | |
| 83 | */ | |
| 84 | export const describeServerProcess = Effect.fn("triage.describeServerProcess")(function* ( | |
| 82 | 85 | serverRuntimeStatePath: string, |
| 83 | 86 | ) { |
| 84 | 87 | // readPersistedServerRuntimeState swallows read/decode failures itself and |
| ... | ... | @@ -87,10 +90,14 @@ const describeServerProcess = Effect.fn("triage.describeServerProcess")(function |
| 87 | 90 | if (Option.isNone(state)) { |
| 88 | 91 | return "not running (no server-runtime.json; the server may never have started here)"; |
| 89 | 92 | } |
| 93 | const version = | |
| 94 | state.value.serverVersion === undefined | |
| 95 | ? "version not recorded" | |
| 96 | : `version ${state.value.serverVersion}`; | |
| 90 | 97 | if (!isProcessAlive(state.value.pid)) { |
| 91 | return `not running (state file is stale: pid ${String(state.value.pid)} is dead; last origin ${state.value.origin})`; | |
| 98 | return `not running (state file is stale: pid ${String(state.value.pid)} is dead; last origin ${state.value.origin}, ${version})`; | |
| 92 | 99 | } |
| 93 | return `running (pid ${String(state.value.pid)}, ${state.value.origin})`; | |
| 100 | return `running (pid ${String(state.value.pid)}, ${state.value.origin}, ${version})`; | |
| 94 | 101 | }); |
| 95 | 102 | |
| 96 | 103 | const pickAgent = (agents: ReadonlyArray<TriageAgent>) => |
| ... | ... | @@ -183,16 +190,12 @@ export const triageCommand = Command.make("triage", { |
| 183 | 190 | ); |
| 184 | 191 | yield* fs.makeDirectory(scratchDir, { recursive: true }); |
| 185 | 192 | |
| 186 | const version = packageJson.version; | |
| 187 | 193 | const contextFilePath = path.join(scratchDir, "context.md"); |
| 188 | 194 | yield* fs.writeFileString( |
| 189 | 195 | contextFilePath, |
| 190 | 196 | buildTriageContext({ |
| 191 | 197 | generatedAt: DateTime.formatIso(now), |
| 192 | version, | |
| 193 | releaseTag: /^[^-+]+-(?:nightly|preview)\./.test(version) | |
| 194 | ? `v${version} (prerelease build; if this tag does not exist, clone main)` | |
| 195 | : `v${version}`, | |
| 198 | cliVersion: packageJson.version, | |
| 196 | 199 | os: `${yield* HostProcess.Platform} ${yield* HostProcess.Architecture} (${NodeOS.release()})`, |
| 197 | 200 | nodeVersion: process.version, |
| 198 | 201 | launchedAs: yield* resolveCliCommand("triage"), |
apps/server/src/cli/triagePrompt.test.ts+32-3| ... | ... | @@ -41,8 +41,7 @@ it("launch prompt stays a single argv-safe line naming the prompt file", () => { |
| 41 | 41 | it("context file carries every path the playbook depends on", () => { |
| 42 | 42 | const context = buildTriageContext({ |
| 43 | 43 | generatedAt: "2026-08-13T00:00:00.000Z", |
| 44 | version: "0.0.33", | |
| 45 | releaseTag: "v0.0.33", | |
| 44 | cliVersion: "0.0.33", | |
| 46 | 45 | os: "linux x64 (7.0.0)", |
| 47 | 46 | nodeVersion: "v24.0.0", |
| 48 | 47 | launchedAs: "npx t3 triage", |
| ... | ... | @@ -70,5 +69,35 @@ it("context file carries every path the playbook depends on", () => { |
| 70 | 69 | assert.include(context, "/home/u/.t3/userdata/secrets"); |
| 71 | 70 | assert.include(context, "/home/u/.t3/source"); |
| 72 | 71 | assert.include(context, "npx t3 triage"); |
| 73 | assert.include(context, "v0.0.33"); | |
| 72 | }); | |
| 73 | ||
| 74 | it("context file labels the CLI version as the CLI's, not the user's build", () => { | |
| 75 | // `npx t3 triage` can resolve a different `t3` than the app the bug | |
| 76 | // happened on (#13337), so the agent must not read it as the user's version. | |
| 77 | const context = buildTriageContext({ | |
| 78 | generatedAt: "2026-09-24T00:00:00.000Z", | |
| 79 | cliVersion: "0.0.42", | |
| 80 | os: "linux x64 (7.2.0)", | |
| 81 | nodeVersion: "v26.8.2", | |
| 82 | launchedAs: "npx t3 triage", | |
| 83 | server: "running (pid 42, http://127.0.0.1:3773, version 0.0.43-nightly.20260923.2173)", | |
| 84 | paths: { | |
| 85 | stateDir: "/home/u/.t3/userdata", | |
| 86 | dbPath: "/home/u/.t3/userdata/state.sqlite", | |
| 87 | settingsPath: "/home/u/.t3/userdata/settings.json", | |
| 88 | logsDir: "/home/u/.t3/userdata/logs", | |
| 89 | serviceLogPath: "/home/u/.t3/userdata/logs/boot-service.log", | |
| 90 | desktopBackendLogGlob: "/home/u/.t3/userdata/logs/server-child*.log*", | |
| 91 | serverTracePath: "/home/u/.t3/userdata/logs/server.trace.ndjson", | |
| 92 | providerEventLogPath: "/home/u/.t3/userdata/logs/provider/events.log", | |
| 93 | terminalLogsDir: "/home/u/.t3/userdata/logs/terminals", | |
| 94 | providerStatusCacheDir: "/home/u/.t3/caches", | |
| 95 | secretsDir: "/home/u/.t3/userdata/secrets", | |
| 96 | sourceCacheDir: "/home/u/.t3/source", | |
| 97 | }, | |
| 98 | }); | |
| 99 | assert.notInclude(context, "Installed version"); | |
| 100 | assert.notInclude(context, "Release tag"); | |
| 101 | assert.include(context, "- Triage CLI version: 0.0.42 ("); | |
| 102 | assert.include(context, "version 0.0.43-nightly.20260923.2173"); | |
| 74 | 103 | }); |
apps/server/src/cli/triagePrompt.ts+27-13| ... | ... | @@ -18,7 +18,7 @@ crashes, auth failures, broken setups, slow launches, or anything else. Your job |
| 18 | 18 | find out what went wrong, unblock the user if you can, and turn what you learned into |
| 19 | 19 | a well written GitHub issue when one is warranted. |
| 20 | 20 | |
| 21 | A triage context file with machine facts (version, OS, paths, server liveness) was | |
| 21 | A triage context file with machine facts (versions, OS, paths, server liveness) was | |
| 22 | 22 | provided alongside this playbook. Everything machine-specific lives there, not here. |
| 23 | 23 | |
| 24 | 24 | ## 1. Ask what went wrong |
| ... | ... | @@ -30,9 +30,15 @@ valuable thing you can extract from this conversation. |
| 30 | 30 | |
| 31 | 31 | ## 2. Read the machine facts |
| 32 | 32 | |
| 33 | Read the triage context file before investigating. It tells you the installed | |
| 34 | version, the OS, whether the server process is currently running, and the exact | |
| 35 | paths for state, logs, and the database. | |
| 33 | Read the triage context file before investigating. It tells you the OS, whether | |
| 34 | the server process is currently running, and the exact paths for state, logs, and | |
| 35 | the database. | |
| 36 | ||
| 37 | Its triage CLI version (called "Installed version" in older context files) is only | |
| 38 | the version of the \`t3\` that ran \`t3 triage\`. It is often not the build the bug | |
| 39 | happened on: \`npx t3 triage\` runs whatever \`t3\` npm resolves, while the user may | |
| 40 | run the desktop app, the background service, or a nightly. The server process line | |
| 41 | gives the local server's version when the server recorded one. | |
| 36 | 42 | |
| 37 | 43 | ## 3. Check for a newer playbook |
| 38 | 44 | |
| ... | ... | @@ -42,8 +48,16 @@ instead of this one. The user may be on an old release with an old copy. |
| 42 | 48 | |
| 43 | 49 | ## 4. Get the source |
| 44 | 50 | |
| 45 | Clone the repo at the tag matching the user's installed version, into the source | |
| 46 | cache directory named in the context file, one subdirectory per commit hash: | |
| 51 | Before cloning, ask the user which version, device, and surface the bug happened | |
| 52 | on. The bug may have happened on another machine, a remote server, the mobile app, | |
| 53 | or an older build they have since updated. Check their answer against local facts | |
| 54 | where they apply: the server version in the context file, or \`serverVersion\` from | |
| 55 | \`<origin>/.well-known/t3/environment\` when a local server is running. If neither | |
| 56 | the user nor the machine can tell you the version, say it is unknown. Do not guess. | |
| 57 | ||
| 58 | Clone the repo at the release tag for that version (\`v\` followed by the version, | |
| 59 | such as \`v0.0.42\`), into the source cache directory named in the context file, | |
| 60 | one subdirectory per commit hash: | |
| 47 | 61 | |
| 48 | 62 | git clone --depth 1 --filter=blob:none --branch <release-tag> \\ |
| 49 | 63 | https://github.com/pingdotgg/t3code <source-cache-dir>/<hash> |
| ... | ... | @@ -94,8 +108,8 @@ comes from this repo's \`main\` branch. |
| 94 | 108 | |
| 95 | 109 | Search existing issues in pingdotgg/t3code (use \`gh\`, or the public GitHub search |
| 96 | 110 | API if \`gh\` is missing or not logged in). Then check whether the problem is already |
| 97 | fixed in a release newer than the user's version: compare versions, read release | |
| 98 | notes and recent commits touching the relevant code. | |
| 111 | fixed in a release newer than the version the bug happened on: compare versions, | |
| 112 | read release notes and recent commits touching the relevant code. | |
| 99 | 113 | |
| 100 | 114 | If the user is behind and the fix likely shipped, say so plainly and give them the |
| 101 | 115 | exact update command for how they run the CLI (the context file records how it was |
| ... | ... | @@ -117,6 +131,8 @@ of \`main\` for that work, never the tag-pinned diagnosis clone. |
| 117 | 131 | - Match the structure of the \`via-triage\` issue template |
| 118 | 132 | (\`.github/ISSUE_TEMPLATE/via-triage.yml\` in the repo): what happened, diagnosis, |
| 119 | 133 | repro steps, environment, evidence, related issues. |
| 134 | - Under Version, give the version the bug happened on from step 4, or say it is | |
| 135 | unknown. Name the triage CLI version only as the CLI's. | |
| 120 | 136 | - Label it \`via-triage\`. Use a plain, specific title with no prefix. |
| 121 | 137 | - Show the user the complete final issue text and get an explicit yes before |
| 122 | 138 | posting. Never post without it. |
| ... | ... | @@ -153,7 +169,7 @@ export const buildTriageLaunchPrompt = (promptFilePath: string) => |
| 153 | 169 | export const buildTriageSeedPrompt = (contextFilePath: string) => `A T3 Code user is \ |
| 154 | 170 | having a problem with their install and started this session with \`t3 triage\`. |
| 155 | 171 | |
| 156 | Machine facts (version, OS, paths, server liveness) are in the triage context file: | |
| 172 | Machine facts (versions, OS, paths, server liveness) are in the triage context file: | |
| 157 | 173 | |
| 158 | 174 | ${contextFilePath} |
| 159 | 175 | |
| ... | ... | @@ -166,8 +182,7 @@ ${TRIAGE_PLAYBOOK}`; |
| 166 | 182 | /** Machine facts for one triage run, pre-formatted so the template stays plain. */ |
| 167 | 183 | export interface TriageContextInput { |
| 168 | 184 | readonly generatedAt: string; |
| 169 | readonly version: string; | |
| 170 | readonly releaseTag: string; | |
| 185 | readonly cliVersion: string; | |
| 171 | 186 | readonly os: string; |
| 172 | 187 | readonly nodeVersion: string; |
| 173 | 188 | readonly launchedAs: string; |
| ... | ... | @@ -193,8 +208,7 @@ export const buildTriageContext = (input: TriageContextInput) => `# T3 Code tria |
| 193 | 208 | |
| 194 | 209 | Generated by \`t3 triage\` at ${input.generatedAt}. |
| 195 | 210 | |
| 196 | - Installed version: ${input.version} | |
| 197 | - Release tag for this version: ${input.releaseTag} | |
| 211 | - Triage CLI version: ${input.cliVersion} (the \`t3\` that ran this command; the bug may have happened on another build) | |
| 198 | 212 | - OS: ${input.os} |
| 199 | 213 | - Node: ${input.nodeVersion} |
| 200 | 214 | - CLI launched as: ${input.launchedAs} |
apps/server/src/serverRuntimeState.ts+9| ... | ... | @@ -5,10 +5,12 @@ import * as Option from "effect/Option"; |
| 5 | 5 | import * as Schema from "effect/Schema"; |
| 6 | 6 | |
| 7 | 7 | import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite"; |
| 8 | import packageJson from "../package.json" with { type: "json" }; | |
| 8 | 9 | import type * as ServerConfig from "./config.ts"; |
| 9 | 10 | import { formatHostForUrl, isWildcardHost } from "./startupAccess.ts"; |
| 10 | 11 | |
| 11 | 12 | export const PersistedServerRuntimeState = Schema.Struct({ |
| 13 | // Schema version of this file, not the server build. See `serverVersion`. | |
| 12 | 14 | version: Schema.Literal(1), |
| 13 | 15 | pid: Schema.Int, |
| 14 | 16 | ownerId: Schema.optional(Schema.String), |
| ... | ... | @@ -25,6 +27,12 @@ export const PersistedServerRuntimeState = Schema.Struct({ |
| 25 | 27 | * the difference between "restart the service" and "stop your terminal". |
| 26 | 28 | */ |
| 27 | 29 | serviceManaged: Schema.optional(Schema.Boolean), |
| 30 | /** | |
| 31 | * The server build that wrote this record. `t3 triage` reports it because | |
| 32 | * the CLI that runs triage can be a different build. Missing on records | |
| 33 | * written by older servers. | |
| 34 | */ | |
| 35 | serverVersion: Schema.optional(Schema.String), | |
| 28 | 36 | }); |
| 29 | 37 | export type PersistedServerRuntimeState = typeof PersistedServerRuntimeState.Type; |
| 30 | 38 | |
| ... | ... | @@ -68,6 +76,7 @@ export const makePersistedServerRuntimeState = (input: { |
| 68 | 76 | ...(input.config.devUrl ? { devUrl: input.config.devUrl.toString() } : {}), |
| 69 | 77 | startedAt: DateTime.formatIso(now), |
| 70 | 78 | ...(input.serviceManaged ? { serviceManaged: true } : {}), |
| 79 | serverVersion: packageJson.version, | |
| 71 | 80 | })); |
| 72 | 81 | |
| 73 | 82 | export const persistServerRuntimeState = (input: { |