Showing the changes to apps/executor (1 of 21 files). Show all
Unified · Split
| @@ -90,7 +90,6 @@ import { · 89 unmodified lines | |||
| 90 | import { decideInteractionHandling } from "./interaction-handling"; | 90 | import { decideInteractionHandling } from "./interaction-handling"; |
| 91 | import { | 91 | import { |
| 92 | renderMcpSessionSummary, | 92 | renderMcpSessionSummary, |
| 93 | renderUpSummary, | ||
| 94 | renderWebSessionSummary, | 93 | renderWebSessionSummary, |
| 95 | } from "./session-summary"; | 94 | } from "./session-summary"; |
| 96 | import { | 95 | import { |
| @@ -415,8 +414,6 @@ const printRootHelp = (workflow: string) => · 318 unmodified lines | |||
| 415 | " Execute code against the local executor server.", | 414 | " Execute code against the local executor server.", |
| 416 | " resume", | 415 | " resume", |
| 417 | " Resume a paused execution.", | 416 | " Resume a paused execution.", |
| 418 | " up / down / status / doctor", | ||
| 419 | " Compatibility commands for the detached local daemon.", | ||
| 420 | "", | 417 | "", |
| 421 | "TIP", | 418 | "TIP", |
| 422 | "", | 419 | "", |
| @@ -625,154 +622,11 @@ const waitForReachability = (baseUrl: string, expected: boolean) => · 202 unmodified lines | |||
| 625 | return yield* failReachabilityTimeout({ baseUrl, expected }); | 622 | return yield* failReachabilityTimeout({ baseUrl, expected }); |
| 626 | }); | 623 | }); |
| 627 | 624 | ||
| 628 | type LocalServerStatus = { | ||
| 629 | baseUrl: string; | ||
| 630 | reachable: boolean; | ||
| 631 | pidFile: string; | ||
| 632 | pid: number | null; | ||
| 633 | pidRunning: boolean; | ||
| 634 | logFile: string; | ||
| 635 | localDataDir: string; | ||
| 636 | webAssetsDir: string | null; | ||
| 637 | installation: { | ||
| 638 | accountId: string; | ||
| 639 | workspaceId: string; | ||
| 640 | } | null; | ||
| 641 | }; | ||
| 642 | |||
| 643 | const getServerStatus = ( | ||
| 644 | baseUrl: string, | ||
| 645 | ): Effect.Effect<LocalServerStatus, Error, FileSystem.FileSystem> => | ||
| 646 | Effect.gen(function* () { | ||
| 647 | const pidRecord = yield* readPidRecord(); | ||
| 648 | const reachable = yield* isServerReachable(baseUrl); | ||
| 649 | const installation = reachable | ||
| 650 | ? yield* getBootstrapClient(baseUrl).pipe( | ||
| 651 | Effect.flatMap((client) => client.local.installation({})), | ||
| 652 | Effect.map((installation) => ({ | ||
| 653 | accountId: installation.actorScopeId, | ||
| 654 | workspaceId: installation.scopeId, | ||
| 655 | })), | ||
| 656 | Effect.catchAll(() => Effect.succeed(null)), | ||
| 657 | ) | ||
| 658 | : null; | ||
| 659 | |||
| 660 | const pid = typeof pidRecord?.pid === "number" ? pidRecord.pid : null; | ||
| 661 | const pidRunning = pid !== null ? isPidRunning(pid) : false; | ||
| 662 | const logFile = pidRecord?.logFile ?? DEFAULT_SERVER_LOG_FILE; | ||
| 663 | |||
| 664 | return { | ||
| 665 | baseUrl, | ||
| 666 | reachable, | ||
| 667 | pidFile: DEFAULT_SERVER_PID_FILE, | ||
| 668 | pid, | ||
| 669 | pidRunning, | ||
| 670 | logFile, | ||
| 671 | localDataDir: CLI_LOCAL_DATA_DIR, | ||
| 672 | webAssetsDir: resolveRuntimeWebAssetsDir(), | ||
| 673 | installation, | ||
| 674 | } satisfies LocalServerStatus; | ||
| 675 | }); | ||
| 676 | |||
| 677 | const renderStatus = (status: LocalServerStatus): string => | ||
| 678 | [ | ||
| 679 | `baseUrl: ${status.baseUrl}`, | ||
| 680 | `reachable: ${status.reachable ? "yes" : "no"}`, | ||
| 681 | `pid: ${status.pid ?? "none"}`, | ||
| 682 | `pidRunning: ${status.pidRunning ? "yes" : "no"}`, | ||
| 683 | `pidFile: ${status.pidFile}`, | ||
| 684 | `logFile: ${status.logFile}`, | ||
| 685 | `localDataDir: ${status.localDataDir}`, | ||
| 686 | `webAssetsDir: ${status.webAssetsDir ?? "missing"}`, | ||
| 687 | `workspaceId: ${status.installation?.workspaceId ?? "unavailable"}`, | ||
| 688 | ].join("\n"); | ||
| 689 | |||
| 690 | const getDoctorReport = (baseUrl: string) => | ||
| 691 | getServerStatus(baseUrl).pipe( | ||
| 692 | Effect.map((status) => { | ||
| 693 | const checks = { | ||
| 694 | serverReachable: { | ||
| 695 | ok: status.reachable, | ||
| 696 | detail: status.reachable ? `reachable at ${status.baseUrl}` : `not reachable at ${status.baseUrl}`, | ||
| 697 | }, | ||
| 698 | pidFile: { | ||
| 699 | ok: status.pid !== null, | ||
| 700 | detail: status.pid !== null ? `pid ${status.pid}` : `missing pid file at ${status.pidFile}`, | ||
| 701 | }, | ||
| 702 | process: { | ||
| 703 | ok: status.pidRunning, | ||
| 704 | detail: status.pidRunning ? `pid ${status.pid}` : "no live daemon process recorded", | ||
| 705 | }, | ||
| 706 | database: { | ||
| 707 | ok: status.localDataDir.length > 0, | ||
| 708 | detail: status.localDataDir, | ||
| 709 | }, | ||
| 710 | webAssets: { | ||
| 711 | ok: status.webAssetsDir !== null, | ||
| 712 | detail: status.webAssetsDir ?? "missing bundled web assets", | ||
| 713 | }, | ||
| 714 | installation: { | ||
| 715 | ok: status.installation !== null, | ||
| 716 | detail: status.installation | ||
| 717 | ? `workspace ${status.installation.workspaceId}` | ||
| 718 | : "local installation unavailable", | ||
| 719 | }, | ||
| 720 | } as const; | ||
| 721 | |||
| 722 | return { | ||
| 723 | ok: Object.values(checks).every((check) => check.ok), | ||
| 724 | status, | ||
| 725 | checks, | ||
| 726 | }; | ||
| 727 | }), | ||
| 728 | ); | ||
| 729 | |||
| 730 | const printJson = (value: unknown) => | ||
| 731 | Effect.sync(() => { | ||
| 732 | console.log(JSON.stringify(value, null, 2)); | ||
| 733 | }); | ||
| 734 | |||
| 735 | const printText = (value: string) => | 625 | const printText = (value: string) => |
| 736 | Effect.sync(() => { | 626 | Effect.sync(() => { |
| 737 | console.log(value); | 627 | console.log(value); |
| 738 | }); | 628 | }); |
| 739 | 629 | ||
| 740 | const stopServer = (baseUrl: string) => | ||
| 741 | Effect.gen(function* () { | ||
| 742 | const fs = yield* FileSystem.FileSystem; | ||
| 743 | const removePidFile = fs.remove(DEFAULT_SERVER_PID_FILE, { | ||
| 744 | force: true, | ||
| 745 | }).pipe(Effect.ignore); | ||
| 746 | const pidRecord = yield* readPidRecord(); | ||
| 747 | const pid = typeof pidRecord?.pid === "number" ? pidRecord.pid : null; | ||
| 748 | |||
| 749 | if (pid === null) { | ||
| 750 | yield* removePidFile; | ||
| 751 | return false; | ||
| 752 | } | ||
| 753 | |||
| 754 | if (!isPidRunning(pid)) { | ||
| 755 | yield* removePidFile; | ||
| 756 | return false; | ||
| 757 | } | ||
| 758 | |||
| 759 | |||
| 760 | yield* Effect.sync(() => { | ||
| 761 | process.kill(pid, "SIGTERM"); | ||
| 762 | }); | ||
| 763 | |||
| 764 | yield* waitForReachability(baseUrl, false).pipe( | ||
| 765 | Effect.catchAll(() => | ||
| 766 | removePidFile.pipe( | ||
| 767 | Effect.ignore, | ||
| 768 | Effect.zipRight(Effect.fail(executorAppEffectError("cli/main", `Timed out stopping local executor server pid ${pid}`))), | ||
| 769 | ), | ||
| 770 | ), | ||
| 771 | ); | ||
| 772 | |||
| 773 | return true; | ||
| 774 | }); | ||
| 775 | |||
| 776 | const ensureServer = (baseUrl: string = DEFAULT_SERVER_BASE_URL) => | 630 | const ensureServer = (baseUrl: string = DEFAULT_SERVER_BASE_URL) => |
| 777 | Effect.gen(function* () { | 631 | Effect.gen(function* () { |
| 778 | const reachable = yield* isServerReachable(baseUrl); | 632 | const reachable = yield* isServerReachable(baseUrl); |
| @@ -1243,63 +1097,6 @@ const mcpCommand = Command.make( · 464 unmodified lines | |||
| 1243 | : runForegroundSession({ kind: "mcp", port }), | 1097 | : runForegroundSession({ kind: "mcp", port }), |
| 1244 | ).pipe(Command.withDescription("Start a foreground MCP session, or run stdio MCP with --stdio")); | 1098 | ).pipe(Command.withDescription("Start a foreground MCP session, or run stdio MCP with --stdio")); |
| 1245 | 1099 | ||
| 1246 | const upCommand = Command.make( | ||
| 1247 | "up", | ||
| 1248 | { | ||
| 1249 | baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)), | ||
| 1250 | }, | ||
| 1251 | ({ baseUrl }) => | ||
| 1252 | ensureServer(baseUrl).pipe( | ||
| 1253 | Effect.flatMap(({ started }) => | ||
| 1254 | getServerStatus(baseUrl).pipe( | ||
| 1255 | Effect.flatMap((status) => printText(renderUpSummary({ started, status }))), | ||
| 1256 | ) | ||
| 1257 | ), | ||
| 1258 | ), | ||
| 1259 | ).pipe(Command.withDescription("Start the local executor server if needed and show how to use it")); | ||
| 1260 | |||
| 1261 | const downCommand = Command.make( | ||
| 1262 | "down", | ||
| 1263 | { | ||
| 1264 | baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)), | ||
| 1265 | }, | ||
| 1266 | ({ baseUrl }) => | ||
| 1267 | stopServer(baseUrl).pipe( | ||
| 1268 | Effect.flatMap((stopped) => | ||
| 1269 | printText(stopped ? "Stopped local executor server." : "Local executor server is not running."), | ||
| 1270 | ), | ||
| 1271 | ), | ||
| 1272 | ).pipe(Command.withDescription("Stop the local executor server")); | ||
| 1273 | |||
| 1274 | const statusCommand = Command.make( | ||
| 1275 | "status", | ||
| 1276 | { | ||
| 1277 | baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)), | ||
| 1278 | json: Options.boolean("json").pipe(Options.withDefault(false)), | ||
| 1279 | }, | ||
| 1280 | ({ baseUrl, json }) => | ||
| 1281 | getServerStatus(baseUrl).pipe( | ||
| 1282 | Effect.flatMap((status) => json ? printJson(status) : printText(renderStatus(status))), | ||
| 1283 | ), | ||
| 1284 | ).pipe(Command.withDescription("Show local executor server status")); | ||
| 1285 | |||
| 1286 | const doctorCommand = Command.make( | ||
| 1287 | "doctor", | ||
| 1288 | { | ||
| 1289 | baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)), | ||
| 1290 | json: Options.boolean("json").pipe(Options.withDefault(false)), | ||
| 1291 | }, | ||
| 1292 | ({ baseUrl, json }) => | ||
| 1293 | getDoctorReport(baseUrl).pipe( | ||
| 1294 | Effect.flatMap((report) => json | ||
| 1295 | ? printJson(report) | ||
| 1296 | : printText([ | ||
| 1297 | `ok: ${report.ok ? "yes" : "no"}`, | ||
| 1298 | ...Object.entries(report.checks).map(([name, check]) => `${name}: ${check.ok ? "ok" : "fail"} - ${check.detail}`), | ||
| 1299 | ].join("\n"))), | ||
| 1300 | ), | ||
| 1301 | ).pipe(Command.withDescription("Check local executor install and daemon health")); | ||
| 1302 | |||
| 1303 | const callCommand = Command.make( | 1100 | const callCommand = Command.make( |
| 1304 | "call", | 1101 | "call", |
| 1305 | { | 1102 | { |
| @@ -1386,10 +1183,6 @@ const root = Command.make("executor").pipe( · 80 unmodified lines | |||
| 1386 | serverCommand, | 1183 | serverCommand, |
| 1387 | webCommand, | 1184 | webCommand, |
| 1388 | mcpCommand, | 1185 | mcpCommand, |
| 1389 | upCommand, | ||
| 1390 | downCommand, | ||
| 1391 | statusCommand, | ||
| 1392 | doctorCommand, | ||
| 1393 | callCommand, | 1186 | callCommand, |
| 1394 | resumeCommand, | 1187 | resumeCommand, |
| 1395 | devCommand, | 1188 | devCommand, |