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 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-APDAIY47.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,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
- markIdle
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) continue;
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startServer
3
- } from "./chunk-APDAIY47.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.0",
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": {