@astrofoundry/pi-astro 0.14.1 → 0.14.3
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.
|
@@ -6,6 +6,7 @@ const baseStats: WelcomeStats = {
|
|
|
6
6
|
contextWindow: 1_000_000,
|
|
7
7
|
extensionCount: 9,
|
|
8
8
|
cavemanLevel: undefined,
|
|
9
|
+
pkgVersion: "0.14.2",
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
describe("composeWelcome", () => {
|
|
@@ -48,6 +49,16 @@ describe("composeWelcome", () => {
|
|
|
48
49
|
expect(() => composeWelcome(40, 1, baseStats)).not.toThrow();
|
|
49
50
|
});
|
|
50
51
|
|
|
52
|
+
it("renders the version subtitle when pkgVersion is provided", () => {
|
|
53
|
+
const lines = composeWelcome(120, 1, baseStats);
|
|
54
|
+
expect(lines.join("\n")).toContain("v0.14.2");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("omits the version row when pkgVersion is undefined", () => {
|
|
58
|
+
const lines = composeWelcome(120, 1, { ...baseStats, pkgVersion: undefined });
|
|
59
|
+
expect(lines.join("\n")).not.toMatch(/v\d+\.\d+\.\d+/);
|
|
60
|
+
});
|
|
61
|
+
|
|
51
62
|
it("produces deterministic output for the same seed", () => {
|
|
52
63
|
const a = composeWelcome(120, 99, baseStats);
|
|
53
64
|
const b = composeWelcome(120, 99, baseStats);
|
|
@@ -9,6 +9,7 @@ export interface WelcomeStats {
|
|
|
9
9
|
contextWindow: number | undefined;
|
|
10
10
|
extensionCount: number;
|
|
11
11
|
cavemanLevel: string | undefined;
|
|
12
|
+
pkgVersion: string | undefined;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const TITLE = "★ ASTRO PI ★";
|
|
@@ -69,10 +70,12 @@ class CompositeMask implements Mask {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
export function composeWelcome(width: number, seed: number, stats: WelcomeStats): string[] {
|
|
72
|
-
const
|
|
73
|
+
const hasVersion = !!stats.pkgVersion;
|
|
74
|
+
const versionRow = 1;
|
|
75
|
+
const artStartRow = hasVersion ? 2 : 1;
|
|
76
|
+
const totalRows = artStartRow + ART_HEIGHT + 6;
|
|
73
77
|
const safeWidth = Math.max(40, width);
|
|
74
78
|
const artStartCol = Math.max(0, Math.floor((safeWidth - ART_WIDTH) / 2));
|
|
75
|
-
const artStartRow = 1;
|
|
76
79
|
|
|
77
80
|
const mask = new CompositeMask(totalRows, safeWidth);
|
|
78
81
|
mask.occupyArt(artStartRow, artStartCol, ART_HEIGHT, ART_WIDTH);
|
|
@@ -81,6 +84,13 @@ export function composeWelcome(width: number, seed: number, stats: WelcomeStats)
|
|
|
81
84
|
const titlePlaced = centerInLine(titleLine, safeWidth);
|
|
82
85
|
mask.occupy(0, titlePlaced.col, visibleWidth(titleLine));
|
|
83
86
|
|
|
87
|
+
let versionPlaced: { line: string; col: number } | null = null;
|
|
88
|
+
if (hasVersion) {
|
|
89
|
+
const versionLine = fg(PALETTE.hint, `v${stats.pkgVersion}`);
|
|
90
|
+
versionPlaced = centerInLine(versionLine, safeWidth);
|
|
91
|
+
mask.occupy(versionRow, versionPlaced.col, visibleWidth(versionLine));
|
|
92
|
+
}
|
|
93
|
+
|
|
84
94
|
const taglineRaw = pickMessage(seed);
|
|
85
95
|
const taglineLine = fg(PALETTE.tagline, taglineRaw);
|
|
86
96
|
const taglineRow = artStartRow + ART_HEIGHT + 1;
|
|
@@ -106,6 +116,7 @@ export function composeWelcome(width: number, seed: number, stats: WelcomeStats)
|
|
|
106
116
|
let line = stars[r] ?? " ".repeat(safeWidth);
|
|
107
117
|
|
|
108
118
|
if (r === 0) line = overlay(line, titlePlaced.line, safeWidth);
|
|
119
|
+
if (versionPlaced && r === versionRow) line = overlay(line, versionPlaced.line, safeWidth);
|
|
109
120
|
if (r === taglineRow) line = overlay(line, taglinePlaced.line, safeWidth);
|
|
110
121
|
if (r === statsRow) line = overlay(line, statsPlaced.line, safeWidth);
|
|
111
122
|
if (r === hintRow) line = overlay(line, hintPlaced.line, safeWidth);
|
|
@@ -23,35 +23,65 @@ interface FakeCtx {
|
|
|
23
23
|
hasUI: boolean;
|
|
24
24
|
cwd: string;
|
|
25
25
|
model: { id: string; contextWindow: number } | undefined;
|
|
26
|
-
sessionManager: { getEntries: () => unknown[] };
|
|
26
|
+
sessionManager: { getEntries: () => unknown[]; getBranch: () => unknown[] };
|
|
27
27
|
ui: {
|
|
28
|
-
|
|
29
|
-
onTerminalInput: ReturnType<typeof vi.fn>;
|
|
28
|
+
custom: ReturnType<typeof vi.fn>;
|
|
30
29
|
};
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
interface CapturedComponent {
|
|
33
|
+
invalidate(): void;
|
|
34
|
+
render(width: number): string[];
|
|
35
|
+
handleInput?(data: string): void;
|
|
36
|
+
dispose?(): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface CapturedFactory {
|
|
40
|
+
options: unknown;
|
|
41
|
+
done: (result: unknown) => void;
|
|
42
|
+
component: CapturedComponent | null;
|
|
43
|
+
settle: Promise<unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeCtx(overrides: Partial<FakeCtx> = {}): FakeCtx & { _factory: CapturedFactory | null } {
|
|
47
|
+
const state: { factory: CapturedFactory | null } = { factory: null };
|
|
48
|
+
const customMock = vi.fn(
|
|
49
|
+
(
|
|
50
|
+
factory: (
|
|
51
|
+
tui: { requestRender: () => void },
|
|
52
|
+
theme: unknown,
|
|
53
|
+
kb: unknown,
|
|
54
|
+
done: (result: unknown) => void,
|
|
55
|
+
) => CapturedComponent,
|
|
56
|
+
options: unknown,
|
|
57
|
+
) => {
|
|
58
|
+
let resolveSettle: (r: unknown) => void = () => {};
|
|
59
|
+
const settle = new Promise<unknown>((resolve) => {
|
|
60
|
+
resolveSettle = resolve;
|
|
61
|
+
});
|
|
62
|
+
const cap: CapturedFactory = {
|
|
63
|
+
options,
|
|
64
|
+
done: (r: unknown) => resolveSettle(r),
|
|
65
|
+
component: null,
|
|
66
|
+
settle,
|
|
67
|
+
};
|
|
68
|
+
state.factory = cap;
|
|
69
|
+
const tui = { requestRender: vi.fn() };
|
|
70
|
+
cap.component = factory(tui, {}, {}, cap.done);
|
|
71
|
+
return cap.settle;
|
|
72
|
+
},
|
|
73
|
+
);
|
|
41
74
|
return {
|
|
42
75
|
hasUI: true,
|
|
43
76
|
cwd: "/Users/astro/proj",
|
|
44
77
|
model: { id: "anthropic/claude-sonnet-4-6", contextWindow: 1_000_000 },
|
|
45
|
-
sessionManager: { getEntries: () => [] },
|
|
46
|
-
ui: {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
get _terminalListener() {
|
|
51
|
-
return state.listener;
|
|
78
|
+
sessionManager: { getEntries: () => [], getBranch: () => [] },
|
|
79
|
+
ui: { custom: customMock },
|
|
80
|
+
get _factory() {
|
|
81
|
+
return state.factory;
|
|
52
82
|
},
|
|
53
83
|
...overrides,
|
|
54
|
-
} as FakeCtx & {
|
|
84
|
+
} as FakeCtx & { _factory: CapturedFactory | null };
|
|
55
85
|
}
|
|
56
86
|
|
|
57
87
|
describe("astro-welcome extension", () => {
|
|
@@ -72,75 +102,116 @@ describe("astro-welcome extension", () => {
|
|
|
72
102
|
return { pi, mod };
|
|
73
103
|
}
|
|
74
104
|
|
|
75
|
-
it("registers session_start and before_agent_start
|
|
105
|
+
it("registers session_start and before_agent_start handlers", async () => {
|
|
76
106
|
const { pi } = await install();
|
|
77
107
|
expect(pi.handlers.session_start).toBeDefined();
|
|
78
108
|
expect(pi.handlers.before_agent_start).toBeDefined();
|
|
79
109
|
});
|
|
80
110
|
|
|
81
|
-
it("
|
|
111
|
+
it("session_start opens a centered overlay via ctx.ui.custom (after a short delay)", async () => {
|
|
82
112
|
const { pi } = await install();
|
|
83
113
|
const ctx = makeCtx();
|
|
84
|
-
await pi.handlers.session_start({
|
|
85
|
-
expect(ctx.ui.
|
|
86
|
-
|
|
114
|
+
await pi.handlers.session_start({}, ctx);
|
|
115
|
+
expect(ctx.ui.custom).not.toHaveBeenCalled();
|
|
116
|
+
vi.advanceTimersByTime(100);
|
|
117
|
+
expect(ctx.ui.custom).toHaveBeenCalledTimes(1);
|
|
118
|
+
const opts = ctx._factory!.options as { overlay: boolean; overlayOptions: { anchor: string } };
|
|
119
|
+
expect(opts.overlay).toBe(true);
|
|
120
|
+
expect(opts.overlayOptions.anchor).toBe("center");
|
|
87
121
|
});
|
|
88
122
|
|
|
89
123
|
it("does nothing when ctx.hasUI is false", async () => {
|
|
90
124
|
const { pi } = await install();
|
|
91
125
|
const ctx = makeCtx({ hasUI: false });
|
|
92
|
-
await pi.handlers.session_start({
|
|
93
|
-
|
|
126
|
+
await pi.handlers.session_start({}, ctx);
|
|
127
|
+
vi.advanceTimersByTime(100);
|
|
128
|
+
expect(ctx.ui.custom).not.toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("does not show welcome a second time within the same process", async () => {
|
|
132
|
+
const { pi } = await install();
|
|
133
|
+
const ctx1 = makeCtx();
|
|
134
|
+
await pi.handlers.session_start({}, ctx1);
|
|
135
|
+
vi.advanceTimersByTime(100);
|
|
136
|
+
ctx1._factory!.component!.handleInput!("x");
|
|
137
|
+
await ctx1._factory!.settle;
|
|
138
|
+
const ctx2 = makeCtx();
|
|
139
|
+
await pi.handlers.session_start({}, ctx2);
|
|
140
|
+
vi.advanceTimersByTime(100);
|
|
141
|
+
expect(ctx2.ui.custom).not.toHaveBeenCalled();
|
|
94
142
|
});
|
|
95
143
|
|
|
96
|
-
it("
|
|
144
|
+
it("skips welcome when session already has assistant activity (e.g. /reload mid-session)", async () => {
|
|
145
|
+
const { pi } = await install();
|
|
146
|
+
const ctx = makeCtx({
|
|
147
|
+
sessionManager: {
|
|
148
|
+
getBranch: () => [
|
|
149
|
+
{ type: "message", message: { role: "assistant" } },
|
|
150
|
+
],
|
|
151
|
+
getEntries: () => [],
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
await pi.handlers.session_start({}, ctx);
|
|
155
|
+
vi.advanceTimersByTime(100);
|
|
156
|
+
expect(ctx.ui.custom).not.toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("auto-dismisses after 6 seconds via done(undefined)", async () => {
|
|
97
160
|
const { pi } = await install();
|
|
98
161
|
const ctx = makeCtx();
|
|
99
|
-
await pi.handlers.session_start({
|
|
162
|
+
await pi.handlers.session_start({}, ctx);
|
|
163
|
+
vi.advanceTimersByTime(100);
|
|
164
|
+
const settle = ctx._factory!.settle;
|
|
100
165
|
vi.advanceTimersByTime(6_000);
|
|
101
|
-
|
|
102
|
-
expect(calls.length).toBe(2);
|
|
103
|
-
expect(calls[1][0]).toBeUndefined();
|
|
166
|
+
await expect(settle).resolves.toBeUndefined();
|
|
104
167
|
});
|
|
105
168
|
|
|
106
|
-
it("
|
|
169
|
+
it("dismisses on first handleInput keystroke", async () => {
|
|
107
170
|
const { pi } = await install();
|
|
108
171
|
const ctx = makeCtx();
|
|
109
|
-
await pi.handlers.session_start({
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
expect(
|
|
114
|
-
expect(ctx.ui.setHeader).toHaveBeenLastCalledWith(undefined);
|
|
172
|
+
await pi.handlers.session_start({}, ctx);
|
|
173
|
+
vi.advanceTimersByTime(100);
|
|
174
|
+
const settle = ctx._factory!.settle;
|
|
175
|
+
ctx._factory!.component!.handleInput!("x");
|
|
176
|
+
await expect(settle).resolves.toBeUndefined();
|
|
115
177
|
});
|
|
116
178
|
|
|
117
|
-
it("
|
|
179
|
+
it("dismisses on before_agent_start (external dismiss after mount)", async () => {
|
|
118
180
|
const { pi } = await install();
|
|
119
181
|
const ctx = makeCtx();
|
|
120
|
-
await pi.handlers.session_start({
|
|
182
|
+
await pi.handlers.session_start({}, ctx);
|
|
183
|
+
vi.advanceTimersByTime(100);
|
|
184
|
+
const settle = ctx._factory!.settle;
|
|
121
185
|
await pi.handlers.before_agent_start({ prompt: "hi" }, ctx);
|
|
122
|
-
expect(
|
|
186
|
+
await expect(settle).resolves.toBeUndefined();
|
|
123
187
|
});
|
|
124
188
|
|
|
125
|
-
it("
|
|
189
|
+
it("before_agent_start fired during the 100ms mount-delay cancels the overlay entirely", async () => {
|
|
126
190
|
const { pi } = await install();
|
|
127
191
|
const ctx = makeCtx();
|
|
128
|
-
await pi.handlers.session_start({
|
|
129
|
-
await pi.handlers.before_agent_start({ prompt: "
|
|
130
|
-
|
|
192
|
+
await pi.handlers.session_start({}, ctx);
|
|
193
|
+
await pi.handlers.before_agent_start({ prompt: "fast" }, ctx);
|
|
194
|
+
vi.advanceTimersByTime(200);
|
|
195
|
+
expect(ctx.ui.custom).not.toHaveBeenCalled();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("dismiss is idempotent — second trigger does not throw", async () => {
|
|
199
|
+
const { pi } = await install();
|
|
200
|
+
const ctx = makeCtx();
|
|
201
|
+
await pi.handlers.session_start({}, ctx);
|
|
202
|
+
vi.advanceTimersByTime(100);
|
|
203
|
+
ctx._factory!.component!.handleInput!("a");
|
|
204
|
+
await ctx._factory!.settle;
|
|
205
|
+
expect(() => ctx._factory!.component!.handleInput!("b")).not.toThrow();
|
|
131
206
|
vi.advanceTimersByTime(6_000);
|
|
132
|
-
expect(ctx.ui.setHeader.mock.calls.length).toBe(before);
|
|
133
207
|
});
|
|
134
208
|
|
|
135
|
-
it("renders
|
|
209
|
+
it("renders all stats and the dismiss hint in the overlay component", async () => {
|
|
136
210
|
const { pi } = await install();
|
|
137
211
|
const ctx = makeCtx();
|
|
138
|
-
await pi.handlers.session_start({
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
theme: unknown,
|
|
142
|
-
) => { render: (w: number) => string[] };
|
|
143
|
-
const lines = factory({}, {}).render(120);
|
|
212
|
+
await pi.handlers.session_start({}, ctx);
|
|
213
|
+
vi.advanceTimersByTime(100);
|
|
214
|
+
const lines = ctx._factory!.component!.render(120);
|
|
144
215
|
const joined = lines.join("\n");
|
|
145
216
|
expect(joined).toContain("ASTRO PI");
|
|
146
217
|
expect(joined).toContain("anthropic/claude-sonnet-4-6");
|
|
@@ -156,13 +227,12 @@ describe("astro-welcome extension", () => {
|
|
|
156
227
|
getEntries: () => [
|
|
157
228
|
{ type: "custom", customType: "caveman-mode", data: { level: "ultra" } },
|
|
158
229
|
],
|
|
230
|
+
getBranch: () => [],
|
|
159
231
|
},
|
|
160
232
|
});
|
|
161
|
-
await pi.handlers.session_start({
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
) => { render: (w: number) => string[] };
|
|
166
|
-
expect(factory({}, {}).render(120).join("\n")).toContain("caveman: ultra");
|
|
233
|
+
await pi.handlers.session_start({}, ctx);
|
|
234
|
+
vi.advanceTimersByTime(100);
|
|
235
|
+
const lines = ctx._factory!.component!.render(120);
|
|
236
|
+
expect(lines.join("\n")).toContain("caveman: ultra");
|
|
167
237
|
});
|
|
168
238
|
});
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { composeWelcome, type WelcomeStats } from "./compose.ts";
|
|
3
|
+
import { ART_HEIGHT, ART_WIDTH } from "./art.ts";
|
|
4
|
+
import { readPackageVersion } from "./version.ts";
|
|
3
5
|
|
|
4
6
|
const DISMISS_AFTER_MS = 6_000;
|
|
7
|
+
const MOUNT_DELAY_MS = 100;
|
|
8
|
+
const OVERLAY_WIDTH = Math.max(ART_WIDTH + 30, 70);
|
|
9
|
+
const OVERLAY_HEIGHT = ART_HEIGHT + 8;
|
|
5
10
|
|
|
6
11
|
function readCavemanLevel(ctx: ExtensionContext): string | undefined {
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
const entries = ctx.sessionManager.getEntries();
|
|
13
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
14
|
+
const entry = entries[i];
|
|
9
15
|
if (entry.type === "custom" && entry.customType === "caveman-mode") {
|
|
10
16
|
const data = entry.data as { level?: string } | undefined;
|
|
11
17
|
return data?.level ?? undefined;
|
|
@@ -14,74 +20,106 @@ function readCavemanLevel(ctx: ExtensionContext): string | undefined {
|
|
|
14
20
|
return undefined;
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return
|
|
23
|
+
function hasSessionActivity(ctx: ExtensionContext): boolean {
|
|
24
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
25
|
+
if (entry.type === "message" && entry.message.role === "assistant") return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
export default function astroWelcomeExtension(pi: ExtensionAPI): void {
|
|
25
|
-
let
|
|
26
|
-
let
|
|
27
|
-
let
|
|
28
|
-
let
|
|
29
|
-
|
|
30
|
-
function dismiss(): void {
|
|
31
|
-
if (dismissed) return;
|
|
32
|
-
dismissed = true;
|
|
33
|
-
if (timer !== null) {
|
|
34
|
-
clearTimeout(timer);
|
|
35
|
-
timer = null;
|
|
36
|
-
}
|
|
37
|
-
if (unsubscribeInput !== null) {
|
|
38
|
-
try {
|
|
39
|
-
unsubscribeInput();
|
|
40
|
-
} catch {
|
|
41
|
-
/* ignore unsubscribe errors */
|
|
42
|
-
}
|
|
43
|
-
unsubscribeInput = null;
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
activeCtx?.ui.setHeader(undefined);
|
|
47
|
-
} catch {
|
|
48
|
-
/* ignore header clear errors */
|
|
49
|
-
}
|
|
50
|
-
activeCtx = null;
|
|
51
|
-
}
|
|
31
|
+
let shownThisProcess = false;
|
|
32
|
+
let externalDismiss: (() => void) | null = null;
|
|
33
|
+
let dismissedBeforeMount = false;
|
|
34
|
+
let pendingMount: ReturnType<typeof setTimeout> | null = null;
|
|
52
35
|
|
|
53
36
|
pi.on("session_start", async (_event, ctx) => {
|
|
37
|
+
if (shownThisProcess) return;
|
|
54
38
|
if (!ctx.hasUI) return;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const seed = Date.now() & 0xffff_ffff;
|
|
39
|
+
if (hasSessionActivity(ctx)) return;
|
|
40
|
+
shownThisProcess = true;
|
|
58
41
|
|
|
42
|
+
const seed = Date.now() & 0xffff_ffff;
|
|
43
|
+
let pkgVersion: string | undefined;
|
|
44
|
+
try {
|
|
45
|
+
pkgVersion = readPackageVersion();
|
|
46
|
+
} catch {
|
|
47
|
+
pkgVersion = undefined;
|
|
48
|
+
}
|
|
59
49
|
const stats: WelcomeStats = {
|
|
60
|
-
modelDisplay:
|
|
50
|
+
modelDisplay: ctx.model?.id,
|
|
61
51
|
contextWindow: ctx.model?.contextWindow,
|
|
62
52
|
extensionCount: pi.getAllTools().length,
|
|
63
53
|
cavemanLevel: readCavemanLevel(ctx),
|
|
54
|
+
pkgVersion,
|
|
64
55
|
};
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
invalidate() {},
|
|
68
|
-
render(width: number): string[] {
|
|
69
|
-
return composeWelcome(width, seed, stats);
|
|
70
|
-
},
|
|
71
|
-
}));
|
|
57
|
+
dismissedBeforeMount = false;
|
|
72
58
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
59
|
+
pendingMount = setTimeout(() => {
|
|
60
|
+
pendingMount = null;
|
|
61
|
+
if (dismissedBeforeMount) return;
|
|
62
|
+
|
|
63
|
+
let dismissed = false;
|
|
64
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
65
|
+
|
|
66
|
+
ctx.ui
|
|
67
|
+
.custom(
|
|
68
|
+
(tui, _theme, _kb, done) => {
|
|
69
|
+
const dismiss = (): void => {
|
|
70
|
+
if (dismissed) return;
|
|
71
|
+
dismissed = true;
|
|
72
|
+
if (timer !== null) {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
timer = null;
|
|
75
|
+
}
|
|
76
|
+
externalDismiss = null;
|
|
77
|
+
done(undefined);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
timer = setTimeout(dismiss, DISMISS_AFTER_MS);
|
|
81
|
+
externalDismiss = dismiss;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
invalidate(): void {
|
|
85
|
+
tui.requestRender();
|
|
86
|
+
},
|
|
87
|
+
render(width: number): string[] {
|
|
88
|
+
return composeWelcome(width, seed, stats);
|
|
89
|
+
},
|
|
90
|
+
handleInput(): void {
|
|
91
|
+
dismiss();
|
|
92
|
+
},
|
|
93
|
+
dispose(): void {
|
|
94
|
+
if (timer !== null) {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
timer = null;
|
|
97
|
+
}
|
|
98
|
+
externalDismiss = null;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
overlay: true,
|
|
104
|
+
overlayOptions: {
|
|
105
|
+
anchor: "center",
|
|
106
|
+
width: OVERLAY_WIDTH,
|
|
107
|
+
maxHeight: OVERLAY_HEIGHT,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
.catch(() => {
|
|
112
|
+
/* overlay close errors are not fatal */
|
|
113
|
+
});
|
|
114
|
+
}, MOUNT_DELAY_MS);
|
|
82
115
|
});
|
|
83
116
|
|
|
84
117
|
pi.on("before_agent_start", async () => {
|
|
85
|
-
|
|
118
|
+
if (pendingMount !== null) {
|
|
119
|
+
clearTimeout(pendingMount);
|
|
120
|
+
pendingMount = null;
|
|
121
|
+
dismissedBeforeMount = true;
|
|
122
|
+
}
|
|
123
|
+
externalDismiss?.();
|
|
86
124
|
});
|
|
87
125
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const packageJsonPath = path.resolve(extensionDir, "..", "..", "package.json");
|
|
7
|
+
|
|
8
|
+
export function readPackageVersion(): string | undefined {
|
|
9
|
+
const raw = fs.readFileSync(packageJsonPath, "utf8");
|
|
10
|
+
const pkg = JSON.parse(raw) as { version?: unknown };
|
|
11
|
+
return typeof pkg.version === "string" ? pkg.version : undefined;
|
|
12
|
+
}
|