pi-usereq 0.4.0 → 0.5.0
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/.gitignore +3 -0
- package/CHANGELOG.md +31 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/{req → pi-usereq}/docs/REFERENCES.md +196 -159
- package/{req → pi-usereq}/docs/REQUIREMENTS.md +65 -44
- package/{req → pi-usereq}/docs/WORKFLOW.md +66 -73
- package/src/core/config.ts +39 -12
- package/src/core/extension-status.ts +94 -58
- package/src/core/pi-notify.ts +198 -11
- package/src/core/runtime-project-paths.ts +3 -32
- package/src/core/settings-menu.ts +9 -4
- package/src/core/static-check.ts +35 -171
- package/src/index.ts +281 -86
- package/tests/attended-results-scenarios.ts +3 -13
- package/tests/cli-command-option-parity.test.ts +21 -12
- package/tests/debug-extension-harness.test.ts +11 -7
- package/tests/extension-registration.test.ts +371 -71
- package/tests/oracle-project.test.ts +8 -3
- package/tests/oracle-standalone.test.ts +7 -13
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_c.c.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_cpp.cpp.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_csharp.cs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_elixir.ex.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_go.go.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_haskell.hs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_java.java.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_javascript.js.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_kotlin.kt.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_lua.lua.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_perl.pl.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_php.php.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_python.py.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_ruby.rb.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_rust.rs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_scala.scala.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_shell.sh.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_swift.swift.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_typescript.ts.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_zig.zig.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_c.c.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_cpp.cpp.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_csharp.cs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_elixir.ex.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_go.go.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_haskell.hs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_java.java.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_javascript.js.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_kotlin.kt.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_lua.lua.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_perl.pl.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_php.php.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_python.py.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_ruby.rb.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_rust.rs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_scala.scala.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_shell.sh.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_swift.swift.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_typescript.ts.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_zig.zig.json +0 -5
package/src/core/config.ts
CHANGED
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
DEFAULT_PI_NOTIFY_SOUND_MID_CMD,
|
|
20
20
|
DEFAULT_PI_NOTIFY_SOUND_TOGGLE_SHORTCUT,
|
|
21
21
|
normalizePiNotifyCommand,
|
|
22
|
+
normalizePiNotifyPushoverCredential,
|
|
23
|
+
normalizePiNotifyPushoverPriority,
|
|
22
24
|
normalizePiNotifyShortcut,
|
|
23
25
|
normalizePiNotifySoundLevel,
|
|
24
26
|
} from "./pi-notify.js";
|
|
@@ -50,6 +52,11 @@ export interface UseReqConfig {
|
|
|
50
52
|
"notify-beep-on-error": boolean;
|
|
51
53
|
"notify-sound": "none" | "low" | "mid" | "high";
|
|
52
54
|
"notify-sound-toggle-shortcut": string;
|
|
55
|
+
"notify-pushover-global-disable": boolean;
|
|
56
|
+
"notify-pushover-on-success": boolean;
|
|
57
|
+
"notify-pushover-user-key": string;
|
|
58
|
+
"notify-pushover-api-token": string;
|
|
59
|
+
"notify-pushover-priority": 0 | 1;
|
|
53
60
|
PI_NOTIFY_SOUND_LOW_CMD: string;
|
|
54
61
|
PI_NOTIFY_SOUND_MID_CMD: string;
|
|
55
62
|
PI_NOTIFY_SOUND_HIGH_CMD: string;
|
|
@@ -83,10 +90,10 @@ export function getProjectConfigPath(projectBase: string): string {
|
|
|
83
90
|
|
|
84
91
|
/**
|
|
85
92
|
* @brief Builds the default project configuration.
|
|
86
|
-
* @details Populates canonical docs/test/source directories, the default startup tool set,
|
|
93
|
+
* @details Populates canonical docs/test/source directories, the default startup tool set, default pi-notify fields, and default Pushover settings while excluding runtime-derived path metadata. Time complexity is O(n) in default tool count. No filesystem side effects occur.
|
|
87
94
|
* @param[in] projectBase {string} Absolute project root path.
|
|
88
95
|
* @return {UseReqConfig} Fresh default configuration object.
|
|
89
|
-
* @satisfies CTN-001, CTN-012, REQ-066, REQ-146
|
|
96
|
+
* @satisfies CTN-001, CTN-012, REQ-066, REQ-129, REQ-146, REQ-163
|
|
90
97
|
*/
|
|
91
98
|
export function getDefaultConfig(_projectBase: string): UseReqConfig {
|
|
92
99
|
return {
|
|
@@ -95,11 +102,16 @@ export function getDefaultConfig(_projectBase: string): UseReqConfig {
|
|
|
95
102
|
"src-dir": [...DEFAULT_SRC_DIRS],
|
|
96
103
|
"static-check": {},
|
|
97
104
|
"enabled-tools": normalizeEnabledPiUsereqTools(undefined),
|
|
98
|
-
"notify-beep-on-end":
|
|
99
|
-
"notify-beep-on-esc":
|
|
100
|
-
"notify-beep-on-error":
|
|
105
|
+
"notify-beep-on-end": true,
|
|
106
|
+
"notify-beep-on-esc": true,
|
|
107
|
+
"notify-beep-on-error": true,
|
|
101
108
|
"notify-sound": "none",
|
|
102
109
|
"notify-sound-toggle-shortcut": DEFAULT_PI_NOTIFY_SOUND_TOGGLE_SHORTCUT,
|
|
110
|
+
"notify-pushover-global-disable": false,
|
|
111
|
+
"notify-pushover-on-success": false,
|
|
112
|
+
"notify-pushover-user-key": "",
|
|
113
|
+
"notify-pushover-api-token": "",
|
|
114
|
+
"notify-pushover-priority": 0,
|
|
103
115
|
PI_NOTIFY_SOUND_LOW_CMD: DEFAULT_PI_NOTIFY_SOUND_LOW_CMD,
|
|
104
116
|
PI_NOTIFY_SOUND_MID_CMD: DEFAULT_PI_NOTIFY_SOUND_MID_CMD,
|
|
105
117
|
PI_NOTIFY_SOUND_HIGH_CMD: DEFAULT_PI_NOTIFY_SOUND_HIGH_CMD,
|
|
@@ -108,11 +120,11 @@ export function getDefaultConfig(_projectBase: string): UseReqConfig {
|
|
|
108
120
|
|
|
109
121
|
/**
|
|
110
122
|
* @brief Loads and sanitizes the persisted project configuration.
|
|
111
|
-
* @details Returns defaults when the config file does not exist. Otherwise parses JSON, validates directory and static-check field shapes, normalizes enabled tool names
|
|
123
|
+
* @details Returns defaults when the config file does not exist. Otherwise parses JSON, validates directory and static-check field shapes, normalizes enabled tool names plus pi-notify and Pushover fields, applies enabled beep defaults for missing flag payloads, and ignores removed or runtime-derived path metadata. Runtime is O(n) in config size. Side effects are limited to filesystem reads.
|
|
112
124
|
* @param[in] projectBase {string} Absolute project root path.
|
|
113
125
|
* @return {UseReqConfig} Sanitized effective configuration.
|
|
114
126
|
* @throws {ReqError} Throws with exit code `11` when the config file contains invalid JSON or a non-object payload.
|
|
115
|
-
* @satisfies CTN-012, REQ-066, REQ-146
|
|
127
|
+
* @satisfies CTN-012, REQ-066, REQ-129, REQ-146, REQ-163
|
|
116
128
|
*/
|
|
117
129
|
export function loadConfig(projectBase: string): UseReqConfig {
|
|
118
130
|
const configPath = getProjectConfigPath(projectBase);
|
|
@@ -140,11 +152,16 @@ export function loadConfig(projectBase: string): UseReqConfig {
|
|
|
140
152
|
? (data["static-check"] as Record<string, StaticCheckEntry[]>)
|
|
141
153
|
: {};
|
|
142
154
|
const enabledTools = normalizeEnabledPiUsereqTools(data["enabled-tools"]);
|
|
143
|
-
const notifyBeepOnEnd = data["notify-beep-on-end"]
|
|
144
|
-
const notifyBeepOnEsc = data["notify-beep-on-esc"]
|
|
145
|
-
const notifyBeepOnError = data["notify-beep-on-error"]
|
|
155
|
+
const notifyBeepOnEnd = data["notify-beep-on-end"] !== false;
|
|
156
|
+
const notifyBeepOnEsc = data["notify-beep-on-esc"] !== false;
|
|
157
|
+
const notifyBeepOnError = data["notify-beep-on-error"] !== false;
|
|
146
158
|
const notifySound = normalizePiNotifySoundLevel(data["notify-sound"]);
|
|
147
159
|
const notifySoundToggleShortcut = normalizePiNotifyShortcut(data["notify-sound-toggle-shortcut"]);
|
|
160
|
+
const pushoverGlobalDisable = data["notify-pushover-global-disable"] === true;
|
|
161
|
+
const pushoverOnSuccess = data["notify-pushover-on-success"] === true;
|
|
162
|
+
const pushoverUserKey = normalizePiNotifyPushoverCredential(data["notify-pushover-user-key"]);
|
|
163
|
+
const pushoverApiToken = normalizePiNotifyPushoverCredential(data["notify-pushover-api-token"]);
|
|
164
|
+
const pushoverPriority = normalizePiNotifyPushoverPriority(data["notify-pushover-priority"]);
|
|
148
165
|
const lowSoundCommand = normalizePiNotifyCommand(data.PI_NOTIFY_SOUND_LOW_CMD, DEFAULT_PI_NOTIFY_SOUND_LOW_CMD);
|
|
149
166
|
const midSoundCommand = normalizePiNotifyCommand(data.PI_NOTIFY_SOUND_MID_CMD, DEFAULT_PI_NOTIFY_SOUND_MID_CMD);
|
|
150
167
|
const highSoundCommand = normalizePiNotifyCommand(data.PI_NOTIFY_SOUND_HIGH_CMD, DEFAULT_PI_NOTIFY_SOUND_HIGH_CMD);
|
|
@@ -160,6 +177,11 @@ export function loadConfig(projectBase: string): UseReqConfig {
|
|
|
160
177
|
"notify-beep-on-error": notifyBeepOnError,
|
|
161
178
|
"notify-sound": notifySound,
|
|
162
179
|
"notify-sound-toggle-shortcut": notifySoundToggleShortcut,
|
|
180
|
+
"notify-pushover-global-disable": pushoverGlobalDisable,
|
|
181
|
+
"notify-pushover-on-success": pushoverOnSuccess,
|
|
182
|
+
"notify-pushover-user-key": pushoverUserKey,
|
|
183
|
+
"notify-pushover-api-token": pushoverApiToken,
|
|
184
|
+
"notify-pushover-priority": pushoverPriority,
|
|
163
185
|
PI_NOTIFY_SOUND_LOW_CMD: lowSoundCommand,
|
|
164
186
|
PI_NOTIFY_SOUND_MID_CMD: midSoundCommand,
|
|
165
187
|
PI_NOTIFY_SOUND_HIGH_CMD: highSoundCommand,
|
|
@@ -168,10 +190,10 @@ export function loadConfig(projectBase: string): UseReqConfig {
|
|
|
168
190
|
|
|
169
191
|
/**
|
|
170
192
|
* @brief Builds the persisted configuration payload that excludes runtime-derived fields.
|
|
171
|
-
* @details Copies only the canonical persisted configuration keys into a fresh object so runtime-derived metadata such as `base-path` and `git-path` can never be written to disk. Runtime is O(n) in config size. No external state is mutated.
|
|
193
|
+
* @details Copies only the canonical persisted configuration keys into a fresh object so runtime-derived metadata such as `base-path` and `git-path` can never be written to disk while preserving notification and Pushover settings verbatim. Runtime is O(n) in config size. No external state is mutated.
|
|
172
194
|
* @param[in] config {UseReqConfig} Effective configuration object.
|
|
173
195
|
* @return {UseReqConfig} Persistable configuration payload.
|
|
174
|
-
* @satisfies CTN-012, REQ-146
|
|
196
|
+
* @satisfies CTN-012, REQ-146, REQ-163
|
|
175
197
|
*/
|
|
176
198
|
function buildPersistedConfig(config: UseReqConfig): UseReqConfig {
|
|
177
199
|
return {
|
|
@@ -194,6 +216,11 @@ function buildPersistedConfig(config: UseReqConfig): UseReqConfig {
|
|
|
194
216
|
"notify-beep-on-error": config["notify-beep-on-error"],
|
|
195
217
|
"notify-sound": config["notify-sound"],
|
|
196
218
|
"notify-sound-toggle-shortcut": config["notify-sound-toggle-shortcut"],
|
|
219
|
+
"notify-pushover-global-disable": config["notify-pushover-global-disable"],
|
|
220
|
+
"notify-pushover-on-success": config["notify-pushover-on-success"],
|
|
221
|
+
"notify-pushover-user-key": config["notify-pushover-user-key"],
|
|
222
|
+
"notify-pushover-api-token": config["notify-pushover-api-token"],
|
|
223
|
+
"notify-pushover-priority": config["notify-pushover-priority"],
|
|
197
224
|
PI_NOTIFY_SOUND_LOW_CMD: config.PI_NOTIFY_SOUND_LOW_CMD,
|
|
198
225
|
PI_NOTIFY_SOUND_MID_CMD: config.PI_NOTIFY_SOUND_MID_CMD,
|
|
199
226
|
PI_NOTIFY_SOUND_HIGH_CMD: config.PI_NOTIFY_SOUND_HIGH_CMD,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* scheduling through exported controller helpers.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import path from "node:path";
|
|
11
12
|
import type {
|
|
12
13
|
AgentEndEvent,
|
|
13
14
|
ContextUsage,
|
|
@@ -15,8 +16,8 @@ import type {
|
|
|
15
16
|
ThemeColor,
|
|
16
17
|
} from "@mariozechner/pi-coding-agent";
|
|
17
18
|
import type { UseReqConfig } from "./config.js";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
19
|
+
import { normalizePathSlashes } from "./path-context.js";
|
|
20
|
+
import { formatPiNotifyBeepStatus, formatPiNotifyPushoverStatus } from "./pi-notify.js";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* @brief Enumerates the CLI-supported theme tokens consumed by status rendering.
|
|
@@ -66,7 +67,7 @@ interface StatusThemeAdapter {
|
|
|
66
67
|
interface ContextUsageOverlaySpec {
|
|
67
68
|
backgroundColor: StatusForegroundColor;
|
|
68
69
|
foregroundColor: StatusForegroundColor;
|
|
69
|
-
text: "CLEAR" | "FULL
|
|
70
|
+
text: "◀ CLEAR ▶ " | " ◀ FULL ▶ ";
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/**
|
|
@@ -112,28 +113,36 @@ export const PI_USEREQ_STATUS_HOOK_NAMES = [
|
|
|
112
113
|
*/
|
|
113
114
|
export type PiUsereqStatusHookName = (typeof PI_USEREQ_STATUS_HOOK_NAMES)[number];
|
|
114
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @brief Describes one prompt request tracked across extension command delivery and runtime execution.
|
|
118
|
+
* @details Stores the bundled prompt command name plus the raw argument string substituted into `%%ARGS%%` so later successful-run side effects can reconstruct prompt-specific completion notifications. The interface is compile-time only and introduces no runtime cost.
|
|
119
|
+
*/
|
|
120
|
+
export interface PiUsereqPromptRequest {
|
|
121
|
+
promptName: string;
|
|
122
|
+
promptArgs: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
115
125
|
/**
|
|
116
126
|
* @brief Stores the mutable runtime facts displayed by the status bar.
|
|
117
|
-
* @details Persists the latest context-usage snapshot, the active run start
|
|
118
|
-
* timestamp, and the most recent normally completed run duration. Runtime state
|
|
119
|
-
* is mutated in-place by controller helpers. Compile-time only and introduces
|
|
120
|
-
* no runtime cost.
|
|
127
|
+
* @details Persists the latest context-usage snapshot, the active run start timestamp, the most recent normally completed run duration, the accumulated duration of all normally completed runs, and prompt-request metadata carried from command dispatch into the next runtime execution. Runtime state is mutated in-place by controller helpers. Compile-time only and introduces no runtime cost.
|
|
121
128
|
*/
|
|
122
129
|
export interface PiUsereqStatusState {
|
|
123
130
|
contextUsage: ContextUsage | undefined;
|
|
124
131
|
runStartTimeMs: number | undefined;
|
|
125
132
|
lastRunDurationMs: number | undefined;
|
|
133
|
+
totalRunDurationMs: number | undefined;
|
|
134
|
+
pendingPromptRequest: PiUsereqPromptRequest | undefined;
|
|
135
|
+
activePromptRequest: PiUsereqPromptRequest | undefined;
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
/**
|
|
129
139
|
* @brief Stores the controller state required for event-driven status updates.
|
|
130
140
|
* @details Keeps the mutable status snapshot, the current configuration, the
|
|
131
|
-
* latest extension context used for rendering, the
|
|
132
|
-
*
|
|
133
|
-
*
|
|
141
|
+
* latest extension context used for rendering, and the interval handle used
|
|
142
|
+
* for live elapsed-time refreshes. Compile-time only and introduces no runtime
|
|
143
|
+
* cost.
|
|
134
144
|
*/
|
|
135
145
|
export interface PiUsereqStatusController {
|
|
136
|
-
readonly getActiveTools: () => readonly string[];
|
|
137
146
|
config: UseReqConfig | undefined;
|
|
138
147
|
latestContext: ExtensionContext | undefined;
|
|
139
148
|
state: PiUsereqStatusState;
|
|
@@ -264,12 +273,12 @@ function refreshContextUsage(
|
|
|
264
273
|
}
|
|
265
274
|
|
|
266
275
|
/**
|
|
267
|
-
* @brief Counts the filled cells rendered by the
|
|
276
|
+
* @brief Counts the filled cells rendered by the 10-cell context bar.
|
|
268
277
|
* @details Uses ceiling semantics for positive percentages so any non-zero
|
|
269
278
|
* usage occupies at least one cell and zero usage occupies none. Runtime is
|
|
270
279
|
* O(1). No external state is mutated.
|
|
271
280
|
* @param[in] contextUsage {ContextUsage | undefined} Normalized context snapshot.
|
|
272
|
-
* @return {number} Filled-cell count in the inclusive range `[0,
|
|
281
|
+
* @return {number} Filled-cell count in the inclusive range `[0, 10]`.
|
|
273
282
|
* @satisfies REQ-122
|
|
274
283
|
*/
|
|
275
284
|
function countFilledContextCells(
|
|
@@ -279,13 +288,13 @@ function countFilledContextCells(
|
|
|
279
288
|
if (percent === null || percent === undefined || percent <= 0) {
|
|
280
289
|
return 0;
|
|
281
290
|
}
|
|
282
|
-
return Math.min(
|
|
291
|
+
return Math.min(10, Math.ceil((percent * 10) / 100));
|
|
283
292
|
}
|
|
284
293
|
|
|
285
294
|
/**
|
|
286
295
|
* @brief Resolves the threshold-specific context-bar overlay when required.
|
|
287
|
-
* @details Returns the empty-state
|
|
288
|
-
* usage is unavailable or non-positive and returns the
|
|
296
|
+
* @details Returns the empty-state `◀ CLEAR ▶ ` overlay when normalized context
|
|
297
|
+
* usage is unavailable or non-positive and returns the centered ` ◀ FULL ▶ `
|
|
289
298
|
* overlay with the active theme `error` token when usage exceeds 90 percent.
|
|
290
299
|
* Runtime is O(1). No external state is mutated.
|
|
291
300
|
* @param[in] contextUsage {ContextUsage | undefined} Normalized context snapshot.
|
|
@@ -300,14 +309,14 @@ function resolveContextUsageOverlay(
|
|
|
300
309
|
return {
|
|
301
310
|
backgroundColor: "accent",
|
|
302
311
|
foregroundColor: "warning",
|
|
303
|
-
text: "CLEAR",
|
|
312
|
+
text: "◀ CLEAR ▶ ",
|
|
304
313
|
};
|
|
305
314
|
}
|
|
306
315
|
if (percent > 90) {
|
|
307
316
|
return {
|
|
308
317
|
backgroundColor: "warning",
|
|
309
318
|
foregroundColor: "error",
|
|
310
|
-
text: "FULL
|
|
319
|
+
text: " ◀ FULL ▶ ",
|
|
311
320
|
};
|
|
312
321
|
}
|
|
313
322
|
return undefined;
|
|
@@ -335,7 +344,7 @@ function formatContextUsageOverlay(
|
|
|
335
344
|
}
|
|
336
345
|
|
|
337
346
|
/**
|
|
338
|
-
* @brief Formats one
|
|
347
|
+
* @brief Formats one 10-cell context-usage bar.
|
|
339
348
|
* @details Renders threshold-specific overlays for empty and high-water states;
|
|
340
349
|
* otherwise renders filled cells with the theme `warning` token on an
|
|
341
350
|
* accent-derived background and unfilled cells in `dim` on the same background
|
|
@@ -343,7 +352,7 @@ function formatContextUsageOverlay(
|
|
|
343
352
|
* mutated.
|
|
344
353
|
* @param[in] theme {StatusThemeAdapter} Normalized status theme.
|
|
345
354
|
* @param[in] contextUsage {ContextUsage | undefined} Normalized context snapshot.
|
|
346
|
-
* @return {string} Rendered
|
|
355
|
+
* @return {string} Rendered 10-cell bar or overlay.
|
|
347
356
|
* @satisfies REQ-121, REQ-122, REQ-126, REQ-127, REQ-128
|
|
348
357
|
*/
|
|
349
358
|
function formatContextUsageBar(
|
|
@@ -355,7 +364,7 @@ function formatContextUsageBar(
|
|
|
355
364
|
return formatContextUsageOverlay(theme, overlay);
|
|
356
365
|
}
|
|
357
366
|
const filledCells = countFilledContextCells(contextUsage);
|
|
358
|
-
return Array.from({ length:
|
|
367
|
+
return Array.from({ length: 10 }, (_value, index) =>
|
|
359
368
|
index < filledCells ? theme.filledContextCell : theme.emptyContextCell,
|
|
360
369
|
).join("");
|
|
361
370
|
}
|
|
@@ -376,6 +385,44 @@ function formatStatusDuration(durationMs: number): string {
|
|
|
376
385
|
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
377
386
|
}
|
|
378
387
|
|
|
388
|
+
/**
|
|
389
|
+
* @brief Formats one optional completed-duration value.
|
|
390
|
+
* @details Returns the canonical unset placeholder `--:--` until the supplied
|
|
391
|
+
* timer receives a normally completed prompt duration, then delegates to
|
|
392
|
+
* `formatStatusDuration(...)`. Runtime is O(1). No external state is mutated.
|
|
393
|
+
* @param[in] durationMs {number | undefined} Optional completed-duration value.
|
|
394
|
+
* @return {string} Rendered duration or unset placeholder.
|
|
395
|
+
* @satisfies REQ-124
|
|
396
|
+
*/
|
|
397
|
+
function formatCompletedStatusDuration(
|
|
398
|
+
durationMs: number | undefined,
|
|
399
|
+
): string {
|
|
400
|
+
return durationMs === undefined ? "--:--" : formatStatusDuration(durationMs);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* @brief Formats the consolidated `elapsed` status-bar value.
|
|
405
|
+
* @details Emits the active prompt segment `⏱︎ <active>`, the latest normally
|
|
406
|
+
* completed segment `⚑ <last>`, and the accumulated successful-runtime segment
|
|
407
|
+
* `⌛︎ <total>` with fixed spacing. Runtime is O(1). No external state is
|
|
408
|
+
* mutated.
|
|
409
|
+
* @param[in] state {PiUsereqStatusState} Mutable status state snapshot.
|
|
410
|
+
* @param[in] nowMs {number} Current wall-clock time in milliseconds.
|
|
411
|
+
* @return {string} Consolidated `elapsed` field value.
|
|
412
|
+
* @satisfies REQ-123, REQ-124, REQ-125, REQ-159
|
|
413
|
+
*/
|
|
414
|
+
function formatElapsedStatusValue(
|
|
415
|
+
state: PiUsereqStatusState,
|
|
416
|
+
nowMs: number,
|
|
417
|
+
): string {
|
|
418
|
+
const activeText = state.runStartTimeMs === undefined
|
|
419
|
+
? "--:--"
|
|
420
|
+
: formatStatusDuration(nowMs - state.runStartTimeMs);
|
|
421
|
+
const lastText = formatCompletedStatusDuration(state.lastRunDurationMs);
|
|
422
|
+
const totalText = formatCompletedStatusDuration(state.totalRunDurationMs);
|
|
423
|
+
return ` ⏱︎ ${activeText} ⚑ ${lastText} ⌛︎ ${totalText}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
379
426
|
/**
|
|
380
427
|
* @brief Formats one standard status-bar field.
|
|
381
428
|
* @details Renders the field label in accent color and the value in warning
|
|
@@ -431,52 +478,38 @@ function didAgentEndAbort(messages: AgentEndEvent["messages"]): boolean {
|
|
|
431
478
|
|
|
432
479
|
/**
|
|
433
480
|
* @brief Builds the full single-line pi-usereq status-bar payload.
|
|
434
|
-
* @details Renders
|
|
435
|
-
* @param[in] cwd {string} Runtime working directory used for
|
|
481
|
+
* @details Renders base, context, elapsed, beep, sound, and pushover fields in the canonical order with dim bullet separators and threshold-specific context-bar overlays. Runtime is O(1). No external state is mutated.
|
|
482
|
+
* @param[in] cwd {string} Runtime working directory used for base-path derivation.
|
|
436
483
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
437
|
-
* @param[in] activeTools {readonly string[]} Active runtime tool names.
|
|
438
484
|
* @param[in] theme {StatusThemeAdapter} Normalized status theme.
|
|
439
485
|
* @param[in] state {PiUsereqStatusState} Mutable status state snapshot.
|
|
440
486
|
* @param[in] nowMs {number} Current wall-clock time in milliseconds.
|
|
441
487
|
* @return {string} Single-line status-bar text.
|
|
442
|
-
* @satisfies REQ-109, REQ-112, REQ-120, REQ-121, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-135, REQ-136, REQ-
|
|
488
|
+
* @satisfies REQ-109, REQ-112, REQ-120, REQ-121, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-135, REQ-136, REQ-148, REQ-156, REQ-159, REQ-170, REQ-171
|
|
443
489
|
*/
|
|
444
490
|
function buildPiUsereqStatusText(
|
|
445
491
|
cwd: string,
|
|
446
492
|
config: UseReqConfig,
|
|
447
|
-
activeTools: readonly string[],
|
|
448
493
|
theme: StatusThemeAdapter,
|
|
449
494
|
state: PiUsereqStatusState,
|
|
450
495
|
nowMs: number,
|
|
451
496
|
): string {
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
const baseText = formatBasePathRelativeToGitPath(cwd, gitPath);
|
|
455
|
-
const sourcePaths = config["src-dir"].join(",");
|
|
456
|
-
const elapsedText = state.runStartTimeMs === undefined
|
|
457
|
-
? "idle"
|
|
458
|
-
: formatStatusDuration(nowMs - state.runStartTimeMs);
|
|
459
|
-
const lastText = state.lastRunDurationMs === undefined
|
|
460
|
-
? "N/A"
|
|
461
|
-
: formatStatusDuration(state.lastRunDurationMs);
|
|
497
|
+
const baseText = normalizePathSlashes(path.resolve(cwd));
|
|
498
|
+
const elapsedText = formatElapsedStatusValue(state, nowMs);
|
|
462
499
|
const beepText = formatPiNotifyBeepStatus(config);
|
|
463
500
|
const soundText = config["notify-sound"];
|
|
501
|
+
const pushoverText = formatPiNotifyPushoverStatus(config);
|
|
464
502
|
return [
|
|
465
|
-
formatStatusField(theme, "git", gitText),
|
|
466
503
|
formatStatusField(theme, "base", baseText),
|
|
467
|
-
formatStatusField(theme, "docs", config["docs-dir"]),
|
|
468
|
-
formatStatusField(theme, "tests", config["tests-dir"]),
|
|
469
|
-
formatStatusField(theme, "src", sourcePaths),
|
|
470
|
-
formatStatusField(theme, "tools", String(activeTools.length)),
|
|
471
504
|
formatRenderedStatusField(
|
|
472
505
|
theme,
|
|
473
506
|
"context",
|
|
474
507
|
formatContextUsageBar(theme, state.contextUsage),
|
|
475
508
|
),
|
|
476
509
|
formatStatusField(theme, "elapsed", elapsedText),
|
|
477
|
-
formatStatusField(theme, "last", lastText),
|
|
478
510
|
formatStatusField(theme, "beep", beepText),
|
|
479
511
|
formatStatusField(theme, "sound", soundText),
|
|
512
|
+
formatStatusField(theme, "pushover", pushoverText),
|
|
480
513
|
].join(theme.separator);
|
|
481
514
|
}
|
|
482
515
|
|
|
@@ -525,24 +558,21 @@ function syncPiUsereqStatusTicker(
|
|
|
525
558
|
|
|
526
559
|
/**
|
|
527
560
|
* @brief Creates an empty pi-usereq status controller.
|
|
528
|
-
* @details Initializes the mutable status snapshot,
|
|
529
|
-
* provider used by render-time tool counting, and starts with no config, no
|
|
530
|
-
* context, and no live ticker. Runtime is O(1). No external state is mutated.
|
|
531
|
-
* @param[in] getActiveTools {() => readonly string[]} Provider for active tools.
|
|
561
|
+
* @details Initializes the mutable status snapshot, including empty prompt-request tracking, and starts with no config, no context, and no live ticker. Runtime is O(1). No external state is mutated.
|
|
532
562
|
* @return {PiUsereqStatusController} New status controller.
|
|
533
563
|
* @satisfies DES-010
|
|
534
564
|
*/
|
|
535
|
-
export function createPiUsereqStatusController(
|
|
536
|
-
getActiveTools: () => readonly string[],
|
|
537
|
-
): PiUsereqStatusController {
|
|
565
|
+
export function createPiUsereqStatusController(): PiUsereqStatusController {
|
|
538
566
|
return {
|
|
539
|
-
getActiveTools,
|
|
540
567
|
config: undefined,
|
|
541
568
|
latestContext: undefined,
|
|
542
569
|
state: {
|
|
543
570
|
contextUsage: undefined,
|
|
544
571
|
runStartTimeMs: undefined,
|
|
545
572
|
lastRunDurationMs: undefined,
|
|
573
|
+
totalRunDurationMs: undefined,
|
|
574
|
+
pendingPromptRequest: undefined,
|
|
575
|
+
activePromptRequest: undefined,
|
|
546
576
|
},
|
|
547
577
|
tickHandle: undefined,
|
|
548
578
|
};
|
|
@@ -574,7 +604,7 @@ export function setPiUsereqStatusConfig(
|
|
|
574
604
|
* @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
|
|
575
605
|
* @param[in] ctx {ExtensionContext} Active extension context.
|
|
576
606
|
* @return {void} No return value.
|
|
577
|
-
* @satisfies REQ-120, REQ-121, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-135, REQ-136
|
|
607
|
+
* @satisfies REQ-120, REQ-121, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-135, REQ-136, REQ-148, REQ-159, REQ-170, REQ-171
|
|
578
608
|
*/
|
|
579
609
|
export function renderPiUsereqStatus(
|
|
580
610
|
controller: PiUsereqStatusController,
|
|
@@ -590,7 +620,6 @@ export function renderPiUsereqStatus(
|
|
|
590
620
|
buildPiUsereqStatusText(
|
|
591
621
|
ctx.cwd,
|
|
592
622
|
controller.config,
|
|
593
|
-
controller.getActiveTools(),
|
|
594
623
|
theme,
|
|
595
624
|
controller.state,
|
|
596
625
|
Date.now(),
|
|
@@ -601,17 +630,17 @@ export function renderPiUsereqStatus(
|
|
|
601
630
|
/**
|
|
602
631
|
* @brief Updates mutable status state for one intercepted lifecycle hook.
|
|
603
632
|
* @details Refreshes stored context usage on every hook, starts run timing on
|
|
604
|
-
* `agent_start`, captures non-aborted run duration on `agent_end`, clears live
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
633
|
+
* `agent_start`, promotes pending prompt-request metadata into the active run, captures non-aborted run duration on `agent_end`, accumulates successful runtime into `Σ`, clears live timing on shutdown, synchronizes
|
|
634
|
+
* the live ticker, and re-renders the status bar when configuration is
|
|
635
|
+
* available. Runtime is O(n) in `agent_end` message count and otherwise O(1).
|
|
636
|
+
* Side effects include in-memory state mutation, interval scheduling, and
|
|
637
|
+
* footer-status updates.
|
|
609
638
|
* @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
|
|
610
639
|
* @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
|
|
611
640
|
* @param[in] event {unknown} Hook payload forwarded from the wrapper.
|
|
612
641
|
* @param[in] ctx {ExtensionContext} Active extension context.
|
|
613
642
|
* @return {void} No return value.
|
|
614
|
-
* @satisfies REQ-117, REQ-118, REQ-119, REQ-123, REQ-124, REQ-125
|
|
643
|
+
* @satisfies REQ-117, REQ-118, REQ-119, REQ-123, REQ-124, REQ-125, REQ-159, REQ-169
|
|
615
644
|
*/
|
|
616
645
|
export function updateExtensionStatus(
|
|
617
646
|
controller: PiUsereqStatusController,
|
|
@@ -625,18 +654,25 @@ export function updateExtensionStatus(
|
|
|
625
654
|
|
|
626
655
|
if (hookName === "agent_start") {
|
|
627
656
|
controller.state.runStartTimeMs = nowMs;
|
|
657
|
+
controller.state.activePromptRequest = controller.state.pendingPromptRequest;
|
|
658
|
+
controller.state.pendingPromptRequest = undefined;
|
|
628
659
|
}
|
|
629
660
|
|
|
630
661
|
if (hookName === "agent_end" && controller.state.runStartTimeMs !== undefined) {
|
|
631
662
|
const durationMs = nowMs - controller.state.runStartTimeMs;
|
|
632
663
|
if (!didAgentEndAbort((event as AgentEndEvent).messages ?? [])) {
|
|
633
664
|
controller.state.lastRunDurationMs = durationMs;
|
|
665
|
+
controller.state.totalRunDurationMs = controller.state.totalRunDurationMs === undefined
|
|
666
|
+
? durationMs
|
|
667
|
+
: controller.state.totalRunDurationMs + durationMs;
|
|
634
668
|
}
|
|
635
669
|
controller.state.runStartTimeMs = undefined;
|
|
636
670
|
}
|
|
637
671
|
|
|
638
672
|
if (hookName === "session_shutdown") {
|
|
639
673
|
controller.state.runStartTimeMs = undefined;
|
|
674
|
+
controller.state.pendingPromptRequest = undefined;
|
|
675
|
+
controller.state.activePromptRequest = undefined;
|
|
640
676
|
}
|
|
641
677
|
|
|
642
678
|
syncPiUsereqStatusTicker(controller);
|