@foxden-app/foxclaw 0.6.8 → 0.6.9

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable FoxClaw changes are listed here. Each release note is bilingual so GitHub Releases and the npm package are useful to both Chinese and English readers.
4
4
 
5
+ ## 0.6.9 - 2026-08-15
6
+
7
+ ### 中文
8
+ - 补齐 Codex CLI 0.147.0 会话日志的其他观察兼容:新的 `item_completed` / `UserMessage` 会继续把 CLI 侧补充输入镜像到 Telegram,保持原有 `codex-cli-user` 体验。
9
+ - 识别新的 `custom_tool_call`、`function_call` 及其 output 记录,严格按 `call_id` 跨轮询分片配对,让长时间工具调用恢复“正在运行/已运行”状态;未知或孤立 output 保持静默,不猜测配对。`Reasoning`、`ContextCompaction`、`Extension` 等内部记录仍不会污染聊天。
10
+
11
+ ### English
12
+ - Completed the remaining Codex CLI 0.147.0 session-log observation compatibility. New `item_completed` / `UserMessage` records continue mirroring CLI-side follow-up input to Telegram with the existing `codex-cli-user` experience.
13
+ - Recognizes new `custom_tool_call`, `function_call`, and matching output records across polling chunks, pairing them strictly by `call_id` so long-running tools regain Running/Completed status. Unknown or orphan outputs stay silent, while internal `Reasoning`, `ContextCompaction`, and `Extension` records remain chat-invisible.
14
+
5
15
  ## 0.6.8 - 2026-08-15
6
16
 
7
17
  ### 中文
@@ -2,6 +2,7 @@ import { type TurnActivityEvent } from './activity.js';
2
2
  export interface SessionLogCursor {
3
3
  activeTurnId: string | null;
4
4
  nextMessageIndex: number;
5
+ pendingToolCalls?: SessionToolCall[];
5
6
  }
6
7
  export interface SessionLogBootstrap {
7
8
  cursor: SessionLogCursor;
@@ -17,6 +18,12 @@ export interface SplitJsonlChunk {
17
18
  lines: string[];
18
19
  remainder: string;
19
20
  }
21
+ interface SessionToolCall {
22
+ callId: string;
23
+ kind: 'custom' | 'function';
24
+ name: string;
25
+ }
20
26
  export declare function splitJsonlChunk(remainder: string, chunk: string): SplitJsonlChunk;
21
27
  export declare function bootstrapSessionLog(lines: string[]): SessionLogBootstrap;
22
28
  export declare function applySessionLog(lines: string[], cursor: SessionLogCursor): SessionLogDiff;
29
+ export {};
@@ -13,34 +13,30 @@ export function splitJsonlChunk(remainder, chunk) {
13
13
  }
14
14
  export function bootstrapSessionLog(lines) {
15
15
  const records = parseRecords(lines);
16
- let activeTurnId = null;
17
- let nextMessageIndex = 0;
16
+ let state = { activeTurnId: null, nextMessageIndex: 0 };
18
17
  let events = [];
19
18
  for (const record of records) {
20
- const next = applySessionRecord(record, { activeTurnId, nextMessageIndex });
19
+ const next = applySessionRecord(record, state);
21
20
  if (next.startedTurnId) {
22
- activeTurnId = next.startedTurnId;
23
- nextMessageIndex = 0;
21
+ state = next.cursor;
24
22
  events = [];
25
23
  continue;
26
24
  }
27
- if (!activeTurnId) {
25
+ if (!state.activeTurnId) {
28
26
  continue;
29
27
  }
30
28
  if (next.turnCompleted) {
31
- activeTurnId = null;
32
- nextMessageIndex = 0;
29
+ state = next.cursor;
33
30
  events = [];
34
31
  continue;
35
32
  }
36
33
  events.push(...next.events);
37
- activeTurnId = next.cursor.activeTurnId;
38
- nextMessageIndex = next.cursor.nextMessageIndex;
34
+ state = next.cursor;
39
35
  }
40
36
  return {
41
- cursor: { activeTurnId, nextMessageIndex },
37
+ cursor: state,
42
38
  events,
43
- startedTurnId: activeTurnId,
39
+ startedTurnId: state.activeTurnId,
44
40
  };
45
41
  }
46
42
  export function applySessionLog(lines, cursor) {
@@ -51,10 +47,7 @@ export function applySessionLog(lines, cursor) {
51
47
  for (const record of records) {
52
48
  const next = applySessionRecord(record, state);
53
49
  if (next.startedTurnId) {
54
- state = {
55
- activeTurnId: next.startedTurnId,
56
- nextMessageIndex: 0,
57
- };
50
+ state = next.cursor;
58
51
  startedTurnIds.push(next.startedTurnId);
59
52
  continue;
60
53
  }
@@ -114,6 +107,25 @@ function applySessionRecord(record, cursor) {
114
107
  }
115
108
  return createSessionTextEvents(activeTurnId, cursor, text, typeof payload.item.phase === 'string' ? payload.item.phase : null, false, false, typeof payload.item.id === 'string' ? payload.item.id : null);
116
109
  }
110
+ if (type === 'event_msg'
111
+ && payload?.type === 'item_completed'
112
+ && payload?.turn_id === activeTurnId
113
+ && normalizeSessionItemType(payload?.item) === 'usermessage') {
114
+ const text = extractSessionItemText(payload.item)?.trim() ?? '';
115
+ if (!text) {
116
+ return { cursor, events: [], startedTurnId: null, turnCompleted: false };
117
+ }
118
+ return {
119
+ cursor,
120
+ events: [{
121
+ kind: 'user_message',
122
+ turnId: activeTurnId,
123
+ text,
124
+ }],
125
+ startedTurnId: null,
126
+ turnCompleted: false,
127
+ };
128
+ }
117
129
  if (type === 'response_item' && payload?.type === 'plan' && typeof payload.text === 'string') {
118
130
  return createSessionTextEvents(activeTurnId, cursor, payload.text, 'commentary', true, true);
119
131
  }
@@ -150,6 +162,18 @@ function applySessionRecord(record, cursor) {
150
162
  turnCompleted: false,
151
163
  };
152
164
  }
165
+ if (type === 'response_item' && payload?.type === 'custom_tool_call') {
166
+ return createSessionToolStart(activeTurnId, cursor, payload, 'custom');
167
+ }
168
+ if (type === 'response_item' && payload?.type === 'function_call') {
169
+ return createSessionToolStart(activeTurnId, cursor, payload, 'function');
170
+ }
171
+ if (type === 'response_item' && payload?.type === 'custom_tool_call_output') {
172
+ return createSessionToolEnd(activeTurnId, cursor, payload, 'custom');
173
+ }
174
+ if (type === 'response_item' && payload?.type === 'function_call_output') {
175
+ return createSessionToolEnd(activeTurnId, cursor, payload, 'function');
176
+ }
153
177
  if (type === 'event_msg' && payload?.type === 'exec_command_end' && payload?.turn_id === activeTurnId) {
154
178
  const exec = createExecEndEvent(payload);
155
179
  if (!exec) {
@@ -209,6 +233,7 @@ function createSessionTextEvents(activeTurnId, cursor, text, phase, forceComment
209
233
  const streamOutputKind = forceCommentary ? 'commentary' : classifyAgentOutput(phase, false);
210
234
  return {
211
235
  cursor: {
236
+ ...cursor,
212
237
  activeTurnId,
213
238
  nextMessageIndex: cursor.nextMessageIndex + 1,
214
239
  },
@@ -243,6 +268,62 @@ function createSessionTextEvents(activeTurnId, cursor, text, phase, forceComment
243
268
  turnCompleted: false,
244
269
  };
245
270
  }
271
+ function createSessionToolStart(turnId, cursor, payload, kind) {
272
+ const callId = typeof payload?.call_id === 'string' ? payload.call_id : null;
273
+ const name = typeof payload?.name === 'string' ? payload.name.trim() : '';
274
+ if (!callId || !name) {
275
+ return { cursor, events: [], startedTurnId: null, turnCompleted: false };
276
+ }
277
+ const pendingToolCalls = [
278
+ ...(cursor.pendingToolCalls ?? []).filter(call => call.callId !== callId),
279
+ { callId, kind, name },
280
+ ];
281
+ const exec = createSessionToolEvent(turnId, callId, name);
282
+ return {
283
+ cursor: { ...cursor, pendingToolCalls },
284
+ events: [{
285
+ kind: 'tool_started',
286
+ turnId,
287
+ exec,
288
+ state: inferToolActivityState(exec),
289
+ }],
290
+ startedTurnId: null,
291
+ turnCompleted: false,
292
+ };
293
+ }
294
+ function createSessionToolEnd(turnId, cursor, payload, kind) {
295
+ const callId = typeof payload?.call_id === 'string' ? payload.call_id : null;
296
+ const pending = callId
297
+ ? (cursor.pendingToolCalls ?? []).find(call => call.callId === callId && call.kind === kind)
298
+ : null;
299
+ if (!pending) {
300
+ return { cursor, events: [], startedTurnId: null, turnCompleted: false };
301
+ }
302
+ const exec = createSessionToolEvent(turnId, pending.callId, pending.name);
303
+ return {
304
+ cursor: {
305
+ ...cursor,
306
+ pendingToolCalls: (cursor.pendingToolCalls ?? []).filter(call => call !== pending),
307
+ },
308
+ events: [{
309
+ kind: 'tool_completed',
310
+ turnId,
311
+ exec,
312
+ state: inferToolActivityState(exec),
313
+ }],
314
+ startedTurnId: null,
315
+ turnCompleted: false,
316
+ };
317
+ }
318
+ function createSessionToolEvent(turnId, callId, name) {
319
+ return {
320
+ callId,
321
+ turnId,
322
+ command: [`Tool ${name}`],
323
+ cwd: null,
324
+ parsedCmd: [],
325
+ };
326
+ }
246
327
  function normalizeSessionItemType(item) {
247
328
  if (typeof item?.type !== 'string') {
248
329
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxden-app/foxclaw",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "Foxden local execution claw for controlling Codex and OpenCode from trusted chat interfaces.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",