codex-agent-view 0.4.2 → 0.4.4
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/.codex-plugin/plugin.json +3 -6
- package/README.ko.md +35 -27
- package/README.md +34 -26
- package/bin/codex-agent-view.mjs +63 -1
- package/package.json +1 -1
- package/public/app.js +630 -139
- package/public/index.html +62 -52
- package/public/styles.css +51 -16
- package/scripts/auto-start-monitor.mjs +10 -2
- package/skills/codex-agent-view/SKILL.md +36 -35
- package/skills/show-agents/SKILL.md +56 -18
- package/src/runtime/config.mjs +131 -0
- package/src/runtime/server.mjs +14 -5
package/src/runtime/config.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
chmod,
|
|
3
|
+
link,
|
|
3
4
|
lstat,
|
|
4
5
|
mkdir,
|
|
5
6
|
readFile,
|
|
@@ -15,6 +16,9 @@ export const LOOPBACK_HOST = "127.0.0.1";
|
|
|
15
16
|
export const DEFAULT_PORT = 43127;
|
|
16
17
|
export const MAX_EVENT_BODY_BYTES = 64 * 1024;
|
|
17
18
|
export const RUNTIME_SCHEMA_VERSION = 1;
|
|
19
|
+
export const VIEWER_CREDENTIAL_SCHEMA_VERSION = 1;
|
|
20
|
+
|
|
21
|
+
const STRONG_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
18
22
|
|
|
19
23
|
export function autoStartPort(env = process.env) {
|
|
20
24
|
const configured = env.CODEX_AGENT_VIEW_AUTO_START_PORT;
|
|
@@ -41,10 +45,20 @@ export function runtimeFile(env = process.env) {
|
|
|
41
45
|
return join(runtimeDirectory(env), "runtime.json");
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
export function viewerCredentialFile(env = process.env) {
|
|
49
|
+
return join(runtimeDirectory(env), "viewer-auth.json");
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
export function createRuntimeToken() {
|
|
45
53
|
return randomBytes(32).toString("base64url");
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
function assertStrongToken(token, message) {
|
|
57
|
+
if (typeof token !== "string" || !STRONG_TOKEN_PATTERN.test(token)) {
|
|
58
|
+
throw new Error(message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
export async function ensurePrivateDirectory(directory) {
|
|
49
63
|
await rejectSymlink(directory);
|
|
50
64
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
@@ -65,6 +79,117 @@ async function rejectSymlink(path) {
|
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
81
|
|
|
82
|
+
async function requireRegularFile(path, description) {
|
|
83
|
+
const stats = await lstat(path);
|
|
84
|
+
if (stats.isSymbolicLink()) {
|
|
85
|
+
throw new Error(`refusing symbolic link ${description}: ${path}`);
|
|
86
|
+
}
|
|
87
|
+
if (!stats.isFile()) {
|
|
88
|
+
throw new Error(`refusing non-regular ${description}: ${path}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function readViewerToken(env = process.env) {
|
|
93
|
+
const path = viewerCredentialFile(env);
|
|
94
|
+
await rejectSymlink(dirname(path));
|
|
95
|
+
await requireRegularFile(path, "viewer credential path");
|
|
96
|
+
const raw = await readFile(path, "utf8");
|
|
97
|
+
let value;
|
|
98
|
+
try {
|
|
99
|
+
value = JSON.parse(raw);
|
|
100
|
+
} catch {
|
|
101
|
+
throw new Error("invalid Codex Agent View viewer credential file");
|
|
102
|
+
}
|
|
103
|
+
if (
|
|
104
|
+
value === null ||
|
|
105
|
+
typeof value !== "object" ||
|
|
106
|
+
Array.isArray(value) ||
|
|
107
|
+
value.schema_version !== VIEWER_CREDENTIAL_SCHEMA_VERSION
|
|
108
|
+
) {
|
|
109
|
+
throw new Error("invalid Codex Agent View viewer credential file");
|
|
110
|
+
}
|
|
111
|
+
assertStrongToken(
|
|
112
|
+
value.token,
|
|
113
|
+
"invalid Codex Agent View viewer credential file",
|
|
114
|
+
);
|
|
115
|
+
return value.token;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function ensureViewerToken(
|
|
119
|
+
env = process.env,
|
|
120
|
+
{ seedToken = createRuntimeToken() } = {},
|
|
121
|
+
) {
|
|
122
|
+
assertStrongToken(seedToken, "invalid Codex Agent View viewer token");
|
|
123
|
+
|
|
124
|
+
const path = viewerCredentialFile(env);
|
|
125
|
+
const directory = dirname(path);
|
|
126
|
+
await ensurePrivateDirectory(directory);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const token = await readViewerToken(env);
|
|
130
|
+
await chmod(path, 0o600);
|
|
131
|
+
return token;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error?.code !== "ENOENT") {
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const temporaryPath = join(
|
|
139
|
+
directory,
|
|
140
|
+
`.viewer-auth-${process.pid}-${randomBytes(8).toString("hex")}.tmp`,
|
|
141
|
+
);
|
|
142
|
+
const serialized = `${JSON.stringify({
|
|
143
|
+
schema_version: VIEWER_CREDENTIAL_SCHEMA_VERSION,
|
|
144
|
+
token: seedToken,
|
|
145
|
+
}, null, 2)}\n`;
|
|
146
|
+
|
|
147
|
+
await writeFile(temporaryPath, serialized, {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
mode: 0o600,
|
|
150
|
+
flag: "wx",
|
|
151
|
+
});
|
|
152
|
+
try {
|
|
153
|
+
await chmod(temporaryPath, 0o600);
|
|
154
|
+
try {
|
|
155
|
+
await link(temporaryPath, path);
|
|
156
|
+
await chmod(path, 0o600);
|
|
157
|
+
return seedToken;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error?.code !== "EEXIST") {
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
const token = await readViewerToken(env);
|
|
163
|
+
await chmod(path, 0o600);
|
|
164
|
+
return token;
|
|
165
|
+
}
|
|
166
|
+
} finally {
|
|
167
|
+
await unlink(temporaryPath).catch((error) => {
|
|
168
|
+
if (error?.code !== "ENOENT") {
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function removeViewerToken(expectedToken, env = process.env) {
|
|
176
|
+
assertStrongToken(expectedToken, "invalid Codex Agent View viewer token");
|
|
177
|
+
const path = viewerCredentialFile(env);
|
|
178
|
+
try {
|
|
179
|
+
const currentToken = await readViewerToken(env);
|
|
180
|
+
if (currentToken !== expectedToken) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
await unlink(path);
|
|
184
|
+
return true;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error?.code === "ENOENT") {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
68
193
|
export async function writeRuntimeInfo(info, env = process.env) {
|
|
69
194
|
const path = runtimeFile(env);
|
|
70
195
|
const directory = dirname(path);
|
|
@@ -105,6 +230,12 @@ export async function readRuntimeInfo(env = process.env) {
|
|
|
105
230
|
) {
|
|
106
231
|
throw new Error("invalid Codex Agent View runtime file");
|
|
107
232
|
}
|
|
233
|
+
if (value.viewer_token !== undefined) {
|
|
234
|
+
assertStrongToken(
|
|
235
|
+
value.viewer_token,
|
|
236
|
+
"invalid Codex Agent View runtime file",
|
|
237
|
+
);
|
|
238
|
+
}
|
|
108
239
|
return value;
|
|
109
240
|
}
|
|
110
241
|
|
package/src/runtime/server.mjs
CHANGED
|
@@ -90,6 +90,7 @@ export async function startMonitorServer({
|
|
|
90
90
|
env = process.env,
|
|
91
91
|
store = createMonitorStore(),
|
|
92
92
|
token = createRuntimeToken(),
|
|
93
|
+
viewerToken = createRuntimeToken(),
|
|
93
94
|
now = Date.now,
|
|
94
95
|
} = {}) {
|
|
95
96
|
if (host !== LOOPBACK_HOST) {
|
|
@@ -109,13 +110,20 @@ export async function startMonitorServer({
|
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
if (
|
|
113
|
-
|
|
113
|
+
if (
|
|
114
|
+
request.method === "GET" &&
|
|
115
|
+
requestUrl.pathname === "/api/state"
|
|
116
|
+
) {
|
|
117
|
+
if (!hasToken(request, token) && !hasToken(request, viewerToken)) {
|
|
118
|
+
sendJson(response, 401, { error: "authorization required" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
sendJson(response, 200, store.getSnapshot());
|
|
114
122
|
return;
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
if (
|
|
118
|
-
sendJson(response,
|
|
125
|
+
if (requestUrl.pathname.startsWith("/api/") && !hasToken(request, token)) {
|
|
126
|
+
sendJson(response, 401, { error: "authorization required" });
|
|
119
127
|
return;
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -182,6 +190,7 @@ export async function startMonitorServer({
|
|
|
182
190
|
host,
|
|
183
191
|
port: address.port,
|
|
184
192
|
token,
|
|
193
|
+
viewer_token: viewerToken,
|
|
185
194
|
pid: process.pid,
|
|
186
195
|
started_at_ms: now(),
|
|
187
196
|
};
|
|
@@ -214,6 +223,6 @@ export async function startMonitorServer({
|
|
|
214
223
|
runtimeInfo,
|
|
215
224
|
server,
|
|
216
225
|
store,
|
|
217
|
-
url: `http://${host}:${address.port}/#token=${encodeURIComponent(
|
|
226
|
+
url: `http://${host}:${address.port}/#token=${encodeURIComponent(viewerToken)}`,
|
|
218
227
|
};
|
|
219
228
|
}
|