codex-ringside 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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/NOTICE +29 -0
- package/README.md +134 -2
- package/README.zh-CN.md +166 -0
- package/bin/codex-ringside.cjs +132 -0
- package/native/bridge.swift +212 -0
- package/package.json +53 -4
- package/src/agent.cjs +179 -0
- package/src/app-server.cjs +96 -0
- package/src/background.cjs +261 -0
- package/src/background.js +800 -0
- package/src/card.js +302 -0
- package/src/cdp.cjs +227 -0
- package/src/install.cjs +222 -0
- package/src/launchd.cjs +91 -0
- package/src/limits.cjs +146 -0
- package/src/resolve.cjs +51 -0
- package/src/startup/controller.cjs +157 -0
- package/src/startup/watch.cjs +115 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const net = require('node:net');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const readline = require('node:readline');
|
|
6
|
+
const { execFile, spawn } = require('node:child_process');
|
|
7
|
+
const { promisify } = require('node:util');
|
|
8
|
+
const { CDP_PORT } = require('../cdp.cjs');
|
|
9
|
+
const { StartupController } = require('./controller.cjs');
|
|
10
|
+
|
|
11
|
+
const run = promisify(execFile);
|
|
12
|
+
|
|
13
|
+
function portInUse(port = CDP_PORT) {
|
|
14
|
+
return new Promise(resolve => {
|
|
15
|
+
const socket = net.createConnection({ host: '127.0.0.1', port });
|
|
16
|
+
const done = value => {
|
|
17
|
+
socket.destroy();
|
|
18
|
+
resolve(value);
|
|
19
|
+
};
|
|
20
|
+
socket.once('connect', () => done(true));
|
|
21
|
+
socket.once('error', error => done(error.code !== 'ECONNREFUSED'));
|
|
22
|
+
socket.setTimeout(350, () => done(true));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readState(file) {
|
|
27
|
+
try {
|
|
28
|
+
const state = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
29
|
+
return Number.isFinite(state.lastAttemptAt ?? 0) ? state : {};
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.code === 'ENOENT') return {};
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function startWatcher({ root, settings, log }) {
|
|
37
|
+
const bridgePath = path.join(root, 'bridge');
|
|
38
|
+
const stateFile = path.join(root, 'state.json');
|
|
39
|
+
const abort = new AbortController();
|
|
40
|
+
let state = readState(stateFile);
|
|
41
|
+
|
|
42
|
+
const native = async (action, ...args) => {
|
|
43
|
+
const { stdout } = await run(bridgePath, [action, settings.app, ...args.map(String)], {
|
|
44
|
+
timeout: 35000, maxBuffer: 65536, signal: abort.signal
|
|
45
|
+
});
|
|
46
|
+
return JSON.parse(stdout);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const adapter = {
|
|
50
|
+
snapshot: () => native('snapshot'),
|
|
51
|
+
quit: (app, stamp) => native('quit', app.pid, app.key, stamp),
|
|
52
|
+
launch: () => native('launch', CDP_PORT),
|
|
53
|
+
show: (app, stamp, frontmost) => native('show', app.pid, app.key, stamp, frontmost),
|
|
54
|
+
portInUse,
|
|
55
|
+
async record(event, details = {}) {
|
|
56
|
+
state = { ...state, ...details, event, updatedAt: Date.now() };
|
|
57
|
+
const temp = `${stateFile}.tmp`;
|
|
58
|
+
fs.writeFileSync(temp, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
59
|
+
fs.renameSync(temp, stateFile);
|
|
60
|
+
log(`startup: ${event}`);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const controller = new StartupController(adapter, {
|
|
65
|
+
lastAttemptAt: state.lastAttemptAt || 0,
|
|
66
|
+
allowClientRestart: settings.allowClientRestart
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let stopped = false;
|
|
70
|
+
let queued = false;
|
|
71
|
+
let pumping = false;
|
|
72
|
+
let pendingTimer;
|
|
73
|
+
|
|
74
|
+
async function pump() {
|
|
75
|
+
queued = true;
|
|
76
|
+
if (pumping || stopped) return;
|
|
77
|
+
pumping = true;
|
|
78
|
+
clearTimeout(pendingTimer);
|
|
79
|
+
try {
|
|
80
|
+
while (queued && !stopped) {
|
|
81
|
+
queued = false;
|
|
82
|
+
await controller.tick();
|
|
83
|
+
}
|
|
84
|
+
} finally {
|
|
85
|
+
pumping = false;
|
|
86
|
+
if (controller.pendingLaunch && !stopped) pendingTimer = setTimeout(pump, 300);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// NSWorkspace notifications, so nothing spawns native processes while Codex is idle.
|
|
91
|
+
const observer = spawn(bridgePath, ['watch', settings.app], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
92
|
+
const lines = readline.createInterface({ input: observer.stdout });
|
|
93
|
+
lines.on('line', () => pump());
|
|
94
|
+
|
|
95
|
+
const stop = () => {
|
|
96
|
+
if (stopped) return;
|
|
97
|
+
stopped = true;
|
|
98
|
+
controller.stop();
|
|
99
|
+
clearTimeout(pendingTimer);
|
|
100
|
+
abort.abort();
|
|
101
|
+
observer.kill('SIGTERM');
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// launchd restarts the whole agent, and the fresh baseline keeps that restart harmless.
|
|
105
|
+
const exited = new Promise(resolve => {
|
|
106
|
+
observer.once('error', resolve);
|
|
107
|
+
observer.once('exit', resolve);
|
|
108
|
+
}).then(() => {
|
|
109
|
+
if (!stopped) throw new Error('bridge watcher exited');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return { stop, exited };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { portInUse, readState, startWatcher };
|