jeopi-cli 16.4.1 → 16.4.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "jeopi-cli",
4
- "version": "16.4.1",
4
+ "version": "16.4.2",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/akillness/jeopi",
7
7
  "author": "Can Boluk",
@@ -57,17 +57,17 @@
57
57
  "@agentclientprotocol/sdk": "0.25.0",
58
58
  "@babel/parser": "^7.29.7",
59
59
  "@mozilla/readability": "^0.6.0",
60
- "jeopi-hashline": "16.4.1",
61
- "jeopi-stats": "16.4.1",
62
- "jeopi-agent-core": "16.4.1",
63
- "jeopi-ai": "16.4.1",
64
- "jeopi-catalog": "16.4.1",
65
- "jeopi-mnemopi": "16.4.1",
66
- "jeopi-natives": "16.4.1",
67
- "jeopi-tui": "16.4.1",
68
- "jeopi-utils": "16.4.1",
69
- "jeopi-wire": "16.4.1",
70
- "jeopi-snapcompact": "16.4.1",
60
+ "jeopi-hashline": "16.4.2",
61
+ "jeopi-stats": "16.4.2",
62
+ "jeopi-agent-core": "16.4.2",
63
+ "jeopi-ai": "16.4.2",
64
+ "jeopi-catalog": "16.4.2",
65
+ "jeopi-mnemopi": "16.4.2",
66
+ "jeopi-natives": "16.4.2",
67
+ "jeopi-tui": "16.4.2",
68
+ "jeopi-utils": "16.4.2",
69
+ "jeopi-wire": "16.4.2",
70
+ "jeopi-snapcompact": "16.4.2",
71
71
  "@opentelemetry/api": "^1.9.1",
72
72
  "@opentelemetry/context-async-hooks": "^2.7.1",
73
73
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -522,6 +522,17 @@ const RETRY_BACKOFF_MAX_DELAY_MS = 8_000;
522
522
  const REFUSAL_BACKOFF_BASE_MS = 2_000;
523
523
  const REFUSAL_BACKOFF_MAX_MS = 30_000;
524
524
  const REFUSAL_BACKOFF_BUDGET_MS = 30 * 60_000;
525
+ /**
526
+ * Consecutive-use cap on the `reasoning_extraction` fast-degrade path (strip
527
+ * unsigned thinking, resend same-model, zero delay — see
528
+ * `#reasoningExtractionDegradeStreak` below). Bounds the worst case where a
529
+ * pathological classifier state (documented: 1,164 refusals over ~4 hours in
530
+ * one incident) refuses every turn's fresh thinking in a row — after this
531
+ * many back-to-back fast-degrades with no intervening success, fall through
532
+ * to the Rung-4 ladder above, which already wall-clock-bounds via
533
+ * {@link REFUSAL_BACKOFF_BUDGET_MS}.
534
+ */
535
+ const REASONING_EXTRACTION_DEGRADE_STREAK_CAP = 3;
525
536
 
526
537
  function positiveEnvMs(name: string): number | undefined {
527
538
  const raw = process.env[name];
@@ -1611,13 +1622,31 @@ export class AgentSession {
1611
1622
  /** Rung-4 classifier-refusal resend streak (post fallback-ladder). */
1612
1623
  #refusalBackoff: { startedAt: number; attempt: number } | undefined = undefined;
1613
1624
  /**
1614
- * One-shot latch: `reasoning_extraction` classifier refusals are payload-shape-bound
1615
- * (see advisor/runtime.ts `#includeThinking` degrade) — resending the identical batch
1616
- * predictably re-refuses. Set the first time `#handleRetryableError` strips unsigned
1617
- * thinking/redactedThinking from active context and resends on the same model; never
1618
- * reset, mirroring the advisor's permanent-for-the-session degrade.
1619
- */
1620
- #reasoningExtractionDegraded = false;
1625
+ * `reasoning_extraction` classifier refusals are payload-shape-bound (see
1626
+ * advisor/runtime.ts `#includeThinking` degrade): resending the IDENTICAL
1627
+ * batch predictably re-refuses, so a bare retry can't help. But this is a
1628
+ * point-in-time strip of the CURRENT active context, not a persistent
1629
+ * setting — unlike the advisor's permanent-for-the-session
1630
+ * `includeThinking` flag, it does not prevent NEW unsigned thinking from a
1631
+ * LATER turn (a fresh advisor exchange, another interrupt, plain
1632
+ * continued conversation) from independently triggering its own,
1633
+ * genuinely-different refusal. A one-shot latch here previously left every
1634
+ * occurrence after the first to fall through to the generic Rung-4
1635
+ * backoff ladder unstripped — resending the same unstripped payload until
1636
+ * the wall-clock budget expired and the turn failed visibly.
1637
+ *
1638
+ * Fixed by gating on whether {@link #stripUnsignedThinkingForRefusalDegrade}
1639
+ * actually found something to strip THIS time (it already returns
1640
+ * `undefined` when there is nothing new to remove, which is itself proof
1641
+ * the resend would be payload-identical and degrading again cannot help)
1642
+ * rather than a permanent flag — each distinct occurrence gets its own
1643
+ * degrade attempt. Bounded by {@link REASONING_EXTRACTION_DEGRADE_STREAK_CAP}
1644
+ * consecutive uses (reset on any turn that actually succeeds) so a
1645
+ * pathological classifier state that refuses every turn's fresh thinking
1646
+ * in a row still falls through to the wall-clock-bounded ladder instead
1647
+ * of resending at zero delay indefinitely.
1648
+ */
1649
+ #reasoningExtractionDegradeStreak = 0;
1621
1650
  #retryPromise: Promise<void> | undefined = undefined;
1622
1651
  #retryResolve: (() => void) | undefined = undefined;
1623
1652
  #activeRetryFallback: ActiveRetryFallbackState | undefined = undefined;
@@ -3674,6 +3703,7 @@ export class AgentSession {
3674
3703
  });
3675
3704
  this.#retryAttempt = 0;
3676
3705
  this.#refusalBackoff = undefined;
3706
+ this.#reasoningExtractionDegradeStreak = 0;
3677
3707
  }
3678
3708
  if (assistantMsg.provider === "opencode-go") {
3679
3709
  this.#modelRegistry.authStorage.recordUsageCost(assistantMsg.provider, assistantMsg.usage.cost.total, {
@@ -13224,29 +13254,39 @@ export class AgentSession {
13224
13254
  const errorMessage = message.errorMessage || "Unknown error";
13225
13255
  // `reasoning_extraction` is payload-shape-bound (see advisor/runtime.ts): resending
13226
13256
  // the identical batch predictably re-refuses, unlike probabilistic classifier flags
13227
- // where backoff/fallback genuinely help. Degrade once — strip unsigned
13228
- // thinking/redactedThinking from active context and resend immediately on the same
13229
- // model — before falling through to the generic fallback-pin / backoff ladder below,
13230
- // which would otherwise retry the unchanged, re-refusing payload.
13257
+ // where backoff/fallback genuinely help. Strip unsigned thinking/redactedThinking
13258
+ // from active context and resend immediately on the same model — before falling
13259
+ // through to the generic fallback-pin / backoff ladder below, which would otherwise
13260
+ // retry the unchanged, re-refusing payload. Gated on the strip itself finding
13261
+ // something to remove THIS time (not a one-shot flag): a later turn can
13262
+ // independently accumulate fresh unsigned thinking and trigger its own genuinely
13263
+ // different refusal, and `#stripUnsignedThinkingForRefusalDegrade` already reports
13264
+ // "nothing to strip" via `undefined` when the payload truly is unchanged — which is
13265
+ // itself the correct signal to fall through instead of degrading again. Bounded by
13266
+ // `REASONING_EXTRACTION_DEGRADE_STREAK_CAP` consecutive uses so a pathological
13267
+ // classifier state refusing every turn's fresh thinking in a row still reaches the
13268
+ // wall-clock-bounded ladder instead of resending at zero delay indefinitely.
13231
13269
  if (
13232
13270
  classifierRefusal &&
13233
13271
  message.stopDetails?.category === "reasoning_extraction" &&
13234
- !this.#reasoningExtractionDegraded
13272
+ this.#reasoningExtractionDegradeStreak < REASONING_EXTRACTION_DEGRADE_STREAK_CAP
13235
13273
  ) {
13236
- this.#reasoningExtractionDegraded = true;
13237
13274
  const stripped = this.#stripUnsignedThinkingForRefusalDegrade(this.agent.state.messages);
13238
- if (stripped) this.agent.replaceMessages(stripped);
13239
- this.#removeAssistantMessageFromActiveContext(message, "reasoning-extraction-degrade");
13240
- await this.#emitSessionEvent({
13241
- type: "auto_retry_start",
13242
- attempt: this.#retryAttempt,
13243
- maxAttempts: retrySettings.maxRetries,
13244
- delayMs: 0,
13245
- errorMessage,
13246
- errorId: message.errorId,
13247
- });
13248
- this.#scheduleAgentContinue({ delayMs: 1, generation });
13249
- return true;
13275
+ if (stripped) {
13276
+ this.#reasoningExtractionDegradeStreak++;
13277
+ this.agent.replaceMessages(stripped);
13278
+ this.#removeAssistantMessageFromActiveContext(message, "reasoning-extraction-degrade");
13279
+ await this.#emitSessionEvent({
13280
+ type: "auto_retry_start",
13281
+ attempt: this.#retryAttempt,
13282
+ maxAttempts: retrySettings.maxRetries,
13283
+ delayMs: 0,
13284
+ errorMessage,
13285
+ errorId: message.errorId,
13286
+ });
13287
+ this.#scheduleAgentContinue({ delayMs: 1, generation });
13288
+ return true;
13289
+ }
13250
13290
  }
13251
13291
 
13252
13292
  const id = this.#classifyRetryMessage(message);