@oh-my-pi/pi-coding-agent 15.11.4 → 15.11.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cli.js +450 -424
  3. package/dist/types/cli/usage-cli.d.ts +10 -1
  4. package/dist/types/commands/usage.d.ts +9 -0
  5. package/dist/types/config/settings-schema.d.ts +53 -3
  6. package/dist/types/modes/components/reset-usage-selector.d.ts +12 -0
  7. package/dist/types/modes/components/session-selector.d.ts +1 -1
  8. package/dist/types/modes/components/tool-execution.d.ts +14 -0
  9. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  10. package/dist/types/modes/interactive-mode.d.ts +10 -0
  11. package/dist/types/modes/session-observer-registry.d.ts +2 -0
  12. package/dist/types/modes/types.d.ts +2 -0
  13. package/dist/types/modes/utils/context-usage.d.ts +6 -1
  14. package/dist/types/session/agent-session.d.ts +14 -1
  15. package/dist/types/session/auth-storage.d.ts +1 -1
  16. package/dist/types/session/codex-auto-reset.d.ts +107 -0
  17. package/dist/types/session/snapcompact-inline.d.ts +105 -4
  18. package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
  19. package/dist/types/task/render.d.ts +1 -0
  20. package/dist/types/tools/todo.d.ts +0 -11
  21. package/package.json +11 -11
  22. package/src/cli/usage-cli.ts +187 -16
  23. package/src/commands/usage.ts +8 -0
  24. package/src/config/settings-schema.ts +56 -3
  25. package/src/config/settings.ts +9 -0
  26. package/src/internal-urls/docs-index.generated.ts +1 -1
  27. package/src/modes/components/reset-usage-selector.ts +161 -0
  28. package/src/modes/components/session-selector.ts +8 -2
  29. package/src/modes/components/settings-selector.ts +62 -47
  30. package/src/modes/components/tool-execution.ts +18 -0
  31. package/src/modes/components/transcript-container.ts +23 -1
  32. package/src/modes/controllers/command-controller.ts +24 -1
  33. package/src/modes/controllers/selector-controller.ts +68 -0
  34. package/src/modes/interactive-mode.ts +59 -0
  35. package/src/modes/session-observer-registry.ts +61 -3
  36. package/src/modes/theme/theme.ts +2 -2
  37. package/src/modes/types.ts +2 -0
  38. package/src/modes/utils/context-usage.ts +75 -1
  39. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  40. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  41. package/src/prompts/system/snapcompact-toolresult-note.md +1 -1
  42. package/src/prompts/tools/browser.md +33 -43
  43. package/src/prompts/tools/eval.md +27 -50
  44. package/src/prompts/tools/irc.md +29 -31
  45. package/src/prompts/tools/read.md +31 -37
  46. package/src/prompts/tools/todo.md +1 -2
  47. package/src/sdk.ts +3 -2
  48. package/src/session/agent-session.ts +131 -6
  49. package/src/session/auth-storage.ts +3 -0
  50. package/src/session/codex-auto-reset.ts +190 -0
  51. package/src/session/snapcompact-inline.ts +396 -59
  52. package/src/slash-commands/builtin-registry.ts +145 -8
  53. package/src/slash-commands/helpers/context-report.ts +28 -1
  54. package/src/slash-commands/helpers/reset-usage.ts +66 -0
  55. package/src/slash-commands/helpers/usage-report.ts +12 -0
  56. package/src/task/index.ts +30 -7
  57. package/src/task/render.ts +34 -19
  58. package/src/tools/todo.ts +8 -128
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.11.4",
4
+ "version": "15.11.6",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,16 +47,16 @@
47
47
  "@agentclientprotocol/sdk": "0.22.1",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.11.4",
51
- "@oh-my-pi/omp-stats": "15.11.4",
52
- "@oh-my-pi/pi-agent-core": "15.11.4",
53
- "@oh-my-pi/pi-ai": "15.11.4",
54
- "@oh-my-pi/pi-catalog": "15.11.4",
55
- "@oh-my-pi/pi-mnemopi": "15.11.4",
56
- "@oh-my-pi/pi-natives": "15.11.4",
57
- "@oh-my-pi/pi-tui": "15.11.4",
58
- "@oh-my-pi/pi-utils": "15.11.4",
59
- "@oh-my-pi/snapcompact": "15.11.4",
50
+ "@oh-my-pi/hashline": "15.11.6",
51
+ "@oh-my-pi/omp-stats": "15.11.6",
52
+ "@oh-my-pi/pi-agent-core": "15.11.6",
53
+ "@oh-my-pi/pi-ai": "15.11.6",
54
+ "@oh-my-pi/pi-catalog": "15.11.6",
55
+ "@oh-my-pi/pi-mnemopi": "15.11.6",
56
+ "@oh-my-pi/pi-natives": "15.11.6",
57
+ "@oh-my-pi/pi-tui": "15.11.6",
58
+ "@oh-my-pi/pi-utils": "15.11.6",
59
+ "@oh-my-pi/snapcompact": "15.11.6",
60
60
  "@opentelemetry/api": "^1.9.1",
61
61
  "@opentelemetry/context-async-hooks": "^2.7.1",
62
62
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -7,7 +7,14 @@
7
7
  * credentials produced no usage report are listed too, so the output
8
8
  * always covers the full credential pool.
9
9
  */
10
- import type { AuthStorage, UsageLimit, UsageReport, UsageUnit } from "@oh-my-pi/pi-ai";
10
+ import {
11
+ type AuthStorage,
12
+ resolveUsedFraction,
13
+ type UsageHistoryEntry,
14
+ type UsageLimit,
15
+ type UsageReport,
16
+ type UsageUnit,
17
+ } from "@oh-my-pi/pi-ai";
11
18
  import { formatDuration, formatNumber } from "@oh-my-pi/pi-utils";
12
19
  import chalk from "chalk";
13
20
  import { ModelRegistry } from "../config/model-registry";
@@ -19,6 +26,10 @@ export interface UsageCommandArgs {
19
26
  json?: boolean;
20
27
  provider?: string;
21
28
  redact?: boolean;
29
+ /** Show recorded usage-limit history instead of a live snapshot. */
30
+ history?: boolean;
31
+ /** History window in days (with `history`). */
32
+ days?: number;
22
33
  }
23
34
 
24
35
  /** Identity slice of a stored credential, for "every account" coverage. */
@@ -139,20 +150,9 @@ function collectIdentityStrings(reports: UsageReport[], accounts: UsageAccountId
139
150
 
140
151
  type LimitStatus = NonNullable<UsageLimit["status"]>;
141
152
 
142
- function resolveFraction(limit: UsageLimit): number | undefined {
143
- const amount = limit.amount;
144
- if (amount.usedFraction !== undefined) return amount.usedFraction;
145
- if (amount.used !== undefined && amount.limit !== undefined && amount.limit > 0) {
146
- return amount.used / amount.limit;
147
- }
148
- if (amount.unit === "percent" && amount.used !== undefined) return amount.used / 100;
149
- if (amount.remainingFraction !== undefined) return Math.max(0, 1 - amount.remainingFraction);
150
- return undefined;
151
- }
152
-
153
153
  function resolveStatus(limit: UsageLimit): LimitStatus {
154
154
  if (limit.status && limit.status !== "unknown") return limit.status;
155
- const fraction = resolveFraction(limit);
155
+ const fraction = resolveUsedFraction(limit);
156
156
  if (fraction === undefined) return "unknown";
157
157
  if (fraction >= 1) return "exhausted";
158
158
  if (fraction >= 0.8) return "warning";
@@ -208,7 +208,7 @@ function describeAmount(limit: UsageLimit): string {
208
208
  } else if (absoluteUnit && amount.remaining !== undefined) {
209
209
  parts.push(`${formatUnitValue(amount.remaining, amount.unit)}${UNIT_SUFFIX[amount.unit]} left`);
210
210
  }
211
- const fraction = resolveFraction(limit);
211
+ const fraction = resolveUsedFraction(limit);
212
212
  if (fraction !== undefined) {
213
213
  parts.push(`${(fraction * 100).toFixed(1)}% used`);
214
214
  } else if (amount.remainingFraction !== undefined) {
@@ -219,7 +219,7 @@ function describeAmount(limit: UsageLimit): string {
219
219
  }
220
220
 
221
221
  function renderBar(limit: UsageLimit): string {
222
- const fraction = resolveFraction(limit);
222
+ const fraction = resolveUsedFraction(limit);
223
223
  if (fraction === undefined) return chalk.dim("·".repeat(BAR_WIDTH));
224
224
  const clamped = Math.min(Math.max(fraction, 0), 1);
225
225
  const filled = Math.round(clamped * BAR_WIDTH);
@@ -325,6 +325,8 @@ function formatAccountHeader(
325
325
  let header = `${icon} ${chalk.bold(redaction?.get(label) ?? label)}`;
326
326
  const planType = report.metadata?.planType;
327
327
  if (typeof planType === "string" && planType) header += chalk.dim(` · plan: ${planType}`);
328
+ const savedResets = report.resetCredits?.availableCount ?? 0;
329
+ if (savedResets > 0) header += chalk.cyan(` · ✦ ${savedResets} saved reset${savedResets === 1 ? "" : "s"}`);
328
330
  if (report.fetchedAt && nowMs - report.fetchedAt > 90_000) {
329
331
  header += chalk.dim(` · fetched ${formatDuration(nowMs - report.fetchedAt)} ago`);
330
332
  }
@@ -375,7 +377,7 @@ export function computeProviderWindowStats(reports: UsageReport[]): ProviderWind
375
377
  for (const report of reports) {
376
378
  const accountMax = new Map<string, number>();
377
379
  for (const limit of report.limits) {
378
- const fraction = resolveFraction(limit);
380
+ const fraction = resolveUsedFraction(limit);
379
381
  if (fraction === undefined) continue;
380
382
  const durationMs = limit.window?.durationMs;
381
383
  const key =
@@ -482,6 +484,144 @@ export function formatUsageBreakdown(
482
484
  return lines.join("\n");
483
485
  }
484
486
 
487
+ const HISTORY_SPARK_WIDTH = 48;
488
+ const SPARK_LEVELS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"] as const;
489
+
490
+ interface HistorySeries {
491
+ title: string;
492
+ /** Snapshots ascending by recordedAt (listUsageHistory order). */
493
+ entries: UsageHistoryEntry[];
494
+ }
495
+
496
+ interface HistoryAccount {
497
+ label: string;
498
+ series: Map<string, HistorySeries>;
499
+ }
500
+
501
+ /** Mirror of {@link limitTitle} for history rows (no scope/tier available). */
502
+ function historySeriesTitle(entry: UsageHistoryEntry): string {
503
+ const label = entry.label;
504
+ const windowLabel = entry.windowLabel;
505
+ if (!windowLabel) return label;
506
+ if (windowLabel.toLowerCase() === "quota window") return label;
507
+ if (label.toLowerCase().includes(windowLabel.toLowerCase())) return label;
508
+ return `${label} (${windowLabel})`;
509
+ }
510
+
511
+ function historyAccountLabel(entry: UsageHistoryEntry): string {
512
+ return entry.email ?? entry.accountId ?? entry.accountKey;
513
+ }
514
+
515
+ function historyStatus(fraction: number | undefined, status: UsageHistoryEntry["status"]): LimitStatus {
516
+ if (status && status !== "unknown") return status;
517
+ if (fraction === undefined) return "unknown";
518
+ if (fraction >= 1) return "exhausted";
519
+ if (fraction >= 0.8) return "warning";
520
+ return "ok";
521
+ }
522
+
523
+ /** Peak-per-bucket sparkline over [sinceMs, nowMs]; empty buckets render dim dots. */
524
+ function renderHistorySparkline(entries: UsageHistoryEntry[], sinceMs: number, nowMs: number): string {
525
+ const span = Math.max(1, nowMs - sinceMs);
526
+ const buckets: Array<number | undefined> = new Array(HISTORY_SPARK_WIDTH).fill(undefined);
527
+ for (const entry of entries) {
528
+ if (entry.usedFraction === undefined) continue;
529
+ const offset = Math.floor(((entry.recordedAt - sinceMs) / span) * HISTORY_SPARK_WIDTH);
530
+ const index = Math.min(HISTORY_SPARK_WIDTH - 1, Math.max(0, offset));
531
+ const prev = buckets[index];
532
+ buckets[index] = prev === undefined ? entry.usedFraction : Math.max(prev, entry.usedFraction);
533
+ }
534
+ return buckets
535
+ .map(fraction => {
536
+ if (fraction === undefined) return chalk.dim("·");
537
+ const clamped = Math.min(Math.max(fraction, 0), 1);
538
+ const level = SPARK_LEVELS[Math.min(SPARK_LEVELS.length - 1, Math.floor(clamped * SPARK_LEVELS.length))];
539
+ return STATUS_COLOR[historyStatus(clamped, undefined)](level);
540
+ })
541
+ .join("");
542
+ }
543
+
544
+ /** Identity strings a history rendering could surface — input for {@link buildRedactionMap}. */
545
+ function collectHistoryIdentityStrings(entries: UsageHistoryEntry[]): string[] {
546
+ const values: string[] = [];
547
+ for (const entry of entries) {
548
+ if (entry.email) values.push(entry.email);
549
+ if (entry.accountId) values.push(entry.accountId);
550
+ values.push(entry.accountKey);
551
+ }
552
+ return values;
553
+ }
554
+
555
+ /**
556
+ * Render recorded usage-limit history: per provider, per account, one
557
+ * peak-per-bucket sparkline per limit window plus latest/peak percentages.
558
+ */
559
+ export function formatUsageHistory(
560
+ entries: UsageHistoryEntry[],
561
+ sinceMs: number,
562
+ nowMs: number,
563
+ redaction?: Map<string, string>,
564
+ ): string {
565
+ const providers = new Map<string, Map<string, HistoryAccount>>();
566
+ for (const entry of entries) {
567
+ let accounts = providers.get(entry.provider);
568
+ if (!accounts) {
569
+ accounts = new Map();
570
+ providers.set(entry.provider, accounts);
571
+ }
572
+ let account = accounts.get(entry.accountKey);
573
+ if (!account) {
574
+ account = { label: historyAccountLabel(entry), series: new Map() };
575
+ accounts.set(entry.accountKey, account);
576
+ }
577
+ let series = account.series.get(entry.limitId);
578
+ if (!series) {
579
+ series = { title: historySeriesTitle(entry), entries: [] };
580
+ account.series.set(entry.limitId, series);
581
+ }
582
+ // Labels can change across snapshots (provider renames); latest wins.
583
+ series.title = historySeriesTitle(entry);
584
+ series.entries.push(entry);
585
+ }
586
+
587
+ const lines: string[] = [];
588
+ lines.push(
589
+ `${chalk.bold("Usage history")}${chalk.dim(` · last ${formatDuration(nowMs - sinceMs)} · peak per bucket`)}`,
590
+ );
591
+
592
+ for (const provider of [...providers.keys()].sort((a, b) => a.localeCompare(b))) {
593
+ const accounts = providers.get(provider) ?? new Map<string, HistoryAccount>();
594
+ lines.push("");
595
+ lines.push(
596
+ `${chalk.bold.cyan(formatProviderName(provider))} ${chalk.dim(`— ${accounts.size} ${accounts.size === 1 ? "account" : "accounts"}`)}`,
597
+ );
598
+ const sortedAccounts = [...accounts.values()].sort((a, b) => a.label.localeCompare(b.label));
599
+ for (const account of sortedAccounts) {
600
+ lines.push(` ${chalk.bold(redaction?.get(account.label) ?? account.label)}`);
601
+ const labelWidth = [...account.series.values()].reduce((max, series) => Math.max(max, series.title.length), 0);
602
+ const sortedSeries = [...account.series.values()].sort((a, b) => a.title.localeCompare(b.title));
603
+ for (const series of sortedSeries) {
604
+ const fractions = series.entries
605
+ .map(entry => entry.usedFraction)
606
+ .filter((fraction): fraction is number => fraction !== undefined);
607
+ const latestEntry = series.entries[series.entries.length - 1];
608
+ const latestFraction = fractions.length > 0 ? fractions[fractions.length - 1] : undefined;
609
+ const peakFraction = fractions.length > 0 ? Math.max(...fractions) : undefined;
610
+ const status = historyStatus(latestFraction, latestEntry?.status);
611
+ const details: string[] = [];
612
+ if (latestFraction !== undefined) details.push(`latest ${(latestFraction * 100).toFixed(1)}%`);
613
+ if (peakFraction !== undefined) details.push(`peak ${(peakFraction * 100).toFixed(1)}%`);
614
+ details.push(`${series.entries.length} snapshot${series.entries.length === 1 ? "" : "s"}`);
615
+ lines.push(
616
+ ` ${STATUS_COLOR[status]("●")} ${series.title.padEnd(labelWidth)} ${renderHistorySparkline(series.entries, sinceMs, nowMs)} ${chalk.dim(details.join(" · "))}`,
617
+ );
618
+ }
619
+ }
620
+ }
621
+
622
+ return lines.join("\n");
623
+ }
624
+
485
625
  function collectStoredAccounts(authStorage: AuthStorage): UsageAccountIdentity[] {
486
626
  const accounts: UsageAccountIdentity[] = [];
487
627
  const all = authStorage.getAll();
@@ -541,6 +681,37 @@ function redactReportForJson(
541
681
  export async function runUsageCommand(cmd: UsageCommandArgs): Promise<void> {
542
682
  const authStorage = await discoverAuthStorage();
543
683
  try {
684
+ if (cmd.history) {
685
+ const days = cmd.days !== undefined && Number.isFinite(cmd.days) && cmd.days > 0 ? cmd.days : 7;
686
+ const nowMs = Date.now();
687
+ const sinceMs = nowMs - days * 86_400_000;
688
+ const entries = authStorage.listUsageHistory({ sinceMs, provider: cmd.provider?.toLowerCase() });
689
+ const redaction = cmd.redact ? buildRedactionMap(collectHistoryIdentityStrings(entries)) : undefined;
690
+ if (cmd.json) {
691
+ const masked = redaction
692
+ ? entries.map(entry => ({
693
+ ...entry,
694
+ accountKey: redaction.get(entry.accountKey) ?? entry.accountKey,
695
+ email: maskIdentity(redaction, entry.email),
696
+ accountId: maskIdentity(redaction, entry.accountId),
697
+ }))
698
+ : entries;
699
+ process.stdout.write(`${JSON.stringify({ generatedAt: nowMs, sinceMs, entries: masked }, null, 2)}\n`);
700
+ return;
701
+ }
702
+ if (entries.length === 0) {
703
+ const scope = cmd.provider ? ` for provider "${cmd.provider}"` : "";
704
+ process.stderr.write(
705
+ chalk.yellow(
706
+ `No usage history recorded${scope} yet. Snapshots accumulate whenever usage is fetched (TUI footer, /usage, omp usage).\n`,
707
+ ),
708
+ );
709
+ process.exitCode = 1;
710
+ return;
711
+ }
712
+ process.stdout.write(`${formatUsageHistory(entries, sinceMs, nowMs, redaction)}\n`);
713
+ return;
714
+ }
544
715
  const modelRegistry = new ModelRegistry(authStorage);
545
716
  const reports =
546
717
  (await authStorage.fetchUsageReports({
@@ -15,6 +15,11 @@ export default class Usage extends Command {
15
15
  description: "Redact account emails/ids (shortest unique prefix) for sharing screenshots",
16
16
  default: false,
17
17
  }),
18
+ history: Flags.boolean({
19
+ description: "Show recorded usage-limit history (hourly snapshots) instead of a live snapshot",
20
+ default: false,
21
+ }),
22
+ days: Flags.integer({ char: "d", description: "History window in days (with --history)", default: 7 }),
18
23
  };
19
24
 
20
25
  static examples = [
@@ -22,6 +27,7 @@ export default class Usage extends Command {
22
27
  "# Only Anthropic accounts\n omp usage --provider anthropic",
23
28
  "# Redact account identifiers for screenshots\n omp usage --redact",
24
29
  "# Machine-readable output\n omp usage --json",
30
+ "# Usage-limit trend over the last 30 days\n omp usage --history --days 30",
25
31
  ];
26
32
 
27
33
  async run(): Promise<void> {
@@ -30,6 +36,8 @@ export default class Usage extends Command {
30
36
  json: flags.json,
31
37
  provider: flags.provider,
32
38
  redact: flags.redact,
39
+ history: flags.history,
40
+ days: flags.days,
33
41
  });
34
42
  }
35
43
  }
@@ -1572,14 +1572,28 @@ export const SETTINGS_SCHEMA = {
1572
1572
 
1573
1573
  // Experimental: snapcompact inline imaging (transient, per-request; never persisted)
1574
1574
  "snapcompact.systemPrompt": {
1575
- type: "boolean",
1576
- default: false,
1575
+ type: "enum",
1576
+ values: ["none", "agents-md", "all"] as const,
1577
+ default: "none",
1577
1578
  ui: {
1578
1579
  tab: "context",
1579
1580
  group: "Experimental",
1580
1581
  label: "Snapcompact System Prompt",
1581
1582
  description:
1582
- "Experimental: render the system prompt as dense PNG image(s) and attach to the first user message (vision models only). Saves tokens; loses system-prompt prompt caching.",
1583
+ "Experimental: render selected system prompt text as dense PNG image(s) and attach to the first user message (vision models only). Saves tokens; loses prompt caching for imaged text.",
1584
+ options: [
1585
+ { value: "none", label: "None", description: "Keep the system prompt as text." },
1586
+ {
1587
+ value: "agents-md",
1588
+ label: "AGENTS.md",
1589
+ description: "Only move loaded context-file instructions to images, when that saves tokens.",
1590
+ },
1591
+ {
1592
+ value: "all",
1593
+ label: "All",
1594
+ description: "Move the full system prompt to images, when that saves tokens.",
1595
+ },
1596
+ ],
1583
1597
  },
1584
1598
  },
1585
1599
 
@@ -3617,6 +3631,39 @@ export const SETTINGS_SCHEMA = {
3617
3631
  ],
3618
3632
  },
3619
3633
  },
3634
+ // Codex saved rate-limit resets (auto-redeem)
3635
+ "codexResets.autoRedeem": {
3636
+ type: "boolean",
3637
+ default: false,
3638
+ ui: {
3639
+ tab: "providers",
3640
+ group: "Services",
3641
+ label: "Codex Auto-Redeem Saved Resets",
3642
+ description:
3643
+ "When a turn is blocked by the Codex weekly limit on the active account and no other account is available, automatically spend one saved rate-limit reset (ChatGPT 'save rate limit resets'). Conservative: never fires for 5-hour-only or Spark limits, near a natural reset, or twice for the same block. Requires retries enabled.",
3644
+ },
3645
+ },
3646
+ "codexResets.minBlockedMinutes": {
3647
+ type: "number",
3648
+ default: 60,
3649
+ ui: {
3650
+ tab: "providers",
3651
+ group: "Services",
3652
+ label: "Codex Auto-Redeem Min Block",
3653
+ description:
3654
+ "Only auto-redeem when the natural weekly reset is at least this many minutes away (don't spend a ~30-day credit to save a short wait).",
3655
+ },
3656
+ },
3657
+ "codexResets.keepCredits": {
3658
+ type: "number",
3659
+ default: 0,
3660
+ ui: {
3661
+ tab: "providers",
3662
+ group: "Services",
3663
+ label: "Codex Auto-Redeem Reserve",
3664
+ description: "Never auto-spend below this many saved resets (0 = the last credit may be spent automatically).",
3665
+ },
3666
+ },
3620
3667
  "provider.appendOnlyContext": {
3621
3668
  type: "enum",
3622
3669
  values: ["auto", "on", "off"] as const,
@@ -4005,6 +4052,11 @@ export interface ShellMinimizerSettings {
4005
4052
  sourceOutlineLevel: "default" | "aggressive";
4006
4053
  legacyFilters: boolean | undefined;
4007
4054
  }
4055
+ export interface CodexResetsSettings {
4056
+ autoRedeem: boolean;
4057
+ minBlockedMinutes: number;
4058
+ keepCredits: number;
4059
+ }
4008
4060
 
4009
4061
  /** Map group prefix -> typed settings interface */
4010
4062
  export interface GroupTypeMap {
@@ -4024,6 +4076,7 @@ export interface GroupTypeMap {
4024
4076
  modelTags: ModelTagsSettings;
4025
4077
  cycleOrder: string[];
4026
4078
  shellMinimizer: ShellMinimizerSettings;
4079
+ codexResets: CodexResetsSettings;
4027
4080
  }
4028
4081
 
4029
4082
  export type GroupPrefix = keyof GroupTypeMap;
@@ -798,6 +798,15 @@ export class Settings {
798
798
  raw["compaction.strategy"] = "shake";
799
799
  }
800
800
 
801
+ // snapcompact.systemPrompt: boolean -> scoped enum.
802
+ const snapcompactObj = raw.snapcompact as Record<string, unknown> | undefined;
803
+ if (snapcompactObj && typeof snapcompactObj.systemPrompt === "boolean") {
804
+ snapcompactObj.systemPrompt = snapcompactObj.systemPrompt ? "all" : "none";
805
+ }
806
+ if (typeof raw["snapcompact.systemPrompt"] === "boolean") {
807
+ raw["snapcompact.systemPrompt"] = raw["snapcompact.systemPrompt"] ? "all" : "none";
808
+ }
809
+
801
810
  // statusLine: rename "plan_mode" segment to "mode"
802
811
  const statusLineObj = raw.statusLine as Record<string, unknown> | undefined;
803
812
  if (statusLineObj) {