pi-idle 1.0.2 → 1.0.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.
- package/package.json +1 -1
- package/pi-idle.ts +14 -0
- package/test.ts +42 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-idle",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Pi extension: shows ✓ in terminal title when idle, spinner (◰◳◲◱) while working, with context-usage percentage beside the checkmark",
|
|
5
5
|
"keywords": ["idle", "done", "spinner", "pi-extension", "pi", "pi-package"],
|
|
6
6
|
"type": "module",
|
package/pi-idle.ts
CHANGED
|
@@ -92,6 +92,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
92
92
|
// ── Lifecycle hooks ────────────────────────────────────────
|
|
93
93
|
|
|
94
94
|
pi.on("session_start", async (_event, ctx) => {
|
|
95
|
+
// Schedule after microtask so pi's init-based updateTerminalTitle()
|
|
96
|
+
// fires first, then we overwrite it with the checkmark.
|
|
97
|
+
await Promise.resolve();
|
|
95
98
|
showDone(ctx);
|
|
96
99
|
});
|
|
97
100
|
|
|
@@ -101,6 +104,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
101
104
|
}
|
|
102
105
|
});
|
|
103
106
|
|
|
107
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
108
|
+
// agent_start always fires after input for every user prompt;
|
|
109
|
+
// backstop in case the input handler missed a non-interactive source.
|
|
110
|
+
startSpinner(ctx);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
pi.on("turn_start", async (_event, ctx) => {
|
|
114
|
+
// Multi-turn agent: keep spinner running between turns.
|
|
115
|
+
startSpinner(ctx);
|
|
116
|
+
});
|
|
117
|
+
|
|
104
118
|
pi.on("agent_end", async (_event, ctx) => {
|
|
105
119
|
showDone(ctx);
|
|
106
120
|
});
|
package/test.ts
CHANGED
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tests:
|
|
5
5
|
* 1. Module loads and exports a default function
|
|
6
|
-
* 2. All
|
|
7
|
-
* 3. `session_start` → plain checkmark in title
|
|
6
|
+
* 2. All 6 lifecycle handlers are registered
|
|
7
|
+
* 3. `session_start` → plain checkmark in title (after microtask yield)
|
|
8
8
|
* 4. `input` (interactive) → spinner starts in title
|
|
9
9
|
* 5. `input` (non-interactive) → no spinner
|
|
10
|
-
* 6. `
|
|
11
|
-
* 7. `
|
|
12
|
-
* 8.
|
|
13
|
-
* 9.
|
|
14
|
-
* 10. Context
|
|
15
|
-
* 11. Context
|
|
10
|
+
* 6. `agent_start` → spinner starts in title
|
|
11
|
+
* 7. `turn_start` → spinner starts in title (multi-turn)
|
|
12
|
+
* 8. `agent_end` → checkmark restored, spinner stopped
|
|
13
|
+
* 9. `session_shutdown` → plain base title
|
|
14
|
+
* 10. Context ≤50% → no indicator in title
|
|
15
|
+
* 11. Context >50% → [N%] in title
|
|
16
|
+
* 12. Context ≥90% → ![N%]! in title
|
|
17
|
+
* 13. Context null → no indicator
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
@@ -33,7 +35,7 @@ function createMockPi(): ExtensionAPI & { _handlers: Map<string, Function> } {
|
|
|
33
35
|
|
|
34
36
|
function createMockCtx(overrides?: Partial<ExtensionContext>): ExtensionContext {
|
|
35
37
|
const theme = {
|
|
36
|
-
fg: vi.fn((_color: string, text: string) =>
|
|
38
|
+
fg: vi.fn((_color: string, text: string) => `\x1b[32m${text}\x1b[0m`),
|
|
37
39
|
};
|
|
38
40
|
return {
|
|
39
41
|
ui: {
|
|
@@ -55,7 +57,7 @@ describe("pi-idle.ts module", () => {
|
|
|
55
57
|
expect(typeof mod.default).toBe("function");
|
|
56
58
|
});
|
|
57
59
|
|
|
58
|
-
it("registers all
|
|
60
|
+
it("registers all six lifecycle handlers", async () => {
|
|
59
61
|
const mockPi = createMockPi();
|
|
60
62
|
const mod = await import("./pi-idle.ts");
|
|
61
63
|
mod.default(mockPi as unknown as ExtensionAPI);
|
|
@@ -65,6 +67,8 @@ describe("pi-idle.ts module", () => {
|
|
|
65
67
|
);
|
|
66
68
|
expect(events.has("session_start")).toBe(true);
|
|
67
69
|
expect(events.has("input")).toBe(true);
|
|
70
|
+
expect(events.has("agent_start")).toBe(true);
|
|
71
|
+
expect(events.has("turn_start")).toBe(true);
|
|
68
72
|
expect(events.has("agent_end")).toBe(true);
|
|
69
73
|
expect(events.has("session_shutdown")).toBe(true);
|
|
70
74
|
});
|
|
@@ -81,11 +85,13 @@ describe("extension handlers", () => {
|
|
|
81
85
|
mod.default(mockPi as unknown as ExtensionAPI);
|
|
82
86
|
});
|
|
83
87
|
|
|
84
|
-
it("session_start: ≤50% context → no indicator in title", async () => {
|
|
88
|
+
it("session_start: ≤50% context → no indicator in title (after microtask yield)", async () => {
|
|
85
89
|
const ctx = createMockCtx(); // 25% ≤ 50%
|
|
86
90
|
const handler = mockPi._handlers.get("session_start")!;
|
|
87
91
|
await handler({ reason: "startup" }, ctx);
|
|
88
92
|
|
|
93
|
+
// session_start yields via await Promise.resolve() to survive pi's
|
|
94
|
+
// init-based updateTerminalTitle(); make sure the microtask has run.
|
|
89
95
|
expect(ctx.ui.setTitle).toHaveBeenCalledWith("✓ π - pi-idle");
|
|
90
96
|
expect(ctx.ui.setStatus).not.toHaveBeenCalled();
|
|
91
97
|
});
|
|
@@ -114,6 +120,31 @@ describe("extension handlers", () => {
|
|
|
114
120
|
expect(ctx.ui.setStatus).not.toHaveBeenCalled();
|
|
115
121
|
});
|
|
116
122
|
|
|
123
|
+
it("agent_start starts spinner in title", async () => {
|
|
124
|
+
const ctx = createMockCtx();
|
|
125
|
+
const handler = mockPi._handlers.get("agent_start")!;
|
|
126
|
+
await handler({}, ctx);
|
|
127
|
+
|
|
128
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
129
|
+
|
|
130
|
+
expect(ctx.ui.setTitle).toHaveBeenCalled();
|
|
131
|
+
const firstCall = (ctx.ui.setTitle as ReturnType<typeof vi.fn>).mock.calls[0][0] as string;
|
|
132
|
+
expect(firstCall).toMatch(/^[◰◳◲◱] π - pi-idle$/);
|
|
133
|
+
expect(ctx.ui.setStatus).not.toHaveBeenCalled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("turn_start starts spinner in title (multi-turn)", async () => {
|
|
137
|
+
const ctx = createMockCtx();
|
|
138
|
+
const handler = mockPi._handlers.get("turn_start")!;
|
|
139
|
+
await handler({ turnIndex: 1, timestamp: Date.now() }, ctx);
|
|
140
|
+
|
|
141
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
142
|
+
|
|
143
|
+
expect(ctx.ui.setTitle).toHaveBeenCalled();
|
|
144
|
+
const firstCall = (ctx.ui.setTitle as ReturnType<typeof vi.fn>).mock.calls[0][0] as string;
|
|
145
|
+
expect(firstCall).toMatch(/^[◰◳◲◱] π - pi-idle$/);
|
|
146
|
+
});
|
|
147
|
+
|
|
117
148
|
it("agent_end restores checkmark in title", async () => {
|
|
118
149
|
const ctx = createMockCtx(); // 25% ≤ 50%
|
|
119
150
|
const handler = mockPi._handlers.get("agent_end")!;
|