@ouro.bot/cli 0.1.0-alpha.644 → 0.1.0-alpha.645

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.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.645",
6
+ "changes": [
7
+ "Rescue openai-codex refresh when the vault refresh token has rotated by retrying with the local Codex login, and classify sign-in-required refresh failures as human-required."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.644",
6
12
  "changes": [
@@ -129,6 +129,29 @@ function parseRefreshFailure(body) {
129
129
  }
130
130
  return body.trim() || "refresh endpoint returned an empty error body";
131
131
  }
132
+ function readLocalCodexAuthTokens(homeDir) {
133
+ const authPath = path.join(homeDir, ".codex", "auth.json");
134
+ let raw;
135
+ try {
136
+ raw = fs.readFileSync(authPath, "utf8");
137
+ }
138
+ catch {
139
+ return { status: "missing" };
140
+ }
141
+ try {
142
+ const parsed = JSON.parse(raw);
143
+ if (!parsed.tokens || typeof parsed.tokens !== "object")
144
+ return { status: "invalid" };
145
+ const accessToken = typeof parsed.tokens.access_token === "string" ? parsed.tokens.access_token.trim() : "";
146
+ const refreshToken = typeof parsed.tokens.refresh_token === "string" ? parsed.tokens.refresh_token.trim() : "";
147
+ if (!accessToken || !refreshToken)
148
+ return { status: "invalid" };
149
+ return { status: "ready", tokens: { accessToken, refreshToken } };
150
+ }
151
+ catch {
152
+ return { status: "invalid" };
153
+ }
154
+ }
132
155
  async function updateLocalCodexAuthIfUnchanged(input) {
133
156
  const authPath = path.join(input.homeDir, ".codex", "auth.json");
134
157
  let raw;
@@ -190,6 +213,14 @@ async function requestOpenAICodexTokenRefresh(input) {
190
213
  return { ok: false, detail: "refresh endpoint returned no access_token" };
191
214
  return { ok: true, accessToken, refreshToken };
192
215
  }
216
+ function refreshFailureRequiresHuman(refresh) {
217
+ const detail = refresh.detail.toLowerCase();
218
+ return refresh.status === 401
219
+ || detail.includes("signing in again")
220
+ || detail.includes("already been used")
221
+ || detail.includes("invalid_grant")
222
+ || detail.includes("refresh token expired");
223
+ }
193
224
  async function refreshOpenAICodexProviderCredentials(agentName, options = {}) {
194
225
  const now = options.now ?? new Date();
195
226
  const readRecord = options.readRecord ?? provider_credentials_1.readProviderCredentialRecord;
@@ -212,25 +243,52 @@ async function refreshOpenAICodexProviderCredentials(agentName, options = {}) {
212
243
  }
213
244
  const oldAccessToken = recordCredentialString(record, "oauthAccessToken");
214
245
  const oldRefreshToken = recordCredentialString(record, "refreshToken");
215
- if (!oldRefreshToken) {
246
+ const homeDir = options.homeDir ?? os.homedir();
247
+ const fallbackLocalAuth = oldRefreshToken ? undefined : readLocalCodexAuthTokens(homeDir);
248
+ let refreshSource = oldRefreshToken
249
+ ? { source: "vault", accessToken: oldAccessToken, refreshToken: oldRefreshToken }
250
+ : fallbackLocalAuth?.status === "ready"
251
+ ? { source: "local-codex-auth", accessToken: fallbackLocalAuth.tokens.accessToken, refreshToken: fallbackLocalAuth.tokens.refreshToken }
252
+ : undefined;
253
+ if (!refreshSource) {
216
254
  return {
217
255
  ok: false,
218
256
  actor: "human-required",
219
- message: `openai-codex has no saved refresh token for ${agentName}. Run '${authCommand(agentName)}'.`,
257
+ message: `openai-codex has no saved refresh token for ${agentName} and no usable local Codex login to import. Run '${authCommand(agentName)}'.`,
220
258
  };
221
259
  }
222
260
  (0, runtime_1.emitNervesEvent)({
223
261
  component: "engine",
224
262
  event: "engine.openai_codex_token_refresh_start",
225
263
  message: "refreshing openai-codex OAuth token",
226
- meta: { agentName, reason: options.reason ?? "unspecified" },
264
+ meta: { agentName, reason: options.reason ?? "unspecified", source: refreshSource.source },
227
265
  });
228
- const refresh = await requestOpenAICodexTokenRefresh({
229
- refreshToken: oldRefreshToken,
266
+ let refresh = await requestOpenAICodexTokenRefresh({
267
+ refreshToken: refreshSource.refreshToken,
230
268
  fetchImpl: options.fetchImpl ?? fetch,
231
269
  });
270
+ if (!refresh.ok && oldRefreshToken) {
271
+ const retryLocalAuth = readLocalCodexAuthTokens(homeDir);
272
+ if (retryLocalAuth.status === "ready" && retryLocalAuth.tokens.refreshToken !== oldRefreshToken) {
273
+ (0, runtime_1.emitNervesEvent)({
274
+ component: "engine",
275
+ event: "engine.openai_codex_token_refresh_local_rescue",
276
+ message: "retrying openai-codex OAuth refresh with local Codex auth tokens",
277
+ meta: { agentName, reason: options.reason ?? "unspecified", status: refresh.status ?? "none", detail: refresh.detail },
278
+ });
279
+ refreshSource = {
280
+ source: "local-codex-auth",
281
+ accessToken: retryLocalAuth.tokens.accessToken,
282
+ refreshToken: retryLocalAuth.tokens.refreshToken,
283
+ };
284
+ refresh = await requestOpenAICodexTokenRefresh({
285
+ refreshToken: refreshSource.refreshToken,
286
+ fetchImpl: options.fetchImpl ?? fetch,
287
+ });
288
+ }
289
+ }
232
290
  if (!refresh.ok) {
233
- const actor = refresh.status === 401 ? "human-required" : "agent-runnable";
291
+ const actor = refreshFailureRequiresHuman(refresh) ? "human-required" : "agent-runnable";
234
292
  (0, runtime_1.emitNervesEvent)({
235
293
  level: actor === "human-required" ? "warn" : "error",
236
294
  component: "engine",
@@ -240,6 +298,7 @@ async function refreshOpenAICodexProviderCredentials(agentName, options = {}) {
240
298
  agentName,
241
299
  reason: options.reason ?? "unspecified",
242
300
  actor,
301
+ source: refreshSource.source,
243
302
  ...(refresh.status ? { status: refresh.status } : {}),
244
303
  detail: refresh.detail,
245
304
  },
@@ -266,10 +325,10 @@ async function refreshOpenAICodexProviderCredentials(agentName, options = {}) {
266
325
  provenance: { source: record.provenance.source },
267
326
  now,
268
327
  });
269
- const localAuth = await updateLocalCodexAuthIfUnchanged({
270
- homeDir: options.homeDir ?? os.homedir(),
271
- oldAccessToken,
272
- oldRefreshToken,
328
+ const localAuthSync = await updateLocalCodexAuthIfUnchanged({
329
+ homeDir,
330
+ oldAccessToken: refreshSource.accessToken,
331
+ oldRefreshToken: refreshSource.refreshToken,
273
332
  newAccessToken: refresh.accessToken,
274
333
  newRefreshToken: refresh.refreshToken,
275
334
  now,
@@ -282,7 +341,8 @@ async function refreshOpenAICodexProviderCredentials(agentName, options = {}) {
282
341
  agentName,
283
342
  reason: options.reason ?? "unspecified",
284
343
  credentialRevision: updated.revision,
285
- localCodexAuth: localAuth,
344
+ source: refreshSource.source,
345
+ localCodexAuth: localAuthSync,
286
346
  },
287
347
  });
288
348
  return { ok: true, refreshed: true, record: updated };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.644",
3
+ "version": "0.1.0-alpha.645",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",