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 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 “idle” on the
46
- final assistant reply (tool calls don’t count as idle).
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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  DEFAULT_PORT,
4
4
  startServer
5
- } from "./chunk-SOAB3RMQ.js";
5
+ } from "./chunk-KNFNSOAX.js";
6
6
 
7
7
  // src/bin.ts
8
8
  import { createRequire } from "module";
@@ -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
- this.broadcast();
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
- markIdle
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) continue;
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startServer
3
- } from "./chunk-SOAB3RMQ.js";
3
+ } from "./chunk-KNFNSOAX.js";
4
4
  export {
5
5
  startServer
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-blocker",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Automatically blocks distracting websites unless Codex is actively running. Forked from Theo Browne's (T3) Claude Blocker",
5
5
  "author": "Adam Blumoff ",
6
6
  "repository": {