@oh-my-pi/pi-coding-agent 17.1.8 → 17.2.1
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 +110 -0
- package/dist/{CHANGELOG-vt8ene9g.md → CHANGELOG-dj46zzrm.md} +110 -0
- package/dist/cli.js +10009 -9654
- package/dist/{template-dys3vk5b.js → template-8vdv6xb4.js} +1 -1
- package/dist/types/advisor/runtime.d.ts +8 -2
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/model-discovery.d.ts +12 -0
- package/dist/types/config/settings-schema.d.ts +73 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +25 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/internal-urls/security-protocol.d.ts +15 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +18 -0
- package/dist/types/security/auth.d.ts +30 -0
- package/dist/types/security/cloud.d.ts +79 -0
- package/dist/types/security/comparison.d.ts +49 -0
- package/dist/types/security/contracts/ids.d.ts +15 -0
- package/dist/types/security/contracts/index.d.ts +4 -0
- package/dist/types/security/contracts/schemas.d.ts +660 -0
- package/dist/types/security/contracts/types.d.ts +234 -0
- package/dist/types/security/contracts/validation.d.ts +5 -0
- package/dist/types/security/coordinator.d.ts +100 -0
- package/dist/types/security/importers/codex-security.d.ts +7 -0
- package/dist/types/security/importers/index.d.ts +2 -0
- package/dist/types/security/importers/sarif.d.ts +9 -0
- package/dist/types/security/index.d.ts +13 -0
- package/dist/types/security/preflight.d.ts +61 -0
- package/dist/types/security/provenance.d.ts +24 -0
- package/dist/types/security/publication.d.ts +78 -0
- package/dist/types/security/remediation.d.ts +27 -0
- package/dist/types/security/resource-output.d.ts +8 -0
- package/dist/types/security/sarif.d.ts +2 -0
- package/dist/types/security/store.d.ts +40 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/slash-commands/helpers/security.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +4 -1
- package/dist/types/task/executor.d.ts +14 -2
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +10 -2
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/security-scan.d.ts +96 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +12 -12
- package/scripts/security-compare.ts +40 -0
- package/src/advisor/runtime.ts +138 -12
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli.ts +9 -0
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-discovery.ts +32 -5
- package/src/config/model-registry.ts +107 -25
- package/src/config/settings-schema.ts +77 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/eval/py/prelude.py +7 -3
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +65 -13
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/internal-urls/router.ts +4 -1
- package/src/internal-urls/security-protocol.ts +261 -0
- package/src/internal-urls/types.ts +1 -1
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +7 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +15 -9
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +31 -17
- package/src/modes/rpc/host-uris.ts +6 -0
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/agents/security-reviewer.md +75 -0
- package/src/prompts/security/scan-coordinator.md +7 -0
- package/src/prompts/security/scan-request.md +21 -0
- package/src/prompts/security/validate-request.md +8 -0
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -0
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/grep.md +7 -6
- package/src/prompts/tools/security-publish.md +1 -0
- package/src/prompts/tools/security-scan.md +1 -0
- package/src/sdk.ts +212 -21
- package/src/security/auth.ts +98 -0
- package/src/security/cloud.ts +686 -0
- package/src/security/comparison.ts +255 -0
- package/src/security/contracts/ids.ts +111 -0
- package/src/security/contracts/index.ts +4 -0
- package/src/security/contracts/schemas.ts +201 -0
- package/src/security/contracts/types.ts +254 -0
- package/src/security/contracts/validation.ts +65 -0
- package/src/security/coordinator.ts +708 -0
- package/src/security/importers/codex-security.ts +387 -0
- package/src/security/importers/index.ts +2 -0
- package/src/security/importers/sarif.ts +357 -0
- package/src/security/index.ts +13 -0
- package/src/security/preflight.ts +405 -0
- package/src/security/provenance.ts +106 -0
- package/src/security/publication.ts +326 -0
- package/src/security/remediation.ts +93 -0
- package/src/security/resource-output.ts +50 -0
- package/src/security/sarif.ts +78 -0
- package/src/security/store.ts +430 -0
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +302 -175
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +11 -3
- package/src/session/session-advisors.ts +140 -16
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +152 -3
- package/src/slash-commands/helpers/security.ts +451 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/agents.ts +2 -0
- package/src/task/executor.ts +20 -4
- package/src/task/render.ts +20 -4
- package/src/task/structured-subagent.ts +6 -4
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/browser.ts +5 -0
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +37 -5
- package/src/tools/path-utils.ts +91 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/security-scan.ts +287 -0
- package/src/tools/write.ts +9 -3
- package/src/tools/xdev.ts +39 -14
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/types.ts +1 -1
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
import { CustomMessageComponent } from "./custom-message";
|
|
52
52
|
import { EvalExecutionComponent } from "./eval-execution";
|
|
53
53
|
import { type LateDiagnosticsFile, LateDiagnosticsMessageComponent } from "./late-diagnostics-message";
|
|
54
|
-
import { ReadToolGroupComponent, readArgsCollapseIntoGroup } from "./read-tool-group";
|
|
54
|
+
import { groupedReadUsageCallIds, ReadToolGroupComponent, readArgsCollapseIntoGroup } from "./read-tool-group";
|
|
55
55
|
import { SkillMessageComponent } from "./skill-message";
|
|
56
56
|
import { ToolExecutionComponent } from "./tool-execution";
|
|
57
57
|
import { TranscriptContainer } from "./transcript-container";
|
|
@@ -86,6 +86,7 @@ export class ChatTranscriptBuilder {
|
|
|
86
86
|
#pendingUsageDuration: number | undefined;
|
|
87
87
|
#pendingUsageTtft: number | undefined;
|
|
88
88
|
#pendingUsageTimestamp: number | undefined;
|
|
89
|
+
#pendingReadUsageCallIds: string[] | undefined;
|
|
89
90
|
#lastAssistantUsage: Usage | undefined;
|
|
90
91
|
#waitingPoll: ToolExecutionComponent | null = null;
|
|
91
92
|
#todoSnapshot: ToolExecutionComponent | null = null;
|
|
@@ -135,6 +136,7 @@ export class ChatTranscriptBuilder {
|
|
|
135
136
|
this.#pendingUsageDuration = undefined;
|
|
136
137
|
this.#pendingUsageTtft = undefined;
|
|
137
138
|
this.#pendingUsageTimestamp = undefined;
|
|
139
|
+
this.#pendingReadUsageCallIds = undefined;
|
|
138
140
|
this.#lastAssistantUsage = undefined;
|
|
139
141
|
this.#waitingPoll = null;
|
|
140
142
|
this.#todoSnapshot = null;
|
|
@@ -194,30 +196,46 @@ export class ChatTranscriptBuilder {
|
|
|
194
196
|
return this.#readGroup;
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
// are placed, sealing the read run so the row sits under it.
|
|
199
|
+
// Defer per-turn metrics until the turn's tool results have materialized.
|
|
200
|
+
// Read-only invisible turns attach the metrics to their shared compact
|
|
201
|
+
// group; every other turn keeps the standalone row below its tool blocks.
|
|
201
202
|
#flushPendingUsage(): void {
|
|
202
203
|
if (!this.#pendingUsage) return;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
const usageAttached =
|
|
205
|
+
this.#pendingReadUsageCallIds !== undefined &&
|
|
206
|
+
(this.#readGroup?.attachUsage(
|
|
207
|
+
this.#pendingReadUsageCallIds,
|
|
207
208
|
this.#pendingUsage,
|
|
208
209
|
this.#pendingUsageDuration,
|
|
209
210
|
this.#pendingUsageTtft,
|
|
210
211
|
this.#pendingUsageTimestamp,
|
|
211
|
-
)
|
|
212
|
-
|
|
212
|
+
) ??
|
|
213
|
+
false);
|
|
214
|
+
if (!usageAttached) {
|
|
215
|
+
this.#readGroup?.seal();
|
|
216
|
+
this.#readGroup = null;
|
|
217
|
+
this.container.addChild(
|
|
218
|
+
createUsageRowBlock(
|
|
219
|
+
this.#pendingUsage,
|
|
220
|
+
this.#pendingUsageDuration,
|
|
221
|
+
this.#pendingUsageTtft,
|
|
222
|
+
this.#pendingUsageTimestamp,
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
213
226
|
this.#pendingUsage = undefined;
|
|
214
227
|
this.#pendingUsageDuration = undefined;
|
|
215
228
|
this.#pendingUsageTtft = undefined;
|
|
216
229
|
this.#pendingUsageTimestamp = undefined;
|
|
230
|
+
this.#pendingReadUsageCallIds = undefined;
|
|
217
231
|
}
|
|
218
232
|
|
|
219
233
|
#appendChatMessage(message: AgentMessage): void {
|
|
220
234
|
if (message.role !== "toolResult") this.#flushPendingUsage();
|
|
235
|
+
if (message.role !== "assistant" && message.role !== "toolResult") {
|
|
236
|
+
this.#readGroup?.seal();
|
|
237
|
+
this.#readGroup = null;
|
|
238
|
+
}
|
|
221
239
|
switch (message.role) {
|
|
222
240
|
case "assistant":
|
|
223
241
|
this.#appendAssistantMessage(message);
|
|
@@ -403,6 +421,7 @@ export class ChatTranscriptBuilder {
|
|
|
403
421
|
this.#pendingUsageDuration = message.duration;
|
|
404
422
|
this.#pendingUsageTtft = message.ttft;
|
|
405
423
|
this.#pendingUsageTimestamp = message.timestamp;
|
|
424
|
+
this.#pendingReadUsageCallIds = this.#pendingUsage ? groupedReadUsageCallIds(message) : undefined;
|
|
406
425
|
}
|
|
407
426
|
|
|
408
427
|
#appendToolResult(message: Extract<AgentMessage, { role: "toolResult" }>): void {
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
getSegmenter,
|
|
4
|
+
matchesKey,
|
|
5
|
+
type OverlayHandle,
|
|
6
|
+
type OverlayOptions,
|
|
7
|
+
truncateToWidth,
|
|
8
|
+
visibleWidth,
|
|
9
|
+
} from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { type ThemeColor, theme } from "../theme/theme";
|
|
11
|
+
|
|
12
|
+
const FRAME_INTERVAL_MS = 85;
|
|
13
|
+
const FRAME_COUNT = 34;
|
|
14
|
+
|
|
15
|
+
const FIREWORK_THEME_COLORS = {
|
|
16
|
+
cyan: "mdLink",
|
|
17
|
+
dim: "dim",
|
|
18
|
+
gold: "warning",
|
|
19
|
+
green: "success",
|
|
20
|
+
pink: "accent",
|
|
21
|
+
violet: "thinkingXhigh",
|
|
22
|
+
white: "text",
|
|
23
|
+
} as const satisfies Record<string, ThemeColor>;
|
|
24
|
+
|
|
25
|
+
type FireworkColor = keyof typeof FIREWORK_THEME_COLORS;
|
|
26
|
+
|
|
27
|
+
/** The active Codex account fields retained between status refreshes. */
|
|
28
|
+
export interface CodexResetUsageSnapshot {
|
|
29
|
+
/** When this usage report was observed, if supplied by the provider. */
|
|
30
|
+
observedAt?: number;
|
|
31
|
+
/** Weekly usage, its quota identity, and its previously scheduled reset deadline. */
|
|
32
|
+
sevenDay?: { percent: number; resetsAt?: number; tier?: string; plan?: string };
|
|
33
|
+
savedResets?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A detected Codex quota event that can trigger the fireworks presentation. */
|
|
37
|
+
export type CodexResetFireworksEvent =
|
|
38
|
+
| { kind: "unscheduled-weekly-reset" }
|
|
39
|
+
| { kind: "saved-reset-banked"; added: number; available: number };
|
|
40
|
+
|
|
41
|
+
interface CanvasCell {
|
|
42
|
+
glyph: string;
|
|
43
|
+
color: FireworkColor;
|
|
44
|
+
priority: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface FireworkBurst {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
start: number;
|
|
51
|
+
color: FireworkColor;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ActiveFireworks {
|
|
55
|
+
component: CodexResetFireworksComponent;
|
|
56
|
+
overlay: OverlayHandle;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface CodexResetFireworksHost {
|
|
60
|
+
ui: {
|
|
61
|
+
showOverlay(component: Component, options?: OverlayOptions): OverlayHandle;
|
|
62
|
+
setFocus(component: Component): void;
|
|
63
|
+
requestRender(): void;
|
|
64
|
+
readonly terminal: { readonly rows: number };
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const BURSTS: readonly FireworkBurst[] = [
|
|
69
|
+
{ x: 0.17, y: 0.46, start: 5, color: "pink" },
|
|
70
|
+
{ x: 0.48, y: 0.2, start: 9, color: "cyan" },
|
|
71
|
+
{ x: 0.78, y: 0.42, start: 13, color: "gold" },
|
|
72
|
+
{ x: 0.31, y: 0.24, start: 17, color: "violet" },
|
|
73
|
+
{ x: 0.65, y: 0.28, start: 21, color: "green" },
|
|
74
|
+
{ x: 0.88, y: 0.18, start: 25, color: "pink" },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Compare consecutive reports for one Codex account. A saved-reset grant takes
|
|
79
|
+
* precedence when both changes arrive in the same report. A verified decrease,
|
|
80
|
+
* or a prior positive balance becoming unavailable, suppresses the weekly event
|
|
81
|
+
* because the user may have redeemed a credit. Other weekly usage drops are
|
|
82
|
+
* celebrated only before the previously scheduled reset deadline.
|
|
83
|
+
*/
|
|
84
|
+
export function detectCodexResetFireworks(
|
|
85
|
+
previous: CodexResetUsageSnapshot,
|
|
86
|
+
current: CodexResetUsageSnapshot,
|
|
87
|
+
): CodexResetFireworksEvent | undefined {
|
|
88
|
+
const previousSavedResets = previous.savedResets;
|
|
89
|
+
const currentSavedResets = current.savedResets;
|
|
90
|
+
if (previousSavedResets !== undefined) {
|
|
91
|
+
if (currentSavedResets === undefined) {
|
|
92
|
+
if (previousSavedResets > 0) return undefined;
|
|
93
|
+
} else {
|
|
94
|
+
if (currentSavedResets > previousSavedResets) {
|
|
95
|
+
return {
|
|
96
|
+
kind: "saved-reset-banked",
|
|
97
|
+
added: currentSavedResets - previousSavedResets,
|
|
98
|
+
available: currentSavedResets,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (currentSavedResets < previousSavedResets) return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!previous.sevenDay || !current.sevenDay) return undefined;
|
|
106
|
+
if (previous.sevenDay.tier !== current.sevenDay.tier || previous.sevenDay.plan !== current.sevenDay.plan) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const previousWeeklyPercent = Math.round(Math.max(0, Math.min(100, previous.sevenDay.percent)));
|
|
110
|
+
const currentWeeklyPercent = Math.round(Math.max(0, Math.min(100, current.sevenDay.percent)));
|
|
111
|
+
if (previousWeeklyPercent === 0 || currentWeeklyPercent >= previousWeeklyPercent) return undefined;
|
|
112
|
+
|
|
113
|
+
const scheduledResetAt = previous.sevenDay.resetsAt;
|
|
114
|
+
if (
|
|
115
|
+
scheduledResetAt === undefined ||
|
|
116
|
+
!Number.isFinite(scheduledResetAt) ||
|
|
117
|
+
typeof current.observedAt !== "number" ||
|
|
118
|
+
!Number.isFinite(current.observedAt) ||
|
|
119
|
+
current.observedAt >= scheduledResetAt
|
|
120
|
+
) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return { kind: "unscheduled-weekly-reset" };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setCell(
|
|
127
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
128
|
+
x: number,
|
|
129
|
+
y: number,
|
|
130
|
+
glyph: string,
|
|
131
|
+
color: FireworkColor,
|
|
132
|
+
priority: number,
|
|
133
|
+
): void {
|
|
134
|
+
const row = canvas[y];
|
|
135
|
+
if (!row || x < 0 || x >= row.length) return;
|
|
136
|
+
const current = row[x];
|
|
137
|
+
if (!current || priority >= current.priority) row[x] = { glyph, color, priority };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function drawText(
|
|
141
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
142
|
+
x: number,
|
|
143
|
+
y: number,
|
|
144
|
+
text: string,
|
|
145
|
+
color: FireworkColor,
|
|
146
|
+
priority: number,
|
|
147
|
+
): void {
|
|
148
|
+
let column = x;
|
|
149
|
+
for (const { segment } of getSegmenter().segment(text)) {
|
|
150
|
+
const width = visibleWidth(segment);
|
|
151
|
+
if (width <= 0) continue;
|
|
152
|
+
setCell(canvas, column, y, segment, color, priority);
|
|
153
|
+
for (let continuation = 1; continuation < width; continuation++) {
|
|
154
|
+
setCell(canvas, column + continuation, y, "", color, priority);
|
|
155
|
+
}
|
|
156
|
+
column += width;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function drawBanner(
|
|
161
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
162
|
+
left: number,
|
|
163
|
+
artWidth: number,
|
|
164
|
+
height: number,
|
|
165
|
+
event: CodexResetFireworksEvent,
|
|
166
|
+
): void {
|
|
167
|
+
if (height < 3 || artWidth < 8) return;
|
|
168
|
+
const panelWidth = Math.min(62, artWidth);
|
|
169
|
+
const panelLeft = left + Math.floor((artWidth - panelWidth) / 2);
|
|
170
|
+
const top = height - 3;
|
|
171
|
+
const innerWidth = panelWidth - 2;
|
|
172
|
+
const titleText =
|
|
173
|
+
event.kind === "unscheduled-weekly-reset" ? " O P E N A I R E S E T " : " S A V E D R E S E T ";
|
|
174
|
+
const subtitleText =
|
|
175
|
+
event.kind === "unscheduled-weekly-reset"
|
|
176
|
+
? "Weekly usage cleared early · ESC to return"
|
|
177
|
+
: event.added === 1
|
|
178
|
+
? `New reset banked · ${event.available} available · ESC to return`
|
|
179
|
+
: `${event.added} resets banked · ${event.available} available · ESC to return`;
|
|
180
|
+
const title = truncateToWidth(titleText, innerWidth, "");
|
|
181
|
+
const subtitle = truncateToWidth(subtitleText, innerWidth, "");
|
|
182
|
+
const titleOffset = Math.floor((innerWidth - visibleWidth(title)) / 2);
|
|
183
|
+
const subtitleOffset = Math.floor((innerWidth - visibleWidth(subtitle)) / 2);
|
|
184
|
+
|
|
185
|
+
drawText(canvas, panelLeft, top, `╭${"─".repeat(innerWidth)}╮`, "violet", 20);
|
|
186
|
+
drawText(canvas, panelLeft + 1 + titleOffset, top, title, "gold", 21);
|
|
187
|
+
drawText(canvas, panelLeft, top + 1, `│${" ".repeat(innerWidth)}│`, "violet", 20);
|
|
188
|
+
drawText(canvas, panelLeft + 1 + subtitleOffset, top + 1, subtitle, "cyan", 21);
|
|
189
|
+
drawText(canvas, panelLeft, top + 2, `╰${"─".repeat(innerWidth)}╯`, "violet", 20);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function drawStars(
|
|
193
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
194
|
+
left: number,
|
|
195
|
+
artWidth: number,
|
|
196
|
+
skyHeight: number,
|
|
197
|
+
frame: number,
|
|
198
|
+
): void {
|
|
199
|
+
if (skyHeight <= 0) return;
|
|
200
|
+
const count = Math.min(26, Math.max(5, Math.floor(artWidth / 3)));
|
|
201
|
+
for (let index = 0; index < count; index++) {
|
|
202
|
+
const x = left + ((index * 37 + 11) % artWidth);
|
|
203
|
+
const y = (index * 7 + 2) % skyHeight;
|
|
204
|
+
const bright = (index + Math.floor(frame / 3)) % 5 === 0;
|
|
205
|
+
setCell(canvas, x, y, bright ? "+" : ".", bright ? "white" : "dim", bright ? 2 : 1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function drawBurst(
|
|
210
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
211
|
+
burst: FireworkBurst,
|
|
212
|
+
left: number,
|
|
213
|
+
artWidth: number,
|
|
214
|
+
skyHeight: number,
|
|
215
|
+
frame: number,
|
|
216
|
+
): void {
|
|
217
|
+
if (skyHeight <= 1) return;
|
|
218
|
+
const centerX = left + Math.round((artWidth - 1) * burst.x);
|
|
219
|
+
const centerY = Math.max(0, Math.min(skyHeight - 2, Math.round((skyHeight - 1) * burst.y)));
|
|
220
|
+
const age = frame - burst.start;
|
|
221
|
+
|
|
222
|
+
if (age >= -6 && age < 0) {
|
|
223
|
+
const progress = (age + 6) / 6;
|
|
224
|
+
const y = skyHeight - 1 - Math.round(progress * (skyHeight - 1 - centerY));
|
|
225
|
+
setCell(canvas, centerX, y, "^", "white", 8);
|
|
226
|
+
setCell(canvas, centerX, y + 1, "|", burst.color, 7);
|
|
227
|
+
setCell(canvas, centerX, y + 2, ".", "gold", 6);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (age < 0 || age > 8) return;
|
|
231
|
+
|
|
232
|
+
const radius = age === 0 ? 0 : 0.8 + age * 0.92;
|
|
233
|
+
const gravity = Math.floor((age * age) / 22);
|
|
234
|
+
const glyphs = ["@", "*", "*", "+", "o", "o", ".", ".", "."] as const;
|
|
235
|
+
const particleColor: FireworkColor = age <= 5 ? burst.color : age <= 7 ? "gold" : "dim";
|
|
236
|
+
|
|
237
|
+
for (let particle = 0; particle < 20; particle++) {
|
|
238
|
+
const angle = (particle / 20) * Math.PI * 2 + burst.start * 0.17;
|
|
239
|
+
const x = centerX + Math.round(Math.cos(angle) * radius * 1.75);
|
|
240
|
+
const y = centerY + Math.round(Math.sin(angle) * radius * 0.58 + gravity);
|
|
241
|
+
setCell(canvas, x, y, glyphs[age], particleColor, 10);
|
|
242
|
+
if (age >= 2 && age <= 6) {
|
|
243
|
+
const trailRadius = Math.max(0, radius - 1.4);
|
|
244
|
+
const trailX = centerX + Math.round(Math.cos(angle) * trailRadius * 1.75);
|
|
245
|
+
const trailY = centerY + Math.round(Math.sin(angle) * trailRadius * 0.58 + gravity);
|
|
246
|
+
setCell(canvas, trailX, trailY, ".", "dim", 5);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (age <= 2) setCell(canvas, centerX, centerY, age === 0 ? "@" : "+", "white", 12);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function renderCanvas(canvas: Array<Array<CanvasCell | undefined>>): string[] {
|
|
253
|
+
return canvas.map(row => {
|
|
254
|
+
let output = "";
|
|
255
|
+
let run = "";
|
|
256
|
+
let runColor: FireworkColor | undefined;
|
|
257
|
+
for (const cell of row) {
|
|
258
|
+
if (cell && cell.color !== runColor) {
|
|
259
|
+
if (run) output += runColor ? theme.fg(FIREWORK_THEME_COLORS[runColor], run) : run;
|
|
260
|
+
run = "";
|
|
261
|
+
runColor = cell.color;
|
|
262
|
+
}
|
|
263
|
+
run += cell?.glyph ?? " ";
|
|
264
|
+
}
|
|
265
|
+
if (run) output += runColor ? theme.fg(FIREWORK_THEME_COLORS[runColor], run) : run;
|
|
266
|
+
return output;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Render one deterministic animation frame for the top-third overlay. */
|
|
271
|
+
function renderCodexResetFireworks(
|
|
272
|
+
width: number,
|
|
273
|
+
height: number,
|
|
274
|
+
frame: number,
|
|
275
|
+
event: CodexResetFireworksEvent,
|
|
276
|
+
): string[] {
|
|
277
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
278
|
+
const safeHeight = Math.max(1, Math.floor(height));
|
|
279
|
+
const artWidth = Math.min(96, safeWidth);
|
|
280
|
+
const left = Math.floor((safeWidth - artWidth) / 2);
|
|
281
|
+
const skyHeight = Math.max(0, safeHeight - 3);
|
|
282
|
+
const canvas = Array.from({ length: safeHeight }, () => new Array<CanvasCell | undefined>(safeWidth));
|
|
283
|
+
|
|
284
|
+
drawStars(canvas, left, artWidth, skyHeight, frame);
|
|
285
|
+
for (const burst of BURSTS) drawBurst(canvas, burst, left, artWidth, skyHeight, frame);
|
|
286
|
+
drawBanner(canvas, left, artWidth, safeHeight, event);
|
|
287
|
+
return renderCanvas(canvas);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
class CodexResetFireworksComponent implements Component {
|
|
291
|
+
#timer: NodeJS.Timeout | undefined;
|
|
292
|
+
#done = Promise.withResolvers<void>();
|
|
293
|
+
#frame = 0;
|
|
294
|
+
#disposed = false;
|
|
295
|
+
|
|
296
|
+
constructor(
|
|
297
|
+
readonly host: CodexResetFireworksHost,
|
|
298
|
+
readonly event: CodexResetFireworksEvent,
|
|
299
|
+
) {}
|
|
300
|
+
|
|
301
|
+
run(): Promise<void> {
|
|
302
|
+
this.#timer ??= setInterval(() => {
|
|
303
|
+
if (this.#disposed) return;
|
|
304
|
+
this.#frame = (this.#frame + 1) % FRAME_COUNT;
|
|
305
|
+
this.host.ui.requestRender();
|
|
306
|
+
}, FRAME_INTERVAL_MS);
|
|
307
|
+
this.host.ui.requestRender();
|
|
308
|
+
return this.#done.promise;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
dispose(): void {
|
|
312
|
+
if (this.#disposed) return;
|
|
313
|
+
this.#disposed = true;
|
|
314
|
+
if (this.#timer) {
|
|
315
|
+
clearInterval(this.#timer);
|
|
316
|
+
this.#timer = undefined;
|
|
317
|
+
}
|
|
318
|
+
this.#done.resolve();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
handleInput(data: string): void {
|
|
322
|
+
if (matchesKey(data, "escape") || matchesKey(data, "esc")) this.#done.resolve();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
render(width: number): readonly string[] {
|
|
326
|
+
const height = Math.max(1, Math.floor(this.host.ui.terminal.rows * 0.33));
|
|
327
|
+
return renderCodexResetFireworks(width, height, this.#frame, this.event);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Owns the at-most-one modal celebration lifecycle for an interactive session. */
|
|
332
|
+
export class CodexResetFireworksController {
|
|
333
|
+
#active: ActiveFireworks | undefined;
|
|
334
|
+
|
|
335
|
+
constructor(private readonly host: CodexResetFireworksHost) {}
|
|
336
|
+
|
|
337
|
+
/** Present a celebration unless another one already owns the modal overlay. */
|
|
338
|
+
show(event: CodexResetFireworksEvent): boolean {
|
|
339
|
+
if (this.#active) return false;
|
|
340
|
+
const component = new CodexResetFireworksComponent(this.host, event);
|
|
341
|
+
const overlay = this.host.ui.showOverlay(component, {
|
|
342
|
+
anchor: "top-center",
|
|
343
|
+
width: "100%",
|
|
344
|
+
maxHeight: "33%",
|
|
345
|
+
margin: 0,
|
|
346
|
+
});
|
|
347
|
+
this.#active = { component, overlay };
|
|
348
|
+
this.host.ui.setFocus(component);
|
|
349
|
+
void component.run().then(() => this.#finish(component));
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Stop the active celebration and release its overlay, if present. */
|
|
354
|
+
dispose(): void {
|
|
355
|
+
const active = this.#active;
|
|
356
|
+
if (!active) return;
|
|
357
|
+
this.#active = undefined;
|
|
358
|
+
active.component.dispose();
|
|
359
|
+
active.overlay.hide();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#finish(component: CodexResetFireworksComponent): void {
|
|
363
|
+
const active = this.#active;
|
|
364
|
+
if (!active || active.component !== component) return;
|
|
365
|
+
this.#active = undefined;
|
|
366
|
+
component.dispose();
|
|
367
|
+
active.overlay.hide();
|
|
368
|
+
}
|
|
369
|
+
}
|