codex-blocker 0.1.1 → 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-SOAB3RMQ.js → chunk-KNFNSOAX.js} +87 -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,6 +226,12 @@ 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
|
}
|
|
212
237
|
if (entryType === "response_item" && innerTypeString === "message") {
|
|
@@ -217,6 +242,28 @@ function parseCodexLine(line, sessionId) {
|
|
|
217
242
|
markWorking = true;
|
|
218
243
|
}
|
|
219
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;
|
|
220
267
|
}
|
|
221
268
|
} catch {
|
|
222
269
|
}
|
|
@@ -225,7 +272,11 @@ function parseCodexLine(line, sessionId) {
|
|
|
225
272
|
previousSessionId,
|
|
226
273
|
cwd,
|
|
227
274
|
markWorking,
|
|
228
|
-
|
|
275
|
+
markActivity,
|
|
276
|
+
markWaitingForInput,
|
|
277
|
+
markIdle,
|
|
278
|
+
markLegacyIdleCandidate,
|
|
279
|
+
assistantMessagePhase
|
|
229
280
|
};
|
|
230
281
|
}
|
|
231
282
|
function extractMessageText(payload) {
|
|
@@ -245,6 +296,7 @@ function extractMessageText(payload) {
|
|
|
245
296
|
|
|
246
297
|
// src/codex.ts
|
|
247
298
|
var DEFAULT_CODEX_HOME = join(homedir(), ".codex");
|
|
299
|
+
var LEGACY_AGENT_MESSAGE_IDLE_GRACE_MS = 4e3;
|
|
248
300
|
async function listRolloutFiles(root) {
|
|
249
301
|
const files = [];
|
|
250
302
|
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
@@ -322,7 +374,8 @@ var CodexSessionWatcher = class {
|
|
|
322
374
|
const fileState = this.fileStates.get(filePath) ?? {
|
|
323
375
|
position: 0,
|
|
324
376
|
remainder: "",
|
|
325
|
-
sessionId: sessionIdFromPath(filePath)
|
|
377
|
+
sessionId: sessionIdFromPath(filePath),
|
|
378
|
+
hasAssistantPhaseSignals: false
|
|
326
379
|
};
|
|
327
380
|
if (!this.fileStates.has(filePath)) {
|
|
328
381
|
this.fileStates.set(filePath, fileState);
|
|
@@ -340,25 +393,52 @@ var CodexSessionWatcher = class {
|
|
|
340
393
|
} catch {
|
|
341
394
|
continue;
|
|
342
395
|
}
|
|
343
|
-
if (newLines.length === 0)
|
|
396
|
+
if (newLines.length === 0) {
|
|
397
|
+
this.maybeFlushLegacyIdle(fileState);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
344
400
|
for (const line of newLines) {
|
|
345
401
|
const parsed = parseCodexLine(line, fileState.sessionId);
|
|
346
402
|
fileState.sessionId = parsed.sessionId;
|
|
347
403
|
if (parsed.previousSessionId) {
|
|
348
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;
|
|
349
411
|
}
|
|
350
412
|
this.state.markCodexSessionSeen(parsed.sessionId, parsed.cwd);
|
|
351
|
-
if (parsed.markWorking) {
|
|
413
|
+
if (parsed.markWorking || parsed.markActivity) {
|
|
414
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
352
415
|
this.state.handleCodexActivity({
|
|
353
416
|
sessionId: parsed.sessionId,
|
|
354
417
|
cwd: parsed.cwd
|
|
355
418
|
});
|
|
356
419
|
}
|
|
420
|
+
if (parsed.markWaitingForInput) {
|
|
421
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
422
|
+
this.state.setWaitingForInput(parsed.sessionId, parsed.cwd);
|
|
423
|
+
}
|
|
357
424
|
if (parsed.markIdle) {
|
|
425
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
358
426
|
this.state.setCodexIdle(parsed.sessionId, parsed.cwd);
|
|
359
427
|
}
|
|
428
|
+
if (parsed.markLegacyIdleCandidate && !fileState.hasAssistantPhaseSignals) {
|
|
429
|
+
fileState.pendingLegacyIdleAt ??= Date.now();
|
|
430
|
+
}
|
|
360
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;
|
|
361
439
|
}
|
|
440
|
+
this.state.setCodexIdle(fileState.sessionId);
|
|
441
|
+
fileState.pendingLegacyIdleAt = void 0;
|
|
362
442
|
}
|
|
363
443
|
};
|
|
364
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