Skip to content
Source
Sign inSign up

triageSession.ts

102 lines3.9 KB

1// @effect-diagnostics nodeBuiltinImport:off
2import * as NodeOS from "node:os";
3​
4import { TRIAGE_PROMPT_FILE_NAME } from "@t3tools/contracts";
5import * as HostProcess from "@t3tools/shared/HostProcess";
6import * as DateTime from "effect/DateTime";
7import * as Effect from "effect/Effect";
8import * as FileSystem from "effect/FileSystem";
9import * as Option from "effect/Option";
10import * as Path from "effect/Path";
11​
12import * as BootService from "../cloud/bootService.ts";
13import * as ManagedEndpointRuntime from "../cloud/ManagedEndpointRuntime.ts";
14import type * as ServerConfig from "../config.ts";
15import { isProcessAlive, readPersistedServerRuntimeState } from "../serverRuntimeState.ts";
16import {
17 buildTriageContext,
18 buildTriageSeedPrompt,
19 type TriageEntry,
20 type TriageSourceClone,
21} from "./triagePrompt.ts";
22​
23export const describeServerProcess = Effect.fn("triage.describeServerProcess")(function* (
24 serverRuntimeStatePath: string,
25) {
26 const state = yield* readPersistedServerRuntimeState(serverRuntimeStatePath);
27 if (Option.isNone(state)) {
28 return "not running (no server-runtime.json; the server may never have started here)";
29 }
30 const version =
31 state.value.serverVersion === undefined
32 ? "version not recorded"
33 : `version ${state.value.serverVersion}`;
34 if (!isProcessAlive(state.value.pid)) {
35 return `not running (state file is stale: pid ${String(state.value.pid)} is dead; last origin ${state.value.origin}, ${version})`;
36 }
37 return `running (pid ${String(state.value.pid)}, ${state.value.origin}, ${version})`;
38});
39​
40/** The source cache the playbook clones into, one subdirectory per commit. */
41export const triageSourceCacheDir = (baseDir: string, path: Path.Path) =>
42 path.join(baseDir, "source");
43​
44/**
45 * Writes `context.md` and the playbook prompt into a fresh `<stateDir>/triage/<time>`
46 * folder. Both `t3 triage` and the in-app entry point start from these files.
47 */
48export const writeTriageSession = Effect.fn("triage.writeTriageSession")(function* (input: {
49 readonly baseDir: string;
50 readonly paths: ServerConfig.ServerDerivedPaths;
51 readonly entry: TriageEntry;
52 readonly server: string;
53 readonly sourceClone?: TriageSourceClone | undefined;
54}) {
55 const fs = yield* FileSystem.FileSystem;
56 const path = yield* Path.Path;
57 const { paths } = input;
58​
59 const now = yield* DateTime.now;
60 const sessionDir = path.join(
61 paths.stateDir,
62 "triage",
63 DateTime.formatIso(now).replaceAll(":", "-").replace(".", "-"),
64 );
65 yield* fs.makeDirectory(sessionDir, { recursive: true });
66​
67 const contextFilePath = path.join(sessionDir, "context.md");
68 yield* fs.writeFileString(
69 contextFilePath,
70 buildTriageContext({
71 generatedAt: DateTime.formatIso(now),
72 entry: input.entry,
73 os: `${yield* HostProcess.Platform} ${yield* HostProcess.Architecture} (${NodeOS.release()})`,
74 nodeVersion: process.version,
75 server: input.server,
76 sourceClone: input.sourceClone,
77 paths: {
78 stateDir: paths.stateDir,
79 dbPath: paths.dbPath,
80 settingsPath: paths.settingsPath,
81 logsDir: paths.logsDir,
82 serviceLogPath: path.join(paths.logsDir, BootService.BOOT_SERVICE_LOG_FILE),
83 desktopBackendLogGlob: path.join(paths.logsDir, "server-child*.log*"),
84 relayClientLogPath: path.join(paths.logsDir, ManagedEndpointRuntime.RELAY_CLIENT_LOG_FILE),
85 serverTracePath: paths.serverTracePath,
86 providerEventLogPath: paths.providerEventLogPath,
87 terminalLogsDir: paths.terminalLogsDir,
88 providerStatusCacheDir: paths.providerStatusCacheDir,
89 secretsDir: paths.secretsDir,
90 sourceCacheDir: triageSourceCacheDir(input.baseDir, path),
91 },
92 }),
93 );
94​
95 const promptFilePath = path.join(sessionDir, TRIAGE_PROMPT_FILE_NAME);
96 yield* fs.writeFileString(
97 promptFilePath,
98 buildTriageSeedPrompt({ contextFilePath, entry: input.entry }),
99 );
100​
101 return { sessionDir, contextFilePath, promptFilePath };
102});