ci / test (push) Successful in 45m25s
- Vendor codex-ui-stack host/ as cloud/codex-ui-host/ (original/ not vendored, 650MB) - Bridge emulates 7 methods missing in codex 0.164.0-alpha.9, exposes 29 experimental-protocol methods (150 -> 179), Linux binary candidates, read-only POST /__codex_git - GET /console 302s to the UI host (:8095) when reachable, else falls back to the classic React console; GET /console-classic always serves it - Alignment tables: docs/codex-native-ui-alignment.md - Tests: test_console 7, new test_codex_ui_host 4, handler method pin 247 -> 249
63 lines
2.0 KiB
JavaScript
63 lines
2.0 KiB
JavaScript
/**
|
|
* Projectless thread workspaces.
|
|
*
|
|
* Codex keeps two kinds of conversation: one attached to a project directory and
|
|
* one that starts from nothing. The desktop main process owns the second kind —
|
|
* it picks a folder under the user's workspace root, creates it, and hands the
|
|
* paths back to the renderer through `projectlessThreads.createPaths`.
|
|
*
|
|
* The browser cannot touch the filesystem, so the Node host does it here and
|
|
* exposes the same three paths over HTTP:
|
|
*
|
|
* cwd the directory this conversation runs in
|
|
* outputDirectory where generated files land (same folder)
|
|
* workspaceRoot the shared root that contains every thread
|
|
*
|
|
* Layout mirrors the desktop app: <root>/<YYYY-MM-DD>/<slug>
|
|
*/
|
|
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
export const WORKSPACE_ROOT =
|
|
// Linux server default: ~/Codex. The desktop default ~/Documents/Codex
|
|
// does not exist on a server, so it is not used here.
|
|
process.env.CODEX_WEB_WORKSPACE_ROOT || path.join(os.homedir(), "Codex");
|
|
|
|
export function ensureWorkspaceRoot() {
|
|
fs.mkdirSync(WORKSPACE_ROOT, { recursive: true });
|
|
return WORKSPACE_ROOT;
|
|
}
|
|
|
|
/** Lowercase, collapse anything awkward, keep CJK, cap the length. */
|
|
function slugify(value) {
|
|
const text = String(value ?? "").trim();
|
|
if (!text) return "";
|
|
return text
|
|
.toLowerCase()
|
|
.replace(/[^\p{Letter}\p{Number}]+/gu, "-")
|
|
.replace(/^-+|-+$/g, "")
|
|
.slice(0, 40);
|
|
}
|
|
|
|
export function createProjectlessPaths({ directoryName, prompt } = {}) {
|
|
const root = ensureWorkspaceRoot();
|
|
const day = new Date().toISOString().slice(0, 10);
|
|
const base = slugify(directoryName) || slugify(prompt) || "thread";
|
|
|
|
let dir = path.join(root, day, base);
|
|
let suffix = 1;
|
|
while (fs.existsSync(dir)) {
|
|
suffix += 1;
|
|
dir = path.join(root, day, `${base}-${suffix}`);
|
|
}
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
|
|
return { cwd: dir, outputDirectory: dir, workspaceRoot: root };
|
|
}
|
|
|
|
export function readWorkspaceRoot() {
|
|
return { workspaceRoot: ensureWorkspaceRoot() };
|
|
}
|