@foxden-app/foxclaw 0.12.0 → 0.13.1

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/antigravity/account_ui.d.ts +34 -0
  3. package/dist/antigravity/account_ui.js +354 -0
  4. package/dist/antigravity/adapter.js +1 -1
  5. package/dist/antigravity/auth.d.ts +4 -1
  6. package/dist/antigravity/auth.js +17 -5
  7. package/dist/antigravity/controller.d.ts +3 -108
  8. package/dist/antigravity/controller.js +2 -2129
  9. package/dist/antigravity/conversation_ui.d.ts +56 -0
  10. package/dist/antigravity/conversation_ui.js +509 -0
  11. package/dist/antigravity/ui_callbacks.d.ts +6 -0
  12. package/dist/antigravity/ui_callbacks.js +6 -0
  13. package/dist/bridge/unified_bridge.d.ts +114 -0
  14. package/dist/bridge/unified_bridge.js +1343 -0
  15. package/dist/channels/bridge_messaging_router.d.ts +8 -4
  16. package/dist/channels/bridge_messaging_router.js +15 -4
  17. package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
  18. package/dist/channels/telegram/telegram_messaging_port.js +22 -2
  19. package/dist/channels/text_actions.d.ts +14 -0
  20. package/dist/channels/text_actions.js +48 -0
  21. package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
  22. package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
  23. package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
  24. package/dist/channels/weixin/weixin_messaging_port.js +34 -7
  25. package/dist/codex_app/activity.d.ts +62 -0
  26. package/dist/codex_app/activity.js +418 -0
  27. package/dist/codex_app/adapter.d.ts +2 -2
  28. package/dist/codex_app/adapter.js +55 -25
  29. package/dist/codex_app/backend_ui.d.ts +4 -0
  30. package/dist/codex_app/backend_ui.js +48 -0
  31. package/dist/codex_app/client.js +0 -12
  32. package/dist/controller/access.d.ts +1 -11
  33. package/dist/controller/access.js +1 -33
  34. package/dist/controller/activity.d.ts +1 -62
  35. package/dist/controller/activity.js +2 -418
  36. package/dist/controller/auth_errors.d.ts +11 -0
  37. package/dist/controller/auth_errors.js +124 -0
  38. package/dist/controller/auth_files.d.ts +13 -0
  39. package/dist/controller/auth_files.js +156 -0
  40. package/dist/controller/auth_presentation.d.ts +77 -0
  41. package/dist/controller/auth_presentation.js +697 -0
  42. package/dist/controller/auth_quota_service.d.ts +27 -0
  43. package/dist/controller/auth_quota_service.js +151 -0
  44. package/dist/controller/controller.d.ts +30 -56
  45. package/dist/controller/controller.js +181 -3497
  46. package/dist/controller/interactions.d.ts +74 -0
  47. package/dist/controller/interactions.js +560 -0
  48. package/dist/controller/local_usage_service.d.ts +19 -0
  49. package/dist/controller/local_usage_service.js +62 -0
  50. package/dist/controller/native_panel_service.d.ts +55 -0
  51. package/dist/controller/native_panel_service.js +214 -0
  52. package/dist/controller/native_panels.d.ts +61 -0
  53. package/dist/controller/native_panels.js +784 -0
  54. package/dist/controller/shared_helpers.d.ts +53 -0
  55. package/dist/controller/shared_helpers.js +399 -0
  56. package/dist/controller/state_types.d.ts +390 -0
  57. package/dist/controller/state_types.js +82 -0
  58. package/dist/controller/turn_rendering.d.ts +22 -0
  59. package/dist/controller/turn_rendering.js +288 -0
  60. package/dist/core/access.d.ts +11 -0
  61. package/dist/core/access.js +33 -0
  62. package/dist/core/attachment_files.d.ts +13 -0
  63. package/dist/core/attachment_files.js +180 -0
  64. package/dist/core/attachment_types.d.ts +22 -0
  65. package/dist/core/attachment_types.js +1 -0
  66. package/dist/core/attachments.d.ts +5 -3
  67. package/dist/core/attachments.js +2 -2
  68. package/dist/core/backend_registry.d.ts +22 -0
  69. package/dist/core/backend_registry.js +50 -0
  70. package/dist/core/backend_ui.d.ts +28 -0
  71. package/dist/core/backend_ui.js +1 -0
  72. package/dist/core/channel_events.d.ts +48 -0
  73. package/dist/core/channel_events.js +1 -0
  74. package/dist/core/channel_port.d.ts +52 -8
  75. package/dist/core/engine_spi.d.ts +19 -4
  76. package/dist/core/listener_scope.d.ts +11 -0
  77. package/dist/core/listener_scope.js +10 -0
  78. package/dist/core/message_text.d.ts +2 -0
  79. package/dist/core/message_text.js +28 -0
  80. package/dist/core/network_errors.d.ts +1 -0
  81. package/dist/core/network_errors.js +23 -0
  82. package/dist/core/orchestrator.d.ts +53 -24
  83. package/dist/core/orchestrator.js +855 -507
  84. package/dist/core/queued_engine_input.d.ts +12 -0
  85. package/dist/core/queued_engine_input.js +16 -0
  86. package/dist/core/runtime_supervisor.d.ts +19 -0
  87. package/dist/core/runtime_supervisor.js +45 -0
  88. package/dist/core/scope_operations.d.ts +7 -0
  89. package/dist/core/scope_operations.js +19 -0
  90. package/dist/core/stream_preview.js +21 -9
  91. package/dist/dsh/adapter.js +18 -5
  92. package/dist/dsh/backend.d.ts +7 -0
  93. package/dist/dsh/backend.js +21 -0
  94. package/dist/dsh/runtime.d.ts +0 -1
  95. package/dist/dsh/runtime.js +4 -13
  96. package/dist/dsh/ui.d.ts +8 -8
  97. package/dist/dsh/ui.js +1 -1
  98. package/dist/main.js +55 -43
  99. package/dist/opencode/adapter.js +28 -9
  100. package/dist/opencode/controller.js +1 -1
  101. package/dist/store/channel_inbox.d.ts +14 -0
  102. package/dist/store/channel_inbox.js +21 -0
  103. package/dist/store/database.d.ts +7 -3
  104. package/dist/store/database.js +7 -2
  105. package/dist/store/task_journal.d.ts +32 -0
  106. package/dist/store/task_journal.js +70 -0
  107. package/dist/telegram/api.d.ts +2 -2
  108. package/dist/telegram/api.js +10 -28
  109. package/dist/telegram/gateway.d.ts +13 -27
  110. package/dist/telegram/gateway.js +86 -43
  111. package/dist/telegram/media.d.ts +2 -34
  112. package/dist/telegram/media.js +1 -180
  113. package/dist/telegram/task_preview.d.ts +13 -0
  114. package/dist/telegram/task_preview.js +92 -0
  115. package/dist/telegram/task_result.d.ts +2 -0
  116. package/dist/telegram/task_result.js +12 -0
  117. package/dist/telegram/text.d.ts +1 -1
  118. package/dist/telegram/text.js +2 -27
  119. package/dist/types.d.ts +1 -1
  120. package/dist/update.d.ts +27 -2
  121. package/dist/update.js +81 -28
  122. package/docs/release.md +13 -11
  123. package/docs/zh/architecture-review.md +106 -0
  124. package/docs/zh/release.md +13 -11
  125. package/docs/zh/telegram-experience.md +40 -0
  126. package/package.json +8 -5
@@ -1,11 +1 @@
1
- import type { AppConfig } from '../config.js';
2
- import type { AccessPresetValue, ApprovalPolicyValue, ChatSessionSettings, SandboxModeValue } from '../types.js';
3
- export interface ResolvedAccessMode {
4
- preset: AccessPresetValue;
5
- approvalPolicy: ApprovalPolicyValue;
6
- sandboxMode: SandboxModeValue;
7
- }
8
- export declare const ACCESS_PRESETS: AccessPresetValue[];
9
- export declare function normalizeAccessPreset(value: string | null | undefined): AccessPresetValue | null;
10
- export declare function resolveConfiguredAccessPreset(settings: Pick<ChatSessionSettings, 'accessPreset'> | null | undefined): AccessPresetValue;
11
- export declare function resolveAccessMode(config: Pick<AppConfig, 'defaultApprovalPolicy' | 'defaultSandboxMode'>, settings: Pick<ChatSessionSettings, 'accessPreset'> | null | undefined): ResolvedAccessMode;
1
+ export * from '../core/access.js';
@@ -1,33 +1 @@
1
- export const ACCESS_PRESETS = ['read-only', 'default', 'full-access'];
2
- export function normalizeAccessPreset(value) {
3
- if (value === 'read-only' || value === 'default' || value === 'full-access') {
4
- return value;
5
- }
6
- return null;
7
- }
8
- export function resolveConfiguredAccessPreset(settings) {
9
- return settings?.accessPreset ?? 'default';
10
- }
11
- export function resolveAccessMode(config, settings) {
12
- const preset = resolveConfiguredAccessPreset(settings);
13
- switch (preset) {
14
- case 'read-only':
15
- return {
16
- preset,
17
- approvalPolicy: 'on-request',
18
- sandboxMode: 'read-only',
19
- };
20
- case 'full-access':
21
- return {
22
- preset,
23
- approvalPolicy: 'never',
24
- sandboxMode: 'danger-full-access',
25
- };
26
- default:
27
- return {
28
- preset: 'default',
29
- approvalPolicy: config.defaultApprovalPolicy,
30
- sandboxMode: config.defaultSandboxMode,
31
- };
32
- }
33
- }
1
+ export * from '../core/access.js';
@@ -1,62 +1 @@
1
- import type { JsonRpcNotification } from '../codex_app/client.js';
2
- export interface RawExecCommandEvent {
3
- callId: string;
4
- turnId: string;
5
- command: string[];
6
- cwd: string | null;
7
- parsedCmd: any[];
8
- }
9
- export type TurnActivityState = 'thinking' | 'reading' | 'searching' | 'editing' | 'running_command' | 'approval_waiting' | 'completed' | 'interrupted';
10
- export type TurnOutputKind = 'commentary' | 'final_answer' | 'tool_summary' | 'error';
11
- export type TurnActivityEvent = {
12
- kind: 'user_message';
13
- turnId: string;
14
- text: string;
15
- } | {
16
- kind: 'agent_message_started';
17
- turnId: string;
18
- itemId: string;
19
- phase: string | null;
20
- outputKind: TurnOutputKind;
21
- isPlan?: boolean;
22
- } | {
23
- kind: 'agent_message_delta';
24
- turnId: string;
25
- itemId: string;
26
- delta: string;
27
- outputKind: TurnOutputKind;
28
- isPlan?: boolean;
29
- } | {
30
- kind: 'agent_message_completed';
31
- turnId: string;
32
- itemId: string;
33
- phase: string | null;
34
- text: string | null;
35
- outputKind: TurnOutputKind;
36
- isPlan?: boolean;
37
- } | {
38
- kind: 'reasoning_started';
39
- turnId: string;
40
- state: 'thinking';
41
- } | {
42
- kind: 'reasoning_completed';
43
- turnId: string;
44
- state: 'thinking';
45
- } | {
46
- kind: 'tool_started';
47
- turnId: string;
48
- exec: RawExecCommandEvent;
49
- state: TurnActivityState;
50
- } | {
51
- kind: 'tool_completed';
52
- turnId: string;
53
- exec: RawExecCommandEvent;
54
- state: TurnActivityState;
55
- } | {
56
- kind: 'turn_completed';
57
- turnId: string;
58
- state: 'completed' | 'interrupted';
59
- };
60
- export declare function normalizeTurnActivityEvent(notification: JsonRpcNotification): TurnActivityEvent | null;
61
- export declare function classifyAgentOutput(phase: string | null, completed: boolean): TurnOutputKind;
62
- export declare function inferToolActivityState(event: RawExecCommandEvent): TurnActivityState;
1
+ export * from '../codex_app/activity.js';
@@ -1,418 +1,2 @@
1
- export function normalizeTurnActivityEvent(notification) {
2
- switch (notification.method) {
3
- case 'item/started':
4
- return normalizeStartedEvent(notification.params);
5
- case 'item/agentMessage/delta':
6
- return normalizeAgentDeltaEvent(notification.params);
7
- case 'item/plan/delta':
8
- return normalizePlanDeltaEvent(notification.params);
9
- case 'item/completed':
10
- return normalizeCompletedEvent(notification.params);
11
- case 'turn/plan/updated':
12
- return normalizePlanUpdatedEvent(notification.params);
13
- case 'codex/event/exec_command_begin':
14
- return normalizeToolEvent(notification.params, 'tool_started');
15
- case 'codex/event/exec_command_end':
16
- return normalizeToolEvent(notification.params, 'tool_completed');
17
- case 'turn/completed': {
18
- const turnId = extractTurnId(notification.params);
19
- if (!turnId)
20
- return null;
21
- return {
22
- kind: 'turn_completed',
23
- turnId,
24
- state: 'completed',
25
- };
26
- }
27
- case 'turn/interrupted':
28
- case 'turn/aborted': {
29
- const turnId = extractTurnId(notification.params);
30
- if (!turnId)
31
- return null;
32
- return {
33
- kind: 'turn_completed',
34
- turnId,
35
- state: 'interrupted',
36
- };
37
- }
38
- default:
39
- return null;
40
- }
41
- }
42
- function normalizeStartedEvent(params) {
43
- const turnId = extractTurnId(params);
44
- if (!turnId)
45
- return null;
46
- const item = params?.item;
47
- const itemType = normalizeEventItemType(item);
48
- if (itemType === 'reasoning') {
49
- return {
50
- kind: 'reasoning_started',
51
- turnId,
52
- state: 'thinking',
53
- };
54
- }
55
- const toolEvent = normalizeThreadItemToolEvent(params, 'tool_started');
56
- if (toolEvent) {
57
- return toolEvent;
58
- }
59
- if (!isRelayableTextItemType(itemType)) {
60
- return null;
61
- }
62
- const itemId = extractItemId(item);
63
- if (!itemId)
64
- return null;
65
- const phase = itemType === 'plan' ? 'commentary' : extractAgentPhase(item);
66
- return {
67
- kind: 'agent_message_started',
68
- turnId,
69
- itemId,
70
- phase,
71
- outputKind: outputKindForItemType(itemType, phase, false),
72
- isPlan: itemType === 'plan',
73
- };
74
- }
75
- function normalizeAgentDeltaEvent(params) {
76
- const turnId = extractTurnId(params);
77
- const delta = extractAgentDeltaText(params);
78
- const itemId = extractItemId(params);
79
- if (!turnId || !delta || !itemId) {
80
- return null;
81
- }
82
- const phase = extractAgentPhase(params);
83
- return {
84
- kind: 'agent_message_delta',
85
- turnId,
86
- itemId,
87
- delta,
88
- outputKind: classifyAgentOutput(phase, false),
89
- };
90
- }
91
- function normalizePlanDeltaEvent(params) {
92
- const turnId = extractTurnId(params);
93
- const delta = extractAgentDeltaText(params);
94
- const itemId = extractItemId(params);
95
- if (!turnId || !delta || !itemId) {
96
- return null;
97
- }
98
- return {
99
- kind: 'agent_message_delta',
100
- turnId,
101
- itemId,
102
- delta,
103
- outputKind: 'commentary',
104
- isPlan: true,
105
- };
106
- }
107
- function normalizeCompletedEvent(params) {
108
- const turnId = extractTurnId(params);
109
- if (!turnId)
110
- return null;
111
- const item = params?.item;
112
- const itemType = normalizeEventItemType(item);
113
- if (itemType === 'reasoning') {
114
- return {
115
- kind: 'reasoning_completed',
116
- turnId,
117
- state: 'thinking',
118
- };
119
- }
120
- const toolEvent = normalizeThreadItemToolEvent(params, 'tool_completed');
121
- if (toolEvent) {
122
- return toolEvent;
123
- }
124
- if (!isRelayableTextItemType(itemType)) {
125
- return null;
126
- }
127
- const itemId = extractItemId(item);
128
- if (!itemId)
129
- return null;
130
- const phase = itemType === 'plan' ? 'commentary' : extractAgentPhase(item);
131
- return {
132
- kind: 'agent_message_completed',
133
- turnId,
134
- itemId,
135
- phase,
136
- text: extractCompletedAgentText(params),
137
- outputKind: outputKindForItemType(itemType, phase, true),
138
- isPlan: itemType === 'plan',
139
- };
140
- }
141
- function normalizePlanUpdatedEvent(params) {
142
- const turnId = extractTurnId(params);
143
- if (!turnId) {
144
- return null;
145
- }
146
- const text = formatPlanUpdateText(params);
147
- if (!text) {
148
- return null;
149
- }
150
- return {
151
- kind: 'agent_message_completed',
152
- turnId,
153
- itemId: `${turnId}:plan`,
154
- phase: 'commentary',
155
- text,
156
- outputKind: 'commentary',
157
- isPlan: true,
158
- };
159
- }
160
- function normalizeToolEvent(params, kind) {
161
- const exec = extractRawExecCommandEvent(params);
162
- if (!exec) {
163
- return null;
164
- }
165
- return {
166
- kind,
167
- turnId: exec.turnId,
168
- exec,
169
- state: inferToolActivityState(exec),
170
- };
171
- }
172
- function normalizeThreadItemToolEvent(params, kind) {
173
- const turnId = extractTurnId(params);
174
- const item = params?.item;
175
- const itemId = extractItemId(item);
176
- const itemType = normalizeEventItemType(item);
177
- if (!turnId || !itemId || !itemType) {
178
- return null;
179
- }
180
- let command = [];
181
- let cwd = null;
182
- let parsedCmd = [];
183
- switch (itemType) {
184
- case 'commandexecution':
185
- command = typeof item.command === 'string' ? [item.command] : [];
186
- cwd = typeof item.cwd === 'string' ? item.cwd : null;
187
- parsedCmd = normalizeCommandActions(item.commandActions);
188
- break;
189
- case 'filechange':
190
- parsedCmd = normalizeFileChanges(item.changes);
191
- break;
192
- case 'websearch':
193
- parsedCmd = [{
194
- type: 'search',
195
- query: typeof item.query === 'string' ? item.query : '',
196
- path: null,
197
- }];
198
- break;
199
- case 'imageview':
200
- parsedCmd = [{ type: 'read', path: typeof item.path === 'string' ? item.path : null }];
201
- break;
202
- case 'mcptoolcall':
203
- command = [`MCP ${String(item.server ?? 'server')}/${String(item.tool ?? 'tool')}`];
204
- break;
205
- case 'dynamictoolcall':
206
- command = [`Tool ${item.namespace ? `${String(item.namespace)}/` : ''}${String(item.tool ?? 'tool')}`];
207
- break;
208
- case 'collabagenttoolcall':
209
- command = [`Agent ${String(item.tool ?? 'operation')}`];
210
- break;
211
- case 'imagegeneration':
212
- command = ['Image generation'];
213
- break;
214
- default:
215
- return null;
216
- }
217
- const exec = {
218
- callId: itemId,
219
- turnId,
220
- command,
221
- cwd,
222
- parsedCmd,
223
- };
224
- return {
225
- kind,
226
- turnId,
227
- exec,
228
- state: inferToolActivityState(exec),
229
- };
230
- }
231
- function normalizeCommandActions(value) {
232
- if (!Array.isArray(value)) {
233
- return [];
234
- }
235
- return value.map((entry) => {
236
- const type = typeof entry?.type === 'string' ? entry.type : '';
237
- if (type === 'listFiles') {
238
- return { ...entry, type: 'list_files' };
239
- }
240
- return entry;
241
- });
242
- }
243
- function normalizeFileChanges(value) {
244
- if (!Array.isArray(value)) {
245
- return [];
246
- }
247
- return value.map((entry) => ({
248
- type: 'apply_patch',
249
- path: typeof entry?.path === 'string' ? entry.path : null,
250
- }));
251
- }
252
- export function classifyAgentOutput(phase, completed) {
253
- if (!phase) {
254
- return completed ? 'final_answer' : 'commentary';
255
- }
256
- const normalized = phase.replace(/[^a-z]/gi, '').toLowerCase();
257
- if (normalized === 'final' || normalized === 'finalanswer' || normalized === 'answer' || normalized === 'response') {
258
- return 'final_answer';
259
- }
260
- return 'commentary';
261
- }
262
- export function inferToolActivityState(event) {
263
- const parsedTypes = (event.parsedCmd ?? [])
264
- .map((entry) => (typeof entry?.type === 'string' ? entry.type : ''))
265
- .filter(Boolean);
266
- if (parsedTypes.includes('search')) {
267
- return 'searching';
268
- }
269
- if (parsedTypes.includes('read') || parsedTypes.includes('list_files')) {
270
- return 'reading';
271
- }
272
- if (parsedTypes.some(type => ['write', 'edit', 'apply_patch', 'move', 'copy', 'delete', 'mkdir'].includes(type))) {
273
- return 'editing';
274
- }
275
- return 'running_command';
276
- }
277
- function extractRawExecCommandEvent(params) {
278
- const msg = params?.msg;
279
- if (!msg || typeof msg !== 'object') {
280
- return null;
281
- }
282
- const callId = typeof msg.call_id === 'string' ? msg.call_id : null;
283
- const turnId = typeof msg.turn_id === 'string' ? msg.turn_id : null;
284
- if (!callId || !turnId) {
285
- return null;
286
- }
287
- return {
288
- callId,
289
- turnId,
290
- command: Array.isArray(msg.command) ? msg.command.map((entry) => String(entry)) : [],
291
- cwd: msg.cwd ? String(msg.cwd) : null,
292
- parsedCmd: Array.isArray(msg.parsed_cmd) ? msg.parsed_cmd : [],
293
- };
294
- }
295
- function extractTurnId(params) {
296
- const candidates = [
297
- params?.turnId,
298
- params?.turn_id,
299
- params?.turn?.id,
300
- params?.turn?.turnId,
301
- params?.item?.turnId,
302
- params?.item?.turn_id,
303
- ];
304
- for (const candidate of candidates) {
305
- if (candidate !== null && candidate !== undefined && String(candidate).trim()) {
306
- return String(candidate);
307
- }
308
- }
309
- return null;
310
- }
311
- function extractItemId(value) {
312
- const candidates = [
313
- value?.itemId,
314
- value?.item_id,
315
- value?.id,
316
- value?.item?.id,
317
- ];
318
- for (const candidate of candidates) {
319
- if (candidate !== null && candidate !== undefined && String(candidate).trim()) {
320
- return String(candidate);
321
- }
322
- }
323
- return null;
324
- }
325
- function extractAgentPhase(value) {
326
- const phase = value?.phase ?? value?.item?.phase ?? null;
327
- return typeof phase === 'string' && phase.trim() ? phase : null;
328
- }
329
- function extractAgentDeltaText(params) {
330
- const candidates = [
331
- params?.delta,
332
- params?.textDelta,
333
- params?.contentDelta,
334
- params?.text,
335
- ];
336
- for (const candidate of candidates) {
337
- const text = extractTextCandidate(candidate);
338
- if (text) {
339
- return text;
340
- }
341
- }
342
- return null;
343
- }
344
- function extractCompletedAgentText(params) {
345
- const itemType = normalizeEventItemType(params?.item ?? params);
346
- if (!isRelayableTextItemType(itemType)) {
347
- return null;
348
- }
349
- const item = params?.item ?? params;
350
- const directText = extractTextCandidate(item?.text)
351
- ?? extractTextCandidate(item?.content)
352
- ?? extractTextCandidate(item?.value);
353
- if (directText !== null) {
354
- return directText;
355
- }
356
- return '';
357
- }
358
- function isRelayableTextItemType(itemType) {
359
- return itemType === 'agentmessage' || itemType === 'assistantmessage' || itemType === 'plan';
360
- }
361
- function outputKindForItemType(itemType, phase, completed) {
362
- if (itemType === 'plan') {
363
- return 'commentary';
364
- }
365
- return classifyAgentOutput(phase, completed);
366
- }
367
- function formatPlanUpdateText(params) {
368
- const lines = [];
369
- const explanation = extractTextCandidate(params?.explanation);
370
- if (explanation) {
371
- lines.push(explanation);
372
- }
373
- const plan = Array.isArray(params?.plan) ? params.plan : [];
374
- for (const entry of plan) {
375
- const step = extractTextCandidate(entry?.step);
376
- if (!step) {
377
- continue;
378
- }
379
- const status = typeof entry?.status === 'string' && entry.status ? ` [${entry.status}]` : '';
380
- lines.push(`- ${step}${status}`);
381
- }
382
- return lines.join('\n').trim() || null;
383
- }
384
- function normalizeEventItemType(value) {
385
- const raw = value?.type ?? value?.itemType ?? value?.item_type ?? value?.kind;
386
- if (typeof raw !== 'string') {
387
- return null;
388
- }
389
- return raw.replace(/[^a-z]/gi, '').toLowerCase();
390
- }
391
- function extractTextCandidate(value) {
392
- if (typeof value === 'string') {
393
- return value;
394
- }
395
- if (!value || typeof value !== 'object') {
396
- return null;
397
- }
398
- for (const key of ['text', 'delta', 'content', 'value']) {
399
- const candidate = value[key];
400
- if (typeof candidate === 'string') {
401
- return candidate;
402
- }
403
- }
404
- for (const key of ['parts', 'segments', 'content']) {
405
- const candidate = value[key];
406
- if (!Array.isArray(candidate)) {
407
- continue;
408
- }
409
- const text = candidate
410
- .map((entry) => extractTextCandidate(entry))
411
- .filter((entry) => entry !== null)
412
- .join('');
413
- if (text) {
414
- return text;
415
- }
416
- }
417
- return null;
418
- }
1
+ // Compatibility export for existing control-plane consumers.
2
+ export * from '../codex_app/activity.js';
@@ -0,0 +1,11 @@
1
+ import { CodexAuthRotationReason } from './state_types.js';
2
+ export declare function classifyCodexAuthRotationError(params: any): CodexAuthRotationReason | null;
3
+ export declare function isCodexAuthInvalidError(text: string): boolean;
4
+ export declare function isCodexQuotaLimitError(text: string): boolean;
5
+ export declare function formatCodexNotificationError(params: any): string;
6
+ export declare function isRetryableCodexTransportError(params: any): boolean;
7
+ export declare function collectCodexErrorText(params: any): string;
8
+ export declare function formatKnownCodexAccessError(raw: string): string | null;
9
+ export declare function isChatGptBackendAccessBlocked(raw: string): boolean;
10
+ export declare function cleanUserFacingError(raw: string): string;
11
+ export declare function clipUserFacingError(raw: string, limit?: number): string;
@@ -0,0 +1,124 @@
1
+ import { stringOrNull } from './shared_helpers.js';
2
+ export function classifyCodexAuthRotationError(params) {
3
+ const collected = collectCodexErrorText(params);
4
+ if (isChatGptBackendAccessBlocked(collected)) {
5
+ return null;
6
+ }
7
+ const code = stringOrNull(params?.error?.codexErrorInfo) ?? stringOrNull(params?.error?.code);
8
+ const message = stringOrNull(params?.error?.message) ?? '';
9
+ const text = `${code ?? ''}\n${message}\n${collected}`;
10
+ if (isCodexAuthInvalidError(text)) {
11
+ return 'auth_invalid';
12
+ }
13
+ if (isCodexQuotaLimitError(text)) {
14
+ return 'quota_limited';
15
+ }
16
+ if (code && /auth|unauthorized|forbidden|login/i.test(code)) {
17
+ return 'auth_invalid';
18
+ }
19
+ return /(not authenticated|unauthorized|forbidden|sign in|log in|login|auth)/i.test(message)
20
+ ? 'auth_invalid'
21
+ : null;
22
+ }
23
+ export function isCodexAuthInvalidError(text) {
24
+ return /token[_\s-]?invalidated/i.test(text)
25
+ || /authentication token has been invalidated/i.test(text)
26
+ || /\b401\s+unauthorized\b/i.test(text)
27
+ || /\bunauthorized\b/i.test(text)
28
+ || /\bnot authenticated\b/i.test(text)
29
+ || /\bsign in again\b/i.test(text)
30
+ || /\blog in again\b/i.test(text);
31
+ }
32
+ export function isCodexQuotaLimitError(text) {
33
+ return /usageLimitExceeded/i.test(text)
34
+ || /you['’]?ve hit your usage limit/i.test(text)
35
+ || /\busage limit(?:s)?\b/i.test(text)
36
+ || /\brate limit(?:ed|s)?\b/i.test(text)
37
+ || /\btoo many requests\b/i.test(text)
38
+ || /\binsufficient[_\s-]?quota\b/i.test(text)
39
+ || /\bquota exceeded\b/i.test(text)
40
+ || /\bbilling hard limit\b/i.test(text)
41
+ || /\bcredits? exhausted\b/i.test(text)
42
+ || /\bout of credits?\b/i.test(text)
43
+ || /\bcredits? limit\b/i.test(text);
44
+ }
45
+ export function formatCodexNotificationError(params) {
46
+ const collected = collectCodexErrorText(params);
47
+ const known = formatKnownCodexAccessError(collected);
48
+ if (known) {
49
+ return known;
50
+ }
51
+ const message = stringOrNull(params?.error?.message);
52
+ if (message) {
53
+ return clipUserFacingError(cleanUserFacingError(message));
54
+ }
55
+ const code = stringOrNull(params?.error?.codexErrorInfo) ?? stringOrNull(params?.error?.code);
56
+ if (code) {
57
+ return clipUserFacingError(cleanUserFacingError(code));
58
+ }
59
+ return clipUserFacingError(cleanUserFacingError(JSON.stringify(params?.error ?? params ?? {})));
60
+ }
61
+ export function isRetryableCodexTransportError(params) {
62
+ if (params?.willRetry !== true) {
63
+ return false;
64
+ }
65
+ const errorInfo = params?.error?.codexErrorInfo;
66
+ return Boolean(errorInfo && typeof errorInfo === 'object' && 'responseStreamDisconnected' in errorInfo);
67
+ }
68
+ export function collectCodexErrorText(params) {
69
+ const parts = [
70
+ stringOrNull(params?.error?.message),
71
+ stringOrNull(params?.error?.additionalDetails),
72
+ stringOrNull(params?.additionalDetails),
73
+ stringOrNull(params?.error?.codexErrorInfo),
74
+ stringOrNull(params?.error?.code),
75
+ ].filter((part) => part !== null);
76
+ if (parts.length > 0) {
77
+ return parts.join(' ');
78
+ }
79
+ try {
80
+ return JSON.stringify(params?.error ?? params ?? {});
81
+ }
82
+ catch {
83
+ return String(params?.error ?? params ?? '');
84
+ }
85
+ }
86
+ export function formatKnownCodexAccessError(raw) {
87
+ if (!isChatGptBackendAccessBlocked(raw)) {
88
+ return null;
89
+ }
90
+ const ray = raw.match(/\bcf-ray:\s*([A-Za-z0-9-]+)/i)?.[1]
91
+ ?? raw.match(/\bRay ID:\s*([A-Za-z0-9-]+)/i)?.[1]
92
+ ?? null;
93
+ return `ChatGPT backend 403 Forbidden: service network/proxy/IP is blocked, not necessarily auth.json invalid. Check HTTP_PROXY/HTTPS_PROXY in the FoxClaw env file and restart FoxClaw${ray ? ` (cf-ray: ${ray})` : ''}.`;
94
+ }
95
+ export function isChatGptBackendAccessBlocked(raw) {
96
+ const value = raw.toLowerCase();
97
+ const targetsChatGptBackend = value.includes('chatgpt.com/backend-api')
98
+ || value.includes('wss://chatgpt.com/backend-api');
99
+ const isForbidden = value.includes('403 forbidden')
100
+ || value.includes('status 403')
101
+ || value.includes('httpstatuscode":403')
102
+ || value.includes('httpstatuscode:403');
103
+ const looksLikeHtmlBlock = value.includes('<html')
104
+ || value.includes('text/html')
105
+ || value.includes('cf-ray')
106
+ || value.includes('unable to load site')
107
+ || value.includes('if you are using a vpn');
108
+ return targetsChatGptBackend && isForbidden && looksLikeHtmlBlock;
109
+ }
110
+ export function cleanUserFacingError(raw) {
111
+ return raw
112
+ .replace(/<script\b[\s\S]*?<\/script>/gi, ' ')
113
+ .replace(/<style\b[\s\S]*?<\/style>/gi, ' ')
114
+ .replace(/<[^>]+>/g, ' ')
115
+ .replace(/\s+/g, ' ')
116
+ .trim();
117
+ }
118
+ export function clipUserFacingError(raw, limit = 900) {
119
+ const cleaned = raw.trim();
120
+ if (cleaned.length <= limit) {
121
+ return cleaned;
122
+ }
123
+ return `${cleaned.slice(0, limit - 3)}...`;
124
+ }
@@ -0,0 +1,13 @@
1
+ import type { CodexAuthCandidateState } from '../store/database.js';
2
+ import { CodexAuthState, CodexAuthSwitchResult } from './state_types.js';
3
+ export declare function codexAuthDir(explicitAuthDir?: string | null): string;
4
+ export declare function listCodexAuthState(disabledNames?: Set<string>, candidateStates?: Map<string, CodexAuthCandidateState>, explicitAuthDir?: string | null): Promise<CodexAuthState>;
5
+ export declare function isCodexAuthCandidateName(name: string): boolean;
6
+ export declare function isCodexApiKeyAuthCandidate(candidatePath: string): Promise<boolean>;
7
+ export declare function codexAuthCandidateNameFromAddName(raw: string): string | null;
8
+ export declare function resolveCurrentAuthTarget(authDir: string, authPath: string): Promise<string | null>;
9
+ export declare function resolveAuthFinalTargetPath(startPath: string): Promise<string>;
10
+ export declare function authPathDisplayLabel(authPath: string): Promise<string>;
11
+ export declare function pointCodexAuthAtTarget(authDir: string, authPath: string, targetPath: string): Promise<void>;
12
+ export declare function restoreCodexAuthTarget(authDir: string, authPath: string, targetPath: string | null, regularAuthContents: string | null): Promise<void>;
13
+ export declare function switchCodexAuth(targetPath: string, explicitAuthDir?: string | null): Promise<CodexAuthSwitchResult>;