remcodex 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/server/src/app.js +186 -0
- package/dist/server/src/cli.js +270 -0
- package/dist/server/src/controllers/codex-options.controller.js +199 -0
- package/dist/server/src/controllers/message.controller.js +21 -0
- package/dist/server/src/controllers/project.controller.js +44 -0
- package/dist/server/src/controllers/session.controller.js +175 -0
- package/dist/server/src/db/client.js +10 -0
- package/dist/server/src/db/migrations.js +32 -0
- package/dist/server/src/gateways/ws.gateway.js +60 -0
- package/dist/server/src/services/codex-app-server-runner.js +363 -0
- package/dist/server/src/services/codex-exec-runner.js +147 -0
- package/dist/server/src/services/codex-rollout-sync.js +977 -0
- package/dist/server/src/services/codex-runner.js +11 -0
- package/dist/server/src/services/codex-stream-events.js +478 -0
- package/dist/server/src/services/event-store.js +328 -0
- package/dist/server/src/services/project-manager.js +130 -0
- package/dist/server/src/services/pty-runner.js +72 -0
- package/dist/server/src/services/session-manager.js +1586 -0
- package/dist/server/src/services/session-timeline-service.js +181 -0
- package/dist/server/src/types/codex-launch.js +2 -0
- package/dist/server/src/types/models.js +37 -0
- package/dist/server/src/utils/ansi.js +143 -0
- package/dist/server/src/utils/codex-launch.js +102 -0
- package/dist/server/src/utils/codex-quota.js +179 -0
- package/dist/server/src/utils/codex-status.js +163 -0
- package/dist/server/src/utils/codex-ui-options.js +114 -0
- package/dist/server/src/utils/command.js +46 -0
- package/dist/server/src/utils/errors.js +16 -0
- package/dist/server/src/utils/ids.js +7 -0
- package/dist/server/src/utils/node-pty.js +29 -0
- package/package.json +36 -0
- package/scripts/fix-node-pty-helper.js +36 -0
- package/web/api.js +175 -0
- package/web/app.js +8082 -0
- package/web/components/composer.js +627 -0
- package/web/components/session-workbench.js +173 -0
- package/web/i18n/index.js +171 -0
- package/web/i18n/locales/de.js +50 -0
- package/web/i18n/locales/en.js +320 -0
- package/web/i18n/locales/es.js +50 -0
- package/web/i18n/locales/fr.js +50 -0
- package/web/i18n/locales/ja.js +50 -0
- package/web/i18n/locales/ko.js +50 -0
- package/web/i18n/locales/pt-BR.js +50 -0
- package/web/i18n/locales/ru.js +50 -0
- package/web/i18n/locales/zh-CN.js +320 -0
- package/web/i18n/locales/zh-Hant.js +53 -0
- package/web/index.html +23 -0
- package/web/message-rich-text.js +218 -0
- package/web/session-command-activity.js +980 -0
- package/web/session-event-adapter.js +826 -0
- package/web/session-timeline-reducer.js +728 -0
- package/web/session-timeline-renderer.js +656 -0
- package/web/session-ws.js +31 -0
- package/web/styles.css +5665 -0
- package/web/vendor/markdown-it.js +6969 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const app_1 = require("./app");
|
|
12
|
+
const command_1 = require("./utils/command");
|
|
13
|
+
function print(message = "") {
|
|
14
|
+
process.stdout.write(`${message}\n`);
|
|
15
|
+
}
|
|
16
|
+
function printError(message = "") {
|
|
17
|
+
process.stderr.write(`${message}\n`);
|
|
18
|
+
}
|
|
19
|
+
function readPackageVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const packageRoot = (0, app_1.resolvePackageRoot)();
|
|
22
|
+
const packageJson = JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.join(packageRoot, "package.json"), "utf8"));
|
|
23
|
+
return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "0.0.0";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isExecutableFile(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
(0, node_fs_1.accessSync)(filePath, node_fs_1.constants.X_OK);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function commandExists(command) {
|
|
39
|
+
const resolved = (0, command_1.resolveExecutable)(command);
|
|
40
|
+
const ok = Boolean(resolved) &&
|
|
41
|
+
(resolved.includes(node_path_1.default.sep) || node_path_1.default.isAbsolute(resolved)) &&
|
|
42
|
+
isExecutableFile(resolved);
|
|
43
|
+
return { ok, resolved };
|
|
44
|
+
}
|
|
45
|
+
function parseFlags(argv) {
|
|
46
|
+
const flags = {
|
|
47
|
+
noOpen: false,
|
|
48
|
+
};
|
|
49
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
50
|
+
const token = argv[index];
|
|
51
|
+
if (token === "--no-open") {
|
|
52
|
+
flags.noOpen = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (token === "--port") {
|
|
56
|
+
const value = argv[index + 1];
|
|
57
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
58
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
59
|
+
flags.port = parsed;
|
|
60
|
+
}
|
|
61
|
+
index += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (token === "--db" || token === "--database") {
|
|
65
|
+
const value = argv[index + 1];
|
|
66
|
+
if (value) {
|
|
67
|
+
flags.databasePath = node_path_1.default.resolve(value);
|
|
68
|
+
}
|
|
69
|
+
index += 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return flags;
|
|
73
|
+
}
|
|
74
|
+
function getLanAddresses() {
|
|
75
|
+
const interfaces = (0, node_os_1.networkInterfaces)();
|
|
76
|
+
const addresses = [];
|
|
77
|
+
Object.values(interfaces).forEach((entries) => {
|
|
78
|
+
entries?.forEach((entry) => {
|
|
79
|
+
if (entry.family === "IPv4" && !entry.internal) {
|
|
80
|
+
addresses.push(entry.address);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
return Array.from(new Set(addresses));
|
|
85
|
+
}
|
|
86
|
+
function openBrowser(url) {
|
|
87
|
+
let command = "";
|
|
88
|
+
let args = [];
|
|
89
|
+
if (process.platform === "darwin") {
|
|
90
|
+
command = "open";
|
|
91
|
+
args = [url];
|
|
92
|
+
}
|
|
93
|
+
else if (process.platform === "win32") {
|
|
94
|
+
command = "cmd";
|
|
95
|
+
args = ["/c", "start", "", url];
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
command = "xdg-open";
|
|
99
|
+
args = [url];
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const child = (0, node_child_process_1.spawn)(command, args, {
|
|
103
|
+
stdio: "ignore",
|
|
104
|
+
detached: true,
|
|
105
|
+
});
|
|
106
|
+
child.unref();
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
/* ignore */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function usage() {
|
|
113
|
+
print("RemCodex");
|
|
114
|
+
print("");
|
|
115
|
+
print("Usage:");
|
|
116
|
+
print(" remcodex Start the local web app");
|
|
117
|
+
print(" remcodex start Start the local web app");
|
|
118
|
+
print(" remcodex doctor Check local environment");
|
|
119
|
+
print(" remcodex version Show version");
|
|
120
|
+
print("");
|
|
121
|
+
print("Options:");
|
|
122
|
+
print(" --port <number> Preferred port");
|
|
123
|
+
print(" --db <path> Use a specific SQLite database path");
|
|
124
|
+
print(" --no-open Do not open a browser automatically");
|
|
125
|
+
}
|
|
126
|
+
async function runDoctor(flags) {
|
|
127
|
+
const version = readPackageVersion();
|
|
128
|
+
const rawCodexCommand = process.env.CODEX_COMMAND ?? "codex";
|
|
129
|
+
const codex = commandExists(rawCodexCommand);
|
|
130
|
+
const packageRoot = (0, app_1.resolvePackageRoot)();
|
|
131
|
+
const databasePath = flags.databasePath ?? process.env.DATABASE_PATH ?? (0, app_1.resolveDefaultDatabasePath)();
|
|
132
|
+
const databaseDir = node_path_1.default.dirname(databasePath);
|
|
133
|
+
const databaseDirExists = (0, node_fs_1.existsSync)(databaseDir);
|
|
134
|
+
const databaseDirWritable = databaseDirExists && (() => {
|
|
135
|
+
try {
|
|
136
|
+
(0, node_fs_1.accessSync)(databaseDir, node_fs_1.constants.W_OK);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
})();
|
|
143
|
+
print(`RemCodex v${version}`);
|
|
144
|
+
print("");
|
|
145
|
+
print(`Node: ${process.version}`);
|
|
146
|
+
print(`Package root: ${packageRoot}`);
|
|
147
|
+
print(`Database path: ${databasePath}`);
|
|
148
|
+
print(`Database dir: ${databaseDirExists ? (databaseDirWritable ? "writable" : "not writable") : "missing (will be created on first start)"}`);
|
|
149
|
+
print(`Default project root: ${process.env.PROJECT_ROOTS?.trim() || (0, node_os_1.homedir)()}`);
|
|
150
|
+
print(`Codex command: ${rawCodexCommand}`);
|
|
151
|
+
print(`Codex resolved: ${codex.resolved}`);
|
|
152
|
+
print(`Codex available: ${codex.ok ? "yes" : "no"}`);
|
|
153
|
+
if (!codex.ok) {
|
|
154
|
+
printError("");
|
|
155
|
+
printError("Codex CLI was not found in PATH.");
|
|
156
|
+
printError("Install Codex first, or set CODEX_COMMAND to the correct executable.");
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
print("");
|
|
160
|
+
print("Environment looks good.");
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
async function runStart(flags) {
|
|
164
|
+
const version = readPackageVersion();
|
|
165
|
+
const rawCodexCommand = process.env.CODEX_COMMAND ?? "codex";
|
|
166
|
+
const codex = commandExists(rawCodexCommand);
|
|
167
|
+
if (!codex.ok) {
|
|
168
|
+
printError("Codex CLI was not found in PATH.");
|
|
169
|
+
printError("Install Codex first, or set CODEX_COMMAND to the correct executable.");
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
const preferredPort = flags.port ?? Number.parseInt(process.env.PORT ?? "3000", 10);
|
|
173
|
+
const codexMode = process.env.CODEX_MODE === "exec-json" ? "exec-json" : "app-server";
|
|
174
|
+
let started = null;
|
|
175
|
+
let activePort = preferredPort;
|
|
176
|
+
for (let offset = 0; offset < 20; offset += 1) {
|
|
177
|
+
const candidate = preferredPort + offset;
|
|
178
|
+
try {
|
|
179
|
+
started = await (0, app_1.startRemCodexServer)({
|
|
180
|
+
port: candidate,
|
|
181
|
+
databasePath: flags.databasePath,
|
|
182
|
+
codexCommand: rawCodexCommand,
|
|
183
|
+
codexMode,
|
|
184
|
+
logStartup: false,
|
|
185
|
+
});
|
|
186
|
+
activePort = candidate;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
191
|
+
if (code === "EADDRINUSE") {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!started) {
|
|
198
|
+
printError(`Could not find an available port starting from ${preferredPort}.`);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
const localUrl = `http://127.0.0.1:${activePort}`;
|
|
202
|
+
const lanUrls = getLanAddresses().map((address) => `http://${address}:${activePort}`);
|
|
203
|
+
print(`RemCodex v${version}`);
|
|
204
|
+
print("");
|
|
205
|
+
print("Starting local workspace...");
|
|
206
|
+
print(`Codex: ${codex.resolved}`);
|
|
207
|
+
print(`Mode: ${started.codexMode}`);
|
|
208
|
+
print(`Database: ${started.databasePath}`);
|
|
209
|
+
print("");
|
|
210
|
+
print(`Local: ${localUrl}`);
|
|
211
|
+
lanUrls.forEach((url) => {
|
|
212
|
+
print(`LAN: ${url}`);
|
|
213
|
+
});
|
|
214
|
+
if (!flags.noOpen) {
|
|
215
|
+
openBrowser(localUrl);
|
|
216
|
+
print("");
|
|
217
|
+
print("Opening browser...");
|
|
218
|
+
}
|
|
219
|
+
const shutdown = async () => {
|
|
220
|
+
process.off("SIGINT", shutdown);
|
|
221
|
+
process.off("SIGTERM", shutdown);
|
|
222
|
+
try {
|
|
223
|
+
await started?.stop();
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
process.on("SIGINT", shutdown);
|
|
230
|
+
process.on("SIGTERM", shutdown);
|
|
231
|
+
return await new Promise((resolve) => {
|
|
232
|
+
started?.server.on("close", () => resolve(0));
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async function main() {
|
|
236
|
+
const argv = process.argv.slice(2);
|
|
237
|
+
if (argv.includes("--version") || argv.includes("-v")) {
|
|
238
|
+
print(readPackageVersion());
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
242
|
+
usage();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const command = argv[0] && !argv[0].startsWith("-") ? argv[0] : "start";
|
|
246
|
+
const flagArgs = command === "start" ? argv.slice(1) : argv;
|
|
247
|
+
const flags = parseFlags(flagArgs);
|
|
248
|
+
switch (command) {
|
|
249
|
+
case "start":
|
|
250
|
+
process.exitCode = await runStart(flags);
|
|
251
|
+
return;
|
|
252
|
+
case "doctor":
|
|
253
|
+
process.exitCode = await runDoctor(flags);
|
|
254
|
+
return;
|
|
255
|
+
case "version":
|
|
256
|
+
print(readPackageVersion());
|
|
257
|
+
return;
|
|
258
|
+
case "help":
|
|
259
|
+
usage();
|
|
260
|
+
return;
|
|
261
|
+
default:
|
|
262
|
+
usage();
|
|
263
|
+
process.exitCode = 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
void main().catch((error) => {
|
|
267
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
268
|
+
printError(message);
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCodexOptionsRouter = createCodexOptionsRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const codex_ui_options_1 = require("../utils/codex-ui-options");
|
|
6
|
+
const codex_quota_1 = require("../utils/codex-quota");
|
|
7
|
+
const codex_status_1 = require("../utils/codex-status");
|
|
8
|
+
function resolveRequestHost(request) {
|
|
9
|
+
const requestHostname = typeof request.hostname === "string" ? request.hostname.trim() : "";
|
|
10
|
+
if (requestHostname) {
|
|
11
|
+
return requestHostname;
|
|
12
|
+
}
|
|
13
|
+
const forwardedHost = request.headers["x-forwarded-host"];
|
|
14
|
+
const hostHeader = Array.isArray(forwardedHost)
|
|
15
|
+
? forwardedHost[0]
|
|
16
|
+
: (typeof forwardedHost === "string" && forwardedHost.trim()
|
|
17
|
+
? forwardedHost
|
|
18
|
+
: request.headers.host);
|
|
19
|
+
const normalized = typeof hostHeader === "string" ? hostHeader.trim().replace(/:\d+$/, "") : "";
|
|
20
|
+
return normalized || null;
|
|
21
|
+
}
|
|
22
|
+
function resolveCodexHosts(request) {
|
|
23
|
+
const fallbackHost = resolveRequestHost(request);
|
|
24
|
+
const rawHosts = (process.env.REMOTE_HOSTS ?? fallbackHost ?? "")
|
|
25
|
+
.split(",")
|
|
26
|
+
.map((item) => item.trim())
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
const hosts = Array.from(new Set(rawHosts));
|
|
29
|
+
const requestedActive = process.env.ACTIVE_REMOTE_HOST?.trim() || "";
|
|
30
|
+
const activeHost = requestedActive && hosts.includes(requestedActive)
|
|
31
|
+
? requestedActive
|
|
32
|
+
: (hosts[0] ?? null);
|
|
33
|
+
return {
|
|
34
|
+
hosts,
|
|
35
|
+
activeHost,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function readNumberField(input) {
|
|
39
|
+
if (typeof input === "number" && Number.isFinite(input)) {
|
|
40
|
+
return input;
|
|
41
|
+
}
|
|
42
|
+
if (typeof input === "string" && input.trim()) {
|
|
43
|
+
const parsed = Number.parseFloat(input);
|
|
44
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function toRemainingPercent(input) {
|
|
49
|
+
const usedPercent = readNumberField(input);
|
|
50
|
+
if (usedPercent == null) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return Math.max(0, Math.min(100, Math.round(100 - usedPercent)));
|
|
54
|
+
}
|
|
55
|
+
function formatRemainTime(input) {
|
|
56
|
+
const resetAt = readNumberField(input);
|
|
57
|
+
if (resetAt == null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const diffSec = Math.max(0, Math.floor(resetAt - Date.now() / 1000));
|
|
61
|
+
const hours = Math.floor(diffSec / 3600);
|
|
62
|
+
const minutes = Math.floor((diffSec % 3600) / 60);
|
|
63
|
+
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
|
|
64
|
+
}
|
|
65
|
+
function formatResetDate(input) {
|
|
66
|
+
const resetAt = readNumberField(input);
|
|
67
|
+
if (resetAt == null) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const date = new Date(resetAt * 1000);
|
|
71
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
72
|
+
month: "numeric",
|
|
73
|
+
day: "numeric",
|
|
74
|
+
}).format(date);
|
|
75
|
+
}
|
|
76
|
+
function normalizeQuota(payload) {
|
|
77
|
+
const rateLimits = payload?.rateLimits && typeof payload.rateLimits === "object" ? payload.rateLimits : {};
|
|
78
|
+
const primary = rateLimits.primary && typeof rateLimits.primary === "object"
|
|
79
|
+
? rateLimits.primary
|
|
80
|
+
: {};
|
|
81
|
+
const secondary = rateLimits.secondary && typeof rateLimits.secondary === "object"
|
|
82
|
+
? rateLimits.secondary
|
|
83
|
+
: {};
|
|
84
|
+
return {
|
|
85
|
+
quota: {
|
|
86
|
+
hour: {
|
|
87
|
+
percent: toRemainingPercent(primary.used_percent),
|
|
88
|
+
remainTime: formatRemainTime(primary.resets_at),
|
|
89
|
+
},
|
|
90
|
+
week: {
|
|
91
|
+
percent: toRemainingPercent(secondary.used_percent),
|
|
92
|
+
resetDate: formatResetDate(secondary.resets_at),
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function readRuntimeFailureHint(runtime) {
|
|
98
|
+
const hints = [];
|
|
99
|
+
if (runtime.sandboxMode === "read-only") {
|
|
100
|
+
hints.push("The current runtime is using a read-only sandbox. File writes and environment-changing commands are blocked.");
|
|
101
|
+
}
|
|
102
|
+
else if (runtime.sandboxMode === "workspace-write") {
|
|
103
|
+
hints.push("The current runtime is using a workspace-write sandbox. It can only write inside the current writable roots.");
|
|
104
|
+
}
|
|
105
|
+
if (!runtime.interactiveApprovalUi) {
|
|
106
|
+
hints.push(`The current ${runtime.executionMode} runtime path does not support interactive approval prompts.`);
|
|
107
|
+
}
|
|
108
|
+
if (runtime.workspaceRoot) {
|
|
109
|
+
hints.push(`The current workspace root is ${runtime.workspaceRoot}.`);
|
|
110
|
+
}
|
|
111
|
+
return hints;
|
|
112
|
+
}
|
|
113
|
+
function createCodexOptionsRouter(deps) {
|
|
114
|
+
const router = (0, express_1.Router)();
|
|
115
|
+
router.get("/mode", (_request, response) => {
|
|
116
|
+
response.json((0, codex_ui_options_1.resolveCodexUiOptions)());
|
|
117
|
+
});
|
|
118
|
+
router.get("/ui-options", (_request, response) => {
|
|
119
|
+
response.json((0, codex_ui_options_1.resolveCodexUiOptions)());
|
|
120
|
+
});
|
|
121
|
+
router.get("/status", (request, response) => {
|
|
122
|
+
const params = request.query;
|
|
123
|
+
let threadId = params.threadId?.trim() || "";
|
|
124
|
+
let cwd = params.cwd?.trim() || "";
|
|
125
|
+
if (params.sessionId?.trim()) {
|
|
126
|
+
const session = deps.sessionManager.getSession(params.sessionId.trim());
|
|
127
|
+
if (session) {
|
|
128
|
+
threadId = threadId || session.codex_thread_id || "";
|
|
129
|
+
if (threadId) {
|
|
130
|
+
if (!cwd) {
|
|
131
|
+
const project = deps.projectManager.getProject(session.project_id);
|
|
132
|
+
cwd = project?.path || "";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
response.json({
|
|
137
|
+
thread: null,
|
|
138
|
+
source: "none",
|
|
139
|
+
fetchedAt: new Date().toISOString(),
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const status = (0, codex_status_1.resolveCodexStatus)({
|
|
146
|
+
threadId: threadId || null,
|
|
147
|
+
cwd: cwd || null,
|
|
148
|
+
executionMode: deps.codexMode === "app-server" ? "codex app-server" : "codex exec --json",
|
|
149
|
+
interactiveApprovalUi: deps.codexMode === "app-server",
|
|
150
|
+
});
|
|
151
|
+
response.json({
|
|
152
|
+
...status,
|
|
153
|
+
runtimeHints: readRuntimeFailureHint(status.runtime),
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
router.get("/quota", (request, response) => {
|
|
157
|
+
const params = request.query;
|
|
158
|
+
if (!params.sessionId?.trim()) {
|
|
159
|
+
response.status(400).json({ error: "sessionId is required." });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const session = deps.sessionManager.getSession(params.sessionId.trim());
|
|
163
|
+
if (!session) {
|
|
164
|
+
response.status(404).json({ error: "Session not found." });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
let payload = deps.eventStore.latestQuota(session.id);
|
|
168
|
+
if (!payload) {
|
|
169
|
+
const project = deps.projectManager.getProject(session.project_id);
|
|
170
|
+
const restored = (0, codex_quota_1.resolveCodexQuotaSnapshot)({
|
|
171
|
+
threadId: session.codex_thread_id,
|
|
172
|
+
cwd: project?.path || null,
|
|
173
|
+
});
|
|
174
|
+
if (restored) {
|
|
175
|
+
deps.eventStore.append(session.id, {
|
|
176
|
+
type: "token_count",
|
|
177
|
+
turnId: null,
|
|
178
|
+
messageId: null,
|
|
179
|
+
callId: null,
|
|
180
|
+
requestId: null,
|
|
181
|
+
phase: null,
|
|
182
|
+
stream: null,
|
|
183
|
+
payload: restored,
|
|
184
|
+
});
|
|
185
|
+
payload = restored;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
response.json(normalizeQuota(payload));
|
|
189
|
+
});
|
|
190
|
+
router.get("/hosts", (request, response) => {
|
|
191
|
+
response.json(resolveCodexHosts(request));
|
|
192
|
+
});
|
|
193
|
+
router.get("/importable-sessions", (_request, response) => {
|
|
194
|
+
response.json({
|
|
195
|
+
items: deps.codexRolloutSync.listImportableSessions(),
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
return router;
|
|
199
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMessageRouter = createMessageRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const codex_launch_1 = require("../utils/codex-launch");
|
|
6
|
+
function createMessageRouter(sessionManager) {
|
|
7
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
8
|
+
router.post("/", (request, response, next) => {
|
|
9
|
+
try {
|
|
10
|
+
const body = request.body;
|
|
11
|
+
const params = request.params;
|
|
12
|
+
const launch = (0, codex_launch_1.normalizeCodexExecLaunchInput)(body.codex);
|
|
13
|
+
const result = sessionManager.sendMessage(params.sessionId, body.content ?? "", launch);
|
|
14
|
+
response.json(result);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
next(error);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return router;
|
|
21
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProjectRouter = createProjectRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
function createProjectRouter(projectManager) {
|
|
6
|
+
const router = (0, express_1.Router)();
|
|
7
|
+
router.get("/", (_request, response) => {
|
|
8
|
+
const items = projectManager.listProjects().map((project) => ({
|
|
9
|
+
projectId: project.id,
|
|
10
|
+
name: project.name,
|
|
11
|
+
path: project.path,
|
|
12
|
+
createdAt: project.created_at,
|
|
13
|
+
}));
|
|
14
|
+
response.json({ items });
|
|
15
|
+
});
|
|
16
|
+
router.post("/", (request, response, next) => {
|
|
17
|
+
try {
|
|
18
|
+
const body = request.body;
|
|
19
|
+
const project = projectManager.createProject({
|
|
20
|
+
name: body.name ?? "",
|
|
21
|
+
path: body.path ?? "",
|
|
22
|
+
createMissing: body.createMissing === true,
|
|
23
|
+
});
|
|
24
|
+
response.status(201).json({
|
|
25
|
+
projectId: project.id,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
next(error);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
router.get("/browse", (request, response, next) => {
|
|
33
|
+
try {
|
|
34
|
+
const targetPath = typeof request.query.path === "string" && request.query.path.trim()
|
|
35
|
+
? request.query.path
|
|
36
|
+
: null;
|
|
37
|
+
response.json(projectManager.browseDirectories(targetPath));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
next(error);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return router;
|
|
44
|
+
}
|