clideck 1.30.1 → 1.30.3

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.
@@ -10,10 +10,42 @@ const codexMenuPoll = new Map(); // sessionId → interval (polling for menu aft
10
10
  const codexPendingStop = new Map(); // sessionId → ts (notify hook arrived; wait for next response.completed)
11
11
  const codexOutputDone = new Map(); // sessionId → ts (fallback if notify never fires)
12
12
  const codexPendingIdle = new Map(); // sessionId → timer (tiny settle before committing idle)
13
- const codexPendingTool = new Set(); // sessionId set while Codex has emitted a tool call that has not produced a result yet
13
+ const codexToolPhasePending = new Set(); // sessionId set once Codex has announced a tool-call phase, cleared when the phase resolves
14
+ const codexPendingTools = new Map(); // sessionId → Set(callId) for approved Codex tool calls still awaiting a result
14
15
  let broadcastFn = null;
15
16
  let sessionsFn = null;
16
17
 
18
+ function getPendingToolSet(id) {
19
+ let set = codexPendingTools.get(id);
20
+ if (!set) { set = new Set(); codexPendingTools.set(id, set); }
21
+ return set;
22
+ }
23
+
24
+ function clearPendingTools(id) {
25
+ codexPendingTools.delete(id);
26
+ }
27
+
28
+ function addPendingTool(id, callId) {
29
+ if (!callId) return;
30
+ getPendingToolSet(id).add(callId);
31
+ }
32
+
33
+ function resolvePendingTool(id, callId) {
34
+ if (!callId) return;
35
+ const set = codexPendingTools.get(id);
36
+ if (!set) return;
37
+ set.delete(callId);
38
+ if (!set.size) codexPendingTools.delete(id);
39
+ }
40
+
41
+ function hasPendingTools(id) {
42
+ return !!codexPendingTools.get(id)?.size;
43
+ }
44
+
45
+ function hasPendingToolState(id) {
46
+ return codexToolPhasePending.has(id) || hasPendingTools(id);
47
+ }
48
+
17
49
  function init(broadcast, getSessions) {
18
50
  broadcastFn = broadcast;
19
51
  sessionsFn = getSessions;
@@ -78,11 +110,8 @@ function handleLogs(req, res) {
78
110
  // Debug telemetry logs — uncomment as needed, do not delete
79
111
  // if (serviceName === 'claude-code' && eventName) console.log(`[telemetry:claude] ${eventName}`);
80
112
  // if (serviceName === 'codex_cli_rs' && eventName) {
81
- // const details = Object.entries(attrs)
82
- // .filter(([k]) => k !== 'event.name')
83
- // .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
84
- // .join(' ');
85
- // console.log(`[telemetry:codex] ${eventName} session=${resolvedId.slice(0,8)}${details ? ' ' + details : ''}`);
113
+ // const kind = attrs['event.kind'] ? ` kind=${attrs['event.kind']}` : '';
114
+ // console.log(`[telemetry:codex] ${eventName}${kind} session=${resolvedId.slice(0,8)}`);
86
115
  // }
87
116
  // if (serviceName === 'gemini-cli' && eventName) console.log(`[telemetry:gemini] ${eventName}`);
88
117
 
@@ -101,15 +130,16 @@ function handleLogs(req, res) {
101
130
  if (eventName === 'codex.user_prompt') {
102
131
  codexPendingStop.delete(resolvedId);
103
132
  codexOutputDone.delete(resolvedId);
104
- codexPendingTool.delete(resolvedId);
133
+ codexToolPhasePending.delete(resolvedId);
134
+ clearPendingTools(resolvedId);
105
135
  broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
106
136
  }
107
137
 
108
- // Function-call output item completion is not end-of-turn. Codex may
109
- // next wait for a tool result for seconds or hours; don't go idle until
110
- // that result arrives and Codex later emits a real completion.
138
+ // Codex can announce a function-call phase before the later tool_decision
139
+ // event carries a call_id. Block idle as soon as the tool phase is known,
140
+ // then refine it to call-specific tracking when tool_decision arrives.
111
141
  if (eventName === 'codex.websocket_event' && attrs['event.kind'] === 'response.function_call_arguments.done') {
112
- codexPendingTool.add(resolvedId);
142
+ codexToolPhasePending.add(resolvedId);
113
143
  }
114
144
 
115
145
  // Fallback: when notify does not fire, require an output item to finish
@@ -122,7 +152,7 @@ function handleLogs(req, res) {
122
152
  // Also poll briefly for a visible choice menu.
123
153
  if (eventName === 'codex.sse_event' && attrs['event.kind'] === 'response.completed') {
124
154
  const pendingStopAt = codexPendingStop.get(resolvedId);
125
- if (codexPendingTool.has(resolvedId)) {
155
+ if (hasPendingToolState(resolvedId)) {
126
156
  // Tool execution is still in-flight; this completion only closed the
127
157
  // function-call phase, not the user's full turn.
128
158
  } else if (pendingStopAt && Date.now() - pendingStopAt <= 5000) {
@@ -146,12 +176,17 @@ function handleLogs(req, res) {
146
176
  if (eventName === 'codex.tool_decision') {
147
177
  codexPendingStop.delete(resolvedId);
148
178
  codexOutputDone.delete(resolvedId);
149
- codexPendingTool.add(resolvedId);
179
+ if ((attrs.decision || '').toLowerCase() !== 'denied') {
180
+ addPendingTool(resolvedId, attrs.call_id || attrs['call.id']);
181
+ } else {
182
+ codexToolPhasePending.delete(resolvedId);
183
+ }
150
184
  cancelCodexMenuPoll(resolvedId);
151
185
  broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
152
186
  }
153
187
  if (eventName === 'codex.tool_result') {
154
- codexPendingTool.delete(resolvedId);
188
+ codexToolPhasePending.delete(resolvedId);
189
+ resolvePendingTool(resolvedId, attrs.call_id || attrs['call.id']);
155
190
  }
156
191
  // Codex: user_prompt or next sse_event cancels menu poll
157
192
  if ((eventName === 'codex.user_prompt' || (eventName === 'codex.sse_event' && attrs['event.kind'] !== 'response.completed'))) {
@@ -244,7 +279,8 @@ function clear(id) {
244
279
  cancelCodexPendingIdle(id);
245
280
  codexPendingStop.delete(id);
246
281
  codexOutputDone.delete(id);
247
- codexPendingTool.delete(id);
282
+ codexToolPhasePending.delete(id);
283
+ clearPendingTools(id);
248
284
  const pending = pendingSetup.get(id);
249
285
  if (pending) { clearTimeout(pending.timer); pendingSetup.delete(id); }
250
286
  }
package/transcript.js CHANGED
@@ -188,27 +188,38 @@ function getUsers(id) {
188
188
  return (entriesById[id] || []).filter(e => e.role === 'user').map(e => e.text);
189
189
  }
190
190
 
191
- function getLastTurns(id, n) {
192
- n = n || 4;
191
+ function readEntries(id) {
193
192
  const file = fpath(id);
194
193
  if (!existsSync(file)) return [];
195
194
  try {
196
- const lines = readFileSync(file, 'utf8').trim().split('\n');
197
- const turns = [];
198
- for (let i = lines.length - 1; i >= 0; i--) {
199
- let entry;
200
- try { entry = JSON.parse(lines[i]); } catch { continue; }
201
- if (turns.length && turns[turns.length - 1].role === entry.role) {
202
- turns[turns.length - 1].text = entry.text + '\n' + turns[turns.length - 1].text;
203
- } else {
204
- turns.push({ role: entry.role, text: entry.text });
205
- if (turns.length >= n) break;
206
- }
207
- }
208
- return turns.reverse();
195
+ const cached = entriesById[id];
196
+ if (Array.isArray(cached) && cached.length) return cached;
197
+ const lines = readFileSync(file, 'utf8').trim().split('\n').filter(Boolean);
198
+ return lines.map(line => { try { return JSON.parse(line); } catch { return null; } }).filter(Boolean);
209
199
  } catch { return []; }
210
200
  }
211
201
 
202
+ function foldTurns(entries, n, order) {
203
+ const turns = [];
204
+ const fromStart = order === 'start';
205
+ const list = fromStart ? entries : [...entries].reverse();
206
+ for (const entry of list) {
207
+ if (turns.length && turns[turns.length - 1].role === entry.role) {
208
+ if (fromStart) turns[turns.length - 1].text += '\n' + entry.text;
209
+ else turns[turns.length - 1].text = entry.text + '\n' + turns[turns.length - 1].text;
210
+ } else {
211
+ turns.push({ role: entry.role, text: entry.text });
212
+ if (turns.length >= n) break;
213
+ }
214
+ }
215
+ return fromStart ? turns : turns.reverse();
216
+ }
217
+
218
+ function getTurns(id, n, order) {
219
+ n = n || 4;
220
+ return foldTurns(readEntries(id), n, order || 'end');
221
+ }
222
+
212
223
  function getCache() { return { ...cache }; }
213
224
 
214
225
  function getReplayText(id, presetId) {
@@ -265,4 +276,4 @@ function detectMenu(lines, presetId) {
265
276
  return choices.length ? choices : null;
266
277
  }
267
278
 
268
- module.exports = { init, trackInput, recordInjectedInput, trackOutput, updateAgentCandidate, commitAgentCandidate, clearAgentCandidate, parseTurnsFromLines, getLastTurns, getCache, getReplayText, clear, setPrefix, setFinalizeOnIdle, detectMenu };
279
+ module.exports = { init, trackInput, recordInjectedInput, trackOutput, updateAgentCandidate, commitAgentCandidate, clearAgentCandidate, parseTurnsFromLines, getTurns, getCache, getReplayText, clear, setPrefix, setFinalizeOnIdle, detectMenu };