telecodex 0.1.5 → 0.1.6

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.
@@ -94,7 +94,6 @@ export function registerOperationalHandlers(deps) {
94
94
  textField("state", formatSessionRuntimeStatus(latestSession.runtimeStatus)),
95
95
  textField("state detail", latestSession.runtimeStatusDetail ?? "none"),
96
96
  textField("state updated", formatIsoTimestamp(latestSession.runtimeStatusUpdatedAt)),
97
- codeField("active turn", latestSession.activeTurnId ?? "none"),
98
97
  textField("active run", activeRun ? formatIsoTimestamp(activeRun.startedAt) : "none"),
99
98
  codeField("active run thread", activeRun?.threadId ?? "none"),
100
99
  textField("active run last event", activeRun?.lastEventType ?? "none"),
@@ -127,7 +126,6 @@ export function registerOperationalHandlers(deps) {
127
126
  title: "Queue",
128
127
  fields: [
129
128
  textField("state", formatSessionRuntimeStatus(session.runtimeStatus)),
130
- codeField("active turn", session.activeTurnId ?? "none"),
131
129
  textField("queue", queueDepth),
132
130
  ],
133
131
  sections: [
@@ -32,7 +32,7 @@ export async function handleUserInput(input) {
32
32
  chatId: numericChatId(session),
33
33
  messageThreadId: numericMessageThreadId(session),
34
34
  }, [
35
- `The current Codex task is still ${describeBusyStatus(session.runtimeStatus)}. Your message was added to the queue.`,
35
+ `Codex is still ${describeBusyStatus(session.runtimeStatus)}. Your message was added to the queue.`,
36
36
  `queue position: ${queueDepth}`,
37
37
  `queued at: ${formatIsoTimestamp(queued.createdAt)}`,
38
38
  "It will be processed automatically after the current run finishes.",
@@ -48,7 +48,7 @@ export async function handleUserInput(input) {
48
48
  sessionKey: session.sessionKey,
49
49
  event: {
50
50
  type: "turn.preparing",
51
- detail: "starting codex sdk run",
51
+ detail: "waiting for first Codex SDK event",
52
52
  },
53
53
  logger,
54
54
  });
@@ -78,17 +78,6 @@ export async function handleUserInput(input) {
78
78
  };
79
79
  }
80
80
  store.setOutputMessage(session.sessionKey, outputMessageId);
81
- const turnId = createLocalTurnId();
82
- await applySessionRuntimeEvent({
83
- bot,
84
- store,
85
- sessionKey: session.sessionKey,
86
- event: {
87
- type: "turn.started",
88
- turnId,
89
- },
90
- logger,
91
- });
92
81
  void runSessionPrompt({
93
82
  sessionKey: session.sessionKey,
94
83
  prompt,
@@ -96,7 +85,6 @@ export async function handleUserInput(input) {
96
85
  codex,
97
86
  buffers,
98
87
  bot,
99
- turnId,
100
88
  bufferKey,
101
89
  ...(logger ? { logger } : {}),
102
90
  });
@@ -118,7 +106,6 @@ export async function refreshSessionIfActiveTurnIsStale(session, store, codex, b
118
106
  sessionKey: latest.sessionKey,
119
107
  event: {
120
108
  type: "turn.failed",
121
- turnId: latest.activeTurnId,
122
109
  message: "The previous run was lost. Send the message again.",
123
110
  },
124
111
  logger,
@@ -179,10 +166,13 @@ export async function processNextQueuedInputForSession(sessionKey, store, codex,
179
166
  }
180
167
  }
181
168
  async function runSessionPrompt(input) {
182
- const { sessionKey, prompt, store, codex, buffers, bot, turnId, bufferKey, logger } = input;
169
+ const { sessionKey, prompt, store, codex, buffers, bot, bufferKey, logger } = input;
183
170
  const session = store.get(sessionKey);
184
171
  if (!session)
185
172
  return;
173
+ logger?.info("starting codex sdk run", {
174
+ ...sessionLogFields(session),
175
+ });
186
176
  try {
187
177
  const result = await codex.run({
188
178
  profile: {
@@ -203,8 +193,19 @@ async function runSessionPrompt(input) {
203
193
  callbacks: {
204
194
  onThreadStarted: async (threadId) => {
205
195
  store.bindThread(sessionKey, threadId);
196
+ logger?.info("codex sdk thread started", {
197
+ sessionKey,
198
+ threadId,
199
+ });
206
200
  },
207
201
  onEvent: async (event) => {
202
+ await applyRuntimeStateForSdkEvent({
203
+ event,
204
+ sessionKey,
205
+ store,
206
+ bot,
207
+ logger,
208
+ });
208
209
  await projectEventToTelegramBuffer(buffers, bufferKey, event);
209
210
  },
210
211
  },
@@ -219,11 +220,14 @@ async function runSessionPrompt(input) {
219
220
  sessionKey,
220
221
  event: {
221
222
  type: "turn.completed",
222
- turnId,
223
223
  },
224
224
  logger,
225
225
  });
226
226
  }
227
+ logger?.info("codex sdk run completed", {
228
+ sessionKey,
229
+ threadId: result.threadId,
230
+ });
227
231
  await buffers.complete(bufferKey, result.finalResponse || undefined);
228
232
  }
229
233
  catch (error) {
@@ -237,16 +241,18 @@ async function runSessionPrompt(input) {
237
241
  event: isAbortError(error)
238
242
  ? {
239
243
  type: "turn.interrupted",
240
- turnId,
241
244
  }
242
245
  : {
243
246
  type: "turn.failed",
244
- turnId,
245
247
  message: error instanceof Error ? error.message : String(error),
246
248
  },
247
249
  logger,
248
250
  });
249
251
  }
252
+ logger?.warn("codex sdk run failed", {
253
+ sessionKey,
254
+ error,
255
+ });
250
256
  if (isAbortError(error)) {
251
257
  await buffers.fail(bufferKey, "Current run interrupted.");
252
258
  }
@@ -258,13 +264,33 @@ async function runSessionPrompt(input) {
258
264
  await processNextQueuedInputForSession(sessionKey, store, codex, buffers, bot, logger);
259
265
  }
260
266
  }
267
+ async function applyRuntimeStateForSdkEvent(input) {
268
+ if (input.event.type !== "turn.started") {
269
+ return;
270
+ }
271
+ const session = input.store.get(input.sessionKey);
272
+ if (!session)
273
+ return;
274
+ if (session.runtimeStatus === "running") {
275
+ return;
276
+ }
277
+ await applySessionRuntimeEvent({
278
+ bot: input.bot,
279
+ store: input.store,
280
+ sessionKey: input.sessionKey,
281
+ event: {
282
+ type: "turn.started",
283
+ },
284
+ logger: input.logger,
285
+ });
286
+ }
261
287
  async function projectEventToTelegramBuffer(buffers, key, event) {
262
288
  switch (event.type) {
263
289
  case "thread.started":
264
290
  buffers.note(key, `thread started: ${event.thread_id}`);
265
291
  return;
266
292
  case "turn.started":
267
- buffers.note(key, "started processing");
293
+ buffers.markTurnStarted(key);
268
294
  return;
269
295
  case "turn.completed":
270
296
  buffers.note(key, `token usage: in ${event.usage.input_tokens}, out ${event.usage.output_tokens}, cached ${event.usage.cached_input_tokens}`);
@@ -357,9 +383,6 @@ function projectTodoList(buffers, key, item) {
357
383
  buffers.setPlan(key, lines.join("\n"));
358
384
  }
359
385
  }
360
- function createLocalTurnId() {
361
- return `sdk-turn-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
362
- }
363
386
  function toSdkInput(input) {
364
387
  return input;
365
388
  }
@@ -31,11 +31,10 @@ export function sessionLogFields(session) {
31
31
  runtimeStatus: session.runtimeStatus,
32
32
  runtimeStatusDetail: session.runtimeStatusDetail,
33
33
  codexThreadId: session.codexThreadId,
34
- activeTurnId: session.activeTurnId,
35
34
  };
36
35
  }
37
36
  export function isSessionBusy(session) {
38
- return session.runtimeStatus === "preparing" || session.runtimeStatus === "running" || session.activeTurnId != null;
37
+ return session.runtimeStatus === "preparing" || session.runtimeStatus === "running";
39
38
  }
40
39
  export function describeBusyStatus(status) {
41
40
  switch (status) {
@@ -86,7 +86,12 @@ export class CodexSdkRuntime {
86
86
  let finalResponse = "";
87
87
  let usage = null;
88
88
  let threadId = input.initialThreadId;
89
- for await (const event of streamed.events) {
89
+ const iterator = streamed.events[Symbol.asyncIterator]();
90
+ while (true) {
91
+ const next = await iterator.next();
92
+ if (next.done)
93
+ break;
94
+ const event = next.value;
90
95
  const activeRun = this.activeRuns.get(input.sessionKey);
91
96
  if (activeRun) {
92
97
  activeRun.lastEventAt = new Date().toISOString();
@@ -13,14 +13,12 @@ export function reduceSessionRuntimeState(session, event, updatedAt = new Date()
13
13
  status: "preparing",
14
14
  detail: event.detail ?? null,
15
15
  updatedAt,
16
- activeTurnId: null,
17
16
  };
18
17
  case "turn.started":
19
18
  return {
20
19
  status: "running",
21
20
  detail: null,
22
21
  updatedAt,
23
- activeTurnId: event.turnId,
24
22
  };
25
23
  case "turn.completed":
26
24
  case "turn.interrupted":
@@ -28,14 +26,12 @@ export function reduceSessionRuntimeState(session, event, updatedAt = new Date()
28
26
  status: "idle",
29
27
  detail: null,
30
28
  updatedAt,
31
- activeTurnId: null,
32
29
  };
33
30
  case "turn.failed":
34
31
  return {
35
32
  status: "failed",
36
33
  detail: event.message?.trim() || null,
37
34
  updatedAt,
38
- activeTurnId: null,
39
35
  };
40
36
  }
41
37
  }
@@ -320,7 +320,6 @@ function mapStoredSession(stored, runtimeState, outputMessageId) {
320
320
  status: "idle",
321
321
  detail: null,
322
322
  updatedAt: stored.updatedAt,
323
- activeTurnId: null,
324
323
  };
325
324
  return {
326
325
  ...stored,
@@ -328,7 +327,6 @@ function mapStoredSession(stored, runtimeState, outputMessageId) {
328
327
  runtimeStatus: runtime.status,
329
328
  runtimeStatusDetail: runtime.detail,
330
329
  runtimeStatusUpdatedAt: runtime.updatedAt,
331
- activeTurnId: runtime.activeTurnId,
332
330
  outputMessageId: outputMessageId ?? null,
333
331
  };
334
332
  }
@@ -1,13 +1,25 @@
1
- import { GrammyError } from "grammy";
1
+ import { GrammyError, HttpError } from "grammy";
2
2
  import { renderPlainChunksForTelegram } from "./renderer.js";
3
3
  import { splitTelegramHtml } from "./splitMessage.js";
4
4
  const telegramCooldownByClient = new WeakMap();
5
+ const MAX_TELEGRAM_RETRY_ATTEMPTS = 5;
6
+ const TELEGRAM_NETWORK_RETRY_BASE_MS = 100;
7
+ const TELEGRAM_NETWORK_RETRY_MAX_MS = 1_000;
8
+ const RETRYABLE_NETWORK_ERROR_CODES = new Set([
9
+ "ECONNRESET",
10
+ "ECONNREFUSED",
11
+ "EPIPE",
12
+ "ETIMEDOUT",
13
+ "ESOCKETTIMEDOUT",
14
+ "ENOTFOUND",
15
+ "EAI_AGAIN",
16
+ ]);
5
17
  export async function sendHtmlMessage(bot, input, logger) {
6
18
  return retryTelegramCall(bot.api, () => bot.api.sendMessage(input.chatId, input.text, {
7
19
  ...(input.messageThreadId == null ? {} : { message_thread_id: input.messageThreadId }),
8
20
  parse_mode: "HTML",
9
21
  link_preview_options: { is_disabled: true },
10
- }), logger, "telegram send rate limited", {
22
+ }), logger, "telegram send retry scheduled", {
11
23
  chatId: input.chatId,
12
24
  messageThreadId: input.messageThreadId,
13
25
  });
@@ -29,9 +41,11 @@ export async function sendPlainChunks(bot, input, logger) {
29
41
  export async function sendTypingAction(bot, input, logger) {
30
42
  await retryTelegramCall(bot.api, () => bot.api.sendChatAction(input.chatId, "typing", {
31
43
  ...(input.messageThreadId == null ? {} : { message_thread_id: input.messageThreadId }),
32
- }), logger, "telegram chat action rate limited", {
44
+ }), logger, "telegram chat action retry scheduled", {
33
45
  chatId: input.chatId,
34
46
  messageThreadId: input.messageThreadId,
47
+ }, {
48
+ allowNetworkRetry: true,
35
49
  });
36
50
  }
37
51
  export async function replaceOrSendHtmlChunks(bot, input, logger) {
@@ -84,9 +98,11 @@ export async function editHtmlMessage(bot, input, logger) {
84
98
  await retryTelegramCall(bot.api, () => bot.api.editMessageText(input.chatId, input.messageId, input.text, {
85
99
  parse_mode: "HTML",
86
100
  link_preview_options: { is_disabled: true },
87
- }), logger, "telegram edit rate limited", {
101
+ }), logger, "telegram edit retry scheduled", {
88
102
  chatId: input.chatId,
89
103
  messageId: input.messageId,
104
+ }, {
105
+ allowNetworkRetry: true,
90
106
  });
91
107
  }
92
108
  export function isMessageNotModifiedError(error) {
@@ -113,29 +129,90 @@ function retryAfterMs(error) {
113
129
  }
114
130
  return null;
115
131
  }
132
+ function retryPlan(error, attempt) {
133
+ const rateLimitDelayMs = retryAfterMs(error);
134
+ if (rateLimitDelayMs != null) {
135
+ return {
136
+ kind: "rate-limit",
137
+ delayMs: rateLimitDelayMs + 250,
138
+ };
139
+ }
140
+ if (isRetryableNetworkError(error)) {
141
+ return {
142
+ kind: "network",
143
+ delayMs: Math.min(TELEGRAM_NETWORK_RETRY_BASE_MS * 2 ** attempt, TELEGRAM_NETWORK_RETRY_MAX_MS),
144
+ };
145
+ }
146
+ return null;
147
+ }
116
148
  function descriptionOf(error) {
117
149
  return typeof error.description === "string" ? error.description : null;
118
150
  }
119
- export async function retryTelegramCall(cooldownKey, operation, logger, message, context) {
151
+ function isRetryableNetworkError(error) {
152
+ if (!(error instanceof HttpError))
153
+ return false;
154
+ return isRetryableNetworkCause(error.error);
155
+ }
156
+ function isRetryableNetworkCause(error) {
157
+ if (error instanceof Error && error.name === "AbortError") {
158
+ return false;
159
+ }
160
+ const code = readErrorCode(error);
161
+ if (code && RETRYABLE_NETWORK_ERROR_CODES.has(code)) {
162
+ return true;
163
+ }
164
+ const message = readErrorMessage(error);
165
+ if (!message)
166
+ return false;
167
+ return (message.includes("socket hang up") ||
168
+ message.includes("connection reset") ||
169
+ message.includes("network request failed") ||
170
+ message.includes("fetch failed") ||
171
+ message.includes("timed out") ||
172
+ message.includes("timeout"));
173
+ }
174
+ function readErrorCode(error) {
175
+ if (typeof error !== "object" || error == null || !("code" in error))
176
+ return null;
177
+ const code = error.code;
178
+ return typeof code === "string" && code ? code : null;
179
+ }
180
+ function readErrorMessage(error) {
181
+ if (error instanceof Error) {
182
+ return error.message.toLowerCase();
183
+ }
184
+ if (typeof error === "string") {
185
+ return error.toLowerCase();
186
+ }
187
+ return null;
188
+ }
189
+ export async function retryTelegramCall(cooldownKey, operation, logger, message, context, options) {
120
190
  for (let attempt = 0;; attempt += 1) {
121
191
  await waitForTelegramCooldown(cooldownKey);
122
192
  try {
123
193
  return await operation();
124
194
  }
125
195
  catch (error) {
126
- const waitMs = retryAfterMs(error);
127
- if (waitMs == null || attempt >= 5) {
196
+ const rateLimitDelayMs = retryAfterMs(error);
197
+ const retry = options?.allowNetworkRetry === true
198
+ ? retryPlan(error, attempt)
199
+ : rateLimitDelayMs == null
200
+ ? null
201
+ : {
202
+ kind: "rate-limit",
203
+ delayMs: rateLimitDelayMs + 250,
204
+ };
205
+ if (retry == null || attempt >= MAX_TELEGRAM_RETRY_ATTEMPTS) {
128
206
  throw error;
129
207
  }
130
- const cooldownMs = waitMs + 250;
131
208
  logger?.warn(message, {
132
209
  ...context,
133
210
  attempt: attempt + 1,
134
- retryAfterMs: waitMs,
135
- sharedCooldownMs: cooldownMs,
211
+ retryKind: retry.kind,
212
+ retryDelayMs: retry.delayMs,
136
213
  error,
137
214
  });
138
- await applyTelegramCooldown(cooldownKey, cooldownMs);
215
+ await applyTelegramCooldown(cooldownKey, retry.delayMs);
139
216
  }
140
217
  }
141
218
  }
@@ -1,15 +1,20 @@
1
1
  import { editHtmlMessage, isMessageNotModifiedError, replaceOrSendHtmlChunks, sendHtmlMessage, sendTypingAction, shouldFallbackToNewMessage, } from "./delivery.js";
2
2
  import { renderMarkdownForTelegram, renderPlainChunksForTelegram, renderPlainForTelegram } from "./renderer.js";
3
- const ACTIVITY_PULSE_INTERVAL_MS = 4_000;
3
+ const DEFAULT_ACTIVITY_PULSE_INTERVAL_MS = 4_000;
4
+ const DEFAULT_ACTIVITY_IDLE_MS = 60_000;
4
5
  export class MessageBuffer {
5
6
  bot;
6
7
  updateIntervalMs;
7
8
  logger;
8
9
  states = new Map();
9
- constructor(bot, updateIntervalMs, logger) {
10
+ activityPulseIntervalMs;
11
+ activityIdleMs;
12
+ constructor(bot, updateIntervalMs, logger, input) {
10
13
  this.bot = bot;
11
14
  this.updateIntervalMs = updateIntervalMs;
12
15
  this.logger = logger;
16
+ this.activityPulseIntervalMs = input?.activityPulseIntervalMs ?? DEFAULT_ACTIVITY_PULSE_INTERVAL_MS;
17
+ this.activityIdleMs = input?.activityIdleMs ?? DEFAULT_ACTIVITY_IDLE_MS;
13
18
  }
14
19
  async create(key, input) {
15
20
  const previous = this.states.get(key);
@@ -22,12 +27,13 @@ export class MessageBuffer {
22
27
  const message = await sendHtmlMessage(this.bot, {
23
28
  chatId: input.chatId,
24
29
  messageThreadId: input.messageThreadId,
25
- text: "Codex is working...",
30
+ text: "Starting Codex...",
26
31
  }, this.logger);
27
32
  const state = {
28
33
  chatId: input.chatId,
29
34
  messageThreadId: input.messageThreadId,
30
35
  messageId: message.message_id,
36
+ phase: "starting",
31
37
  text: "",
32
38
  progressLines: [],
33
39
  planText: "",
@@ -36,6 +42,7 @@ export class MessageBuffer {
36
42
  timer: null,
37
43
  activityTimer: null,
38
44
  activityInFlight: false,
45
+ lastActivityAt: Date.now(),
39
46
  lastSentText: "",
40
47
  queue: Promise.resolve(),
41
48
  };
@@ -51,6 +58,7 @@ export class MessageBuffer {
51
58
  if (!state)
52
59
  return;
53
60
  state.text = text;
61
+ this.touchActivity(state);
54
62
  this.scheduleFlush(key, state);
55
63
  }
56
64
  note(key, line) {
@@ -68,6 +76,17 @@ export class MessageBuffer {
68
76
  if (state.progressLines.length > 8) {
69
77
  state.progressLines.splice(0, state.progressLines.length - 8);
70
78
  }
79
+ this.touchActivity(state);
80
+ this.scheduleFlush(key, state);
81
+ }
82
+ markTurnStarted(key) {
83
+ const state = this.states.get(key);
84
+ if (!state)
85
+ return;
86
+ if (state.phase === "running")
87
+ return;
88
+ state.phase = "running";
89
+ this.touchActivity(state);
71
90
  this.scheduleFlush(key, state);
72
91
  }
73
92
  setPlan(key, text) {
@@ -75,6 +94,7 @@ export class MessageBuffer {
75
94
  if (!state)
76
95
  return;
77
96
  state.planText = text.trim();
97
+ this.touchActivity(state);
78
98
  this.scheduleFlush(key, state);
79
99
  }
80
100
  setReasoningSummary(key, text) {
@@ -82,6 +102,7 @@ export class MessageBuffer {
82
102
  if (!state)
83
103
  return;
84
104
  state.reasoningSummaryText = text.trim();
105
+ this.touchActivity(state);
85
106
  this.scheduleFlush(key, state);
86
107
  }
87
108
  setToolOutput(key, text) {
@@ -89,6 +110,7 @@ export class MessageBuffer {
89
110
  if (!state)
90
111
  return;
91
112
  state.toolOutputText = truncateTail(text.replace(/\r/g, "").trim(), 2000);
113
+ this.touchActivity(state);
92
114
  this.scheduleFlush(key, state);
93
115
  }
94
116
  rename(from, to) {
@@ -144,7 +166,7 @@ export class MessageBuffer {
144
166
  const latest = this.states.get(key);
145
167
  if (!latest)
146
168
  return;
147
- const text = renderPlainForTelegram(truncateForEdit(composePendingText(latest)));
169
+ const text = renderPlainForTelegram(truncateForEdit(composePendingText(latest), latest.phase));
148
170
  if (text === latest.lastSentText)
149
171
  return;
150
172
  await this.safeEdit(latest, text);
@@ -193,10 +215,12 @@ export class MessageBuffer {
193
215
  }
194
216
  }
195
217
  startActivityPulse(state) {
218
+ if (state.activityTimer)
219
+ return;
196
220
  void this.sendActivityPulse(state);
197
221
  const timer = setInterval(() => {
198
222
  void this.sendActivityPulse(state);
199
- }, ACTIVITY_PULSE_INTERVAL_MS);
223
+ }, this.activityPulseIntervalMs);
200
224
  timer.unref?.();
201
225
  state.activityTimer = timer;
202
226
  }
@@ -207,6 +231,10 @@ export class MessageBuffer {
207
231
  state.activityTimer = null;
208
232
  }
209
233
  async sendActivityPulse(state) {
234
+ if (Date.now() - state.lastActivityAt >= this.activityIdleMs) {
235
+ this.stopActivityPulse(state);
236
+ return;
237
+ }
210
238
  if (state.activityInFlight)
211
239
  return;
212
240
  state.activityInFlight = true;
@@ -227,6 +255,12 @@ export class MessageBuffer {
227
255
  state.activityInFlight = false;
228
256
  }
229
257
  }
258
+ touchActivity(state) {
259
+ state.lastActivityAt = Date.now();
260
+ if (!state.activityTimer) {
261
+ this.startActivityPulse(state);
262
+ }
263
+ }
230
264
  async replaceWithChunks(state, chunks) {
231
265
  const messageId = await replaceOrSendHtmlChunks(this.bot, {
232
266
  chatId: state.chatId,
@@ -248,13 +282,13 @@ export class MessageBuffer {
248
282
  await run;
249
283
  }
250
284
  }
251
- function truncateForEdit(text) {
285
+ function truncateForEdit(text, phase) {
252
286
  if (text.length <= 3800)
253
- return text || "Codex is working...";
287
+ return text || pendingBanner(phase);
254
288
  return `${text.slice(0, 3800)}\n\n...`;
255
289
  }
256
290
  function composePendingText(state) {
257
- const sections = ["Codex is working..."];
291
+ const sections = [pendingBanner(state.phase)];
258
292
  if (state.planText) {
259
293
  sections.push(`[Plan]\n${state.planText}`);
260
294
  }
@@ -275,6 +309,9 @@ function composePendingText(state) {
275
309
  }
276
310
  return sections.join("\n\n");
277
311
  }
312
+ function pendingBanner(phase) {
313
+ return phase === "running" ? "Codex is working..." : "Starting Codex...";
314
+ }
278
315
  function truncateTail(text, maxLength) {
279
316
  if (text.length <= maxLength)
280
317
  return text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "telecodex",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Telegram bridge for local Codex.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,7 +52,7 @@
52
52
  "@grammyjs/parse-mode": "^2.3.0",
53
53
  "@grammyjs/runner": "^2.0.3",
54
54
  "@napi-rs/keyring": "^1.2.0",
55
- "@openai/codex-sdk": "^0.120.0",
55
+ "@openai/codex-sdk": "^0.121.0",
56
56
  "clipboardy": "^4.0.0",
57
57
  "grammy": "^1.42.0",
58
58
  "markdown-it": "^14.1.0",