@oh-my-pi/pi-coding-agent 15.11.4 → 15.11.7
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 +82 -1
- package/dist/cli.js +520 -451
- package/dist/types/cli/bench-cli.d.ts +78 -0
- package/dist/types/cli/usage-cli.d.ts +10 -1
- package/dist/types/commands/bench.d.ts +29 -0
- package/dist/types/commands/usage.d.ts +9 -0
- package/dist/types/config/model-resolver.d.ts +3 -2
- package/dist/types/config/settings-schema.d.ts +125 -3
- package/dist/types/edit/renderer.d.ts +1 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -1
- package/dist/types/modes/components/reset-usage-selector.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/settings-selector.d.ts +8 -1
- package/dist/types/modes/components/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -0
- package/dist/types/modes/session-observer-registry.d.ts +2 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +3 -0
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +10 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +3 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/context-usage.d.ts +6 -1
- package/dist/types/session/agent-session.d.ts +14 -1
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/codex-auto-reset.d.ts +107 -0
- package/dist/types/session/snapcompact-inline.d.ts +107 -4
- package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
- package/dist/types/task/render.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/renderers.d.ts +13 -0
- package/dist/types/tools/ssh.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +0 -11
- package/package.json +11 -11
- package/src/cli/bench-cli.ts +437 -0
- package/src/cli/usage-cli.ts +187 -16
- package/src/cli-commands.ts +1 -0
- package/src/commands/bench.ts +42 -0
- package/src/commands/usage.ts +8 -0
- package/src/config/model-registry.ts +52 -5
- package/src/config/model-resolver.ts +36 -5
- package/src/config/settings-schema.ts +148 -3
- package/src/config/settings.ts +9 -0
- package/src/edit/renderer.ts +5 -0
- package/src/hindsight/client.ts +26 -1
- package/src/hindsight/state.ts +6 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/mcp/transports/stdio.ts +81 -7
- package/src/modes/components/oauth-selector.ts +67 -7
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/session-selector.ts +8 -2
- package/src/modes/components/settings-selector.ts +89 -47
- package/src/modes/components/snapcompact-shape-preview-doc.md +11 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/tool-execution.ts +26 -0
- package/src/modes/components/transcript-container.ts +23 -1
- package/src/modes/controllers/command-controller.ts +24 -1
- package/src/modes/controllers/input-controller.ts +8 -6
- package/src/modes/controllers/selector-controller.ts +72 -2
- package/src/modes/interactive-mode.ts +83 -0
- package/src/modes/session-observer-registry.ts +61 -3
- package/src/modes/setup-wizard/index.ts +1 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +24 -6
- package/src/modes/setup-wizard/scenes/providers.ts +36 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +10 -1
- package/src/modes/setup-wizard/scenes/theme.ts +28 -1
- package/src/modes/setup-wizard/scenes/types.ts +10 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +22 -6
- package/src/modes/setup-wizard/wizard-overlay.ts +38 -1
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/context-usage.ts +75 -1
- package/src/prompts/bench.md +7 -0
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -1
- package/src/prompts/tools/browser.md +33 -43
- package/src/prompts/tools/eval.md +27 -50
- package/src/prompts/tools/irc.md +29 -31
- package/src/prompts/tools/read.md +31 -37
- package/src/prompts/tools/todo.md +1 -2
- package/src/sdk.ts +4 -2
- package/src/session/agent-session.ts +136 -6
- package/src/session/auth-storage.ts +3 -0
- package/src/session/codex-auto-reset.ts +190 -0
- package/src/session/snapcompact-inline.ts +404 -75
- package/src/slash-commands/builtin-registry.ts +145 -8
- package/src/slash-commands/helpers/context-report.ts +28 -1
- package/src/slash-commands/helpers/reset-usage.ts +66 -0
- package/src/slash-commands/helpers/usage-report.ts +12 -0
- package/src/task/index.ts +30 -7
- package/src/task/render.ts +34 -19
- package/src/tools/bash.ts +3 -0
- package/src/tools/eval-render.ts +4 -0
- package/src/tools/renderers.ts +13 -0
- package/src/tools/ssh.ts +3 -0
- package/src/tools/todo.ts +8 -128
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision predicate for auto-redeeming a saved OpenAI Codex rate-limit
|
|
3
|
+
* reset, plus the process-wide coordinator that serializes attempts.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS IS REACTIVE-ONLY (never proactive):
|
|
6
|
+
* The only trustworthy "blocked right now" signal is a live 429 /
|
|
7
|
+
* `usage_limit_reached` from a request authenticated as the session's active
|
|
8
|
+
* Codex credential. The session hook calls this predicate from the usage-limit
|
|
9
|
+
* branch of the retry pipeline, *after* free remedies (sibling-account switch)
|
|
10
|
+
* fail and *before* model fallback. A proactive surface (the status-line usage
|
|
11
|
+
* poll) cannot be used: at `used_percent < 100` the account is not actually
|
|
12
|
+
* limited, so redeeming would be a credit-wasting no-op; at exactly 100 the
|
|
13
|
+
* user may be idle, so the freshly-reset weekly window would tick away with
|
|
14
|
+
* nobody working. Saved resets are a scarce, ~monthly, effectively
|
|
15
|
+
* irreversible resource — every gate here is biased to precision over recall:
|
|
16
|
+
* we would rather miss a redeem than waste a credit.
|
|
17
|
+
*
|
|
18
|
+
* THE DECISION-2 TRAP (status MUST NOT be used to find the blocker):
|
|
19
|
+
* `openai-codex.ts` applies the top-level `rate_limit.limit_reached` flag to
|
|
20
|
+
* BOTH the primary (5h) and secondary (weekly) `buildUsageLimit` calls, so when
|
|
21
|
+
* an account is blocked, *both* limit entries carry `status: "exhausted"`
|
|
22
|
+
* regardless of which window is actually at 100%. Only `amount.usedFraction`
|
|
23
|
+
* disambiguates which window is the real blocker. This module therefore keys
|
|
24
|
+
* eligibility off exact limit ids (`openai-codex:primary` /
|
|
25
|
+
* `openai-codex:secondary`) and `usedFraction`, never off `status`.
|
|
26
|
+
*
|
|
27
|
+
* ANTI-WASTE GATES (in evaluation order): the policy must be OFF unless opted
|
|
28
|
+
* in; the active model must be Codex (not Spark — a Spark block lives on a
|
|
29
|
+
* separate meter and it is unknown whether a credit even resets it); a fresh
|
|
30
|
+
* usage report for the active account must confirm `limitReached`; the WEEKLY
|
|
31
|
+
* (secondary) window must be genuinely exhausted — a 5h-only block self-heals
|
|
32
|
+
* within the hour, so a credit spent there buys nothing; the natural reset must be far
|
|
33
|
+
* enough away to justify spending a ~30-day credit yet within one plausible
|
|
34
|
+
* window length; a credit must be verifiably available above the reserve; and
|
|
35
|
+
* the same block episode must not have been attempted already (debounce +
|
|
36
|
+
* per-account cooldown). All of this is pure — no fetches, no IO. The only
|
|
37
|
+
* stateful piece is the {@link CodexAutoRedeemCoordinator} container, whose
|
|
38
|
+
* read-only views are passed in so the predicate itself stays deterministic.
|
|
39
|
+
*/
|
|
40
|
+
import type { OAuthAccountIdentity, ResetCreditTarget, UsageReport } from "@oh-my-pi/pi-ai";
|
|
41
|
+
import { reportMatchesActiveAccount } from "../slash-commands/helpers/active-oauth-account";
|
|
42
|
+
|
|
43
|
+
/** Weekly window counts as exhausted at `usedFraction >= 0.999` (used_percent >= 99.9). */
|
|
44
|
+
export const WEEKLY_EXHAUSTED_MIN_FRACTION = 0.999;
|
|
45
|
+
/** A weekly reset can never be more than one window length (7d) away; +1h slack for skew. */
|
|
46
|
+
export const MAX_PLAUSIBLE_REMAINING_MS = 7 * 24 * 3_600_000 + 60 * 60_000;
|
|
47
|
+
/** Report must be no older than the 5-min usage cache TTL plus slack. */
|
|
48
|
+
export const REPORT_FRESHNESS_MS = 10 * 60_000;
|
|
49
|
+
/** Per-account cooldown that catches blockKey drift across a minute boundary. */
|
|
50
|
+
export const ATTEMPT_COOLDOWN_MS = 60_000;
|
|
51
|
+
/** Minute bucket for blockKey, absorbing `reset_after_seconds`-derived jitter. */
|
|
52
|
+
export const DEBOUNCE_BUCKET_MS = 60_000;
|
|
53
|
+
|
|
54
|
+
export type CodexAutoRedeemSkipReason =
|
|
55
|
+
| "disabled"
|
|
56
|
+
| "wrong-provider"
|
|
57
|
+
| "spark-model"
|
|
58
|
+
| "no-identity"
|
|
59
|
+
| "no-report"
|
|
60
|
+
| "stale-report"
|
|
61
|
+
| "not-limit-reached"
|
|
62
|
+
| "weekly-not-exhausted"
|
|
63
|
+
| "no-reset-time"
|
|
64
|
+
| "reset-too-soon"
|
|
65
|
+
| "reset-implausible"
|
|
66
|
+
| "credits-unknown"
|
|
67
|
+
| "reserve"
|
|
68
|
+
| "already-attempted"
|
|
69
|
+
| "cooldown";
|
|
70
|
+
|
|
71
|
+
export interface CodexAutoRedeemInput {
|
|
72
|
+
nowMs: number;
|
|
73
|
+
/** `this.model.provider`. */
|
|
74
|
+
provider: string;
|
|
75
|
+
/** `this.model.id`. */
|
|
76
|
+
modelId: string;
|
|
77
|
+
settings: { autoRedeem: boolean; minBlockedMinutes: number; keepCredits: number };
|
|
78
|
+
/** `getOAuthAccountIdentity("openai-codex", sessionId)`, captured at hook entry before any await. */
|
|
79
|
+
identity: OAuthAccountIdentity | undefined;
|
|
80
|
+
/** `session.fetchUsageReports()` (≤5-min cache). */
|
|
81
|
+
reports: UsageReport[] | null;
|
|
82
|
+
attemptedBlockKeys: ReadonlySet<string>;
|
|
83
|
+
lastAttemptAtByAccount: ReadonlyMap<string, number>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type CodexAutoRedeemDecision =
|
|
87
|
+
| {
|
|
88
|
+
redeem: true;
|
|
89
|
+
target: ResetCreditTarget;
|
|
90
|
+
accountKey: string;
|
|
91
|
+
blockKey: string;
|
|
92
|
+
weeklyResetAtMs: number;
|
|
93
|
+
remainingMs: number;
|
|
94
|
+
availableCount: number;
|
|
95
|
+
}
|
|
96
|
+
| { redeem: false; reason: CodexAutoRedeemSkipReason };
|
|
97
|
+
|
|
98
|
+
/** Trimmed lowercase, or undefined when blank. Mirrors `normalizeIdentityValue` in active-oauth-account.ts. */
|
|
99
|
+
function normalize(value: unknown): string | undefined {
|
|
100
|
+
return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Decide whether to auto-redeem a saved Codex reset for the active account.
|
|
105
|
+
*
|
|
106
|
+
* Pure: every gate below is a function of the snapshot inputs only. Order
|
|
107
|
+
* matters — cheapest / most-decisive gates first so the common "not eligible"
|
|
108
|
+
* paths short-circuit before any account/report matching.
|
|
109
|
+
*/
|
|
110
|
+
export function evaluateCodexAutoRedeem(input: CodexAutoRedeemInput): CodexAutoRedeemDecision {
|
|
111
|
+
const { nowMs, settings } = input;
|
|
112
|
+
if (!settings.autoRedeem) return { redeem: false, reason: "disabled" };
|
|
113
|
+
if (input.provider !== "openai-codex") return { redeem: false, reason: "wrong-provider" };
|
|
114
|
+
// Unknown #1: it is unknown whether a credit resets the separate Spark meter.
|
|
115
|
+
if (input.modelId.includes("-spark")) return { redeem: false, reason: "spark-model" };
|
|
116
|
+
|
|
117
|
+
const accountKey = normalize(input.identity?.accountId) ?? normalize(input.identity?.email);
|
|
118
|
+
if (!accountKey) return { redeem: false, reason: "no-identity" };
|
|
119
|
+
|
|
120
|
+
const report = input.reports?.find(
|
|
121
|
+
r => r.provider === "openai-codex" && reportMatchesActiveAccount(r, input.identity),
|
|
122
|
+
);
|
|
123
|
+
if (!report) return { redeem: false, reason: "no-report" };
|
|
124
|
+
if (nowMs - report.fetchedAt > REPORT_FRESHNESS_MS) return { redeem: false, reason: "stale-report" };
|
|
125
|
+
// The wire's own blocked flag must confirm the 429.
|
|
126
|
+
if (report.metadata?.limitReached !== true) return { redeem: false, reason: "not-limit-reached" };
|
|
127
|
+
|
|
128
|
+
// EXACT ids — never `status` (see the Decision-2 trap in the module docs).
|
|
129
|
+
// The saved reset applies to the WEEKLY window, so that is the blocker we act
|
|
130
|
+
// on. A 5h-only block (weekly still has headroom) self-heals within the hour,
|
|
131
|
+
// so spending a scarce ~monthly credit there would be wasted.
|
|
132
|
+
const weekly = report.limits.find(l => l.id === "openai-codex:secondary");
|
|
133
|
+
const wUsed = weekly?.amount.usedFraction;
|
|
134
|
+
if (!weekly || wUsed === undefined || wUsed < WEEKLY_EXHAUSTED_MIN_FRACTION) {
|
|
135
|
+
return { redeem: false, reason: "weekly-not-exhausted" };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const resetsAt = weekly.window?.resetsAt;
|
|
139
|
+
if (resetsAt === undefined) return { redeem: false, reason: "no-reset-time" };
|
|
140
|
+
const remainingMs = resetsAt - nowMs;
|
|
141
|
+
// anti-waste: too close to the natural reset — let it roll over instead of spending a credit.
|
|
142
|
+
if (remainingMs < settings.minBlockedMinutes * 60_000) return { redeem: false, reason: "reset-too-soon" };
|
|
143
|
+
if (remainingMs > MAX_PLAUSIBLE_REMAINING_MS) return { redeem: false, reason: "reset-implausible" };
|
|
144
|
+
|
|
145
|
+
const available = report.resetCredits?.availableCount;
|
|
146
|
+
// can't verify availability from the snapshot → don't spend (precision over recall).
|
|
147
|
+
if (available === undefined) return { redeem: false, reason: "credits-unknown" };
|
|
148
|
+
if (available - Math.max(0, Math.trunc(settings.keepCredits)) < 1) {
|
|
149
|
+
return { redeem: false, reason: "reserve" };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const blockKey = `${accountKey}|${Math.round(resetsAt / DEBOUNCE_BUCKET_MS)}`;
|
|
153
|
+
if (input.attemptedBlockKeys.has(blockKey)) return { redeem: false, reason: "already-attempted" };
|
|
154
|
+
const lastAt = input.lastAttemptAtByAccount.get(accountKey);
|
|
155
|
+
if (lastAt !== undefined && nowMs - lastAt < ATTEMPT_COOLDOWN_MS) return { redeem: false, reason: "cooldown" };
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
redeem: true,
|
|
159
|
+
target: { accountId: input.identity?.accountId, email: input.identity?.email },
|
|
160
|
+
accountKey,
|
|
161
|
+
blockKey,
|
|
162
|
+
weeklyResetAtMs: resetsAt,
|
|
163
|
+
remainingMs,
|
|
164
|
+
availableCount: available,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Process-wide (NOT per-session) coordinator state. Parallel subagent sessions
|
|
170
|
+
* share the same Codex accounts and must not race a double-spend, so this is a
|
|
171
|
+
* single shared container, not a per-session field.
|
|
172
|
+
*
|
|
173
|
+
* - `attemptedBlockKeys`: one attempt EVER per block episode, regardless of
|
|
174
|
+
* outcome — recorded before calling the consume so exceptions can't re-enter.
|
|
175
|
+
* - `lastAttemptAtByAccount`: per-account cooldown timestamps (epoch ms),
|
|
176
|
+
* catching blockKey drift across a minute boundary.
|
|
177
|
+
* - `inFlightByAccount`: serializes per account — a second session for the same
|
|
178
|
+
* account adopts the in-flight promise instead of starting a second consume.
|
|
179
|
+
*/
|
|
180
|
+
export interface CodexAutoRedeemCoordinator {
|
|
181
|
+
attemptedBlockKeys: Set<string>;
|
|
182
|
+
lastAttemptAtByAccount: Map<string, number>;
|
|
183
|
+
inFlightByAccount: Map<string, Promise<boolean>>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const defaultCodexAutoRedeemCoordinator: CodexAutoRedeemCoordinator = {
|
|
187
|
+
attemptedBlockKeys: new Set(),
|
|
188
|
+
lastAttemptAtByAccount: new Map(),
|
|
189
|
+
inFlightByAccount: new Map(),
|
|
190
|
+
};
|