@ravefox/space 0.0.0-stage → 0.2.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 +203 -0
- package/NOTICE +44 -0
- package/README.md +49 -2
- package/assets/webhook-sdk/python/bot.py +63 -0
- package/assets/webhook-sdk/python/commonly.py +232 -0
- package/node_modules/@commonlyai/seats/README.md +25 -0
- package/node_modules/@commonlyai/seats/package.json +22 -0
- package/node_modules/@commonlyai/seats/src/adapters/hermes.js +264 -0
- package/node_modules/@commonlyai/seats/src/adapters/stub.js +24 -0
- package/node_modules/@commonlyai/seats/src/contract.js +48 -0
- package/node_modules/@commonlyai/seats/src/failures.js +44 -0
- package/node_modules/@commonlyai/seats/src/harness-plan.js +329 -0
- package/node_modules/@commonlyai/seats/src/index.js +22 -0
- package/node_modules/@commonlyai/seats/src/process-group.js +269 -0
- package/node_modules/@commonlyai/seats/src/record/format.js +737 -0
- package/node_modules/@commonlyai/seats/src/record/otel.js +238 -0
- package/node_modules/@commonlyai/seats/src/runtime-observation.js +29 -0
- package/node_modules/@commonlyai/seats/src/seat-model.js +17 -0
- package/node_modules/@commonlyai/seats/src/supervisor.js +308 -0
- package/node_modules/@commonlyai/seats/src/trace/events.js +46 -0
- package/node_modules/@commonlyai/seats/src/trace/store.js +276 -0
- package/node_modules/@commonlyai/seats/src/turn-processes.js +226 -0
- package/node_modules/@commonlyai/seats/src/upstream-refusal.js +418 -0
- package/node_modules/@commonlyai/seats/src/valet/env.js +201 -0
- package/node_modules/@commonlyai/seats/src/valet/grant.js +123 -0
- package/node_modules/@commonlyai/seats/src/valet/permissions.js +213 -0
- package/node_modules/@commonlyai/seats/src/wire/provider-proxy.js +529 -0
- package/package.json +56 -4
- package/skills/commonly/SKILL.md +233 -0
- package/src/commands/agent.js +6334 -0
- package/src/commands/daemon.js +933 -0
- package/src/commands/dev.js +153 -0
- package/src/commands/doctor.js +141 -0
- package/src/commands/inbox.js +330 -0
- package/src/commands/login.js +194 -0
- package/src/commands/mcp.js +56 -0
- package/src/commands/pod.js +120 -0
- package/src/commands/task.js +279 -0
- package/src/commands/verify.js +48 -0
- package/src/index.js +99 -0
- package/src/lib/adapter-login.js +76 -0
- package/src/lib/adapters/broker-bridge-entry.js +76 -0
- package/src/lib/adapters/claude.js +2207 -0
- package/src/lib/adapters/codex-broker-bridge.mjs +298 -0
- package/src/lib/adapters/codex.js +1898 -0
- package/src/lib/adapters/dsh.js +513 -0
- package/src/lib/adapters/goose.js +510 -0
- package/src/lib/adapters/hermes.js +18 -0
- package/src/lib/adapters/index.js +68 -0
- package/src/lib/adapters/opencode.js +1681 -0
- package/src/lib/adapters/pi-commonly-mcp.mjs +71 -0
- package/src/lib/adapters/pi-mcp-client.mjs +617 -0
- package/src/lib/adapters/pi-shell-env.mjs +41 -0
- package/src/lib/adapters/pi.js +1025 -0
- package/src/lib/adapters/qwen.js +523 -0
- package/src/lib/adapters/seat-model.js +8 -0
- package/src/lib/adapters/stub.js +1 -0
- package/src/lib/adapters/tested-version.js +106 -0
- package/src/lib/api.js +205 -0
- package/src/lib/claim-outcome.js +128 -0
- package/src/lib/claimed-work-gate.js +73 -0
- package/src/lib/cli-hint.js +33 -0
- package/src/lib/commit-attribution.js +391 -0
- package/src/lib/config.js +155 -0
- package/src/lib/content-refusal.js +94 -0
- package/src/lib/credential-file.js +208 -0
- package/src/lib/daemon-agent-states.js +95 -0
- package/src/lib/daemon-hint.js +18 -0
- package/src/lib/daemon-logs.js +43 -0
- package/src/lib/daemon-service-windows.js +263 -0
- package/src/lib/daemon-service.js +486 -0
- package/src/lib/daemon-state.js +84 -0
- package/src/lib/daemon-store.js +119 -0
- package/src/lib/daemon-supervisor.js +629 -0
- package/src/lib/daemon-watch.js +115 -0
- package/src/lib/declared-mcp-guard.js +284 -0
- package/src/lib/default-environment.js +207 -0
- package/src/lib/device-login.js +126 -0
- package/src/lib/digest-frame.js +151 -0
- package/src/lib/doctor.js +291 -0
- package/src/lib/egress-proxy.js +160 -0
- package/src/lib/enforcement.js +946 -0
- package/src/lib/environment.js +681 -0
- package/src/lib/first-output-watchdog.js +191 -0
- package/src/lib/git-readonly-server.js +138 -0
- package/src/lib/git-readonly.js +463 -0
- package/src/lib/git-remote-commonly.mjs +100 -0
- package/src/lib/grant-broker-guard.js +345 -0
- package/src/lib/hooks-config.js +238 -0
- package/src/lib/host-gh-broker.js +152 -0
- package/src/lib/host-gh-shim.mjs +98 -0
- package/src/lib/inbox.js +483 -0
- package/src/lib/instance-proxy.js +76 -0
- package/src/lib/machine-host.js +29 -0
- package/src/lib/mcp-credential-delivery.js +121 -0
- package/src/lib/mcp-first-run.js +50 -0
- package/src/lib/mcp-home.js +644 -0
- package/src/lib/mcp-ready.js +68 -0
- package/src/lib/mcp-server-version.js +123 -0
- package/src/lib/mcp-token.js +36 -0
- package/src/lib/mcp-warm-child.mjs +412 -0
- package/src/lib/memory-bridge.js +191 -0
- package/src/lib/memory-import.js +156 -0
- package/src/lib/missing-harness.js +31 -0
- package/src/lib/model-unavailable.js +154 -0
- package/src/lib/native-runtime-inventory.js +40 -0
- package/src/lib/outsider-grant-mcp.js +82 -0
- package/src/lib/own-key-launch.js +42 -0
- package/src/lib/pod-catch-up.js +55 -0
- package/src/lib/pod-focus.js +224 -0
- package/src/lib/pod-tail.js +68 -0
- package/src/lib/poll-liveness.js +103 -0
- package/src/lib/poll-retry.js +81 -0
- package/src/lib/poller.js +200 -0
- package/src/lib/provider-proxy.js +42 -0
- package/src/lib/public-seat-mcp-flag.js +36 -0
- package/src/lib/public-tool-error.js +60 -0
- package/src/lib/public-withheld-tools.js +48 -0
- package/src/lib/reply-receipt.js +55 -0
- package/src/lib/resolve-on-path.js +22 -0
- package/src/lib/runtime-inventory.js +56 -0
- package/src/lib/runtime-observation.js +37 -0
- package/src/lib/sandbox/bwrap.js +275 -0
- package/src/lib/sandbox/codex-perimeter.js +16 -0
- package/src/lib/sandbox/host-check.js +268 -0
- package/src/lib/sandbox/mode.js +49 -0
- package/src/lib/sandbox/opencode-loopback.mjs +68 -0
- package/src/lib/sandbox/seatbelt.js +684 -0
- package/src/lib/sandbox/turn-dir.js +56 -0
- package/src/lib/sandbox/windows-unsandboxed.js +183 -0
- package/src/lib/sandbox/workspace-git-config.js +243 -0
- package/src/lib/sandbox/workspace-pin.js +158 -0
- package/src/lib/seat-runtime-controls.js +61 -0
- package/src/lib/seat-stop.js +97 -0
- package/src/lib/server-skills.js +32 -0
- package/src/lib/session-recycle.js +219 -0
- package/src/lib/session-store.js +215 -0
- package/src/lib/skill-prompt.js +74 -0
- package/src/lib/skills-import.js +113 -0
- package/src/lib/spawn-credential.js +402 -0
- package/src/lib/spawn-retry.js +311 -0
- package/src/lib/stable-install.js +176 -0
- package/src/lib/supervised-turn.js +352 -0
- package/src/lib/task.js +235 -0
- package/src/lib/turn-failure-notice.js +177 -0
- package/src/lib/unconfined-seat-guard.js +94 -0
- package/src/lib/update-check.js +192 -0
- package/src/lib/upstream-refusal.js +1 -0
- package/src/lib/webhook-server.js +109 -0
- package/src/lib/windows-command.js +393 -0
- package/src/lib/workspace-git.js +87 -0
- package/src/lib/workspace-trust.js +162 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The grant broker, as a stdio MCP server codex can run (F175b).
|
|
4
|
+
*
|
|
5
|
+
* codex is admitted for the grant broker (ADAPTERS_WITH_GRANT_BROKER), but the
|
|
6
|
+
* broker is a Streamable HTTP server (`grantBrokerServer`) and codex.js wires
|
|
7
|
+
* stdio servers only, so a granted codex seat used to receive the entry and
|
|
8
|
+
* drop it. codex's own url transport is no way round that: it reads a bearer
|
|
9
|
+
* token from an environment variable (`bearer_token_env_var`), which would put
|
|
10
|
+
* the token back in codex's environment and in every MCP child it spawns, the
|
|
11
|
+
* thing TASK-083 took out.
|
|
12
|
+
*
|
|
13
|
+
* So codex runs this file, and this file speaks HTTP to the broker. It holds no
|
|
14
|
+
* token of its own. It is told three things, none of them a secret:
|
|
15
|
+
* - COMMONLY_BROKER_URL: the broker url, already resolved to this instance;
|
|
16
|
+
* - COMMONLY_BROKER_HEADERS: the declared headers as TEMPLATES, still holding
|
|
17
|
+
* `${COMMONLY_AGENT_TOKEN}`;
|
|
18
|
+
* - COMMONLY_TOKEN_FILE: the path of this spawn's 0600 credential file, the
|
|
19
|
+
* same file the commonly MCP server reads.
|
|
20
|
+
* The token is read from that file once, at start, and kept in this process's
|
|
21
|
+
* memory and the request header only. On a public seat the file is deleted as
|
|
22
|
+
* soon as its readers have it (F460): this bridge then writes
|
|
23
|
+
* COMMONLY_MCP_READY_FILE, as the commonly MCP server does, on the host's first
|
|
24
|
+
* tools/list.
|
|
25
|
+
*
|
|
26
|
+
* It is a pass-through: each JSON-RPC line from codex is POSTed as it is, and
|
|
27
|
+
* every message in the answer (plain JSON or an event stream) is written back
|
|
28
|
+
* as a line. The broker is stateless, but a session id and the negotiated
|
|
29
|
+
* protocol version are carried the way the spec asks, so a stateful server
|
|
30
|
+
* would work too.
|
|
31
|
+
*
|
|
32
|
+
* It imports node builtins only. On a public seat codex runs it under the
|
|
33
|
+
* seat's permission profile, which is granted read on THIS file alone; an
|
|
34
|
+
* import from elsewhere in the CLI would need a wider grant. So the two small
|
|
35
|
+
* pieces it shares with the rest of the CLI are restated here, and a test pins
|
|
36
|
+
* the import list.
|
|
37
|
+
*/
|
|
38
|
+
import { readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
39
|
+
import { isAbsolute } from 'node:path';
|
|
40
|
+
import { createInterface } from 'node:readline';
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
export const BRIDGE_PATH = fileURLToPath(import.meta.url);
|
|
45
|
+
export const BRIDGE_URL_VAR = 'COMMONLY_BROKER_URL';
|
|
46
|
+
export const BRIDGE_HEADERS_VAR = 'COMMONLY_BROKER_HEADERS';
|
|
47
|
+
export const TOKEN_PLACEHOLDER = '${COMMONLY_AGENT_TOKEN}';
|
|
48
|
+
|
|
49
|
+
// The same name as credential-file.js CREDENTIAL_FILE_VAR (a test pins it).
|
|
50
|
+
export const CREDENTIAL_FILE_VAR = 'COMMONLY_TOKEN_FILE';
|
|
51
|
+
// The same name as commonly-mcp's READY_FILE_ENV (a test pins it).
|
|
52
|
+
export const READY_FILE_VAR = 'COMMONLY_MCP_READY_FILE';
|
|
53
|
+
|
|
54
|
+
const MAX_ERROR_TEXT = 2048;
|
|
55
|
+
|
|
56
|
+
// Streamable HTTP answers a POST with one JSON object or with an event stream
|
|
57
|
+
// of `data:` payloads. Same reading as pi-mcp-client.mjs parseMessages: the body
|
|
58
|
+
// decides before the header does.
|
|
59
|
+
const ssePayloads = (text) => text.split(/\r?\n\r?\n/)
|
|
60
|
+
.map((block) => block.split(/\r?\n/)
|
|
61
|
+
.filter((line) => line.startsWith('data:'))
|
|
62
|
+
.map((line) => line.slice(5).trimStart())
|
|
63
|
+
.join('\n'))
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
|
|
66
|
+
const parseMessages = (text, contentType) => {
|
|
67
|
+
const body = text.trim();
|
|
68
|
+
if (!body) return [];
|
|
69
|
+
if (contentType.includes('text/event-stream')) return ssePayloads(body);
|
|
70
|
+
if (body.startsWith('{') || body.startsWith('[')) return [body];
|
|
71
|
+
return ssePayloads(body);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const rpcError = (id, code, message) => ({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
|
|
75
|
+
|
|
76
|
+
export const createBrokerBridge = ({
|
|
77
|
+
url,
|
|
78
|
+
headerTemplates = {},
|
|
79
|
+
tokenFile,
|
|
80
|
+
readyFile = null,
|
|
81
|
+
fetchImpl = globalThis.fetch,
|
|
82
|
+
write,
|
|
83
|
+
writeReady = writeFileSync,
|
|
84
|
+
warn = (text) => process.stderr.write(`[commonly-broker-bridge] ${text}\n`),
|
|
85
|
+
timeoutMs = 120_000,
|
|
86
|
+
}) => {
|
|
87
|
+
let sessionId = null;
|
|
88
|
+
let protocolVersion = null;
|
|
89
|
+
let lastToken = null;
|
|
90
|
+
let readyWritten = !readyFile;
|
|
91
|
+
// Nothing the bridge writes back may hold the token, whatever produced it.
|
|
92
|
+
const scrub = (text) => (lastToken ? String(text).split(lastToken).join('[token]') : String(text));
|
|
93
|
+
|
|
94
|
+
// Once: the file is deleted after its readers have it (F460).
|
|
95
|
+
const readToken = () => {
|
|
96
|
+
if (lastToken) return lastToken;
|
|
97
|
+
if (!tokenFile) throw new Error(`no credential file (${CREDENTIAL_FILE_VAR} is unset)`);
|
|
98
|
+
let token;
|
|
99
|
+
try {
|
|
100
|
+
token = readFileSync(tokenFile, 'utf8').trim();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new Error(`cannot read the credential file: ${error?.code || error?.message || error}`);
|
|
103
|
+
}
|
|
104
|
+
if (!token) throw new Error('the credential file is empty');
|
|
105
|
+
lastToken = token;
|
|
106
|
+
return token;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const needsToken = () => Object.values(headerTemplates).some((v) => String(v).includes(TOKEN_PLACEHOLDER));
|
|
110
|
+
// Read at start, before the host asks for anything, so a file deleted at the
|
|
111
|
+
// ready deadline finds the token already held. A file that cannot be read
|
|
112
|
+
// now is reported on the first request instead.
|
|
113
|
+
const prime = () => {
|
|
114
|
+
if (!needsToken()) return true;
|
|
115
|
+
try {
|
|
116
|
+
readToken();
|
|
117
|
+
return true;
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
// Exclusive, 0600, holds nothing secret; written once the token is held.
|
|
123
|
+
const markReady = () => {
|
|
124
|
+
if (readyWritten) return;
|
|
125
|
+
readyWritten = true;
|
|
126
|
+
if (!isAbsolute(readyFile)) {
|
|
127
|
+
warn(`${READY_FILE_VAR} is not an absolute path; not writing it`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
writeReady(readyFile, 'ready\n', { flag: 'wx', mode: 0o600 });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
warn(`could not write the ready file: ${error?.code || error?.message || error}`);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const headers = () => {
|
|
138
|
+
const templates = Object.entries(headerTemplates);
|
|
139
|
+
const token = needsToken() ? readToken() : null;
|
|
140
|
+
return {
|
|
141
|
+
'content-type': 'application/json',
|
|
142
|
+
accept: 'application/json, text/event-stream',
|
|
143
|
+
...Object.fromEntries(templates.map(([k, v]) => [k, String(v).split(TOKEN_PLACEHOLDER).join(token)])),
|
|
144
|
+
...(sessionId ? { 'mcp-session-id': sessionId } : {}),
|
|
145
|
+
...(protocolVersion ? { 'mcp-protocol-version': protocolVersion } : {}),
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// One exchange: POST the line, then hand each JSON-RPC message in the answer
|
|
150
|
+
// to `onMessage` as it arrives. An event stream is read event by event, so a
|
|
151
|
+
// notification sent before the response reaches codex then, and reading stops
|
|
152
|
+
// once `onMessage` says the request is answered, whether or not the server
|
|
153
|
+
// closes the stream. The timer covers the whole exchange, body included: a
|
|
154
|
+
// stream that never answers still ends in an error, not a wait forever.
|
|
155
|
+
const exchange = async (body, onMessage) => {
|
|
156
|
+
const controller = new AbortController();
|
|
157
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
158
|
+
try {
|
|
159
|
+
const res = await fetchImpl(url, {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
headers: headers(),
|
|
162
|
+
body,
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
// The request carries the seat token, so no redirect is followed: a
|
|
165
|
+
// redirect may point at another origin (same rule as connectHttpMcp).
|
|
166
|
+
redirect: 'error',
|
|
167
|
+
});
|
|
168
|
+
const contentType = res.headers?.get?.('content-type') || '';
|
|
169
|
+
const reader = res.ok && contentType.includes('text/event-stream') ? res.body?.getReader?.() : null;
|
|
170
|
+
if (!reader) return { res, text: await res.text() };
|
|
171
|
+
// Not every fetch errors a body it has already handed over when the
|
|
172
|
+
// signal fires, so the timeout ends the read itself.
|
|
173
|
+
const stopReading = () => reader.cancel().catch(() => {});
|
|
174
|
+
controller.signal.addEventListener('abort', stopReading, { once: true });
|
|
175
|
+
const decoder = new TextDecoder();
|
|
176
|
+
let buffered = '';
|
|
177
|
+
const drain = (final) => {
|
|
178
|
+
const blocks = buffered.split(/\r?\n\r?\n/);
|
|
179
|
+
buffered = final ? '' : blocks.pop();
|
|
180
|
+
return blocks.flatMap((block) => ssePayloads(`${block}\n\n`)).some((raw) => onMessage(raw));
|
|
181
|
+
};
|
|
182
|
+
for (;;) {
|
|
183
|
+
// eslint-disable-next-line no-await-in-loop
|
|
184
|
+
const { value, done } = await reader.read();
|
|
185
|
+
if (done) {
|
|
186
|
+
buffered += decoder.decode();
|
|
187
|
+
drain(true);
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
buffered += decoder.decode(value, { stream: true });
|
|
191
|
+
if (drain(false)) {
|
|
192
|
+
stopReading();
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (controller.signal.aborted) throw Object.assign(new Error('aborted'), { name: 'AbortError' });
|
|
197
|
+
return { res, text: null };
|
|
198
|
+
} finally {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const handleLine = async (line) => {
|
|
204
|
+
if (!line.trim()) return;
|
|
205
|
+
let message;
|
|
206
|
+
try {
|
|
207
|
+
message = JSON.parse(line);
|
|
208
|
+
} catch {
|
|
209
|
+
write(JSON.stringify(rpcError(null, -32700, 'parse error')));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const id = message?.id;
|
|
213
|
+
const isRequest = id !== undefined && id !== null && typeof message?.method === 'string';
|
|
214
|
+
let answered = false;
|
|
215
|
+
const fail = (text) => {
|
|
216
|
+
if (isRequest && !answered) write(JSON.stringify(rpcError(id, -32000, `grant broker: ${scrub(text)}`)));
|
|
217
|
+
else process.stderr.write(`[commonly-broker-bridge] ${scrub(text)}\n`);
|
|
218
|
+
};
|
|
219
|
+
// Writes one message back to codex; true once this request is answered.
|
|
220
|
+
const forward = (raw) => {
|
|
221
|
+
let answer;
|
|
222
|
+
try {
|
|
223
|
+
answer = JSON.parse(raw);
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (message?.method === 'initialize' && answer?.id === id
|
|
228
|
+
&& typeof answer?.result?.protocolVersion === 'string') {
|
|
229
|
+
protocolVersion = answer.result.protocolVersion;
|
|
230
|
+
}
|
|
231
|
+
write(JSON.stringify(answer));
|
|
232
|
+
if (isRequest && answer?.id === id && ('result' in answer || 'error' in answer)) answered = true;
|
|
233
|
+
if (answered && message?.method === 'tools/list' && 'result' in answer && (lastToken || !needsToken())) markReady();
|
|
234
|
+
return answered;
|
|
235
|
+
};
|
|
236
|
+
let res;
|
|
237
|
+
let text;
|
|
238
|
+
try {
|
|
239
|
+
({ res, text } = await exchange(line, forward));
|
|
240
|
+
} catch (error) {
|
|
241
|
+
fail(error?.name === 'AbortError' ? `timed out after ${timeoutMs}ms` : (error?.message || String(error)));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const header = res.headers?.get?.('mcp-session-id');
|
|
245
|
+
if (header && !sessionId) sessionId = header;
|
|
246
|
+
if (!res.ok) {
|
|
247
|
+
fail(`HTTP ${res.status}${text ? `: ${text.slice(0, MAX_ERROR_TEXT)}` : ''}`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (text !== null) {
|
|
251
|
+
for (const raw of parseMessages(text || '', res.headers?.get?.('content-type') || '')) forward(raw);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
return { handleLine, prime };
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const isMain = () => {
|
|
259
|
+
try {
|
|
260
|
+
return realpathSync(process.argv[1] || '') === realpathSync(BRIDGE_PATH);
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
if (isMain()) {
|
|
267
|
+
const url = process.env[BRIDGE_URL_VAR];
|
|
268
|
+
let headerTemplates = {};
|
|
269
|
+
try {
|
|
270
|
+
headerTemplates = JSON.parse(process.env[BRIDGE_HEADERS_VAR] || '{}');
|
|
271
|
+
} catch {
|
|
272
|
+
process.stderr.write(`[commonly-broker-bridge] ${BRIDGE_HEADERS_VAR} is not JSON\n`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
if (!url) {
|
|
276
|
+
process.stderr.write(`[commonly-broker-bridge] ${BRIDGE_URL_VAR} is unset\n`);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
const bridge = createBrokerBridge({
|
|
280
|
+
url,
|
|
281
|
+
headerTemplates,
|
|
282
|
+
tokenFile: process.env[CREDENTIAL_FILE_VAR],
|
|
283
|
+
readyFile: process.env[READY_FILE_VAR] || null,
|
|
284
|
+
// A closed stdout (codex gone) must not become an unhandled rejection.
|
|
285
|
+
write: (line) => { try { process.stdout.write(`${line}\n`); } catch { /* codex has gone */ } },
|
|
286
|
+
});
|
|
287
|
+
bridge.prime();
|
|
288
|
+
const pending = new Set();
|
|
289
|
+
const lines = createInterface({ input: process.stdin });
|
|
290
|
+
lines.on('line', (line) => {
|
|
291
|
+
const task = bridge.handleLine(line).finally(() => pending.delete(task));
|
|
292
|
+
pending.add(task);
|
|
293
|
+
});
|
|
294
|
+
lines.on('close', async () => {
|
|
295
|
+
await Promise.allSettled([...pending]);
|
|
296
|
+
process.exit(0);
|
|
297
|
+
});
|
|
298
|
+
}
|