conductor-oss 0.2.17 → 0.2.19
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/commands/dashboard.js +2 -2
- package/dist/commands/start.d.ts +3 -6
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +389 -220
- package/dist/commands/start.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -6
- package/dist/index.js.map +1 -1
- package/native/conductor +0 -0
- package/node_modules/@conductor-oss/core/dist/session-manager.js +1 -1
- package/node_modules/@conductor-oss/core/dist/session-manager.js.map +1 -1
- package/node_modules/@conductor-oss/plugin-agent-amp/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-ccr/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-claude-code/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-codex/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-cursor-cli/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-droid/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-gemini/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-github-copilot/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-opencode/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-agent-qwen-code/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-mcp-server/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-notifier-desktop/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-notifier-discord/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-runtime-tmux/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-scm-github/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-terminal-web/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-tracker-github/package.json +1 -1
- package/node_modules/@conductor-oss/plugin-workspace-worktree/package.json +1 -1
- package/package.json +21 -22
- package/web/.next/standalone/packages/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/packages/web/.next/app-path-routes-manifest.json +0 -1
- package/web/.next/standalone/packages/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/packages/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/packages/web/.next/routes-manifest.json +0 -6
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/api/access/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/attachments/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/boards/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/context-files/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/directory/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/github/repos/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/health/boards/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/health/sessions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/preferences/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/actions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/checks/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/diff/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feedback/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/files/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/keys/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/kill/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/send/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/branches/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/packages/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/packages/web/.next/server/app/page/react-loadable-manifest.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page/server-reference-manifest.json +7 -7
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page.js.nft.json +1 -1
- package/web/.next/standalone/packages/web/.next/server/app/unlock/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/app-paths-manifest.json +0 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__1ed2e6c1._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__3d6b30a3._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__a0b6570d._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0e4dc4f7.js +2 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__2814b563._.js → [root-of-the-server]__1c826f12._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__6622b514._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__869d9ac0._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__9dc23e5a._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__b388693f._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_0e1412de._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_b0abbdd9._.js → _20a4007d._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_69e05fca._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_80efe193._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_b6d31783._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_0973acf3._.js → _b88bcf2c._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_c0f0e227._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_f36ddaa9._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_270cb834._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_@clerk_nextjs_dist_esm_app-router_ae92e1b6._.js → node_modules_@clerk_nextjs_dist_esm_app-router_2985ec6c._.js} +2 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_ad33a435._.js +3 -0
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_f2ebd7a9._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_79316445._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_a078c137._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_app_page_tsx_cd282e82._.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{packages_web_src_components_f2c9e753._.js → packages_web_src_components_6cec00fe._.js} +1 -1
- package/web/.next/standalone/packages/web/.next/server/functions-config-manifest.json +1 -2
- package/web/.next/standalone/packages/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/packages/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.json +8 -8
- package/web/.next/standalone/packages/web/.next/static/chunks/062888342200567f.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/695b7cb206c6dadd.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/6b43741a27171ff7.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/8221b78965a50858.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/858f7ae5c23d7b37.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/8de84e208e201d72.css +3 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{9785347bf1d88302.js → ad82e3dcd5fe1a50.js} +3 -3
- package/web/.next/standalone/packages/web/.next/static/chunks/c4ea57fb949fb623.js +1 -0
- package/web/.next/standalone/packages/web/.next/static/chunks/{4c566fd1e4a92935.js → d9d05e7b540400af.js} +1 -1
- package/web/.next/{static/chunks/719697e99b51d55b.js → standalone/packages/web/.next/static/chunks/f5d9ad0f62ede339.js} +1 -1
- package/web/.next/standalone/packages/web/src/app/page.tsx +3 -4555
- package/web/.next/standalone/packages/web/src/app/sessions/[id]/page.tsx +2 -115
- package/web/.next/standalone/packages/web/src/components/layout/AppShell.tsx +62 -2
- package/web/.next/standalone/packages/web/src/components/layout/TopBar.tsx +19 -19
- package/web/.next/standalone/packages/web/src/components/layout/WorkspaceSidebarPanel.tsx +68 -10
- package/web/.next/standalone/packages/web/src/features/dashboard/DashboardClient.tsx +4587 -0
- package/web/.next/standalone/packages/web/src/features/dashboard/components/WorkspaceOverview.tsx +296 -0
- package/web/.next/standalone/packages/web/src/features/sessions/SessionPageClient.tsx +135 -0
- package/web/.next/standalone/packages/web/src/hooks/useSessionFeed.ts +17 -13
- package/web/.next/standalone/packages/web/src/hooks/useSessions.ts +37 -7
- package/web/.next/standalone/packages/web/src/proxy.ts +1 -0
- package/web/.next/static/chunks/062888342200567f.js +1 -0
- package/web/.next/static/chunks/695b7cb206c6dadd.js +1 -0
- package/web/.next/static/chunks/6b43741a27171ff7.js +1 -0
- package/web/.next/static/chunks/8221b78965a50858.js +1 -0
- package/web/.next/static/chunks/858f7ae5c23d7b37.js +1 -0
- package/web/.next/static/chunks/8de84e208e201d72.css +3 -0
- package/web/.next/static/chunks/{9785347bf1d88302.js → ad82e3dcd5fe1a50.js} +3 -3
- package/web/.next/static/chunks/c4ea57fb949fb623.js +1 -0
- package/web/.next/static/chunks/{4c566fd1e4a92935.js → d9d05e7b540400af.js} +1 -1
- package/web/.next/{standalone/packages/web/.next/static/chunks/719697e99b51d55b.js → static/chunks/f5d9ad0f62ede339.js} +1 -1
- package/dist/commands/watch.d.ts +0 -12
- package/dist/commands/watch.d.ts.map +0 -1
- package/dist/commands/watch.js +0 -84
- package/dist/commands/watch.js.map +0 -1
- package/dist/commands/webhook.d.ts +0 -12
- package/dist/commands/webhook.d.ts.map +0 -1
- package/dist/commands/webhook.js +0 -59
- package/dist/commands/webhook.js.map +0 -1
- package/node_modules/@conductor-oss/plugin-webhook/dist/index.d.ts +0 -28
- package/node_modules/@conductor-oss/plugin-webhook/dist/index.d.ts.map +0 -1
- package/node_modules/@conductor-oss/plugin-webhook/dist/index.js +0 -295
- package/node_modules/@conductor-oss/plugin-webhook/dist/index.js.map +0 -1
- package/node_modules/@conductor-oss/plugin-webhook/package.json +0 -11
- package/web/.next/standalone/packages/web/.next/server/app/api/agents/route/app-paths-manifest.json +0 -3
- package/web/.next/standalone/packages/web/.next/server/app/api/agents/route/build-manifest.json +0 -11
- package/web/.next/standalone/packages/web/.next/server/app/api/agents/route/server-reference-manifest.json +0 -4
- package/web/.next/standalone/packages/web/.next/server/app/api/agents/route.js +0 -8
- package/web/.next/standalone/packages/web/.next/server/app/api/agents/route.js.map +0 -5
- package/web/.next/standalone/packages/web/.next/server/app/api/agents/route.js.nft.json +0 -1
- package/web/.next/standalone/packages/web/.next/server/app/api/agents/route_client-reference-manifest.js +0 -2
- package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__7633d324._.js +0 -4
- package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__bf8faac8._.js +0 -4
- package/web/.next/standalone/packages/web/.next/server/chunks/packages_web__next-internal_server_app_api_agents_route_actions_29063d1a.js +0 -3
- package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_39a27288._.js +0 -3
- package/web/.next/standalone/packages/web/.next/static/chunks/1867dba46fcc022e.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/995a5af4e8529901.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/ab8ef6c7dfc78082.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/b5e2d1ef92e508a0.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/dc65fd7512517f7d.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/e8283780c26eaa91.js +0 -1
- package/web/.next/standalone/packages/web/.next/static/chunks/fe557eb4039d8a8d.css +0 -3
- package/web/.next/standalone/packages/web/src/app/api/agents/route.ts +0 -223
- package/web/.next/static/chunks/1867dba46fcc022e.js +0 -1
- package/web/.next/static/chunks/995a5af4e8529901.js +0 -1
- package/web/.next/static/chunks/ab8ef6c7dfc78082.js +0 -1
- package/web/.next/static/chunks/b5e2d1ef92e508a0.js +0 -1
- package/web/.next/static/chunks/dc65fd7512517f7d.js +0 -1
- package/web/.next/static/chunks/e8283780c26eaa91.js +0 -1
- package/web/.next/static/chunks/fe557eb4039d8a8d.css +0 -3
- /package/web/.next/standalone/packages/web/.next/static/{U7zhuWy5hxiSO9ZXtDOfi → UGPSYGs8x8jahGnVodP_c}/_buildManifest.js +0 -0
- /package/web/.next/standalone/packages/web/.next/static/{U7zhuWy5hxiSO9ZXtDOfi → UGPSYGs8x8jahGnVodP_c}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/packages/web/.next/static/{U7zhuWy5hxiSO9ZXtDOfi → UGPSYGs8x8jahGnVodP_c}/_ssgManifest.js +0 -0
- /package/web/.next/static/{U7zhuWy5hxiSO9ZXtDOfi → UGPSYGs8x8jahGnVodP_c}/_buildManifest.js +0 -0
- /package/web/.next/static/{U7zhuWy5hxiSO9ZXtDOfi → UGPSYGs8x8jahGnVodP_c}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{U7zhuWy5hxiSO9ZXtDOfi → UGPSYGs8x8jahGnVodP_c}/_ssgManifest.js +0 -0
package/dist/commands/start.js
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `co start`
|
|
3
3
|
*
|
|
4
|
-
* Starts the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* The lifecycle manager polls sessions periodically, advancing their
|
|
8
|
-
* state machine (checking CI, reviews, merging, sending reactions, etc.).
|
|
9
|
-
* The web dashboard provides a browser UI for monitoring and interaction.
|
|
4
|
+
* Starts the Rust backend and web dashboard in the foreground.
|
|
5
|
+
* The JS launcher is intentionally thin: it resolves paths, launches
|
|
6
|
+
* the Rust backend, and wires the frontend to it.
|
|
10
7
|
*/
|
|
11
8
|
import { spawn, spawnSync } from "node:child_process";
|
|
12
9
|
import { randomBytes } from "node:crypto";
|
|
13
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
11
|
import { homedir } from "node:os";
|
|
15
|
-
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
16
13
|
import chalk from "chalk";
|
|
17
14
|
import ora from "ora";
|
|
15
|
+
import { parse as parseYaml } from "yaml";
|
|
18
16
|
import { buildConductorBoard, buildConductorYaml } from "@conductor-oss/core";
|
|
19
|
-
import { createServices, loadConfig } from "../services.js";
|
|
20
17
|
function commandExists(command) {
|
|
21
18
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
22
19
|
const result = spawnSync(checker, [command], { stdio: "ignore" });
|
|
@@ -46,14 +43,43 @@ function resolveBuiltinRemoteAuth(enabled) {
|
|
|
46
43
|
sessionSecret,
|
|
47
44
|
};
|
|
48
45
|
}
|
|
46
|
+
function asObject(value) {
|
|
47
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function asTrimmedString(value) {
|
|
53
|
+
if (typeof value !== "string")
|
|
54
|
+
return null;
|
|
55
|
+
const trimmed = value.trim();
|
|
56
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
57
|
+
}
|
|
58
|
+
function asBoolean(value) {
|
|
59
|
+
return value === true;
|
|
60
|
+
}
|
|
61
|
+
function coercePort(value, fallback) {
|
|
62
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535) {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
if (typeof value === "string") {
|
|
66
|
+
const parsed = parseInt(value.trim(), 10);
|
|
67
|
+
if (Number.isInteger(parsed) && parsed >= 1 && parsed <= 65535) {
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
49
73
|
function resolveTrustedHeaderAuth(config) {
|
|
74
|
+
const access = asObject(config["access"]);
|
|
75
|
+
const trustedHeaders = asObject(access["trustedHeaders"]);
|
|
50
76
|
return {
|
|
51
|
-
enabled:
|
|
52
|
-
provider:
|
|
53
|
-
emailHeader:
|
|
54
|
-
jwtHeader:
|
|
55
|
-
teamDomain:
|
|
56
|
-
audience:
|
|
77
|
+
enabled: asBoolean(trustedHeaders["enabled"]),
|
|
78
|
+
provider: trustedHeaders["provider"] === "generic" ? "generic" : "cloudflare-access",
|
|
79
|
+
emailHeader: asTrimmedString(trustedHeaders["emailHeader"]) || "Cf-Access-Authenticated-User-Email",
|
|
80
|
+
jwtHeader: asTrimmedString(trustedHeaders["jwtHeader"]) || "Cf-Access-Jwt-Assertion",
|
|
81
|
+
teamDomain: asTrimmedString(trustedHeaders["teamDomain"]),
|
|
82
|
+
audience: asTrimmedString(trustedHeaders["audience"]),
|
|
57
83
|
};
|
|
58
84
|
}
|
|
59
85
|
export function extractCloudflareTunnelUrl(output) {
|
|
@@ -162,13 +188,40 @@ async function waitForDashboard(url, timeoutMs = 15_000) {
|
|
|
162
188
|
catch {
|
|
163
189
|
// Dashboard is still starting.
|
|
164
190
|
}
|
|
165
|
-
await new Promise((
|
|
191
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 500));
|
|
166
192
|
}
|
|
167
193
|
return false;
|
|
168
194
|
}
|
|
169
195
|
function getDefaultLauncherWorkspace() {
|
|
170
196
|
return resolve(homedir(), ".openclaw", "workspace");
|
|
171
197
|
}
|
|
198
|
+
function resolveWorkspacePathHint(configHint) {
|
|
199
|
+
if (!configHint) {
|
|
200
|
+
return getDefaultLauncherWorkspace();
|
|
201
|
+
}
|
|
202
|
+
const resolved = resolve(configHint);
|
|
203
|
+
if (existsSync(resolved) && basename(resolved).match(/^conductor\.ya?ml$/i)) {
|
|
204
|
+
return dirname(resolved);
|
|
205
|
+
}
|
|
206
|
+
return resolved;
|
|
207
|
+
}
|
|
208
|
+
function findConfigFile(startDir) {
|
|
209
|
+
const baseDir = resolveWorkspacePathHint(startDir);
|
|
210
|
+
let currentDir = baseDir;
|
|
211
|
+
for (;;) {
|
|
212
|
+
for (const filename of ["conductor.yaml", "conductor.yml"]) {
|
|
213
|
+
const candidate = join(currentDir, filename);
|
|
214
|
+
if (existsSync(candidate)) {
|
|
215
|
+
return candidate;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const parentDir = dirname(currentDir);
|
|
219
|
+
if (parentDir === currentDir) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
currentDir = parentDir;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
172
225
|
function ensureDashboardBootstrapWorkspace() {
|
|
173
226
|
const workspacePath = getDefaultLauncherWorkspace();
|
|
174
227
|
const configPath = join(workspacePath, "conductor.yaml");
|
|
@@ -190,55 +243,223 @@ function ensureDashboardBootstrapWorkspace() {
|
|
|
190
243
|
}
|
|
191
244
|
return { workspacePath, configPath };
|
|
192
245
|
}
|
|
246
|
+
function loadLauncherSettings(configHint) {
|
|
247
|
+
const explicitConfigPath = configHint && basename(configHint).match(/^conductor\.ya?ml$/i)
|
|
248
|
+
? resolve(configHint)
|
|
249
|
+
: null;
|
|
250
|
+
const configPath = explicitConfigPath ?? findConfigFile(configHint ?? undefined);
|
|
251
|
+
if (!configPath) {
|
|
252
|
+
const bootstrap = ensureDashboardBootstrapWorkspace();
|
|
253
|
+
return {
|
|
254
|
+
workspacePath: bootstrap.workspacePath,
|
|
255
|
+
configPath: bootstrap.configPath,
|
|
256
|
+
dashboardPort: 4747,
|
|
257
|
+
backendPort: 4748,
|
|
258
|
+
access: {
|
|
259
|
+
requireAuth: false,
|
|
260
|
+
defaultRole: "operator",
|
|
261
|
+
trustedHeaders: resolveTrustedHeaderAuth({}),
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const parsed = parseYaml(readFileSync(configPath, "utf8"));
|
|
266
|
+
const config = asObject(parsed);
|
|
267
|
+
const access = asObject(config["access"]);
|
|
268
|
+
const server = asObject(config["server"]);
|
|
269
|
+
return {
|
|
270
|
+
workspacePath: dirname(configPath),
|
|
271
|
+
configPath,
|
|
272
|
+
dashboardPort: 4747,
|
|
273
|
+
backendPort: coercePort(server["port"], coercePort(config["port"], 4748)),
|
|
274
|
+
access: {
|
|
275
|
+
requireAuth: asBoolean(access["requireAuth"]),
|
|
276
|
+
defaultRole: asTrimmedString(access["defaultRole"]),
|
|
277
|
+
trustedHeaders: resolveTrustedHeaderAuth(config),
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function resolveBackendPort(cliValue, configuredPort) {
|
|
282
|
+
const raw = cliValue?.trim() || process.env["CONDUCTOR_BACKEND_PORT"]?.trim();
|
|
283
|
+
if (!raw)
|
|
284
|
+
return configuredPort;
|
|
285
|
+
return coercePort(raw, configuredPort);
|
|
286
|
+
}
|
|
287
|
+
function resolveFrontendPort(cliValue, configuredPort) {
|
|
288
|
+
const raw = cliValue?.trim() || process.env["PORT"]?.trim();
|
|
289
|
+
if (!raw)
|
|
290
|
+
return configuredPort;
|
|
291
|
+
return coercePort(raw, configuredPort);
|
|
292
|
+
}
|
|
293
|
+
function resolveRepoCargoRoot(workspacePath) {
|
|
294
|
+
const candidate = resolve(workspacePath);
|
|
295
|
+
if (existsSync(join(candidate, "Cargo.toml"))
|
|
296
|
+
&& existsSync(join(candidate, "crates", "conductor-cli", "Cargo.toml"))) {
|
|
297
|
+
return candidate;
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
function resolveBundledRustBinary() {
|
|
302
|
+
const binaryName = process.platform === "win32" ? "conductor.exe" : "conductor";
|
|
303
|
+
const cliDir = new URL(".", import.meta.url).pathname;
|
|
304
|
+
const candidates = [
|
|
305
|
+
resolve(cliDir, "..", "..", "native", binaryName),
|
|
306
|
+
resolve(cliDir, "..", "..", "..", "native", binaryName),
|
|
307
|
+
];
|
|
308
|
+
for (const candidate of candidates) {
|
|
309
|
+
if (existsSync(candidate)) {
|
|
310
|
+
return candidate;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
function resolveRustBackendLaunch(workspacePath, configPath, backendPort) {
|
|
316
|
+
const bundledBinary = resolveBundledRustBinary();
|
|
317
|
+
if (bundledBinary) {
|
|
318
|
+
return {
|
|
319
|
+
cmd: bundledBinary,
|
|
320
|
+
args: [
|
|
321
|
+
"--workspace",
|
|
322
|
+
workspacePath,
|
|
323
|
+
"--config",
|
|
324
|
+
configPath,
|
|
325
|
+
"start",
|
|
326
|
+
"--host",
|
|
327
|
+
"127.0.0.1",
|
|
328
|
+
"--port",
|
|
329
|
+
String(backendPort),
|
|
330
|
+
],
|
|
331
|
+
cwd: workspacePath,
|
|
332
|
+
label: "bundled Rust backend",
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const repoCargoRoot = resolveRepoCargoRoot(workspacePath);
|
|
336
|
+
if (!repoCargoRoot) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
const binaryName = process.platform === "win32" ? "conductor.exe" : "conductor";
|
|
340
|
+
const prebuiltCandidates = [
|
|
341
|
+
join(repoCargoRoot, "target", "release", binaryName),
|
|
342
|
+
join(repoCargoRoot, "target", "debug", binaryName),
|
|
343
|
+
];
|
|
344
|
+
for (const candidate of prebuiltCandidates) {
|
|
345
|
+
if (existsSync(candidate)) {
|
|
346
|
+
return {
|
|
347
|
+
cmd: candidate,
|
|
348
|
+
args: [
|
|
349
|
+
"--workspace",
|
|
350
|
+
workspacePath,
|
|
351
|
+
"--config",
|
|
352
|
+
configPath,
|
|
353
|
+
"start",
|
|
354
|
+
"--host",
|
|
355
|
+
"127.0.0.1",
|
|
356
|
+
"--port",
|
|
357
|
+
String(backendPort),
|
|
358
|
+
],
|
|
359
|
+
cwd: repoCargoRoot,
|
|
360
|
+
label: "prebuilt Rust backend",
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
cmd: "cargo",
|
|
366
|
+
args: [
|
|
367
|
+
"run",
|
|
368
|
+
"-p",
|
|
369
|
+
"conductor-cli",
|
|
370
|
+
"--",
|
|
371
|
+
"--workspace",
|
|
372
|
+
workspacePath,
|
|
373
|
+
"--config",
|
|
374
|
+
configPath,
|
|
375
|
+
"start",
|
|
376
|
+
"--host",
|
|
377
|
+
"127.0.0.1",
|
|
378
|
+
"--port",
|
|
379
|
+
String(backendPort),
|
|
380
|
+
],
|
|
381
|
+
cwd: repoCargoRoot,
|
|
382
|
+
label: "cargo-run Rust backend",
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
async function waitForHttpService(url, timeoutMs = 15_000) {
|
|
386
|
+
const startedAt = Date.now();
|
|
387
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
388
|
+
try {
|
|
389
|
+
const response = await fetch(url, { redirect: "manual" });
|
|
390
|
+
if (response.ok || response.status < 500) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// Service is still starting.
|
|
396
|
+
}
|
|
397
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 500));
|
|
398
|
+
}
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
async function killStalePortListener(port) {
|
|
402
|
+
try {
|
|
403
|
+
const { execSync } = await import("node:child_process");
|
|
404
|
+
const pids = execSync(`lsof -ti :${port} -sTCP:LISTEN 2>/dev/null`, { encoding: "utf8" }).trim();
|
|
405
|
+
if (!pids) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
for (const pid of pids.split("\n").filter(Boolean)) {
|
|
409
|
+
if (pid !== String(process.pid)) {
|
|
410
|
+
process.kill(Number(pid), "SIGTERM");
|
|
411
|
+
console.log(`[port] Killed stale process ${pid} on port ${port}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 1000));
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
// no stale process — expected
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function resolveDashboardWebMode(mode) {
|
|
421
|
+
switch (mode?.trim().toLowerCase()) {
|
|
422
|
+
case "dev":
|
|
423
|
+
return "dev";
|
|
424
|
+
case "production":
|
|
425
|
+
case "prod":
|
|
426
|
+
return "production";
|
|
427
|
+
case "standalone":
|
|
428
|
+
return "standalone";
|
|
429
|
+
default:
|
|
430
|
+
return "auto";
|
|
431
|
+
}
|
|
432
|
+
}
|
|
193
433
|
export function registerStart(program) {
|
|
194
434
|
program
|
|
195
435
|
.command("start")
|
|
196
|
-
.description("Start
|
|
436
|
+
.description("Start the Rust backend and web dashboard (foreground)")
|
|
197
437
|
.option("--no-dashboard", "Skip starting the web dashboard")
|
|
198
|
-
.option("--no-watcher", "
|
|
438
|
+
.option("--no-watcher", "Deprecated. Rust backend startup no longer uses the JS watcher")
|
|
199
439
|
.option("--open", "Open the dashboard in your default browser")
|
|
200
440
|
.option("--tunnel", "Expose the dashboard on a free public Cloudflare Quick Tunnel")
|
|
201
441
|
.option("--host <host>", "Dashboard bind host. Defaults to 127.0.0.1 for local-only access")
|
|
202
442
|
.option("-p, --port <port>", "Dashboard port override")
|
|
203
|
-
.option("-
|
|
443
|
+
.option("--no-backend", "Do not launch a separate local Rust backend")
|
|
444
|
+
.option("--backend-port <port>", "Rust backend port override (default: from config or 4748)")
|
|
445
|
+
.option("-w, --workspace <path>", "Workspace path or conductor.yaml path")
|
|
204
446
|
.action(async (opts) => {
|
|
205
447
|
try {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!/No conductor\.ya?ml found/i.test(message)) {
|
|
218
|
-
throw err;
|
|
219
|
-
}
|
|
220
|
-
const bootstrap = ensureDashboardBootstrapWorkspace();
|
|
221
|
-
process.env["CONDUCTOR_WORKSPACE"] = bootstrap.workspacePath;
|
|
222
|
-
process.env["CO_CONFIG_PATH"] = bootstrap.configPath;
|
|
223
|
-
config = await loadConfig(bootstrap.workspacePath);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
const workspacePath = opts.workspace
|
|
227
|
-
?? process.env["CONDUCTOR_WORKSPACE"]
|
|
228
|
-
?? (config.configPath ? dirname(config.configPath) : `${process.env["HOME"]}/.conductor/workspace`);
|
|
229
|
-
if (!process.env["CONDUCTOR_WORKSPACE"]) {
|
|
230
|
-
process.env["CONDUCTOR_WORKSPACE"] = workspacePath;
|
|
231
|
-
}
|
|
232
|
-
if (!process.env["CO_CONFIG_PATH"] && config.configPath) {
|
|
233
|
-
process.env["CO_CONFIG_PATH"] = config.configPath;
|
|
234
|
-
}
|
|
235
|
-
const { sessionManager, registry } = await createServices(config);
|
|
236
|
-
const supportedAgents = registry.list("agent").map((agent) => agent.name);
|
|
237
|
-
// Mutable ref — set after boardWatcher is created, used by lifecycle callback
|
|
238
|
-
let boardWatcherRef = null;
|
|
239
|
-
const port = opts.port ? parseInt(opts.port, 10) : (config.port ?? 3000);
|
|
448
|
+
const configHint = opts.workspace
|
|
449
|
+
|| process.env["CO_CONFIG_PATH"]?.trim()
|
|
450
|
+
|| process.env["CONDUCTOR_WORKSPACE"]
|
|
451
|
+
|| null;
|
|
452
|
+
const settings = loadLauncherSettings(configHint);
|
|
453
|
+
const workspacePath = settings.workspacePath;
|
|
454
|
+
const configPath = settings.configPath;
|
|
455
|
+
const dashboardPort = resolveFrontendPort(opts.port, settings.dashboardPort);
|
|
456
|
+
const backendPort = resolveBackendPort(opts.backendPort, settings.backendPort);
|
|
457
|
+
const explicitBackendUrl = process.env["CONDUCTOR_BACKEND_URL"]?.trim() || null;
|
|
458
|
+
const bindHost = opts.host?.trim() || "127.0.0.1";
|
|
240
459
|
const shutdownTasks = [];
|
|
241
460
|
let isShuttingDown = false;
|
|
461
|
+
process.env["CONDUCTOR_WORKSPACE"] = workspacePath;
|
|
462
|
+
process.env["CO_CONFIG_PATH"] = configPath;
|
|
242
463
|
const requestShutdown = () => {
|
|
243
464
|
void (async () => {
|
|
244
465
|
if (isShuttingDown)
|
|
@@ -248,8 +469,8 @@ export function registerStart(program) {
|
|
|
248
469
|
try {
|
|
249
470
|
await task();
|
|
250
471
|
}
|
|
251
|
-
catch (
|
|
252
|
-
console.error(
|
|
472
|
+
catch (error) {
|
|
473
|
+
console.error(error);
|
|
253
474
|
}
|
|
254
475
|
}
|
|
255
476
|
process.exit(0);
|
|
@@ -262,100 +483,77 @@ export function registerStart(program) {
|
|
|
262
483
|
console.log(chalk.bold.cyan(" Conductor -- Starting"));
|
|
263
484
|
console.log(chalk.dim(line));
|
|
264
485
|
console.log();
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const core = await import("@conductor-oss/core");
|
|
268
|
-
core.syncWorkspaceSupportFiles(config, {
|
|
269
|
-
workspacePath,
|
|
270
|
-
agentNames: supportedAgents,
|
|
271
|
-
});
|
|
272
|
-
// Startup config sync: regenerate drifted project-local conductor.yaml mirrors
|
|
273
|
-
if (typeof core.startupConfigSync === "function") {
|
|
274
|
-
const syncResult = core.startupConfigSync(config);
|
|
275
|
-
if (syncResult.fixed > 0) {
|
|
276
|
-
console.log(chalk.dim(` Synced ${syncResult.fixed} project-local config(s) from canonical`));
|
|
277
|
-
}
|
|
486
|
+
if (opts.watcher === false) {
|
|
487
|
+
console.log(chalk.dim(" JS watcher flag ignored: runtime ownership has moved to the Rust backend path."));
|
|
278
488
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
// ---- Start board watcher ----
|
|
301
|
-
if (opts.watcher !== false) {
|
|
302
|
-
const watchSpinner = ora("Starting board watcher").start();
|
|
303
|
-
try {
|
|
304
|
-
const boardPatternsOrConfig = config.boards?.length ? config.boards : config;
|
|
305
|
-
const boards = core.discoverBoards(workspacePath, boardPatternsOrConfig);
|
|
306
|
-
if (boards.length === 0) {
|
|
307
|
-
watchSpinner.warn("No CONDUCTOR.md boards found");
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
const boardProjectMap = core.buildBoardProjectMap(boards, config);
|
|
311
|
-
const boardWatcher = core.createBoardWatcher({
|
|
312
|
-
config,
|
|
313
|
-
sessionManager,
|
|
314
|
-
agentNames: supportedAgents,
|
|
315
|
-
boardPaths: boards,
|
|
316
|
-
boardProjectMap,
|
|
317
|
-
pollIntervalMs: 5000,
|
|
318
|
-
workspacePath,
|
|
319
|
-
onDispatch: (projectId, sessionId, task) => {
|
|
320
|
-
console.log(`[board-watcher] Dispatched ${sessionId} -> ${projectId}: "${task}"`);
|
|
489
|
+
// ---- Start Rust backend ----
|
|
490
|
+
let backendProcess = null;
|
|
491
|
+
const shouldLaunchBackend = opts.backend !== false && !explicitBackendUrl;
|
|
492
|
+
const backendUrl = explicitBackendUrl ?? (shouldLaunchBackend ? `http://127.0.0.1:${backendPort}` : null);
|
|
493
|
+
if (shouldLaunchBackend) {
|
|
494
|
+
const backendSpinner = ora(`Starting Rust backend on http://127.0.0.1:${backendPort}`).start();
|
|
495
|
+
const launch = resolveRustBackendLaunch(workspacePath, configPath, backendPort);
|
|
496
|
+
if (!launch) {
|
|
497
|
+
backendSpinner.warn("Rust backend binary was not found. Build or package the Rust backend first.");
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
try {
|
|
501
|
+
await killStalePortListener(backendPort);
|
|
502
|
+
backendProcess = spawn(launch.cmd, launch.args, {
|
|
503
|
+
cwd: launch.cwd,
|
|
504
|
+
stdio: "inherit",
|
|
505
|
+
detached: false,
|
|
506
|
+
env: {
|
|
507
|
+
...process.env,
|
|
321
508
|
},
|
|
322
509
|
});
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
510
|
+
backendProcess.on("error", () => {
|
|
511
|
+
backendSpinner.warn("Rust backend failed to start.");
|
|
512
|
+
});
|
|
513
|
+
const backendReady = await waitForHttpService(`${backendUrl}/api/health`);
|
|
514
|
+
if (backendReady) {
|
|
515
|
+
backendSpinner.succeed(`Rust backend running on ${backendUrl} (${launch.label})`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
backendSpinner.warn(`Rust backend did not become ready at ${backendUrl} in time.`);
|
|
519
|
+
}
|
|
520
|
+
shutdownTasks.push(() => {
|
|
521
|
+
if (backendProcess && backendProcess.exitCode === null) {
|
|
522
|
+
backendProcess.kill("SIGTERM");
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
backendSpinner.warn(`Rust backend failed: ${error}`);
|
|
327
528
|
}
|
|
328
|
-
}
|
|
329
|
-
catch (err) {
|
|
330
|
-
watchSpinner.warn(`Board watcher failed: ${err}`);
|
|
331
529
|
}
|
|
332
530
|
}
|
|
531
|
+
else if (explicitBackendUrl) {
|
|
532
|
+
console.log(chalk.dim(` Backend: using existing Rust backend at ${explicitBackendUrl}`));
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
console.log(chalk.yellow(" Backend: not launched; frontend API requests will fail without CONDUCTOR_BACKEND_URL."));
|
|
536
|
+
}
|
|
333
537
|
// ---- Start web dashboard ----
|
|
334
538
|
let dashboardProcess = null;
|
|
335
539
|
let publicDashboardUrl = null;
|
|
336
540
|
let unlockDashboardUrl = null;
|
|
337
|
-
const bindHost = opts.host?.trim() || "127.0.0.1";
|
|
338
541
|
const externalAccessRequested = opts.tunnel === true || !isLoopbackHost(bindHost);
|
|
339
542
|
const clerkConfigured = Boolean(process.env["NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY"] && process.env["CLERK_SECRET_KEY"]);
|
|
340
|
-
const trustedHeaderAuth =
|
|
543
|
+
const trustedHeaderAuth = settings.access.trustedHeaders;
|
|
341
544
|
const builtinRemoteAuth = resolveBuiltinRemoteAuth(externalAccessRequested && !clerkConfigured);
|
|
342
545
|
if (opts.dashboard !== false) {
|
|
343
546
|
const dashSpinner = ora("Starting web dashboard").start();
|
|
344
547
|
try {
|
|
345
548
|
const cliDir = new URL(".", import.meta.url).pathname;
|
|
346
|
-
const {
|
|
347
|
-
const { cpSync, existsSync, mkdirSync } = await import("node:fs");
|
|
348
|
-
// Search order:
|
|
349
|
-
// 1. Standalone build inside CLI package (npm install -g)
|
|
350
|
-
// 2. Sibling packages/web in monorepo dev setup
|
|
351
|
-
// 3. config.configPath-relative monorepo root
|
|
549
|
+
const { cpSync, readdirSync, statSync } = await import("node:fs");
|
|
352
550
|
let webDir = null;
|
|
353
551
|
const candidates = [
|
|
354
|
-
resolve(cliDir, "..", "web"),
|
|
355
|
-
resolve(cliDir, "..", "..", "..", "web"),
|
|
356
|
-
resolve(cliDir, "..", "..", "web"),
|
|
357
|
-
|
|
358
|
-
]
|
|
552
|
+
resolve(cliDir, "..", "web"),
|
|
553
|
+
resolve(cliDir, "..", "..", "..", "web"),
|
|
554
|
+
resolve(cliDir, "..", "..", "web"),
|
|
555
|
+
resolve(dirname(configPath), "packages", "web"),
|
|
556
|
+
];
|
|
359
557
|
for (const candidate of candidates) {
|
|
360
558
|
if (existsSync(join(candidate, "package.json"))) {
|
|
361
559
|
webDir = candidate;
|
|
@@ -366,52 +564,49 @@ export function registerStart(program) {
|
|
|
366
564
|
dashSpinner.warn("Dashboard not found. Run: pnpm --filter @conductor-oss/web build");
|
|
367
565
|
return;
|
|
368
566
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
for (const pid of pids.split("\n").filter(Boolean)) {
|
|
375
|
-
if (pid !== String(process.pid)) {
|
|
376
|
-
process.kill(Number(pid), "SIGTERM");
|
|
377
|
-
console.log(`[dashboard] Killed stale process ${pid} on port ${port}`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
catch { /* no stale process — expected */ }
|
|
384
|
-
// Prefer standalone build (output: standalone in next.config), then production, then dev
|
|
385
|
-
// Find standalone server.js (location varies by monorepo nesting)
|
|
386
|
-
const { readdirSync, statSync: fsStat } = await import("node:fs");
|
|
567
|
+
await killStalePortListener(dashboardPort);
|
|
568
|
+
const webMode = resolveDashboardWebMode(process.env["CONDUCTOR_WEB_MODE"]);
|
|
569
|
+
const isSourceCheckout = existsSync(join(webDir, "src", "app", "page.tsx"))
|
|
570
|
+
&& existsSync(join(webDir, "next.config.ts"));
|
|
571
|
+
const preferDevServer = webMode === "dev" || (webMode === "auto" && isSourceCheckout);
|
|
387
572
|
const standaloneDir = join(webDir, ".next", "standalone");
|
|
573
|
+
const hasNextBuild = existsSync(join(webDir, ".next"));
|
|
388
574
|
let standaloneServer = null;
|
|
389
575
|
const searchQueue = [standaloneDir];
|
|
390
|
-
for (let depth = 0; depth < 6 && searchQueue.length > 0 && !standaloneServer; depth
|
|
576
|
+
for (let depth = 0; depth < 6 && searchQueue.length > 0 && !standaloneServer; depth += 1) {
|
|
391
577
|
const nextQueue = [];
|
|
392
|
-
for (const
|
|
393
|
-
const candidate = join(
|
|
578
|
+
for (const currentDir of searchQueue) {
|
|
579
|
+
const candidate = join(currentDir, "server.js");
|
|
394
580
|
if (existsSync(candidate)) {
|
|
395
581
|
standaloneServer = candidate;
|
|
396
582
|
break;
|
|
397
583
|
}
|
|
398
584
|
try {
|
|
399
|
-
for (const entry of readdirSync(
|
|
400
|
-
const full = join(
|
|
401
|
-
if (
|
|
585
|
+
for (const entry of readdirSync(currentDir)) {
|
|
586
|
+
const full = join(currentDir, String(entry));
|
|
587
|
+
if (statSync(full).isDirectory() && entry !== "node_modules") {
|
|
402
588
|
nextQueue.push(full);
|
|
403
589
|
}
|
|
404
590
|
}
|
|
405
591
|
}
|
|
406
|
-
catch {
|
|
592
|
+
catch {
|
|
593
|
+
// ignore
|
|
594
|
+
}
|
|
407
595
|
}
|
|
408
596
|
searchQueue.splice(0, searchQueue.length, ...nextQueue);
|
|
409
597
|
}
|
|
410
|
-
const hasNextBuild = existsSync(join(webDir, ".next"));
|
|
411
598
|
let cmd;
|
|
412
599
|
let args;
|
|
413
600
|
let dashboardCwd = webDir;
|
|
414
|
-
if (
|
|
601
|
+
if (preferDevServer) {
|
|
602
|
+
cmd = "pnpm";
|
|
603
|
+
args = ["run", "dev", "--hostname", bindHost, "--port", String(dashboardPort)];
|
|
604
|
+
}
|
|
605
|
+
else if (webMode === "production" && hasNextBuild) {
|
|
606
|
+
cmd = "pnpm";
|
|
607
|
+
args = ["run", "start", "--hostname", bindHost, "--port", String(dashboardPort)];
|
|
608
|
+
}
|
|
609
|
+
else if (standaloneServer) {
|
|
415
610
|
const standaloneAppDir = dirname(standaloneServer);
|
|
416
611
|
const standaloneStaticDir = join(standaloneAppDir, ".next", "static");
|
|
417
612
|
const sourceStaticDir = join(webDir, ".next", "static");
|
|
@@ -429,27 +624,12 @@ export function registerStart(program) {
|
|
|
429
624
|
dashboardCwd = standaloneDir;
|
|
430
625
|
}
|
|
431
626
|
else if (hasNextBuild) {
|
|
432
|
-
// Use pnpm run start (next start) — reliable, serves static assets correctly
|
|
433
627
|
cmd = "pnpm";
|
|
434
|
-
args = [
|
|
435
|
-
"run",
|
|
436
|
-
"start",
|
|
437
|
-
"--hostname",
|
|
438
|
-
bindHost,
|
|
439
|
-
"--port",
|
|
440
|
-
String(port),
|
|
441
|
-
];
|
|
628
|
+
args = ["run", "start", "--hostname", bindHost, "--port", String(dashboardPort)];
|
|
442
629
|
}
|
|
443
630
|
else {
|
|
444
631
|
cmd = "pnpm";
|
|
445
|
-
args = [
|
|
446
|
-
"run",
|
|
447
|
-
"dev",
|
|
448
|
-
"--hostname",
|
|
449
|
-
bindHost,
|
|
450
|
-
"--port",
|
|
451
|
-
String(port),
|
|
452
|
-
];
|
|
632
|
+
args = ["run", "dev", "--hostname", bindHost, "--port", String(dashboardPort)];
|
|
453
633
|
}
|
|
454
634
|
dashboardProcess = spawn(cmd, args, {
|
|
455
635
|
cwd: dashboardCwd,
|
|
@@ -457,10 +637,16 @@ export function registerStart(program) {
|
|
|
457
637
|
detached: false,
|
|
458
638
|
env: {
|
|
459
639
|
...process.env,
|
|
460
|
-
PORT: String(
|
|
640
|
+
PORT: String(dashboardPort),
|
|
461
641
|
HOSTNAME: bindHost,
|
|
462
642
|
CONDUCTOR_WORKSPACE: workspacePath,
|
|
463
|
-
CO_CONFIG_PATH:
|
|
643
|
+
CO_CONFIG_PATH: configPath,
|
|
644
|
+
...(backendUrl
|
|
645
|
+
? {
|
|
646
|
+
CONDUCTOR_BACKEND_URL: backendUrl,
|
|
647
|
+
CONDUCTOR_BACKEND_PORT: String(backendPort),
|
|
648
|
+
}
|
|
649
|
+
: {}),
|
|
464
650
|
...(builtinRemoteAuth
|
|
465
651
|
? {
|
|
466
652
|
CONDUCTOR_REMOTE_ACCESS_TOKEN: builtinRemoteAuth.accessToken,
|
|
@@ -481,25 +667,21 @@ export function registerStart(program) {
|
|
|
481
667
|
: {}),
|
|
482
668
|
}
|
|
483
669
|
: {}),
|
|
484
|
-
...(
|
|
485
|
-
? {
|
|
486
|
-
CONDUCTOR_REQUIRE_AUTH: "true",
|
|
487
|
-
}
|
|
670
|
+
...(settings.access.requireAuth
|
|
671
|
+
? { CONDUCTOR_REQUIRE_AUTH: "true" }
|
|
488
672
|
: {}),
|
|
489
|
-
...(
|
|
490
|
-
? {
|
|
491
|
-
CONDUCTOR_ACCESS_DEFAULT_ROLE: config.access.defaultRole,
|
|
492
|
-
}
|
|
673
|
+
...(settings.access.defaultRole
|
|
674
|
+
? { CONDUCTOR_ACCESS_DEFAULT_ROLE: settings.access.defaultRole }
|
|
493
675
|
: {}),
|
|
494
676
|
},
|
|
495
677
|
});
|
|
496
678
|
dashboardProcess.on("error", () => {
|
|
497
679
|
dashSpinner.warn("Dashboard failed to start. Try: cd packages/web && pnpm build");
|
|
498
680
|
});
|
|
499
|
-
const dashboardInternalUrl = `http://127.0.0.1:${
|
|
681
|
+
const dashboardInternalUrl = `http://127.0.0.1:${dashboardPort}`;
|
|
500
682
|
const dashboardUrl = isLoopbackHost(bindHost)
|
|
501
|
-
? `http://localhost:${
|
|
502
|
-
: `http://${bindHost}:${
|
|
683
|
+
? `http://localhost:${dashboardPort}`
|
|
684
|
+
: `http://${bindHost}:${dashboardPort}`;
|
|
503
685
|
if (builtinRemoteAuth) {
|
|
504
686
|
unlockDashboardUrl = buildRemoteUnlockUrl(dashboardUrl, builtinRemoteAuth.accessToken);
|
|
505
687
|
}
|
|
@@ -518,7 +700,6 @@ export function registerStart(program) {
|
|
|
518
700
|
}
|
|
519
701
|
});
|
|
520
702
|
publicDashboardUrl = await tunnel.url;
|
|
521
|
-
config.dashboardUrl = publicDashboardUrl;
|
|
522
703
|
if (builtinRemoteAuth) {
|
|
523
704
|
unlockDashboardUrl = buildRemoteUnlockUrl(publicDashboardUrl, builtinRemoteAuth.accessToken);
|
|
524
705
|
}
|
|
@@ -546,33 +727,22 @@ export function registerStart(program) {
|
|
|
546
727
|
});
|
|
547
728
|
}
|
|
548
729
|
}
|
|
549
|
-
catch {
|
|
550
|
-
dashSpinner.warn(
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
// ---- Start webhook server (if enabled in config) ----
|
|
554
|
-
if (config.webhook?.enabled) {
|
|
555
|
-
const webhookSpinner = ora("Starting webhook server").start();
|
|
556
|
-
try {
|
|
557
|
-
const { createWebhookServer } = await import("@conductor-oss/plugin-webhook");
|
|
558
|
-
const webhookServer = createWebhookServer(config, config.webhook);
|
|
559
|
-
await webhookServer.start();
|
|
560
|
-
shutdownTasks.push(() => webhookServer.stop());
|
|
561
|
-
webhookSpinner.succeed(`Webhook server running on port ${config.webhook.port}`);
|
|
562
|
-
}
|
|
563
|
-
catch (err) {
|
|
564
|
-
webhookSpinner.warn(`Webhook server failed to start: ${err}`);
|
|
730
|
+
catch (error) {
|
|
731
|
+
dashSpinner.warn(`Could not start dashboard: ${error}`);
|
|
565
732
|
}
|
|
566
733
|
}
|
|
567
734
|
// ---- Summary ----
|
|
568
735
|
console.log();
|
|
569
736
|
console.log(chalk.bold.green("Conductor is running."));
|
|
570
|
-
console.log(chalk.dim(` Config: ${
|
|
737
|
+
console.log(chalk.dim(` Config: ${configPath}`));
|
|
571
738
|
if (opts.dashboard !== false) {
|
|
572
739
|
const dashboardSummaryUrl = isLoopbackHost(bindHost)
|
|
573
|
-
? `http://localhost:${
|
|
574
|
-
: `http://${bindHost}:${
|
|
740
|
+
? `http://localhost:${dashboardPort}`
|
|
741
|
+
: `http://${bindHost}:${dashboardPort}`;
|
|
575
742
|
console.log(chalk.dim(` Dashboard: ${dashboardSummaryUrl}`));
|
|
743
|
+
if (backendUrl) {
|
|
744
|
+
console.log(chalk.dim(` Backend: ${backendUrl}`));
|
|
745
|
+
}
|
|
576
746
|
if (publicDashboardUrl) {
|
|
577
747
|
console.log(chalk.dim(` Public: ${publicDashboardUrl}`));
|
|
578
748
|
}
|
|
@@ -594,30 +764,29 @@ export function registerStart(program) {
|
|
|
594
764
|
}
|
|
595
765
|
}
|
|
596
766
|
}
|
|
597
|
-
|
|
598
|
-
console.log(chalk.dim(" Watcher: Obsidian CONDUCTOR.md boards"));
|
|
599
|
-
}
|
|
600
|
-
if (config.webhook?.enabled) {
|
|
601
|
-
console.log(chalk.dim(` Webhook: http://localhost:${config.webhook.port}/api/webhook`));
|
|
602
|
-
}
|
|
767
|
+
console.log(chalk.dim(" Runtime: Rust backend + Next frontend"));
|
|
603
768
|
console.log(chalk.dim(" Press Ctrl-C to stop.\n"));
|
|
604
|
-
// Keep process alive. Dashboard is optional for orchestrator health.
|
|
605
|
-
// If it crashes (e.g. EADDRINUSE), keep lifecycle + board watcher running.
|
|
606
769
|
if (dashboardProcess) {
|
|
607
770
|
dashboardProcess.on("exit", (code, signal) => {
|
|
608
771
|
if (code !== 0 && code !== null) {
|
|
609
772
|
console.error(chalk.yellow(`Dashboard exited with code ${code}${signal ? ` (signal ${signal})` : ""}. ` +
|
|
610
|
-
"Keeping
|
|
773
|
+
"Keeping the Rust backend running."));
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
if (backendProcess) {
|
|
778
|
+
backendProcess.on("exit", (code, signal) => {
|
|
779
|
+
if (code !== 0 && code !== null) {
|
|
780
|
+
console.error(chalk.red(`Rust backend exited with code ${code}${signal ? ` (signal ${signal})` : ""}.`));
|
|
611
781
|
}
|
|
612
782
|
});
|
|
613
783
|
}
|
|
614
|
-
// Always keep process alive via interval heartbeat.
|
|
615
784
|
setInterval(() => {
|
|
616
|
-
//
|
|
785
|
+
// Keep the launcher attached while child processes run.
|
|
617
786
|
}, 60_000);
|
|
618
787
|
}
|
|
619
|
-
catch (
|
|
620
|
-
console.error(chalk.red(`Failed to start: ${
|
|
788
|
+
catch (error) {
|
|
789
|
+
console.error(chalk.red(`Failed to start: ${error}`));
|
|
621
790
|
process.exit(1);
|
|
622
791
|
}
|
|
623
792
|
});
|