@oh-my-pi/pi-coding-agent 17.2.1 → 17.2.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/{CHANGELOG-dj46zzrm.md → CHANGELOG-xfpkakrn.md} +47 -0
  3. package/dist/cli.js +3891 -3885
  4. package/dist/types/config/keybindings.d.ts +6 -1
  5. package/dist/types/config/settings-schema.d.ts +16 -5
  6. package/dist/types/edit/hashline/block-resolver.d.ts +5 -5
  7. package/dist/types/edit/hashline/diff.d.ts +4 -4
  8. package/dist/types/eval/executor-base.d.ts +27 -0
  9. package/dist/types/eval/js/context-manager.d.ts +7 -0
  10. package/dist/types/eval/js/executor.d.ts +2 -0
  11. package/dist/types/extensibility/extensions/runner.d.ts +64 -5
  12. package/dist/types/extensibility/extensions/types.d.ts +15 -0
  13. package/dist/types/extensibility/extensions/wrapper.d.ts +1 -1
  14. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  15. package/dist/types/live/visualizer.d.ts +3 -1
  16. package/dist/types/modes/acp/acp-agent.d.ts +3 -3
  17. package/dist/types/modes/components/keybinding-hints.d.ts +1 -1
  18. package/dist/types/modes/controllers/mcp-command-controller.d.ts +13 -0
  19. package/dist/types/modes/interactive-mode.d.ts +1 -0
  20. package/dist/types/modes/theme/theme.d.ts +1 -1
  21. package/dist/types/modes/types.d.ts +2 -0
  22. package/dist/types/secrets/index.d.ts +22 -0
  23. package/dist/types/secrets/obfuscator.d.ts +1 -1
  24. package/dist/types/session/agent-session-types.d.ts +4 -0
  25. package/dist/types/session/agent-session.d.ts +9 -0
  26. package/dist/types/session/codex-auto-reset.d.ts +182 -64
  27. package/dist/types/session/session-storage.d.ts +5 -2
  28. package/dist/types/session/session-tools.d.ts +2 -1
  29. package/dist/types/session/turn-recovery.d.ts +7 -1
  30. package/dist/types/system-prompt.d.ts +8 -0
  31. package/dist/types/task/executor.d.ts +27 -0
  32. package/dist/types/task/persisted-revive.d.ts +7 -0
  33. package/dist/types/tools/bash-interceptor.d.ts +1 -1
  34. package/dist/types/tools/browser/attach.d.ts +9 -1
  35. package/dist/types/tools/browser/launch.d.ts +20 -1
  36. package/dist/types/tools/browser/registry.d.ts +2 -0
  37. package/dist/types/tools/browser/tab-protocol.d.ts +8 -0
  38. package/dist/types/tools/browser/tab-supervisor.d.ts +1 -0
  39. package/dist/types/tools/browser/tab-worker.d.ts +3 -1
  40. package/dist/types/tools/shell-tokenize.d.ts +11 -0
  41. package/dist/types/utils/image-loading.d.ts +2 -0
  42. package/dist/types/web/search/providers/duckduckgo.d.ts +9 -0
  43. package/dist/types/web/search/types.d.ts +2 -2
  44. package/package.json +12 -12
  45. package/src/commit/agentic/index.ts +2 -1
  46. package/src/commit/pipeline.ts +3 -2
  47. package/src/config/keybindings.ts +6 -1
  48. package/src/config/settings-schema.ts +16 -3
  49. package/src/edit/hashline/block-resolver.ts +5 -5
  50. package/src/edit/hashline/diff.ts +22 -15
  51. package/src/edit/hashline/execute.ts +14 -13
  52. package/src/edit/renderer.ts +3 -3
  53. package/src/eval/executor-base.ts +39 -0
  54. package/src/eval/jl/executor.ts +13 -2
  55. package/src/eval/js/context-manager.ts +97 -14
  56. package/src/eval/js/executor.ts +3 -0
  57. package/src/eval/js/index.ts +1 -0
  58. package/src/eval/py/executor.ts +55 -15
  59. package/src/eval/rb/executor.ts +8 -1
  60. package/src/extensibility/extensions/runner.ts +121 -4
  61. package/src/extensibility/extensions/types.ts +15 -0
  62. package/src/extensibility/extensions/wrapper.ts +19 -2
  63. package/src/extensibility/legacy-pi-coding-agent-shim.ts +5 -5
  64. package/src/live/visualizer.ts +8 -1
  65. package/src/lsp/index.ts +51 -11
  66. package/src/main.ts +3 -0
  67. package/src/modes/acp/acp-agent.ts +16 -5
  68. package/src/modes/components/assistant-message.ts +5 -10
  69. package/src/modes/components/custom-editor.ts +1 -1
  70. package/src/modes/components/keybinding-hints.ts +4 -13
  71. package/src/modes/components/status-line/segments.ts +4 -5
  72. package/src/modes/components/tool-execution.ts +4 -5
  73. package/src/modes/controllers/input-controller.ts +18 -40
  74. package/src/modes/controllers/live-command-controller.ts +1 -0
  75. package/src/modes/controllers/mcp-command-controller.ts +28 -11
  76. package/src/modes/interactive-mode.ts +59 -2
  77. package/src/modes/theme/theme.ts +13 -6
  78. package/src/modes/types.ts +2 -0
  79. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  80. package/src/prompts/system/plan-mode-active.md +5 -5
  81. package/src/sdk.ts +47 -16
  82. package/src/secrets/index.ts +83 -3
  83. package/src/secrets/obfuscator.ts +70 -15
  84. package/src/session/agent-session-types.ts +7 -1
  85. package/src/session/agent-session.ts +393 -112
  86. package/src/session/codex-auto-reset.ts +594 -123
  87. package/src/session/eval-runner.ts +2 -0
  88. package/src/session/messages.ts +1 -1
  89. package/src/session/session-maintenance.ts +48 -1
  90. package/src/session/session-manager.ts +4 -3
  91. package/src/session/session-stats.ts +14 -3
  92. package/src/session/session-storage.ts +47 -15
  93. package/src/session/session-tools.ts +30 -2
  94. package/src/session/turn-recovery.ts +15 -2
  95. package/src/slash-commands/builtin-registry.ts +21 -15
  96. package/src/slash-commands/helpers/reset-usage.ts +2 -0
  97. package/src/system-prompt.ts +13 -1
  98. package/src/task/executor.ts +168 -15
  99. package/src/task/persisted-revive.ts +26 -1
  100. package/src/telemetry-export.ts +8 -4
  101. package/src/tools/bash-interceptor.ts +85 -8
  102. package/src/tools/bash.ts +1 -1
  103. package/src/tools/browser/attach.ts +35 -10
  104. package/src/tools/browser/launch.ts +60 -11
  105. package/src/tools/browser/registry.ts +18 -2
  106. package/src/tools/browser/tab-protocol.ts +8 -0
  107. package/src/tools/browser/tab-supervisor.ts +31 -4
  108. package/src/tools/browser/tab-worker.ts +32 -2
  109. package/src/tools/output-meta.ts +16 -1
  110. package/src/tools/shell-tokenize.ts +101 -0
  111. package/src/utils/image-loading.ts +8 -3
  112. package/src/utils/shell-snapshot.ts +31 -21
  113. package/src/web/search/providers/duckduckgo.ts +183 -15
  114. package/src/web/search/types.ts +6 -1
@@ -1,111 +1,229 @@
1
1
  /**
2
- * Pure decision predicate for auto-redeeming a saved OpenAI Codex rate-limit
3
- * reset, plus the process-wide coordinator that serializes attempts.
2
+ * Pool-wide planner for spending saved OpenAI Codex rate-limit resets, plus
3
+ * the process-wide coordinator that serializes attempts.
4
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.
5
+ * A saved reset is a scarce, ~monthly credit — but it is also perishable:
6
+ * every credit carries an `expiresAt`, and an expired credit is worth exactly
7
+ * nothing. The planner therefore balances two failure modes instead of only
8
+ * one: wasting a credit on a window that would have healed anyway, and letting
9
+ * a credit die unspent. Two rules, evaluated over EVERY stored Codex account
10
+ * (not just the session's active one):
11
+ *
12
+ * - `expiring-credit` (salvage; any trigger): the account's soonest available
13
+ * credit expires within `salvageHorizonMs` and the weekly window is at least
14
+ * {@link SALVAGE_MIN_USED_FRACTION} used, so redeeming restores real quota.
15
+ * The `keepCredits` reserve is deliberately ignored here reserving a
16
+ * credit that is about to expire preserves nothing. If the window resets
17
+ * naturally before the credit expires, the next sweep sees a mostly-free
18
+ * window and skips: the credit had nothing left to restore. The backend may
19
+ * refuse a partial-usage consume with `nothing_to_reset`; that outcome is
20
+ * NON-terminal — the episode is deferred (not buried), so the credit is
21
+ * retried as usage grows or a window exhausts before expiry.
22
+ * - `blocked-account` (restore; `blocked` trigger only): a live 429 blocked
23
+ * the turn and no sibling credential could take over, so the pool is dry.
24
+ * Candidates are accounts with at least one genuinely exhausted chat window
25
+ * — 5h primary or weekly secondary. A banked reset clears the account's
26
+ * chat rate limits generally, not just the weekly window: OpenAI's own
27
+ * client consumes one for a 5h-only block while the weekly window still has
28
+ * ~80% headroom (openai/codex#28525). The natural unblock is the LATEST
29
+ * reset among the exhausted windows (the account stays blocked until every
30
+ * one rolls over) and must be far enough away to justify the spend, with
31
+ * credits above the reserve. One candidate is redeemed — active account
32
+ * first, then the account whose credit dies soonest — and the redeem clears
33
+ * its credential blocks so the retry's re-rank picks it up.
34
+ *
35
+ * TRIGGERS: `blocked` runs from the usage-limit branch of the retry pipeline
36
+ * after sibling switch fails, on force-refreshed reports (the cached snapshot
37
+ * predates the 429 that got us there). `sweep` piggybacks on every successful
38
+ * usage-report fetch — the status line polls every 5 minutes while the TUI is
39
+ * open — so expiring credits are caught even when nothing is blocked.
17
40
  *
18
41
  * THE DECISION-2 TRAP (status MUST NOT be used to find the blocker):
19
42
  * `openai-codex.ts` applies the top-level `rate_limit.limit_reached` flag to
20
43
  * BOTH the primary (5h) and secondary (weekly) `buildUsageLimit` calls, so when
21
44
  * an account is blocked, *both* limit entries carry `status: "exhausted"`
22
45
  * 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`.
46
+ * disambiguates. This module keys eligibility off exact limit ids
47
+ * (`openai-codex:primary` / `openai-codex:secondary`) and `usedFraction`,
48
+ * never off `status`, so the plan names the true blocking window(s) and their
49
+ * real unblock time. Whether a short wait (e.g. a 5h-only block) is worth a
50
+ * credit is the `minBlockedMinutes` knob's call, not hardcoded fiat.
26
51
  *
27
- * ANTI-WASTE GATES (in evaluation order): the policy must not be set to "no";
28
- * 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.
52
+ * All of this is pure no fetches, no IO. The only stateful piece is the
53
+ * {@link CodexAutoRedeemCoordinator} container, whose read-only views are
54
+ * passed in so the planner itself stays deterministic.
39
55
  */
40
- import type { OAuthAccountIdentity, ResetCreditTarget, UsageReport } from "@oh-my-pi/pi-ai";
56
+ import type { OAuthAccountIdentity, ResetCreditAccountStatus, ResetCreditTarget, UsageReport } from "@oh-my-pi/pi-ai";
41
57
  import type { CodexAutoRedeemMode } from "../config/settings-schema.js";
42
- /** Weekly window counts as exhausted at `usedFraction >= 0.999` (used_percent >= 99.9). */
43
- export declare const WEEKLY_EXHAUSTED_MIN_FRACTION = 0.999;
58
+ /** A chat window counts as exhausted at `usedFraction >= 0.999` (used_percent >= 99.9). */
59
+ export declare const WINDOW_EXHAUSTED_MIN_FRACTION = 0.999;
44
60
  /** A weekly reset can never be more than one window length (7d) away; +1h slack for skew. */
45
- export declare const MAX_PLAUSIBLE_REMAINING_MS: number;
61
+ export declare const MAX_PLAUSIBLE_WEEKLY_REMAINING_MS: number;
62
+ /** A 5h reset can never be more than one window length (5h) away; +1h slack for skew. */
63
+ export declare const MAX_PLAUSIBLE_PRIMARY_REMAINING_MS: number;
64
+ /** Below this usage on BOTH chat windows a salvaged reset restores too little to bother (and risks a `nothing_to_reset` no-op). */
65
+ export declare const SALVAGE_MIN_USED_FRACTION = 0.25;
66
+ /** Retry spacing after a non-terminal consume outcome (`nothing_to_reset`, transport failure). */
67
+ export declare const REDEEM_RETRY_DEFER_MS: number;
46
68
  /** Report must be no older than the 5-min usage cache TTL plus slack. */
47
69
  export declare const REPORT_FRESHNESS_MS: number;
48
- /** Per-account cooldown that catches blockKey drift across a minute boundary. */
70
+ /** Per-account cooldown that catches attempt-key drift across a minute boundary. */
49
71
  export declare const ATTEMPT_COOLDOWN_MS = 60000;
50
- /** Minute bucket for blockKey, absorbing `reset_after_seconds`-derived jitter. */
72
+ /** Minute bucket for attempt keys, absorbing `reset_after_seconds`/expiry jitter. */
51
73
  export declare const DEBOUNCE_BUCKET_MS = 60000;
74
+ /** Floor between salvage sweeps; dedupe keys make sweeps idempotent, this just avoids useless re-planning. */
75
+ export declare const SWEEP_MIN_INTERVAL_MS = 60000;
52
76
  export declare function shouldEvaluateCodexAutoRedeem(mode: CodexAutoRedeemMode): boolean;
53
77
  export declare function shouldPromptCodexAutoRedeem(mode: CodexAutoRedeemMode): boolean;
54
- export type CodexAutoRedeemSkipReason = "disabled" | "wrong-provider" | "spark-model" | "no-identity" | "no-report" | "stale-report" | "not-limit-reached" | "weekly-not-exhausted" | "no-reset-time" | "reset-too-soon" | "reset-implausible" | "credits-unknown" | "reserve" | "already-attempted" | "cooldown";
55
- export interface CodexAutoRedeemInput {
78
+ /** What woke the planner. `sweep` may only salvage; `blocked` may also restore. */
79
+ export type CodexResetTrigger = "blocked" | "sweep";
80
+ /** Why one account produced no action (or, with `accountKey: "*"`, a whole rule was off). */
81
+ export type CodexResetSkipReason = "disabled" | "wrong-provider" | "spark-model" | "no-identity" | "stale-report" | "not-limit-reached" | "no-exhausted-window" | "deferred" | "no-reset-time" | "reset-too-soon" | "reset-implausible" | "credits-unknown" | "no-credits" | "reserve" | "no-expiring-credit" | "window-mostly-free" | "already-attempted" | "cooldown";
82
+ export interface CodexResetPlanInput {
56
83
  nowMs: number;
57
- /** `this.model.provider`. */
84
+ trigger: CodexResetTrigger;
85
+ /** `this.model.provider` — gates the `blocked-account` rule only. */
58
86
  provider: string;
59
- /** `this.model.id`. */
87
+ /** `this.model.id` — gates the `blocked-account` rule only. */
60
88
  modelId: string;
61
89
  settings: {
62
- autoRedeem: boolean;
90
+ enabled: boolean;
91
+ /** `blocked-account`: skip when the natural unblock is closer than this. */
63
92
  minBlockedMinutes: number;
93
+ /** `blocked-account`: never spend below this many remaining credits. */
64
94
  keepCredits: number;
95
+ /** `expiring-credit`: salvage window; `<= 0` disables the rule. */
96
+ salvageHorizonMs: number;
65
97
  };
66
- /** `getOAuthAccountIdentity("openai-codex", sessionId)`, captured at hook entry before any await. */
98
+ /** Active account (marks the preferred restore candidate); may be undefined. */
67
99
  identity: OAuthAccountIdentity | undefined;
68
- /** `session.fetchUsageReports()` (≤5-min cache). */
100
+ /** Usage reports for ALL stored accounts (one per account for Codex). */
69
101
  reports: UsageReport[] | null;
70
- attemptedBlockKeys: ReadonlySet<string>;
102
+ attemptedKeys: ReadonlySet<string>;
103
+ /** Episodes parked by a non-terminal consume outcome, keyed by attempt key (epoch ms). */
104
+ deferredUntilByKey: ReadonlyMap<string, number>;
71
105
  lastAttemptAtByAccount: ReadonlyMap<string, number>;
106
+ /**
107
+ * Live 429 evidence for the ACTIVE account: absolute epoch ms when the
108
+ * provider said the account unblocks, derived from the usage-limit error's
109
+ * parsed retry hint AT THE ERROR (absolute, so slow usage IO between the
110
+ * error and planning cannot drift it). Authoritative when the usage report
111
+ * is stale or missing — the report layer can adopt a pre-block in-flight
112
+ * fetch or serve the last-good snapshot when `/wham/usage` fails (it is
113
+ * IP-throttled, so failure right after a 429 is common), and such a
114
+ * snapshot still shows `limitReached: false` with healthy windows. With no
115
+ * usable report at all, a candidate is synthesized from `identity` and the
116
+ * redeem re-checks credits live. Only used on `blocked`.
117
+ */
118
+ activeBlockUnblockAtMs?: number;
72
119
  }
73
- export interface CodexAutoRedeemRedeemDecision {
74
- redeem: true;
120
+ /** One credit to spend. `redeemResetCredit` picks the account's soonest-expiring credit. */
121
+ export interface CodexResetAction {
122
+ reason: "blocked-account" | "expiring-credit";
75
123
  target: ResetCreditTarget;
76
124
  accountKey: string;
77
- blockKey: string;
78
- weeklyResetAtMs: number;
79
- remainingMs: number;
80
- availableCount: number;
125
+ /** Once-per-episode dedupe key; record in `attemptedKeys` BEFORE consuming. */
126
+ attemptKey: string;
127
+ /** Human label for notices/prompts (email preferred). */
128
+ label: string;
129
+ /** Redeemable credits per the report; undefined for a synthesized live-429 candidate. */
130
+ availableCount?: number;
131
+ weeklyUsedFraction?: number;
132
+ /** `blocked-account`: ms until the natural unblock (latest exhausted-window reset). */
133
+ remainingMs?: number;
134
+ /** `blocked-account`: the exhausted chat windows a redeem would clear. */
135
+ blockedWindows?: ("5h" | "weekly")[];
136
+ /** `expiring-credit`: the fuller chat window a redeem restores (for messaging). */
137
+ salvageWindow?: "5h" | "weekly";
138
+ /** `expiring-credit`: used fraction of {@link CodexResetAction.salvageWindow}. */
139
+ salvageUsedFraction?: number;
140
+ /** `expiring-credit`: ms until the credit expires. */
141
+ expiresInMs?: number;
142
+ /** True when this is the session's active account. */
143
+ active: boolean;
144
+ }
145
+ export interface CodexResetSkip {
146
+ /** Normalized account key, or `"*"` for a rule-wide gate. */
147
+ accountKey: string;
148
+ rule: "blocked-account" | "expiring-credit" | "account";
149
+ reason: CodexResetSkipReason;
150
+ }
151
+ export interface CodexResetPlan {
152
+ /** At most one `blocked-account` action (first), then salvages by soonest expiry. */
153
+ actions: CodexResetAction[];
154
+ /** Diagnostics for `logger.debug` and tests. */
155
+ skipped: CodexResetSkip[];
81
156
  }
82
- export type CodexAutoRedeemDecision = CodexAutoRedeemRedeemDecision | {
83
- redeem: false;
84
- reason: CodexAutoRedeemSkipReason;
85
- };
86
157
  /**
87
- * Decide whether to auto-redeem a saved Codex reset for the active account.
158
+ * Plan which saved Codex resets to spend right now. Pure: a function of the
159
+ * snapshot inputs only. Callers execute the returned actions in order,
160
+ * re-checking `attemptedKeys` immediately before each consume.
161
+ */
162
+ export declare function planCodexResetRedemptions(input: CodexResetPlanInput): CodexResetPlan;
163
+ /** One attempt per (account, weekly-reset-minute) block episode. */
164
+ export declare function blockedAttemptKey(accountKey: string, weeklyResetsAtMs: number): string;
165
+ /** One attempt per (account, credit-expiry-minute) salvage episode. */
166
+ export declare function salvageAttemptKey(accountKey: string, creditExpiresAtMs: number): string;
167
+ /**
168
+ * Overlay LIVE per-account credit state (from the dedicated
169
+ * `rate-limit-reset-credits` route) onto usage reports before a blocked pass.
88
170
  *
89
- * Pure: every gate below is a function of the snapshot inputs only. Order
90
- * matters cheapest / most-decisive gates first so the common "not eligible"
91
- * paths short-circuit before any account/report matching.
171
+ * `/wham/usage` credit counts can be stale or pre-feature, and the usage
172
+ * provider only consults the live detail endpoint when the usage payload
173
+ * already reports a POSITIVE count — a stale ZERO is never corrected there.
174
+ * Live data therefore replaces the report's credit block wholesale; accounts
175
+ * with no live row (or a failed lookup) get the block stripped, so the planner
176
+ * treats them as `credits-unknown` instead of trusting a stale count: siblings
177
+ * stay conservative while the active account can still be synthesized from
178
+ * live 429 evidence (the redeem re-lists atomically either way).
179
+ */
180
+ export declare function overlayLiveResetCredits(reports: UsageReport[] | null, statuses: readonly ResetCreditAccountStatus[]): UsageReport[] | null;
181
+ /**
182
+ * Whether a consume outcome permanently settles its episode. Terminal codes
183
+ * either spent the credit (`reset`), or prove this credit can never be spent
184
+ * (`already_redeemed`, `no_credit` — a live listing that really had nothing).
185
+ * Everything else — `nothing_to_reset` (limits not constrained enough right
186
+ * now), `credit_list_failed` (flaky listing), thrown transport failures,
187
+ * unknown codes — leaves the credit banked: the executor releases the attempt
188
+ * key and
189
+ * defers the episode by {@link REDEEM_RETRY_DEFER_MS} instead of burying a
190
+ * live credit for the rest of the process.
92
191
  */
93
- export declare function evaluateCodexAutoRedeem(input: CodexAutoRedeemInput): CodexAutoRedeemDecision;
192
+ export declare function isTerminalRedeemOutcome(code: string): boolean;
94
193
  /**
95
194
  * Process-wide (NOT per-session) coordinator state. Parallel subagent sessions
96
195
  * share the same Codex accounts and must not race a double-spend, so this is a
97
196
  * single shared container, not a per-session field.
98
197
  *
99
- * - `attemptedBlockKeys`: one attempt EVER per block episode, regardless of
100
- * outcome — recorded before calling the consume so exceptions can't re-enter.
198
+ * - `attemptedKeys`: one attempt per episode key — recorded before calling the
199
+ * consume so exceptions can't re-enter. Non-terminal outcomes (see
200
+ * {@link isTerminalRedeemOutcome}) release the key again and park the
201
+ * episode in `deferredUntilByKey` instead.
202
+ * - `deferredUntilByKey`: earliest retry time for episodes whose consume was
203
+ * refused non-terminally (`nothing_to_reset`, transport failure).
101
204
  * - `lastAttemptAtByAccount`: per-account cooldown timestamps (epoch ms),
102
- * catching blockKey drift across a minute boundary.
103
- * - `inFlightByAccount`: serializes per account — a second session for the same
104
- * account adopts the in-flight promise instead of starting a second consume.
205
+ * catching attempt-key drift across a minute boundary.
206
+ * - `inFlightByAccount`: serializes blocked passes per account — a second
207
+ * session for the same account adopts the in-flight promise instead of
208
+ * starting a second consume.
209
+ * - `sweepInFlight` / `lastSweepAt` / `sweepPromise`: re-entrancy guard, floor,
210
+ * and settlement handle for the salvage sweep (a redeem refreshes usage,
211
+ * which would recurse into a sweep; the promise lets tests and diagnostics
212
+ * await a fire-and-forget sweep instead of polling).
213
+ * - `notifiedKeys`: headless "run /usage reset" notices already emitted, so a
214
+ * 5-minute sweep cadence can't spam the transcript.
105
215
  */
106
216
  export interface CodexAutoRedeemCoordinator {
107
- attemptedBlockKeys: Set<string>;
217
+ attemptedKeys: Set<string>;
218
+ deferredUntilByKey: Map<string, number>;
108
219
  lastAttemptAtByAccount: Map<string, number>;
109
220
  inFlightByAccount: Map<string, Promise<boolean>>;
221
+ sweepInFlight: boolean;
222
+ lastSweepAt: number;
223
+ /** Settlement of the most recently scheduled sweep (never rejects). */
224
+ sweepPromise: Promise<void> | undefined;
225
+ notifiedKeys: Set<string>;
110
226
  }
227
+ /** Fresh, empty coordinator: backs the process-wide default; inject one per test for isolation. */
228
+ export declare function createCodexAutoRedeemCoordinator(): CodexAutoRedeemCoordinator;
111
229
  export declare const defaultCodexAutoRedeemCoordinator: CodexAutoRedeemCoordinator;
@@ -6,14 +6,17 @@ export interface SessionStorageStat {
6
6
  }
7
7
  export interface SessionStorageWriter {
8
8
  /**
9
- * Append one newline-terminated line. File and memory storage perform the
10
- * write synchronously in-body; indexed backends queue in call order.
9
+ * Append one newline-terminated line. File storage batches same-turn appends
10
+ * until its microtask boundary; memory storage updates in-body; indexed
11
+ * backends queue in call order.
11
12
  *
12
13
  * `line` MUST include the trailing newline.
13
14
  */
14
15
  append(line: string): Promise<void>;
15
16
  /** Resolve once all queued appends complete. No fsync. */
16
17
  flush(): Promise<void>;
18
+ /** Drain synchronously flushable queued work when the backend supports it. No fsync. */
19
+ flushSync?(): void;
17
20
  /** False once close() has begun/finished. */
18
21
  isOpen(): boolean;
19
22
  close(): Promise<void>;
@@ -50,6 +50,7 @@ interface SessionToolsOptions {
50
50
  ensureWriteRegistered?: () => Promise<boolean>;
51
51
  rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<{
52
52
  systemPrompt: string[];
53
+ xdevCatalogNames?: readonly string[];
53
54
  }>;
54
55
  getLocalCalendarDate?: () => string;
55
56
  getMcpServerInstructions?: () => Map<string, string> | undefined;
@@ -159,7 +160,7 @@ export declare class SessionTools {
159
160
  */
160
161
  resetAnnouncedMounts(): void;
161
162
  /** Consumes the hidden notice for unannounced `xd://` mount changes. */
162
- takePendingXdevMountNotice(): CustomMessage<XdevMountNoticeDetails> | undefined;
163
+ takePendingXdevMountNotice(baseCatalogDelivered: boolean): CustomMessage<XdevMountNoticeDetails> | undefined;
163
164
  /** Rediscovers reloadable skills and refreshes prompt metadata. */
164
165
  refreshSkills(): Promise<void>;
165
166
  /** Selects enabled tools, ignoring names absent from the registry. */
@@ -46,7 +46,13 @@ export interface TurnRecoveryHost {
46
46
  sessionMessageAlreadyPersisted(message: AssistantMessage): boolean;
47
47
  setModelWithProviderSessionReset(model: Model): Promise<void>;
48
48
  resetCurrentResponsesProviderSession(reason: string): void;
49
- maybeAutoRedeemCodexReset(): Promise<boolean>;
49
+ /**
50
+ * Spend a saved Codex reset for the blocked pool, if eligible.
51
+ * `activeBlockUnblockAtMs` is the absolute unblock time parsed from the
52
+ * live usage-limit error — authoritative for the active account when the
53
+ * usage report still shows a pre-block snapshot.
54
+ */
55
+ maybeAutoRedeemCodexReset(activeBlockUnblockAtMs?: number): Promise<boolean>;
50
56
  runAutoCompaction(reason: "overflow" | "threshold" | "idle" | "incomplete", willRetry: boolean, deferred?: boolean, allowDefer?: boolean, options?: {
51
57
  autoContinue?: boolean;
52
58
  triggerContextTokens?: number;
@@ -148,6 +148,14 @@ export interface BuildSystemPromptOptions {
148
148
  export interface BuildSystemPromptResult {
149
149
  /** Ordered system prompt blocks. Providers should preserve entries as distinct messages/blocks. */
150
150
  systemPrompt: string[];
151
+ /**
152
+ * Names of `xd://` devices whose catalog/protocol section this prompt renders.
153
+ * Empty/undefined when no catalog was emitted (no mounted devices, or a custom
154
+ * prompt template that omits the section). Lets the session fold these devices
155
+ * into its announced-mount baseline so a same-turn mount notice does not re-list
156
+ * a catalog the prompt already carries (issue #7139).
157
+ */
158
+ xdevCatalogNames?: readonly string[];
151
159
  }
152
160
  /** Build the system prompt with tools, guidelines, and context */
153
161
  export declare function buildSystemPrompt(options?: BuildSystemPromptOptions): Promise<BuildSystemPromptResult>;
@@ -250,6 +250,33 @@ export declare function finalizeSubprocessOutput(args: FinalizeSubprocessOutputA
250
250
  export declare function createMCPProxyTools(mcpManager: MCPManager): CustomTool[];
251
251
  export declare function createSubagentSettings(baseSettings: Settings, overrides?: Partial<Record<SettingPath, unknown>>, inheritedServiceTier?: ServiceTierByFamily | null): Settings;
252
252
  export type AbortReason = "signal" | "terminate" | "timeout" | "budget";
253
+ /** Inputs for {@link attachIrcWakeTurnMonitor}. */
254
+ export interface IrcWakeTurnMonitorOptions {
255
+ /** Registry id of the kept-alive subagent whose autonomous IRC wake turns are monitored. */
256
+ id: string;
257
+ index?: number;
258
+ agent: AgentDefinition;
259
+ description?: string;
260
+ modelOverride?: string | string[];
261
+ eventBus?: EventBus;
262
+ parentToolCallId?: string;
263
+ /** Fallback session file when the registry ref carries none. */
264
+ sessionFile?: string;
265
+ maxRuntimeMs?: number;
266
+ outputSchema?: unknown;
267
+ outputSchemaMode?: StructuredSubagentSchemaMode;
268
+ outputSchemaSource?: StructuredSubagentSchemaSource;
269
+ artifactsDir?: string;
270
+ }
271
+ /**
272
+ * Bracket a kept-alive subagent's autonomous IRC wake turns with a task run
273
+ * monitor so RPC/collab subscribers see the same `subagent_lifecycle` /
274
+ * `subagent_progress` frames a first run emits. Shared by the live executor
275
+ * reviver and the persisted cold-revive path so a resumed process's parked
276
+ * subagents are not blind spots. The observer runs after the session has
277
+ * flushed its post-prompt settle (see {@link AgentSession.setIrcWakeTurnObserver}).
278
+ */
279
+ export declare function attachIrcWakeTurnMonitor(session: AgentSession, options: IrcWakeTurnMonitorOptions): void;
253
280
  /**
254
281
  * Settle a subagent's registry lifecycle after a run: terminal teardown for
255
282
  * hard aborts, unregister for one-shot helpers, park for isolated runs, and
@@ -3,6 +3,7 @@ import type { Settings } from "../config/settings.js";
3
3
  import type { PersistedSubagentReviverFactory } from "../registry/agent-lifecycle.js";
4
4
  import type { AgentSession } from "../session/agent-session.js";
5
5
  import type { AuthStorage } from "../session/auth-storage.js";
6
+ import type { EventBus } from "../utils/event-bus.js";
6
7
  /**
7
8
  * Ambient context the reviver needs at revive time. The top-level session is
8
9
  * kept LIVE (cwd / artifact manager read on demand) so a later `/new` or cwd
@@ -16,6 +17,12 @@ export interface PersistedSubagentReviveContext {
16
17
  settings: Settings;
17
18
  /** LSP policy of the top-level session; revived subagents inherit it rather than defaulting on. */
18
19
  enableLsp: boolean;
20
+ /**
21
+ * Shared event bus feeding RPC/collab subagent subscriptions. Passed through
22
+ * to the wake-turn monitor so an IRC send to a cold-revived subagent emits
23
+ * the same lifecycle/progress frames a live run does.
24
+ */
25
+ eventBus?: EventBus;
19
26
  }
20
27
  /**
21
28
  * Build the factory the {@link AgentLifecycleManager} uses to cold-revive a
@@ -21,4 +21,4 @@ export interface InterceptionResult {
21
21
  * @param availableTools Set of tool names that are available
22
22
  * @returns InterceptionResult indicating if the command should be blocked
23
23
  */
24
- export declare function checkBashInterception(command: string, availableTools: string[], rules?: BashInterceptorRule[]): InterceptionResult;
24
+ export declare function checkBashInterception(command: string, availableTools: string[], rules?: BashInterceptorRule[], originalCommand?: string): InterceptionResult;
@@ -16,12 +16,20 @@ export declare function findReusableCdp(exe: string, signal?: AbortSignal): Prom
16
16
  cdpUrl: string;
17
17
  pid: number;
18
18
  } | null>;
19
+ export declare function shouldPreserveConnectedBrowserFocus(target?: string): boolean;
19
20
  /**
20
21
  * Pick the best page target on an attached browser. Prefer discoverable page
21
22
  * targets first so Chromium/Edge attach flows that hide pages from
22
23
  * `browser.pages()` can still return a usable tab.
24
+ *
25
+ * `preferVisible` is for attaching to a browser a human is using: among equally
26
+ * usable tabs, take the one that is actually foregrounded rather than whichever
27
+ * target CDP happens to enumerate first.
23
28
  */
24
- export declare function pickElectronTarget(browser: Browser, matcher?: string): Promise<Page>;
29
+ export declare function pickElectronTarget(browser: Browser, options?: {
30
+ matcher?: string;
31
+ preferVisible?: boolean;
32
+ }): Promise<Page>;
25
33
  /**
26
34
  * SIGTERM the process tree, wait briefly, then SIGKILL anything still alive.
27
35
  * Single-process variant for our own spawned children.
@@ -27,7 +27,26 @@ export interface LaunchHeadlessOptions {
27
27
  /** Additional exact Puppeteer default arguments to suppress. */
28
28
  ignoreDefaultArgs?: readonly string[];
29
29
  }
30
- export declare function launchHeadlessBrowser(opts: LaunchHeadlessOptions): Promise<Browser>;
30
+ /** Result of a headless Chromium launch. */
31
+ export interface LaunchHeadlessResult {
32
+ browser: Browser;
33
+ /**
34
+ * OMP-owned temporary Chromium profile directory to remove after the browser
35
+ * process tree exits, or `undefined` when the caller supplied its own
36
+ * `--user-data-dir` (which OMP must not delete).
37
+ */
38
+ userDataDir?: string;
39
+ }
40
+ export declare function launchHeadlessBrowser(opts: LaunchHeadlessOptions): Promise<LaunchHeadlessResult>;
41
+ /**
42
+ * Remove an OMP-owned headless Chromium profile directory, tolerating the brief
43
+ * window on Windows in which Chromium (or an orphaned browser subprocess) still
44
+ * holds the profile lock. The shared temp remover centralizes retry handling
45
+ * for EBUSY/EPERM/ENOTEMPTY; if the directory is still busy afterwards we warn
46
+ * and leave it for a later cleanup pass rather than throwing — a shutdown cleanup
47
+ * failure must never crash the process (issue #7058).
48
+ */
49
+ export declare function removeUserDataDir(dir: string): Promise<void>;
31
50
  export declare function applyViewport(page: Page, viewport?: {
32
51
  width: number;
33
52
  height: number;
@@ -25,6 +25,8 @@ export interface PuppeteerBrowserHandle extends BrowserHandleCommon {
25
25
  browser: Browser;
26
26
  cdpUrl?: string;
27
27
  pid?: number;
28
+ /** OMP-owned temp Chromium profile directory removed on dispose (headless launches). */
29
+ userDataDir?: string;
28
30
  subprocess?: Subprocess;
29
31
  stealth: {
30
32
  browserSession: CDPSession | null;
@@ -59,12 +59,20 @@ export type WorkerInitPayload = {
59
59
  safeDir: string;
60
60
  targetId: string;
61
61
  dialogs?: "accept" | "dismiss";
62
+ url?: string;
63
+ waitUntil?: "load" | "domcontentloaded" | "networkidle0" | "networkidle2";
64
+ timeoutMs: number;
62
65
  /**
63
66
  * Post-timeout recycle: before adopting the page, dismiss any open JS dialog and
64
67
  * stop a pending navigation so a blocked target cannot stall worker init (which
65
68
  * previously force-killed the tab). Never set for first-time Electron attach.
66
69
  */
67
70
  recover?: boolean;
71
+ /**
72
+ * Whether the worker may raise this tab before capturing a screenshot. Unset
73
+ * behaves as `true`; the supervisor clears it for browsers we did not launch.
74
+ */
75
+ activateForScreenshot?: boolean;
68
76
  };
69
77
  export type ToolReply = {
70
78
  ok: true;
@@ -47,6 +47,7 @@ interface TabSessionBase<TBrowser extends BrowserHandle = BrowserHandle> {
47
47
  export interface WorkerTabSession extends TabSessionBase<PuppeteerBrowserHandle> {
48
48
  backend: "worker";
49
49
  worker: WorkerHandle;
50
+ activateForScreenshot: boolean;
50
51
  }
51
52
  export interface CmuxTabSession extends TabSessionBase<CmuxBrowserHandle> {
52
53
  backend: "cmux";
@@ -1,5 +1,5 @@
1
1
  import type { HTMLElement } from "linkedom";
2
- import type { ElementHandle } from "puppeteer-core";
2
+ import type { ElementHandle, Page } from "puppeteer-core";
3
3
  import type { Transport } from "./tab-protocol.js";
4
4
  declare module "puppeteer-core" {
5
5
  interface Frame {
@@ -15,6 +15,7 @@ declare global {
15
15
  var innerHeight: number;
16
16
  var document: {
17
17
  elementFromPoint(x: number, y: number): Element | null;
18
+ readonly visibilityState: "visible" | "hidden";
18
19
  };
19
20
  }
20
21
  export interface OpTimeouts {
@@ -66,6 +67,7 @@ export interface InflightOp {
66
67
  }
67
68
  /** Human-readable label for a screenshot op, used in op tracking + timeout errors. */
68
69
  export declare function describeScreenshot(opts?: ScreenshotOptions): string;
70
+ export declare function preparePageForScreenshot(page: Pick<Page, "bringToFront" | "evaluate">, signal: AbortSignal | undefined, activate: boolean): Promise<void>;
69
71
  /** Summarize still-running helpers (oldest first) so a cell timeout names what stalled. */
70
72
  export declare function describeInflight(inflight: Map<number, InflightOp>): string;
71
73
  export declare class WorkerCore {
@@ -12,3 +12,14 @@
12
12
  * cannot find the structure they need.
13
13
  */
14
14
  export declare function tokenizeShellSegments(command: string): string[][];
15
+ /**
16
+ * Returns the original text of flat shell command segments. Unlike
17
+ * `tokenizeShellSegments`, this preserves quoting and escaping so the results
18
+ * are safe to match against user-configured regular expressions.
19
+ *
20
+ * The extractor deliberately declines to split syntax whose execution context
21
+ * cannot be determined with this small scanner (heredocs, command substitution,
22
+ * backticks, grouping, and malformed quoting). Callers must still check the
23
+ * complete input in that case.
24
+ */
25
+ export declare function extractFlatShellCommandSegments(command: string): string[];
@@ -48,6 +48,8 @@ export declare class ImageInputTooLargeError extends Error {
48
48
  readonly maxBytes: number;
49
49
  constructor(bytes: number, maxBytes: number);
50
50
  }
51
+ /** Converts an image to PNG, rejecting when the runtime cannot decode or encode it. */
52
+ export declare function convertImageToPng(image: ImageContent): Promise<ImageContent>;
51
53
  export declare function ensureSupportedImageInput(image: ImageContent): Promise<ImageContent | null>;
52
54
  export interface NormalizeModelContextImagesOptions {
53
55
  /** Model the images are bound for; used to derive encoder constraints (WebP exclusion for Ollama). */
@@ -2,6 +2,15 @@ import type { AuthStorage } from "@oh-my-pi/pi-ai";
2
2
  import type { SearchResponse } from "../../../web/search/types.js";
3
3
  import type { SearchParams } from "./base.js";
4
4
  import { SearchProvider } from "./base.js";
5
+ /**
6
+ * Map a parsed `lang:` locale onto DuckDuckGo's documented `kl` values.
7
+ *
8
+ * Shared queries use `language-region` order while DDG generally uses
9
+ * `region-language`. Provider-specific exceptions resolve through
10
+ * {@link DDG_LOCALE_ALIASES}; all other values must survive the documented
11
+ * allowlist after swapping or the caller keeps its default region.
12
+ */
13
+ export declare function localeToKl(lang: string | undefined): string | undefined;
5
14
  /** Execute a DuckDuckGo web search via the no-JS HTML frontend. */
6
15
  export declare function searchDuckDuckGo(params: SearchParams): Promise<SearchResponse>;
7
16
  /** Search provider for DuckDuckGo (no API key required). */
@@ -26,7 +26,7 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
26
26
  }, {
27
27
  readonly value: "xai";
28
28
  readonly label: "xAI";
29
- readonly description: "Grok web search via xAI Responses API (requires XAI_API_KEY)";
29
+ readonly description: "Grok web search via xAI Responses API (uses SuperGrok/X Premium+ OAuth via /login xai-oauth, or XAI_API_KEY)";
30
30
  }, {
31
31
  readonly value: "zai";
32
32
  readonly label: "Z.AI";
@@ -128,7 +128,7 @@ export declare const SEARCH_PROVIDER_CHOICES: ({
128
128
  } | {
129
129
  readonly value: "xai";
130
130
  readonly label: "xAI";
131
- readonly description: "Grok web search via xAI Responses API (requires XAI_API_KEY)";
131
+ readonly description: "Grok web search via xAI Responses API (uses SuperGrok/X Premium+ OAuth via /login xai-oauth, or XAI_API_KEY)";
132
132
  } | {
133
133
  readonly value: "zai";
134
134
  readonly label: "Z.AI";