codex-blocker 0.1.0 → 0.1.2
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/README.md +7 -4
- package/dist/bin.js +1 -1
- package/dist/{chunk-APDAIY47.js → chunk-KNFNSOAX.js} +110 -7
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,8 +42,10 @@ npx codex-blocker --version
|
|
|
42
42
|
## How It Works
|
|
43
43
|
|
|
44
44
|
1. **Codex sessions** — The server tails Codex session logs under `~/.codex/sessions`
|
|
45
|
-
to detect activity. It marks a session “working” on your prompt and
|
|
46
|
-
|
|
45
|
+
to detect activity. It marks a session “working” on your prompt and on intermediate
|
|
46
|
+
assistant/tool activity, marks `waiting_for_input` when Codex emits
|
|
47
|
+
`request_user_input`, and marks “idle” when it sees a terminal assistant reply
|
|
48
|
+
(`phase: "final_answer"`), with legacy fallback support for older Codex logs.
|
|
47
49
|
|
|
48
50
|
2. **Server** — Runs on localhost and:
|
|
49
51
|
- Tracks active Codex sessions
|
|
@@ -51,7 +53,7 @@ npx codex-blocker --version
|
|
|
51
53
|
- Broadcasts state via WebSocket to the Chrome extension
|
|
52
54
|
|
|
53
55
|
3. **Extension** — Connects to the server and:
|
|
54
|
-
- Blocks configured sites when no sessions are working
|
|
56
|
+
- Blocks configured sites when no sessions are working, or when any session is waiting for user input
|
|
55
57
|
- Shows a modal overlay (soft block, not network block)
|
|
56
58
|
- Updates in real-time without page refresh
|
|
57
59
|
|
|
@@ -72,7 +74,8 @@ Connect to `ws://localhost:8765/ws` to receive real-time state updates:
|
|
|
72
74
|
"type": "state",
|
|
73
75
|
"blocked": true,
|
|
74
76
|
"sessions": 1,
|
|
75
|
-
"working": 0
|
|
77
|
+
"working": 0,
|
|
78
|
+
"waitingForInput": 1
|
|
76
79
|
}
|
|
77
80
|
```
|
|
78
81
|
|
package/dist/bin.js
CHANGED
|
@@ -39,7 +39,7 @@ var SessionState = class {
|
|
|
39
39
|
).length;
|
|
40
40
|
return {
|
|
41
41
|
type: "state",
|
|
42
|
-
blocked: working === 0,
|
|
42
|
+
blocked: waitingForInput > 0 || working === 0,
|
|
43
43
|
sessions: sessions.length,
|
|
44
44
|
working,
|
|
45
45
|
waitingForInput
|
|
@@ -48,12 +48,15 @@ var SessionState = class {
|
|
|
48
48
|
handleCodexActivity(activity) {
|
|
49
49
|
this.ensureSession(activity.sessionId, activity.cwd);
|
|
50
50
|
const session = this.sessions.get(activity.sessionId);
|
|
51
|
+
const statusChanged = session.status !== "working";
|
|
51
52
|
session.status = "working";
|
|
52
53
|
session.waitingForInputSince = void 0;
|
|
53
54
|
session.lastActivity = /* @__PURE__ */ new Date();
|
|
54
55
|
session.lastSeen = /* @__PURE__ */ new Date();
|
|
55
56
|
session.idleTimeoutMs = activity.idleTimeoutMs;
|
|
56
|
-
|
|
57
|
+
if (statusChanged) {
|
|
58
|
+
this.broadcast();
|
|
59
|
+
}
|
|
57
60
|
}
|
|
58
61
|
setCodexIdle(sessionId, cwd) {
|
|
59
62
|
this.ensureSession(sessionId, cwd);
|
|
@@ -65,6 +68,18 @@ var SessionState = class {
|
|
|
65
68
|
this.broadcast();
|
|
66
69
|
}
|
|
67
70
|
}
|
|
71
|
+
setWaitingForInput(sessionId, cwd) {
|
|
72
|
+
this.ensureSession(sessionId, cwd);
|
|
73
|
+
const session = this.sessions.get(sessionId);
|
|
74
|
+
const statusChanged = session.status !== "waiting_for_input";
|
|
75
|
+
session.status = "waiting_for_input";
|
|
76
|
+
session.waitingForInputSince ??= /* @__PURE__ */ new Date();
|
|
77
|
+
session.lastActivity = /* @__PURE__ */ new Date();
|
|
78
|
+
session.lastSeen = /* @__PURE__ */ new Date();
|
|
79
|
+
if (statusChanged) {
|
|
80
|
+
this.broadcast();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
68
83
|
markCodexSessionSeen(sessionId, cwd) {
|
|
69
84
|
const created = this.ensureSession(sessionId, cwd);
|
|
70
85
|
const session = this.sessions.get(sessionId);
|
|
@@ -122,7 +137,7 @@ var SessionState = class {
|
|
|
122
137
|
(s) => s.status === "waiting_for_input"
|
|
123
138
|
).length;
|
|
124
139
|
return {
|
|
125
|
-
blocked: working === 0,
|
|
140
|
+
blocked: waitingForInput > 0 || working === 0,
|
|
126
141
|
sessions: sessions.length,
|
|
127
142
|
working,
|
|
128
143
|
waitingForInput
|
|
@@ -188,7 +203,11 @@ function parseCodexLine(line, sessionId) {
|
|
|
188
203
|
let previousSessionId;
|
|
189
204
|
let cwd;
|
|
190
205
|
let markWorking = false;
|
|
206
|
+
let markActivity = false;
|
|
207
|
+
let markWaitingForInput = false;
|
|
191
208
|
let markIdle = false;
|
|
209
|
+
let markLegacyIdleCandidate = false;
|
|
210
|
+
let assistantMessagePhase;
|
|
192
211
|
try {
|
|
193
212
|
const payload = JSON.parse(line);
|
|
194
213
|
const entryType = typeof payload.type === "string" ? payload.type : void 0;
|
|
@@ -207,8 +226,45 @@ function parseCodexLine(line, sessionId) {
|
|
|
207
226
|
markWorking = true;
|
|
208
227
|
}
|
|
209
228
|
if (entryType === "event_msg" && innerTypeString === "agent_message") {
|
|
229
|
+
markLegacyIdleCandidate = true;
|
|
230
|
+
}
|
|
231
|
+
if (entryType === "event_msg" && innerTypeString === "agent_reasoning") {
|
|
232
|
+
markActivity = true;
|
|
233
|
+
}
|
|
234
|
+
if (entryType === "event_msg" && innerTypeString === "item_completed") {
|
|
210
235
|
markIdle = true;
|
|
211
236
|
}
|
|
237
|
+
if (entryType === "response_item" && innerTypeString === "message") {
|
|
238
|
+
const role = innerPayload && typeof innerPayload === "object" ? innerPayload.role : void 0;
|
|
239
|
+
if (role === "user") {
|
|
240
|
+
const messageText = extractMessageText(innerPayload);
|
|
241
|
+
if (!messageText || !messageText.trim().startsWith("<environment_context>")) {
|
|
242
|
+
markWorking = true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (role === "assistant" && innerPayload && typeof innerPayload === "object") {
|
|
246
|
+
const phase = innerPayload.phase;
|
|
247
|
+
if (typeof phase === "string" && phase.length > 0) {
|
|
248
|
+
assistantMessagePhase = phase;
|
|
249
|
+
if (phase === "final_answer") {
|
|
250
|
+
markIdle = true;
|
|
251
|
+
} else if (phase === "commentary") {
|
|
252
|
+
markActivity = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (entryType === "response_item" && innerTypeString === "function_call") {
|
|
258
|
+
const callName = innerPayload && typeof innerPayload === "object" ? innerPayload.name : void 0;
|
|
259
|
+
if (callName === "request_user_input") {
|
|
260
|
+
markWaitingForInput = true;
|
|
261
|
+
} else {
|
|
262
|
+
markActivity = true;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (entryType === "response_item" && (innerTypeString === "reasoning" || innerTypeString === "function_call_output" || innerTypeString === "custom_tool_call" || innerTypeString === "custom_tool_call_output")) {
|
|
266
|
+
markActivity = true;
|
|
267
|
+
}
|
|
212
268
|
} catch {
|
|
213
269
|
}
|
|
214
270
|
return {
|
|
@@ -216,12 +272,31 @@ function parseCodexLine(line, sessionId) {
|
|
|
216
272
|
previousSessionId,
|
|
217
273
|
cwd,
|
|
218
274
|
markWorking,
|
|
219
|
-
|
|
275
|
+
markActivity,
|
|
276
|
+
markWaitingForInput,
|
|
277
|
+
markIdle,
|
|
278
|
+
markLegacyIdleCandidate,
|
|
279
|
+
assistantMessagePhase
|
|
220
280
|
};
|
|
221
281
|
}
|
|
282
|
+
function extractMessageText(payload) {
|
|
283
|
+
if (!payload || typeof payload !== "object") return void 0;
|
|
284
|
+
const record = payload;
|
|
285
|
+
const content = record.content;
|
|
286
|
+
if (typeof content === "string") return content;
|
|
287
|
+
if (!Array.isArray(content)) return void 0;
|
|
288
|
+
const parts = [];
|
|
289
|
+
for (const item of content) {
|
|
290
|
+
if (!item || typeof item !== "object") continue;
|
|
291
|
+
const text = item.text;
|
|
292
|
+
if (typeof text === "string") parts.push(text);
|
|
293
|
+
}
|
|
294
|
+
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
295
|
+
}
|
|
222
296
|
|
|
223
297
|
// src/codex.ts
|
|
224
298
|
var DEFAULT_CODEX_HOME = join(homedir(), ".codex");
|
|
299
|
+
var LEGACY_AGENT_MESSAGE_IDLE_GRACE_MS = 4e3;
|
|
225
300
|
async function listRolloutFiles(root) {
|
|
226
301
|
const files = [];
|
|
227
302
|
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
@@ -299,7 +374,8 @@ var CodexSessionWatcher = class {
|
|
|
299
374
|
const fileState = this.fileStates.get(filePath) ?? {
|
|
300
375
|
position: 0,
|
|
301
376
|
remainder: "",
|
|
302
|
-
sessionId: sessionIdFromPath(filePath)
|
|
377
|
+
sessionId: sessionIdFromPath(filePath),
|
|
378
|
+
hasAssistantPhaseSignals: false
|
|
303
379
|
};
|
|
304
380
|
if (!this.fileStates.has(filePath)) {
|
|
305
381
|
this.fileStates.set(filePath, fileState);
|
|
@@ -317,25 +393,52 @@ var CodexSessionWatcher = class {
|
|
|
317
393
|
} catch {
|
|
318
394
|
continue;
|
|
319
395
|
}
|
|
320
|
-
if (newLines.length === 0)
|
|
396
|
+
if (newLines.length === 0) {
|
|
397
|
+
this.maybeFlushLegacyIdle(fileState);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
321
400
|
for (const line of newLines) {
|
|
322
401
|
const parsed = parseCodexLine(line, fileState.sessionId);
|
|
323
402
|
fileState.sessionId = parsed.sessionId;
|
|
324
403
|
if (parsed.previousSessionId) {
|
|
325
404
|
this.state.removeSession(parsed.previousSessionId);
|
|
405
|
+
fileState.hasAssistantPhaseSignals = false;
|
|
406
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
407
|
+
}
|
|
408
|
+
if (parsed.assistantMessagePhase) {
|
|
409
|
+
fileState.hasAssistantPhaseSignals = true;
|
|
410
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
326
411
|
}
|
|
327
412
|
this.state.markCodexSessionSeen(parsed.sessionId, parsed.cwd);
|
|
328
|
-
if (parsed.markWorking) {
|
|
413
|
+
if (parsed.markWorking || parsed.markActivity) {
|
|
414
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
329
415
|
this.state.handleCodexActivity({
|
|
330
416
|
sessionId: parsed.sessionId,
|
|
331
417
|
cwd: parsed.cwd
|
|
332
418
|
});
|
|
333
419
|
}
|
|
420
|
+
if (parsed.markWaitingForInput) {
|
|
421
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
422
|
+
this.state.setWaitingForInput(parsed.sessionId, parsed.cwd);
|
|
423
|
+
}
|
|
334
424
|
if (parsed.markIdle) {
|
|
425
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
335
426
|
this.state.setCodexIdle(parsed.sessionId, parsed.cwd);
|
|
336
427
|
}
|
|
428
|
+
if (parsed.markLegacyIdleCandidate && !fileState.hasAssistantPhaseSignals) {
|
|
429
|
+
fileState.pendingLegacyIdleAt ??= Date.now();
|
|
430
|
+
}
|
|
337
431
|
}
|
|
432
|
+
this.maybeFlushLegacyIdle(fileState);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
maybeFlushLegacyIdle(fileState) {
|
|
436
|
+
if (!fileState.pendingLegacyIdleAt) return;
|
|
437
|
+
if (Date.now() - fileState.pendingLegacyIdleAt < LEGACY_AGENT_MESSAGE_IDLE_GRACE_MS) {
|
|
438
|
+
return;
|
|
338
439
|
}
|
|
440
|
+
this.state.setCodexIdle(fileState.sessionId);
|
|
441
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
339
442
|
}
|
|
340
443
|
};
|
|
341
444
|
|
package/dist/server.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ declare class SessionState {
|
|
|
24
24
|
private getStateMessage;
|
|
25
25
|
handleCodexActivity(activity: CodexActivity): void;
|
|
26
26
|
setCodexIdle(sessionId: string, cwd?: string): void;
|
|
27
|
+
setWaitingForInput(sessionId: string, cwd?: string): void;
|
|
27
28
|
markCodexSessionSeen(sessionId: string, cwd?: string): void;
|
|
28
29
|
removeSession(sessionId: string): void;
|
|
29
30
|
private ensureSession;
|
package/dist/server.js
CHANGED
package/package.json
CHANGED