@someonegg/codex-hub 0.0.0-stage → 0.1.0
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 +101 -2
- package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/client/assets/index-Dx1LBBmn.js +304 -0
- package/dist/client/assets/index-lmwuA3r2.css +1 -0
- package/dist/client/index.html +16 -0
- package/dist/server/server/adapters/codex/agent-identity.js +9 -0
- package/dist/server/server/adapters/codex/catalog-source.js +2 -0
- package/dist/server/server/adapters/codex/codex-session-source.js +300 -0
- package/dist/server/server/adapters/codex/identity-resolver.js +107 -0
- package/dist/server/server/adapters/codex/interaction-parser.js +90 -0
- package/dist/server/server/adapters/codex/internal-event-parser.js +56 -0
- package/dist/server/server/adapters/codex/io-errors.js +5 -0
- package/dist/server/server/adapters/codex/jsonl-catalog-source.js +58 -0
- package/dist/server/server/adapters/codex/limits.js +10 -0
- package/dist/server/server/adapters/codex/message-normalizer.js +121 -0
- package/dist/server/server/adapters/codex/path-policy.js +88 -0
- package/dist/server/server/adapters/codex/project-directory-resolver.js +79 -0
- package/dist/server/server/adapters/codex/response-item-parser.js +100 -0
- package/dist/server/server/adapters/codex/rollout-decoder.js +286 -0
- package/dist/server/server/adapters/codex/session-index-reader.js +101 -0
- package/dist/server/server/adapters/codex/session-manager.js +62 -0
- package/dist/server/server/adapters/codex/session-normalizer.js +257 -0
- package/dist/server/server/adapters/codex/tool-normalizer.js +58 -0
- package/dist/server/server/adapters/codex/user-input-normalizer.js +123 -0
- package/dist/server/server/api/session-api-mapper.js +194 -0
- package/dist/server/server/application/session-read-service.js +109 -0
- package/dist/server/server/application/session-reader.js +2 -0
- package/dist/server/server/catalog/config.js +4 -0
- package/dist/server/server/catalog/session-catalog-service.js +103 -0
- package/dist/server/server/config.js +145 -0
- package/dist/server/server/create-hub.js +64 -0
- package/dist/server/server/domain/project-directory.js +5 -0
- package/dist/server/server/domain/session-domain.js +2 -0
- package/dist/server/server/domain/session-text.js +45 -0
- package/dist/server/server/http/api-route-handler.js +2 -0
- package/dist/server/server/http/api-router.js +63 -0
- package/dist/server/server/http/create-server.js +148 -0
- package/dist/server/server/http/query-params.js +19 -0
- package/dist/server/server/http/request-body.js +48 -0
- package/dist/server/server/http/router.js +16 -0
- package/dist/server/server/http/security.js +19 -0
- package/dist/server/server/http/session-interaction-routes.js +101 -0
- package/dist/server/server/http/session-management-routes.js +90 -0
- package/dist/server/server/http/session-path.js +13 -0
- package/dist/server/server/http/session-read-routes.js +198 -0
- package/dist/server/server/http/session-updates-routes.js +84 -0
- package/dist/server/server/http/terminal-preview-websocket.js +179 -0
- package/dist/server/server/http/upload-routes.js +74 -0
- package/dist/server/server/interaction/interaction-service.js +247 -0
- package/dist/server/server/interaction/message.js +19 -0
- package/dist/server/server/interaction/pane-binding-manager.js +60 -0
- package/dist/server/server/interaction/reconcile-session-state.js +21 -0
- package/dist/server/server/interaction/session-runtime.js +86 -0
- package/dist/server/server/interaction/terminal-preview-diff.js +71 -0
- package/dist/server/server/interaction/terminal-preview-service.js +194 -0
- package/dist/server/server/interaction/tmux-launcher.js +75 -0
- package/dist/server/server/interaction/tmux-preview-listener.js +101 -0
- package/dist/server/server/interaction/tmux-service.js +269 -0
- package/dist/server/server/interaction/transport.js +9 -0
- package/dist/server/server/main.js +41 -0
- package/dist/server/server/management/operation-service.js +540 -0
- package/dist/server/server/management/session-exit-service.js +50 -0
- package/dist/server/server/management/session-manager.js +59 -0
- package/dist/server/server/repository/catalog-snapshot-store.js +304 -0
- package/dist/server/server/repository/diagnostic-logger.js +70 -0
- package/dist/server/server/repository/opaque-cursor.js +79 -0
- package/dist/server/server/repository/refresh-coordinator.js +14 -0
- package/dist/server/server/repository/session-queries.js +151 -0
- package/dist/server/server/repository/session-query-criteria.js +2 -0
- package/dist/server/server/repository/timeline-prefix-index.js +342 -0
- package/dist/server/server/repository/timeline-prefix-registry.js +55 -0
- package/dist/server/server/security/opaque-id.js +13 -0
- package/dist/server/server/source/session-source.js +2 -0
- package/dist/server/server/updates/session-updates-service.js +147 -0
- package/dist/server/server/updates/update-revision.js +25 -0
- package/dist/server/shared/api-contract.js +2 -0
- package/dist/server/shared/domain.js +2 -0
- package/dist/server/shared/interaction.js +12 -0
- package/dist/server/shared/uploads.js +2 -0
- package/package.json +79 -4
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { lstat } from "node:fs/promises";
|
|
4
|
+
import { createConnection } from "node:net";
|
|
5
|
+
import { normalizeMessage } from "./message.js";
|
|
6
|
+
import { InteractionTransportError, } from "./transport.js";
|
|
7
|
+
export const MAX_TERMINAL_PREVIEW_BYTES = 256_000;
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
9
|
+
const MAX_COMMAND_OUTPUT_BYTES = 64 * 1024;
|
|
10
|
+
export class TmuxInteractionService {
|
|
11
|
+
runner;
|
|
12
|
+
timeoutMs;
|
|
13
|
+
#queues = new Map();
|
|
14
|
+
constructor(runner = new SpawnTmuxCommandRunner(), timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
15
|
+
this.runner = runner;
|
|
16
|
+
this.timeoutMs = timeoutMs;
|
|
17
|
+
}
|
|
18
|
+
#queries = new Map();
|
|
19
|
+
async connect(attempt) {
|
|
20
|
+
const binding = await this.resolveTarget(attempt);
|
|
21
|
+
if (!binding)
|
|
22
|
+
throw disconnected();
|
|
23
|
+
return binding;
|
|
24
|
+
}
|
|
25
|
+
resolveTarget(attempt) {
|
|
26
|
+
if (!attempt.valid || !attempt.socketPath.startsWith("/") || !/^%\d+$/.test(attempt.paneId))
|
|
27
|
+
return Promise.resolve(null);
|
|
28
|
+
const { socketPath, paneId } = attempt;
|
|
29
|
+
let query = this.#queries.get(socketPath);
|
|
30
|
+
if (!query) {
|
|
31
|
+
query = this.#query(socketPath);
|
|
32
|
+
this.#queries.set(socketPath, query);
|
|
33
|
+
// Share only in-flight observations, never completed results.
|
|
34
|
+
void query.then(() => this.#queries.delete(socketPath), () => this.#queries.delete(socketPath));
|
|
35
|
+
}
|
|
36
|
+
return query.then((panes) => panes.get(paneId) ?? null);
|
|
37
|
+
}
|
|
38
|
+
async #query(socketPath) {
|
|
39
|
+
try {
|
|
40
|
+
await validateSocket(socketPath);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof InteractionTransportError && error.code === "disconnected")
|
|
44
|
+
return new Map();
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
let result;
|
|
48
|
+
try {
|
|
49
|
+
result = await this.runner.run(socketPath, ["list-panes", "-a", "-F", "#{pane_id}|#{pane_dead}|#{session_name}"], undefined, this.timeoutMs);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof InteractionTransportError &&
|
|
53
|
+
error.code === "command_failed" &&
|
|
54
|
+
!(await socketListening(socketPath, this.timeoutMs)))
|
|
55
|
+
return new Map();
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
const rows = result.stdout
|
|
59
|
+
.replace(/\n$/, "")
|
|
60
|
+
.split("\n")
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.map((line) => line.split("|"));
|
|
63
|
+
if (rows.some((row) => row.length < 3 ||
|
|
64
|
+
!/^%\d+$/.test(row[0]) ||
|
|
65
|
+
!/^[01]$/.test(row[1]) ||
|
|
66
|
+
!row.slice(2).join("|"))) {
|
|
67
|
+
throw new InteractionTransportError("command_failed", "Invalid tmux pane observation");
|
|
68
|
+
}
|
|
69
|
+
const panes = new Map();
|
|
70
|
+
for (const row of rows) {
|
|
71
|
+
const paneId = row[0];
|
|
72
|
+
// Linked windows can report a pane more than once; preserve the first session name.
|
|
73
|
+
if (!panes.has(paneId))
|
|
74
|
+
panes.set(paneId, row[1] === "1" ? null : { socketPath, paneId, sessionName: row.slice(2).join("|") });
|
|
75
|
+
}
|
|
76
|
+
return panes;
|
|
77
|
+
}
|
|
78
|
+
killPane(binding, assertAvailable = () => { }) {
|
|
79
|
+
return this.#serialize(binding, async () => {
|
|
80
|
+
await this.#revalidate(binding);
|
|
81
|
+
assertAvailable();
|
|
82
|
+
await this.#run(binding, ["kill-pane", "-t", binding.paneId]);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
sendMessage(binding, message, assertAvailable = () => { }) {
|
|
86
|
+
const normalized = normalizeMessage(message);
|
|
87
|
+
return this.#serialize(binding, async () => {
|
|
88
|
+
await this.#revalidate(binding);
|
|
89
|
+
assertAvailable();
|
|
90
|
+
const bufferName = `codex-hub-${process.pid}-${randomUUID()}`;
|
|
91
|
+
let bufferMayExist = true;
|
|
92
|
+
try {
|
|
93
|
+
await this.#run(binding, ["load-buffer", "-b", bufferName, "-"], normalized);
|
|
94
|
+
assertAvailable();
|
|
95
|
+
await this.#run(binding, [
|
|
96
|
+
"paste-buffer",
|
|
97
|
+
"-p",
|
|
98
|
+
"-d",
|
|
99
|
+
"-b",
|
|
100
|
+
bufferName,
|
|
101
|
+
"-t",
|
|
102
|
+
binding.paneId,
|
|
103
|
+
]);
|
|
104
|
+
bufferMayExist = false;
|
|
105
|
+
assertAvailable();
|
|
106
|
+
await this.#run(binding, ["send-keys", "-t", binding.paneId, "Enter"]);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
if (bufferMayExist) {
|
|
110
|
+
try {
|
|
111
|
+
await this.#run(binding, ["delete-buffer", "-b", bufferName]);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Preserve the original failure; the buffer may not exist or may already be deleted.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
sendKeys(binding, keys, assertAvailable = () => { }) {
|
|
121
|
+
return this.#serialize(binding, async () => {
|
|
122
|
+
await this.#revalidate(binding);
|
|
123
|
+
assertAvailable();
|
|
124
|
+
await this.#run(binding, ["send-keys", "-t", binding.paneId, ...keys]);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
captureTerminal(binding, assertAvailable = () => { }) {
|
|
128
|
+
return this.#serialize(binding, async () => {
|
|
129
|
+
await this.#revalidate(binding);
|
|
130
|
+
assertAvailable();
|
|
131
|
+
const result = await this.runner.run(binding.socketPath, ["capture-pane", "-p", "-t", binding.paneId], undefined, this.timeoutMs, { maxOutputBytes: MAX_TERMINAL_PREVIEW_BYTES, truncateStart: true });
|
|
132
|
+
return { content: result.stdout, truncated: result.truncated === true };
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** Query errors never establish that the selected pane exited. */
|
|
136
|
+
async inspect(binding) {
|
|
137
|
+
return (await this.resolveTarget({ valid: true, ordinal: 0, ...binding })) ? "alive" : "exited";
|
|
138
|
+
}
|
|
139
|
+
async #revalidate(binding) {
|
|
140
|
+
if (!(await this.resolveTarget({ valid: true, ordinal: 0, ...binding })))
|
|
141
|
+
throw disconnected();
|
|
142
|
+
}
|
|
143
|
+
async #run(binding, args, input) {
|
|
144
|
+
await this.runner.run(binding.socketPath, args, input, this.timeoutMs);
|
|
145
|
+
}
|
|
146
|
+
#serialize(binding, operation) {
|
|
147
|
+
const key = `${binding.socketPath}\0${binding.paneId}`;
|
|
148
|
+
const previous = this.#queues.get(key) ?? Promise.resolve();
|
|
149
|
+
const current = previous.catch(() => undefined).then(operation);
|
|
150
|
+
const settled = current.then(() => undefined, () => undefined);
|
|
151
|
+
this.#queues.set(key, settled);
|
|
152
|
+
void settled.finally(() => {
|
|
153
|
+
if (this.#queues.get(key) === settled)
|
|
154
|
+
this.#queues.delete(key);
|
|
155
|
+
});
|
|
156
|
+
return current;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function validateSocket(socketPath) {
|
|
160
|
+
try {
|
|
161
|
+
const info = await lstat(socketPath);
|
|
162
|
+
const currentUid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
163
|
+
if (!info.isSocket() || currentUid === null || info.uid !== currentUid)
|
|
164
|
+
throw new InteractionTransportError("command_failed", "Unsafe tmux socket");
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (error instanceof InteractionTransportError)
|
|
168
|
+
throw error;
|
|
169
|
+
if (error.code === "ENOENT")
|
|
170
|
+
throw disconnected();
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function disconnected() {
|
|
175
|
+
return new InteractionTransportError("disconnected", "tmux target is not connected");
|
|
176
|
+
}
|
|
177
|
+
export class SpawnTmuxCommandRunner {
|
|
178
|
+
run(socketPath, args, input, timeoutMs, options) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
const child = spawn("tmux", ["-S", socketPath, ...args], {
|
|
181
|
+
shell: false,
|
|
182
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
183
|
+
});
|
|
184
|
+
let stdout = Buffer.alloc(0);
|
|
185
|
+
const stderr = [];
|
|
186
|
+
let outputBytes = 0;
|
|
187
|
+
let truncated = false;
|
|
188
|
+
let settled = false;
|
|
189
|
+
const finish = (callback) => {
|
|
190
|
+
if (settled)
|
|
191
|
+
return;
|
|
192
|
+
settled = true;
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
callback();
|
|
195
|
+
};
|
|
196
|
+
const timer = setTimeout(() => {
|
|
197
|
+
child.kill("SIGKILL");
|
|
198
|
+
finish(() => reject(new InteractionTransportError("timeout", "tmux operation timed out; the result is unknown")));
|
|
199
|
+
}, timeoutMs);
|
|
200
|
+
const collectStdout = (chunk) => {
|
|
201
|
+
if (options?.truncateStart) {
|
|
202
|
+
stdout = Buffer.concat([stdout, chunk]);
|
|
203
|
+
if (stdout.byteLength > options.maxOutputBytes) {
|
|
204
|
+
stdout = trimLeadingPartialUtf8(stdout.subarray(stdout.byteLength - options.maxOutputBytes));
|
|
205
|
+
truncated = true;
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
outputBytes += chunk.byteLength;
|
|
210
|
+
if (outputBytes <= MAX_COMMAND_OUTPUT_BYTES)
|
|
211
|
+
stdout = Buffer.concat([stdout, chunk]);
|
|
212
|
+
else
|
|
213
|
+
child.kill("SIGKILL");
|
|
214
|
+
};
|
|
215
|
+
const collectStderr = (chunk) => {
|
|
216
|
+
outputBytes += chunk.byteLength;
|
|
217
|
+
if (outputBytes <= MAX_COMMAND_OUTPUT_BYTES)
|
|
218
|
+
stderr.push(chunk);
|
|
219
|
+
else
|
|
220
|
+
child.kill("SIGKILL");
|
|
221
|
+
};
|
|
222
|
+
child.stdout.on("data", collectStdout);
|
|
223
|
+
child.stderr.on("data", collectStderr);
|
|
224
|
+
child.once("error", (error) => finish(() => reject(new InteractionTransportError("command_failed", error.message))));
|
|
225
|
+
child.stdin.once("error", (error) => finish(() => reject(new InteractionTransportError("command_failed", error.message))));
|
|
226
|
+
child.once("close", (code) => finish(() => {
|
|
227
|
+
if (outputBytes > MAX_COMMAND_OUTPUT_BYTES || code !== 0) {
|
|
228
|
+
reject(new InteractionTransportError("command_failed", Buffer.concat(stderr).toString("utf8").trim() || "tmux command failed"));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
resolve({ stdout: stdout.toString("utf8"), truncated });
|
|
232
|
+
}));
|
|
233
|
+
if (input === undefined)
|
|
234
|
+
child.stdin.end();
|
|
235
|
+
else
|
|
236
|
+
child.stdin.end(input, "utf8");
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function trimLeadingPartialUtf8(buffer) {
|
|
241
|
+
let start = 0;
|
|
242
|
+
while (start < buffer.byteLength && (buffer[start] & 0xc0) === 0x80)
|
|
243
|
+
start += 1;
|
|
244
|
+
return buffer.subarray(start);
|
|
245
|
+
}
|
|
246
|
+
// A refused connection establishes a stopped server even when tmux left its socket file.
|
|
247
|
+
// Timeouts, permissions and other socket failures remain unknown.
|
|
248
|
+
function socketListening(path, timeoutMs) {
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
const socket = createConnection({ path });
|
|
251
|
+
socket.setTimeout(timeoutMs);
|
|
252
|
+
socket.once("connect", () => {
|
|
253
|
+
socket.destroy();
|
|
254
|
+
resolve(true);
|
|
255
|
+
});
|
|
256
|
+
socket.once("timeout", () => {
|
|
257
|
+
socket.destroy();
|
|
258
|
+
reject(new InteractionTransportError("timeout", "Socket validation timed out"));
|
|
259
|
+
});
|
|
260
|
+
socket.once("error", (error) => {
|
|
261
|
+
socket.destroy();
|
|
262
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOENT")
|
|
263
|
+
resolve(false);
|
|
264
|
+
else
|
|
265
|
+
reject(error);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=tmux-service.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { commandLineHelp, loadConfig } from "./config.js";
|
|
3
|
+
import { createCodexHub } from "./create-hub.js";
|
|
4
|
+
import { createApiRouter } from "./http/api-router.js";
|
|
5
|
+
import { startServer } from "./http/create-server.js";
|
|
6
|
+
import { createTerminalPreviewWebSocket } from "./http/terminal-preview-websocket.js";
|
|
7
|
+
import { uploadDirectory } from "./http/upload-routes.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
const command = loadConfig();
|
|
10
|
+
if (command.kind === "help") {
|
|
11
|
+
console.log(commandLineHelp());
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const { config } = command;
|
|
15
|
+
await uploadDirectory(config.uploadDirectory);
|
|
16
|
+
const hub = await createCodexHub(config.homes, config.tmuxSocket, config.interactionEnabled);
|
|
17
|
+
const server = await startServer(config, createApiRouter({ ...hub, uploadDirectory: config.uploadDirectory }), hub.close, createTerminalPreviewWebSocket(hub.interaction));
|
|
18
|
+
const address = server.address();
|
|
19
|
+
const port = typeof address === "object" && address ? address.port : config.port;
|
|
20
|
+
const host = config.host.includes(":") ? `[${config.host}]` : config.host;
|
|
21
|
+
const protocol = config.tls.enabled ? "https" : "http";
|
|
22
|
+
const mutualTls = config.tls.enabled && config.tls.certificateAuthorityPath
|
|
23
|
+
? " (client certificates required)"
|
|
24
|
+
: "";
|
|
25
|
+
console.log(`Codex Hub listening on ${protocol}://${host}:${port}${mutualTls}`);
|
|
26
|
+
function close() {
|
|
27
|
+
server.close((error) => {
|
|
28
|
+
if (error) {
|
|
29
|
+
console.error("Failed to stop Codex Hub");
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
process.once("SIGINT", close);
|
|
35
|
+
process.once("SIGTERM", close);
|
|
36
|
+
}
|
|
37
|
+
main().catch((error) => {
|
|
38
|
+
console.error(error instanceof Error ? error.message : "Failed to start Codex Hub");
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=main.js.map
|