pi-usereq 0.60.0 → 0.62.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/README.md +1 -1
- package/package.json +4 -4
- package/src/core/context-file-size.ts +109 -0
- package/src/core/prompt-command-runtime.ts +131 -3
- package/src/core/prompt-command-state.ts +46 -0
- package/src/core/prompts.ts +10 -3
- package/src/core/static-check.ts +1 -1
- package/src/index.ts +258 -41
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-usereq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.62.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Ogekuri/PI-useReq.git"
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@earendil-works/pi-ai": "^0.
|
|
36
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
37
|
-
"@earendil-works/pi-tui": "^0.
|
|
35
|
+
"@earendil-works/pi-ai": "^1.0.0",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "^1.0.0",
|
|
37
|
+
"@earendil-works/pi-tui": "^1.0.0",
|
|
38
38
|
"@sinclair/typebox": "^0.34.49"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @brief Measures runtime character and token sizes of the canonical context files.
|
|
4
|
+
* @details Provides reusable helpers that compute character and `cl100k_base` token estimates for `REQUIREMENTS.md`, `REFERENCES.md`, and `WORKFLOW.md` under `<base-path>/<docs-dir>` so configuration menus and command summaries share one measurement contract. Runtime is O(n) in aggregate context-file size. Side effects are limited to filesystem reads.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { DEFAULT_DOCS_DIR, type UseReqConfig } from "./config.js";
|
|
10
|
+
import { normalizeRelativeDirContract } from "./path-context.js";
|
|
11
|
+
import { countFileMetrics } from "./token-counter.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @brief Stores the canonical context-file names in their documented injection order.
|
|
15
|
+
* @details The tuple is the single ordering source reused by measurement helpers and size renderers so every caller reports `REQUIREMENTS.md`, `REFERENCES.md`, and `WORKFLOW.md` consistently. Lookup complexity is O(1).
|
|
16
|
+
* @satisfies REQ-373
|
|
17
|
+
*/
|
|
18
|
+
export const CONTEXT_FILE_NAMES = ["REQUIREMENTS.md", "REFERENCES.md", "WORKFLOW.md"] as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @brief Narrows context-file identifiers to the canonical measured document set.
|
|
22
|
+
* @details Compile-time alias derived from `CONTEXT_FILE_NAMES` and reused by the measured-facts record keys. The alias introduces no runtime cost.
|
|
23
|
+
*/
|
|
24
|
+
export type ContextFileName = (typeof CONTEXT_FILE_NAMES)[number];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @brief Describes the measured size facts of one canonical context file.
|
|
28
|
+
* @details Stores the filesystem existence flag plus the exact character count and the `cl100k_base` token estimate computed from the file content. The interface is compile-time only and introduces no runtime cost.
|
|
29
|
+
* @satisfies REQ-374
|
|
30
|
+
*/
|
|
31
|
+
export interface ContextFileSizeFacts {
|
|
32
|
+
readonly exists: boolean;
|
|
33
|
+
readonly chars: number;
|
|
34
|
+
readonly tokens: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @brief Stores the canonical missing-file size facts.
|
|
39
|
+
* @details Reused as the deterministic result for missing, non-file, and unreadable targets so callers never receive `undefined` facts. Lookup complexity is O(1).
|
|
40
|
+
* @satisfies REQ-374
|
|
41
|
+
*/
|
|
42
|
+
export const MISSING_CONTEXT_FILE_SIZE: ContextFileSizeFacts = Object.freeze({ exists: false, chars: 0, tokens: 0 });
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @brief Resolves one canonical context-file absolute path for one project base.
|
|
46
|
+
* @details Joins the project base with the trailing-slash-free configured `docs-dir` (falling back to `DEFAULT_DOCS_DIR`) and the supplied canonical file name so menus, summaries, and the `%%CONTEXT_FILES%%` renderer address identical targets. Runtime is O(p) in path length. No external state is mutated.
|
|
47
|
+
* @param[in] projectBase {string} Absolute project root path.
|
|
48
|
+
* @param[in] config {UseReqConfig} Effective project configuration supplying the docs directory.
|
|
49
|
+
* @param[in] fileName {ContextFileName} Canonical context-file name.
|
|
50
|
+
* @return {string} Absolute context-file path.
|
|
51
|
+
* @satisfies REQ-373
|
|
52
|
+
*/
|
|
53
|
+
export function resolveContextFilePath(
|
|
54
|
+
projectBase: string,
|
|
55
|
+
config: UseReqConfig,
|
|
56
|
+
fileName: ContextFileName,
|
|
57
|
+
): string {
|
|
58
|
+
const normalizedDocsDir = normalizeRelativeDirContract(config["docs-dir"]) || DEFAULT_DOCS_DIR;
|
|
59
|
+
return path.join(projectBase, normalizedDocsDir, fileName);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @brief Measures one context file into deterministic size facts.
|
|
64
|
+
* @details Probes filesystem existence and file kind, reads UTF-8 content, and reuses the shared `countFileMetrics` tokenizer for the `cl100k_base` token estimate. Missing, non-file, and unreadable targets return `MISSING_CONTEXT_FILE_SIZE` facts without throwing. Runtime is O(n) in file size. Side effects are limited to filesystem reads.
|
|
65
|
+
* @param[in] filePath {string} Absolute context-file path to measure.
|
|
66
|
+
* @return {ContextFileSizeFacts} Measured size facts for the target.
|
|
67
|
+
* @satisfies REQ-373, REQ-374
|
|
68
|
+
*/
|
|
69
|
+
export function measureContextFileSize(filePath: string): ContextFileSizeFacts {
|
|
70
|
+
try {
|
|
71
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
72
|
+
return MISSING_CONTEXT_FILE_SIZE;
|
|
73
|
+
}
|
|
74
|
+
const metrics = countFileMetrics(fs.readFileSync(filePath, "utf8"));
|
|
75
|
+
return { exists: true, chars: metrics.chars, tokens: metrics.tokens };
|
|
76
|
+
} catch {
|
|
77
|
+
return MISSING_CONTEXT_FILE_SIZE;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @brief Measures every canonical context file for one project base.
|
|
83
|
+
* @details Iterates `CONTEXT_FILE_NAMES` in documented order, resolves each configured `<base-path>/<docs-dir>` target through `resolveContextFilePath`, and returns the keyed facts record consumed by configuration menus and command summaries. Runtime is O(n) in aggregate context-file size. Side effects are limited to filesystem reads.
|
|
84
|
+
* @param[in] projectBase {string} Absolute project root path.
|
|
85
|
+
* @param[in] config {UseReqConfig} Effective project configuration supplying the docs directory.
|
|
86
|
+
* @return {Record<ContextFileName, ContextFileSizeFacts>} Measured size facts keyed by canonical file name.
|
|
87
|
+
* @satisfies REQ-373, REQ-374
|
|
88
|
+
*/
|
|
89
|
+
export function measureContextFileSizes(
|
|
90
|
+
projectBase: string,
|
|
91
|
+
config: UseReqConfig,
|
|
92
|
+
): Record<ContextFileName, ContextFileSizeFacts> {
|
|
93
|
+
const facts = {} as Record<ContextFileName, ContextFileSizeFacts>;
|
|
94
|
+
for (const fileName of CONTEXT_FILE_NAMES) {
|
|
95
|
+
facts[fileName] = measureContextFileSize(resolveContextFilePath(projectBase, config, fileName));
|
|
96
|
+
}
|
|
97
|
+
return facts;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @brief Formats one measured context-file size as a compact character and token estimate.
|
|
102
|
+
* @details Emits the deterministic `<chars>c/<tokens>t` shape reused by menu rows, the top-level summary, and the command invocation summary so every surface exposes identical size facts. Runtime is O(n) in rendered length. No external state is mutated.
|
|
103
|
+
* @param[in] facts {ContextFileSizeFacts} Measured context-file size facts.
|
|
104
|
+
* @return {string} Compact `<chars>c/<tokens>t` size string.
|
|
105
|
+
* @satisfies REQ-375, REQ-376, REQ-377
|
|
106
|
+
*/
|
|
107
|
+
export function formatContextFileSize(facts: ContextFileSizeFacts): string {
|
|
108
|
+
return `${facts.chars}c/${facts.tokens}t`;
|
|
109
|
+
}
|
|
@@ -63,6 +63,126 @@ export interface PromptCommandExecutionPlan {
|
|
|
63
63
|
worktreeDir?: string;
|
|
64
64
|
worktreePath?: string;
|
|
65
65
|
worktreeRootPath?: string;
|
|
66
|
+
originalModel?: PromptCommandModelSelection;
|
|
67
|
+
originalThinkingLevel?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @brief Describes one serializable model-selection fact captured at prompt-command preflight.
|
|
72
|
+
* @details Stores the active model provider and identifier as plain strings so the selection can travel inside the prompt execution plan and process-scoped persistence across session replacement without retaining live SDK model objects, and MUST NOT be persisted into pi-usereq configuration files per CTN-022. The interface is compile-time only and introduces no runtime cost.
|
|
73
|
+
* @satisfies REQ-366, CTN-022
|
|
74
|
+
*/
|
|
75
|
+
export interface PromptCommandModelSelection {
|
|
76
|
+
provider: string;
|
|
77
|
+
modelId: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @brief Describes the loosely-typed runtime surface required to re-apply one captured selection.
|
|
82
|
+
* @details Narrows the pi runtime to the model registry lookup, the active model plus thinking-level probes used by the duplicate-skip guard, and the session-scoped `setModel`/`setThinkingLevel` appliers that leave the configured default model untouched. All members stay optional so legacy hosts and offline replays degrade to a documented warning instead of aborting orchestration. The interface is compile-time only and introduces no runtime cost.
|
|
83
|
+
* @satisfies REQ-367, REQ-370, REQ-371
|
|
84
|
+
*/
|
|
85
|
+
export interface PromptCommandModelSelectionSurface {
|
|
86
|
+
modelRegistry?: { find?: (provider: unknown, modelId: unknown) => unknown };
|
|
87
|
+
model?: unknown;
|
|
88
|
+
thinkingLevel?: unknown;
|
|
89
|
+
setModel?: (model: unknown) => Promise<boolean>;
|
|
90
|
+
setThinkingLevel?: (level: unknown) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @brief Describes the re-apply request consumed by `reapplyPromptCommandSessionSelection`.
|
|
95
|
+
* @details Carries the captured selection plus thinking level, the runtime surface, and the optional stale-context-safe warning callback so callers keep notification ownership inside `src/index.ts` while the helper stays independent from extension UI types per DES-012. The interface is compile-time only and introduces no runtime cost.
|
|
96
|
+
*/
|
|
97
|
+
export interface PromptCommandModelSelectionReapplyOptions {
|
|
98
|
+
selection: PromptCommandModelSelection | undefined;
|
|
99
|
+
thinkingLevel: string | undefined;
|
|
100
|
+
surface: PromptCommandModelSelectionSurface | undefined;
|
|
101
|
+
notifyWarning?: (message: string) => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @brief Describes the deterministic outcome facts of one guarded selection re-application.
|
|
106
|
+
* @details Distinguishes not-attempted skips (missing selection, missing surface, or post-switch state already matching the captured selection) from attempted applications, and carries the normalized warning message emitted when resolution, authentication, or the runtime applier fails. The interface is compile-time only and introduces no runtime cost.
|
|
107
|
+
* @satisfies REQ-370, REQ-372
|
|
108
|
+
*/
|
|
109
|
+
export interface PromptCommandModelSelectionReapplyResult {
|
|
110
|
+
attempted: boolean;
|
|
111
|
+
applied: boolean;
|
|
112
|
+
skipped: boolean;
|
|
113
|
+
warningMessage?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @brief Re-applies one captured model selection onto the active post-switch session.
|
|
118
|
+
* @details Resolves the captured provider plus identifier through the surface model registry, skips the whole operation when the selection or surface is unavailable and when the active model and thinking level already match the captured selection so no duplicate `model_change` session entry is emitted, otherwise awaits the session-scoped `setModel` applier, then re-applies the captured thinking level when the active level differs. Every resolution, authentication, and applier failure degrades into one warning notification through the supplied callback without throwing, so prompt orchestration and worktree closure continue unchanged. Runtime is O(1) plus one awaited model mutation. No external state is mutated beyond the applied session selection.
|
|
119
|
+
* @param[in] options {PromptCommandModelSelectionReapplyOptions} Captured selection, runtime surface, and warning callback.
|
|
120
|
+
* @return {Promise<PromptCommandModelSelectionReapplyResult>} Attempted, applied, skipped, and warning facts for the re-application.
|
|
121
|
+
* @satisfies REQ-366, REQ-367, REQ-368, REQ-369, REQ-370, REQ-371, REQ-372, DES-021
|
|
122
|
+
*/
|
|
123
|
+
export async function reapplyPromptCommandSessionSelection(
|
|
124
|
+
options: PromptCommandModelSelectionReapplyOptions,
|
|
125
|
+
): Promise<PromptCommandModelSelectionReapplyResult> {
|
|
126
|
+
const selection = options.selection;
|
|
127
|
+
const surface = options.surface;
|
|
128
|
+
if (!selection || !surface) {
|
|
129
|
+
return { attempted: false, applied: false, skipped: true };
|
|
130
|
+
}
|
|
131
|
+
const notifyWarning = options.notifyWarning;
|
|
132
|
+
const registryFind = surface.modelRegistry?.find;
|
|
133
|
+
const resolvedCandidate = typeof registryFind === "function"
|
|
134
|
+
? registryFind.call(surface.modelRegistry, selection.provider, selection.modelId)
|
|
135
|
+
: undefined;
|
|
136
|
+
const resolved = resolvedCandidate as { provider?: unknown; id?: unknown } | null | undefined;
|
|
137
|
+
if (!resolved || typeof resolved.provider !== "string" || typeof resolved.id !== "string") {
|
|
138
|
+
const message = `WARNING: Could not re-apply model ${selection.provider}/${selection.modelId}: model is not available in the active model registry.`;
|
|
139
|
+
notifyWarning?.(message);
|
|
140
|
+
return { attempted: true, applied: false, skipped: false, warningMessage: message };
|
|
141
|
+
}
|
|
142
|
+
const currentModel = surface.model as { provider?: unknown; id?: unknown } | null | undefined;
|
|
143
|
+
const modelMatches = currentModel !== null
|
|
144
|
+
&& currentModel !== undefined
|
|
145
|
+
&& currentModel.provider === selection.provider
|
|
146
|
+
&& currentModel.id === selection.modelId;
|
|
147
|
+
const levelMatches = typeof options.thinkingLevel !== "string"
|
|
148
|
+
|| surface.thinkingLevel === options.thinkingLevel;
|
|
149
|
+
if (modelMatches && levelMatches) {
|
|
150
|
+
return { attempted: false, applied: false, skipped: true };
|
|
151
|
+
}
|
|
152
|
+
if (!modelMatches) {
|
|
153
|
+
const setModelFn = surface.setModel;
|
|
154
|
+
if (typeof setModelFn !== "function") {
|
|
155
|
+
const message = `WARNING: Could not re-apply model ${selection.provider}/${selection.modelId}: the runtime does not expose setModel.`;
|
|
156
|
+
notifyWarning?.(message);
|
|
157
|
+
return { attempted: true, applied: false, skipped: false, warningMessage: message };
|
|
158
|
+
}
|
|
159
|
+
let setModelResult: unknown;
|
|
160
|
+
try {
|
|
161
|
+
setModelResult = await setModelFn.call(surface, resolved);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const message = `WARNING: Could not re-apply model ${selection.provider}/${selection.modelId}: ${error instanceof Error ? error.message : String(error)}.`;
|
|
164
|
+
notifyWarning?.(message);
|
|
165
|
+
return { attempted: true, applied: false, skipped: false, warningMessage: message };
|
|
166
|
+
}
|
|
167
|
+
if (setModelResult === false) {
|
|
168
|
+
const message = `WARNING: Could not re-apply model ${selection.provider}/${selection.modelId}: no API key configured for ${selection.provider}.`;
|
|
169
|
+
notifyWarning?.(message);
|
|
170
|
+
return { attempted: true, applied: false, skipped: false, warningMessage: message };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const setThinkingLevelFn = surface.setThinkingLevel;
|
|
174
|
+
if (
|
|
175
|
+
typeof options.thinkingLevel === "string"
|
|
176
|
+
&& surface.thinkingLevel !== options.thinkingLevel
|
|
177
|
+
&& typeof setThinkingLevelFn === "function"
|
|
178
|
+
) {
|
|
179
|
+
try {
|
|
180
|
+
setThinkingLevelFn.call(surface, options.thinkingLevel);
|
|
181
|
+
} catch {
|
|
182
|
+
// REQ-372: thinking-level failures are advisory-only and MUST NOT abort orchestration.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { attempted: true, applied: true, skipped: false };
|
|
66
186
|
}
|
|
67
187
|
|
|
68
188
|
/**
|
|
@@ -109,11 +229,11 @@ interface PromptCommandSessionSwitchOptions {
|
|
|
109
229
|
|
|
110
230
|
/**
|
|
111
231
|
* @brief Describes the minimal session-bound surface available after session replacement.
|
|
112
|
-
* @details Extends the shared prompt-command context with `sendUserMessage(...)` so prompt dispatch can target the replacement session without reusing stale pre-switch runtime objects. The interface is compile-time only and introduces no runtime cost.
|
|
232
|
+
* @details Extends the shared prompt-command context with a string-only `sendUserMessage(...)` probe so prompt dispatch can target the replacement session without reusing stale pre-switch runtime objects. The string content parameter mirrors the prompt-delivery contract consumed by `deliverPromptCommand(...)` while remaining structurally compatible with the pi 1.0.0 replacement-session delivery signatures. The interface is compile-time only and introduces no runtime cost.
|
|
113
233
|
*/
|
|
114
234
|
interface PromptCommandActiveContext extends PromptCommandSessionContext {
|
|
115
235
|
sendUserMessage?: (
|
|
116
|
-
content: string
|
|
236
|
+
content: string,
|
|
117
237
|
options?: PromptCommandSessionMessageOptions,
|
|
118
238
|
) => Promise<void> | void;
|
|
119
239
|
}
|
|
@@ -1584,9 +1704,11 @@ export function validatePromptRequiredDocs(
|
|
|
1584
1704
|
* @param[in] currentSessionDir {string | undefined} Active session directory reused when the execution session is forked.
|
|
1585
1705
|
* @param[in] currentSessionBranch {PromptCommandSessionEntry[] | undefined} Active in-memory session branch copied when the origin session file is not flushed yet.
|
|
1586
1706
|
* @param[in] debugOptions {PromptCommandDebugOptions | undefined} Optional prompt debug logging context.
|
|
1707
|
+
* @param[in] originalModel {PromptCommandModelSelection | undefined} Active model provider plus identifier captured by the command handler for later re-application.
|
|
1708
|
+
* @param[in] originalThinkingLevel {string | undefined} Active thinking level captured by the command handler for later re-application.
|
|
1587
1709
|
* @return {PromptCommandExecutionPlan} Prepared execution plan.
|
|
1588
1710
|
* @throws {ReqError} Throws when repository validation, required-doc validation, worktree creation, or session preparation fails.
|
|
1589
|
-
* @satisfies REQ-200, REQ-203, REQ-206, REQ-207, REQ-215, REQ-219, REQ-220, REQ-245, REQ-256, REQ-271
|
|
1711
|
+
* @satisfies REQ-200, REQ-203, REQ-206, REQ-207, REQ-215, REQ-219, REQ-220, REQ-245, REQ-256, REQ-271, REQ-366
|
|
1590
1712
|
*/
|
|
1591
1713
|
export function preparePromptCommandExecution(
|
|
1592
1714
|
promptName: PromptCommandName,
|
|
@@ -1597,6 +1719,8 @@ export function preparePromptCommandExecution(
|
|
|
1597
1719
|
currentSessionDir: string | undefined,
|
|
1598
1720
|
currentSessionBranch: PromptCommandSessionEntry[] | undefined,
|
|
1599
1721
|
debugOptions?: PromptCommandDebugOptions,
|
|
1722
|
+
originalModel?: PromptCommandModelSelection,
|
|
1723
|
+
originalThinkingLevel?: string,
|
|
1600
1724
|
): PromptCommandExecutionPlan {
|
|
1601
1725
|
const gitPath = validatePromptGitState(projectBase, config);
|
|
1602
1726
|
const reuseCurrentSessionFile = isUsablePromptSessionFile(currentSessionFile, projectBase);
|
|
@@ -1624,6 +1748,8 @@ export function preparePromptCommandExecution(
|
|
|
1624
1748
|
baseDir: staticWorktreePaths.baseDir,
|
|
1625
1749
|
originalSessionFile,
|
|
1626
1750
|
executionSessionFile: originalSessionFile,
|
|
1751
|
+
originalModel,
|
|
1752
|
+
originalThinkingLevel,
|
|
1627
1753
|
};
|
|
1628
1754
|
}
|
|
1629
1755
|
const worktreeDir = buildPromptWorktreeName(gitPath, config);
|
|
@@ -1663,6 +1789,8 @@ export function preparePromptCommandExecution(
|
|
|
1663
1789
|
worktreeDir: worktreePaths.worktreeDir,
|
|
1664
1790
|
worktreePath: worktreePaths.worktreePath,
|
|
1665
1791
|
worktreeRootPath: worktreePaths.worktreeRootPath,
|
|
1792
|
+
originalModel,
|
|
1793
|
+
originalThinkingLevel,
|
|
1666
1794
|
};
|
|
1667
1795
|
}
|
|
1668
1796
|
|
|
@@ -209,3 +209,49 @@ export function restorePersistedPromptCommandRuntimeStateForSession(
|
|
|
209
209
|
}
|
|
210
210
|
return undefined;
|
|
211
211
|
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @brief Describes the loosely-typed extension-api surface persisted for model re-application.
|
|
215
|
+
* @details Stores only the session-scoped model appliers plus thinking-level probe needed to re-apply one captured selection after session replacement. The structural shape avoids importing concrete pi SDK types into this process-scoped module. The interface is compile-time only and introduces no runtime cost.
|
|
216
|
+
* @satisfies REQ-367, REQ-368, REQ-369
|
|
217
|
+
*/
|
|
218
|
+
export interface PersistedPromptCommandRuntimeApi {
|
|
219
|
+
setModel?: (model: unknown) => unknown;
|
|
220
|
+
setThinkingLevel?: (level: unknown) => unknown;
|
|
221
|
+
getThinkingLevel?: () => unknown;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @brief Defines the global property used for latest live extension-api persistence.
|
|
226
|
+
* @details The property lives on `globalThis` because pi invalidates the extension api captured by one session's extension instance whenever that session is replaced and reloads the extension against the replacement session while the host process persists. Access complexity is O(1).
|
|
227
|
+
*/
|
|
228
|
+
const PI_USEREQ_PROMPT_COMMAND_RUNTIME_API_KEY = "__piUsereqPromptCommandRuntimeApi";
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @brief Writes the latest live extension api into process-scoped persistence.
|
|
232
|
+
* @details Replaces the stored loosely-typed model-api surface so model re-application helpers always bind to the extension instance of the currently active session runner and never to an api invalidated by session replacement. Runtime is O(1). Side effect: mutates process-scoped state.
|
|
233
|
+
* @param[in] runtimeApi {PersistedPromptCommandRuntimeApi | undefined} Latest extension-api surface written at extension bind time, or undefined to clear the stored surface.
|
|
234
|
+
* @return {void} No return value.
|
|
235
|
+
* @satisfies REQ-367, REQ-368, REQ-369
|
|
236
|
+
*/
|
|
237
|
+
export function writePersistedPromptCommandRuntimeApi(
|
|
238
|
+
runtimeApi: PersistedPromptCommandRuntimeApi | undefined,
|
|
239
|
+
): void {
|
|
240
|
+
const globalState = globalThis as typeof globalThis & {
|
|
241
|
+
__piUsereqPromptCommandRuntimeApi?: PersistedPromptCommandRuntimeApi | undefined;
|
|
242
|
+
};
|
|
243
|
+
globalState[PI_USEREQ_PROMPT_COMMAND_RUNTIME_API_KEY] = runtimeApi;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* @brief Reads the latest live extension api from process-scoped persistence.
|
|
248
|
+
* @details Returns the extension-api surface written by the most recent extension bind so post-switch model re-application uses a session-scoped api that remains valid after session replacement, returning undefined so callers degrade to their own captured api. Runtime is O(1). No external state is mutated.
|
|
249
|
+
* @return {PersistedPromptCommandRuntimeApi | undefined} Latest stored extension-api surface, or undefined when no extension has bound in this process.
|
|
250
|
+
* @satisfies REQ-367, REQ-368, REQ-369
|
|
251
|
+
*/
|
|
252
|
+
export function readPersistedPromptCommandRuntimeApi(): PersistedPromptCommandRuntimeApi | undefined {
|
|
253
|
+
const globalState = globalThis as typeof globalThis & {
|
|
254
|
+
__piUsereqPromptCommandRuntimeApi?: PersistedPromptCommandRuntimeApi | undefined;
|
|
255
|
+
};
|
|
256
|
+
return globalState[PI_USEREQ_PROMPT_COMMAND_RUNTIME_API_KEY];
|
|
257
|
+
}
|
package/src/core/prompts.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
comparePiUsereqStartupToolNames,
|
|
18
18
|
type PiUsereqStartupToolName,
|
|
19
19
|
} from "./pi-usereq-tools.js";
|
|
20
|
+
import { formatContextFileSize, type ContextFileSizeFacts } from "./context-file-size.js";
|
|
20
21
|
import { formatRuntimePathForDisplay, normalizeRelativeDirContract } from "./path-context.js";
|
|
21
22
|
import type {
|
|
22
23
|
PromptCommandExecutionPlan,
|
|
@@ -374,21 +375,27 @@ export const PROMPT_COMMAND_SUMMARY_CUSTOM_TYPE = "pi-usereq-prompt-command";
|
|
|
374
375
|
|
|
375
376
|
/**
|
|
376
377
|
* @brief Builds the on-screen command invocation summary for one bundled prompt-backed `req-<prompt>` command.
|
|
377
|
-
* @details Renders the command name without the `req-` prefix in uppercase, the user request arguments, and the active configuration fields (`docs-dir`, `src-dir`, `tests-dir`, enabled context files, `AUTO_GIT_COMMIT`, effective `GIT_WORKTREE_ENABLED`, `GIT_WORKTREE_PREFIX`, enabled static-check languages, and `enabled-tools`) so the TUI shows only a compact summary while the full rendered prompt is delivered hidden to the LLM agent. Static-check languages are emitted in canonical `DEFAULT_STATIC_CHECK_LANGUAGES` order; enabled tools are emitted in documented menu order via `comparePiUsereqStartupToolNames`. The `context files`, `static code checks`, and `enabled tools` fields render the literal `none` placeholder whenever their respective enabled-item list is empty so the summary never shows a blank value. Sections are emitted in the order `Command:`, `Configuration:` with its bullet list, then `User's Request:`, each separated by one blank line. Runtime is O(l + t log t) where l is language count and t is enabled-tool count. No external state is mutated.
|
|
378
|
+
* @details Renders the command name without the `req-` prefix in uppercase, the user request arguments, and the active configuration fields (`docs-dir`, `src-dir`, `tests-dir`, enabled context files, `AUTO_GIT_COMMIT`, effective `GIT_WORKTREE_ENABLED`, `GIT_WORKTREE_PREFIX`, enabled static-check languages, and `enabled-tools`) so the TUI shows only a compact summary while the full rendered prompt is delivered hidden to the LLM agent. When measured context-file size facts are supplied, every enabled existing context file in the `context files` field is rendered as `name(<chars>c/<tokens>t)`; without facts, or for enabled missing files, the plain `name` form is rendered. Static-check languages are emitted in canonical `DEFAULT_STATIC_CHECK_LANGUAGES` order; enabled tools are emitted in documented menu order via `comparePiUsereqStartupToolNames`. The `context files`, `static code checks`, and `enabled tools` fields render the literal `none` placeholder whenever their respective enabled-item list is empty so the summary never shows a blank value. Sections are emitted in the order `Command:`, `Configuration:` with its bullet list, then `User's Request:`, each separated by one blank line. Runtime is O(l + t log t) where l is language count and t is enabled-tool count. No external state is mutated.
|
|
378
379
|
* @param[in] promptName {string} Bundled prompt name without the `req-` prefix.
|
|
379
380
|
* @param[in] args {string} User request arguments passed to the slash command.
|
|
380
381
|
* @param[in] config {UseReqConfig} Effective project configuration supplying directory, git, static-check, and tool fields.
|
|
382
|
+
* @param[in] contextFileSizes {Record<string, ContextFileSizeFacts> | undefined} Optional measured context-file size facts keyed by canonical file name, consumed for `context files` size suffixes.
|
|
381
383
|
* @return {string} Multi-line command invocation summary text.
|
|
382
|
-
* @satisfies REQ-335, REQ-336, REQ-337, REQ-338, REQ-353
|
|
384
|
+
* @satisfies REQ-335, REQ-336, REQ-337, REQ-338, REQ-353, REQ-377
|
|
383
385
|
*/
|
|
384
386
|
export function renderPromptCommandSummary(
|
|
385
387
|
promptName: string,
|
|
386
388
|
args: string,
|
|
387
389
|
config: UseReqConfig,
|
|
390
|
+
contextFileSizes?: Record<string, ContextFileSizeFacts>,
|
|
388
391
|
): string {
|
|
389
392
|
const contextFiles = CONTEXT_FILE_DESCRIPTORS
|
|
390
393
|
.filter((descriptor) => config[descriptor.flagKey])
|
|
391
|
-
.map((descriptor) =>
|
|
394
|
+
.map((descriptor) => {
|
|
395
|
+
const baseName = descriptor.fileName.replace(/\.md$/, "").toLowerCase();
|
|
396
|
+
const facts = contextFileSizes?.[descriptor.fileName];
|
|
397
|
+
return facts?.exists ? `${baseName}(${formatContextFileSize(facts)})` : baseName;
|
|
398
|
+
})
|
|
392
399
|
.join(", ") || "none";
|
|
393
400
|
const enabledLanguages = DEFAULT_STATIC_CHECK_LANGUAGES
|
|
394
401
|
.filter((language) => config["static-check"][language]?.enabled === "enable")
|
package/src/core/static-check.ts
CHANGED
|
@@ -404,7 +404,7 @@ export class StaticCheckCommand extends StaticCheckBase {
|
|
|
404
404
|
throw new ReqError(`Error: external command '${cmd}' not found on PATH.`, 1);
|
|
405
405
|
}
|
|
406
406
|
super(inputs, extraArgs, failOnly);
|
|
407
|
-
this.cmd =
|
|
407
|
+
this.cmd = resolvedCmd;
|
|
408
408
|
this.label = `Command[${cmd}]`;
|
|
409
409
|
}
|
|
410
410
|
|
package/src/index.ts
CHANGED
|
@@ -83,6 +83,12 @@ import {
|
|
|
83
83
|
normalizeEnabledPiUsereqTools,
|
|
84
84
|
type PiUsereqStartupToolName,
|
|
85
85
|
} from "./core/pi-usereq-tools.js";
|
|
86
|
+
import {
|
|
87
|
+
formatContextFileSize,
|
|
88
|
+
measureContextFileSizes,
|
|
89
|
+
type ContextFileName,
|
|
90
|
+
type ContextFileSizeFacts,
|
|
91
|
+
} from "./core/context-file-size.js";
|
|
86
92
|
import {
|
|
87
93
|
PROMPT_COMMAND_SUMMARY_CUSTOM_TYPE,
|
|
88
94
|
renderPrompt,
|
|
@@ -95,8 +101,11 @@ import {
|
|
|
95
101
|
finalizePromptCommandExecution,
|
|
96
102
|
getPromptCommandErrorContext,
|
|
97
103
|
preparePromptCommandExecution,
|
|
104
|
+
reapplyPromptCommandSessionSelection,
|
|
98
105
|
restorePromptCommandExecution,
|
|
99
106
|
type PromptCommandExecutionPlan,
|
|
107
|
+
type PromptCommandModelSelection,
|
|
108
|
+
type PromptCommandModelSelectionSurface,
|
|
100
109
|
} from "./core/prompt-command-runtime.js";
|
|
101
110
|
import {
|
|
102
111
|
REQ_REFERENCES_COMMAND_DESCRIPTION,
|
|
@@ -136,8 +145,11 @@ import { PROMPT_COMMAND_NAMES } from "./core/prompt-command-catalog.js";
|
|
|
136
145
|
import { resolveRuntimeGitPath } from "./core/runtime-project-paths.js";
|
|
137
146
|
import {
|
|
138
147
|
clearPersistedPromptCommandRuntimeState,
|
|
148
|
+
readPersistedPromptCommandRuntimeApi,
|
|
139
149
|
readPersistedPromptCommandRuntimeState,
|
|
150
|
+
writePersistedPromptCommandRuntimeApi,
|
|
140
151
|
writePersistedPromptCommandRuntimeState,
|
|
152
|
+
type PersistedPromptCommandRuntimeApi,
|
|
141
153
|
} from "./core/prompt-command-state.js";
|
|
142
154
|
import { ensureBundledResourcesAccessible, readBundledPromptDescription } from "./core/resources.js";
|
|
143
155
|
import { ReqError } from "./core/errors.js";
|
|
@@ -1301,13 +1313,15 @@ function isPiAgentSettledEventSupported(
|
|
|
1301
1313
|
/**
|
|
1302
1314
|
* @brief Finalizes a matched successful worktree-backed prompt at the current lifecycle point.
|
|
1303
1315
|
* @details Executes the deferred stash-assisted merge, transcript preservation, base-path restore, and worktree plus branch deletion through `finalizePromptCommandExecution(...)`, surfaces `error` or warning-only notifications, clears the pending finalization outcome plus prompt state, and transitions workflow state through `merging` to `idle`. Reused by the `agent_settled` handler on 0.80.4+ hosts and directly by the `agent_end` fallback on legacy hosts that never emit `agent_settled`. Runtime is dominated by git finalization. Side effects include branch merges, worktree deletion, notifications, and workflow-state transitions.
|
|
1316
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance supplying the model re-apply appliers.
|
|
1304
1317
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller whose pending and active prompt state is cleared.
|
|
1305
1318
|
* @param[in] promptRequest {PiUsereqPromptRequest} Matched successful worktree-backed prompt execution plan.
|
|
1306
1319
|
* @param[in] ctx {ExtensionContext} Active extension context used for finalization and notifications.
|
|
1307
1320
|
* @return {Promise<void>} Promise resolved when finalization and state transitions complete.
|
|
1308
|
-
* @satisfies REQ-208, REQ-228, REQ-229, REQ-230, REQ-282, REQ-291, REQ-292, REQ-354, REQ-355
|
|
1321
|
+
* @satisfies REQ-208, REQ-228, REQ-229, REQ-230, REQ-282, REQ-291, REQ-292, REQ-354, REQ-355, REQ-368, REQ-370, REQ-371, REQ-372
|
|
1309
1322
|
*/
|
|
1310
1323
|
async function finalizeMatchedPromptSuccess(
|
|
1324
|
+
pi: ExtensionAPI,
|
|
1311
1325
|
statusController: PiUsereqStatusController,
|
|
1312
1326
|
promptRequest: PiUsereqPromptRequest,
|
|
1313
1327
|
ctx: ExtensionContext,
|
|
@@ -1333,6 +1347,14 @@ async function finalizeMatchedPromptSuccess(
|
|
|
1333
1347
|
: undefined,
|
|
1334
1348
|
);
|
|
1335
1349
|
promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
|
|
1350
|
+
if (finalization.cleanupSucceeded && !finalization.errorMessage) {
|
|
1351
|
+
await reapplyCapturedPromptModelSelection(
|
|
1352
|
+
promptRequest.originalModel,
|
|
1353
|
+
promptRequest.originalThinkingLevel,
|
|
1354
|
+
pi,
|
|
1355
|
+
promptContext,
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1336
1358
|
} catch (error) {
|
|
1337
1359
|
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
1338
1360
|
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1346,6 +1368,12 @@ async function finalizeMatchedPromptSuccess(
|
|
|
1346
1368
|
: undefined,
|
|
1347
1369
|
) ?? promptContext) as typeof ctx;
|
|
1348
1370
|
cleanupSucceeded = true;
|
|
1371
|
+
await reapplyCapturedPromptModelSelection(
|
|
1372
|
+
promptRequest.originalModel,
|
|
1373
|
+
promptRequest.originalThinkingLevel,
|
|
1374
|
+
pi,
|
|
1375
|
+
promptContext,
|
|
1376
|
+
);
|
|
1349
1377
|
} catch (restoreError) {
|
|
1350
1378
|
promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
|
|
1351
1379
|
errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
@@ -1451,6 +1479,12 @@ async function handleExtensionStatusEvent(
|
|
|
1451
1479
|
const requestForActivation = activePromptRequest ?? pendingPromptRequest;
|
|
1452
1480
|
if (requestForActivation !== undefined) {
|
|
1453
1481
|
await activatePromptCommandExecution(requestForActivation, ctx);
|
|
1482
|
+
await reapplyCapturedPromptModelSelection(
|
|
1483
|
+
requestForActivation.originalModel,
|
|
1484
|
+
requestForActivation.originalThinkingLevel,
|
|
1485
|
+
pi,
|
|
1486
|
+
ctx,
|
|
1487
|
+
);
|
|
1454
1488
|
}
|
|
1455
1489
|
}
|
|
1456
1490
|
const notifyRequest: PiNotifyEventRequest | undefined = hookName === "agent_end"
|
|
@@ -1555,6 +1589,7 @@ async function handleExtensionStatusEvent(
|
|
|
1555
1589
|
// directly at `agent_end` to avoid parking in `merging` forever.
|
|
1556
1590
|
statusController.state.pendingFinalizationOutcome = outcome;
|
|
1557
1591
|
await finalizeMatchedPromptSuccess(
|
|
1592
|
+
pi,
|
|
1558
1593
|
statusController,
|
|
1559
1594
|
activePromptRequest,
|
|
1560
1595
|
promptContext,
|
|
@@ -1635,7 +1670,7 @@ async function handleExtensionStatusEvent(
|
|
|
1635
1670
|
&& settledPromptRequest.worktreeDir !== undefined
|
|
1636
1671
|
&& pendingOutcome === "completed"
|
|
1637
1672
|
) {
|
|
1638
|
-
await finalizeMatchedPromptSuccess(statusController, settledPromptRequest, ctx);
|
|
1673
|
+
await finalizeMatchedPromptSuccess(pi, statusController, settledPromptRequest, ctx);
|
|
1639
1674
|
}
|
|
1640
1675
|
}
|
|
1641
1676
|
if (hookName === "session_shutdown") {
|
|
@@ -3041,6 +3076,141 @@ function registerPiNotifyShortcut(
|
|
|
3041
3076
|
});
|
|
3042
3077
|
}
|
|
3043
3078
|
|
|
3079
|
+
/**
|
|
3080
|
+
* @brief Describes the loosely-typed pi extension API surface required by model re-application.
|
|
3081
|
+
* @details Narrows the runtime API to the model appliers and thinking-level probe accessed defensively so legacy hosts and offline harnesses without `setModel`, `setThinkingLevel`, or `getThinkingLevel` degrade to a documented warning instead of a runtime exception. The alias is compile-time only and introduces no runtime cost.
|
|
3082
|
+
*/
|
|
3083
|
+
type PromptModelSelectionApiSurface = {
|
|
3084
|
+
setModel?: (model: unknown) => unknown;
|
|
3085
|
+
setThinkingLevel?: (level: unknown) => unknown;
|
|
3086
|
+
getThinkingLevel?: () => unknown;
|
|
3087
|
+
};
|
|
3088
|
+
|
|
3089
|
+
/**
|
|
3090
|
+
* @brief Describes the loosely-typed context surface required by model re-application.
|
|
3091
|
+
* @details Narrows extension contexts to the active model, thinking level, and model registry probe used to capture the pre-switch selection and to evaluate the duplicate-skip guard after session replacement. The alias is compile-time only and introduces no runtime cost.
|
|
3092
|
+
*/
|
|
3093
|
+
type PromptModelSelectionContextSurface = {
|
|
3094
|
+
model?: unknown;
|
|
3095
|
+
thinkingLevel?: unknown;
|
|
3096
|
+
modelRegistry?: { find?: (provider: unknown, modelId: unknown) => unknown };
|
|
3097
|
+
};
|
|
3098
|
+
|
|
3099
|
+
/**
|
|
3100
|
+
* @brief Captures the active model provider and identifier from one loosely-typed context model.
|
|
3101
|
+
* @details Validates that the runtime model object exposes non-empty string provider and identifier fields and emits the serializable selection fact stored inside the prompt execution plan, returning undefined when the runtime exposes no usable model so preflight capture never throws. Runtime is O(1). No external state is mutated.
|
|
3102
|
+
* @param[in] model {unknown} Active runtime model object from `ctx.model`.
|
|
3103
|
+
* @return {PromptCommandModelSelection | undefined} Serializable provider plus identifier fact, or undefined when unavailable.
|
|
3104
|
+
* @satisfies REQ-366
|
|
3105
|
+
*/
|
|
3106
|
+
function resolveCurrentModelSelection(
|
|
3107
|
+
model: unknown,
|
|
3108
|
+
): PromptCommandModelSelection | undefined {
|
|
3109
|
+
const candidate = model as { provider?: unknown; id?: unknown } | null | undefined;
|
|
3110
|
+
if (candidate === null || candidate === undefined) {
|
|
3111
|
+
return undefined;
|
|
3112
|
+
}
|
|
3113
|
+
if (
|
|
3114
|
+
typeof candidate.provider !== "string"
|
|
3115
|
+
|| typeof candidate.id !== "string"
|
|
3116
|
+
|| candidate.provider === ""
|
|
3117
|
+
|| candidate.id === ""
|
|
3118
|
+
) {
|
|
3119
|
+
return undefined;
|
|
3120
|
+
}
|
|
3121
|
+
return { provider: candidate.provider, modelId: candidate.id };
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
/**
|
|
3125
|
+
* @brief Captures the active thinking level from the runtime API or fallback context.
|
|
3126
|
+
* @details Prefers `pi.getThinkingLevel()` when the host exposes it and falls back to the context `thinkingLevel` probe, returning undefined for legacy hosts or offline harnesses that provide neither so capture stays advisory. Runtime is O(1). No external state is mutated.
|
|
3127
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
3128
|
+
* @param[in] ctx {unknown} Active extension or command context used as fallback probe.
|
|
3129
|
+
* @return {string | undefined} Active thinking level, or undefined when unavailable.
|
|
3130
|
+
* @satisfies REQ-366, REQ-371
|
|
3131
|
+
*/
|
|
3132
|
+
function resolveCurrentThinkingLevel(pi: ExtensionAPI, ctx: unknown): string | undefined {
|
|
3133
|
+
const api = pi as unknown as PromptModelSelectionApiSurface;
|
|
3134
|
+
if (typeof api.getThinkingLevel === "function") {
|
|
3135
|
+
const level = api.getThinkingLevel();
|
|
3136
|
+
if (typeof level === "string" && level !== "") {
|
|
3137
|
+
return level;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
const context = ctx as PromptModelSelectionContextSurface | null | undefined;
|
|
3141
|
+
if (
|
|
3142
|
+
context !== null
|
|
3143
|
+
&& context !== undefined
|
|
3144
|
+
&& typeof context.thinkingLevel === "string"
|
|
3145
|
+
&& context.thinkingLevel !== ""
|
|
3146
|
+
) {
|
|
3147
|
+
return context.thinkingLevel;
|
|
3148
|
+
}
|
|
3149
|
+
return undefined;
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
/**
|
|
3153
|
+
* @brief Builds the loosely-typed model-selection surface from the runtime API plus context.
|
|
3154
|
+
* @details Wraps `setModel` and `setThinkingLevel` with defensive function probes so the surface only exposes appliers the running host actually implements, and copies the context model, thinking level, and model registry used by the guarded re-apply comparison. The supplied api MUST be the latest live extension api bound to the active session runner because pi invalidates captured extension apis after session replacement. Returns undefined when no context is available so re-application degrades to a not-attempted skip. Runtime is O(1). No external state is mutated.
|
|
3155
|
+
* @param[in] pi {ExtensionAPI} Latest live extension api instance supplying the session-scoped model appliers; after session replacement callers pass the rebound api rather than a captured pre-switch api.
|
|
3156
|
+
* @param[in] ctx {unknown} Active extension or command context supplying model probes.
|
|
3157
|
+
* @return {PromptCommandModelSelectionSurface | undefined} Re-apply surface, or undefined when no context exists.
|
|
3158
|
+
* @satisfies REQ-367, REQ-370, REQ-371
|
|
3159
|
+
*/
|
|
3160
|
+
function resolvePromptCommandModelSurface(
|
|
3161
|
+
pi: ExtensionAPI,
|
|
3162
|
+
ctx: unknown,
|
|
3163
|
+
): PromptCommandModelSelectionSurface | undefined {
|
|
3164
|
+
const context = ctx as PromptModelSelectionContextSurface | null | undefined;
|
|
3165
|
+
if (context === null || context === undefined) {
|
|
3166
|
+
return undefined;
|
|
3167
|
+
}
|
|
3168
|
+
const api = pi as unknown as PromptModelSelectionApiSurface;
|
|
3169
|
+
const setModelFn = api.setModel;
|
|
3170
|
+
const setThinkingLevelFn = api.setThinkingLevel;
|
|
3171
|
+
return {
|
|
3172
|
+
modelRegistry: context.modelRegistry,
|
|
3173
|
+
model: context.model,
|
|
3174
|
+
thinkingLevel: context.thinkingLevel,
|
|
3175
|
+
setModel: typeof setModelFn === "function"
|
|
3176
|
+
? (model: unknown) => setModelFn.call(pi, model) as Promise<boolean>
|
|
3177
|
+
: undefined,
|
|
3178
|
+
setThinkingLevel: typeof setThinkingLevelFn === "function"
|
|
3179
|
+
? (level: unknown) => {
|
|
3180
|
+
setThinkingLevelFn.call(pi, level);
|
|
3181
|
+
}
|
|
3182
|
+
: undefined,
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
/**
|
|
3187
|
+
* @brief Re-applies one captured model selection onto the active post-switch session.
|
|
3188
|
+
* @details Builds the guarded re-apply surface from the latest process-scoped extension api plus context, preferring the api written by the most recent extension bind because pi invalidates the api captured before session replacement, and falls back to the caller-supplied api for hosts that do not rebind. Forwards the captured selection plus thinking level into `reapplyPromptCommandSessionSelection(...)` and routes every warning through `notifyContextSafely(...)` so stale replacement contexts after session replacement never abort orchestration. Runtime is O(1) plus one awaited model mutation. Side effects include session-scoped model and thinking-level mutation plus stale-safe warning notifications.
|
|
3189
|
+
* @param[in] selection {PromptCommandModelSelection | undefined} Captured model provider plus identifier.
|
|
3190
|
+
* @param[in] thinkingLevel {string | undefined} Captured thinking level.
|
|
3191
|
+
* @param[in] pi {ExtensionAPI} Extension api captured by the running handler, used only as fallback when no rebound api is stored.
|
|
3192
|
+
* @param[in] ctx {unknown} Active extension or command context used for probes and warning delivery.
|
|
3193
|
+
* @return {Promise<void>} Promise resolved once the guarded re-application completed.
|
|
3194
|
+
* @satisfies REQ-367, REQ-368, REQ-369, REQ-370, REQ-371, REQ-372
|
|
3195
|
+
*/
|
|
3196
|
+
async function reapplyCapturedPromptModelSelection(
|
|
3197
|
+
selection: PromptCommandModelSelection | undefined,
|
|
3198
|
+
thinkingLevel: string | undefined,
|
|
3199
|
+
pi: ExtensionAPI,
|
|
3200
|
+
ctx: unknown,
|
|
3201
|
+
): Promise<void> {
|
|
3202
|
+
const liveApi = readPersistedPromptCommandRuntimeApi();
|
|
3203
|
+
const activeApi = liveApi ?? pi;
|
|
3204
|
+
await reapplyPromptCommandSessionSelection({
|
|
3205
|
+
selection,
|
|
3206
|
+
thinkingLevel,
|
|
3207
|
+
surface: resolvePromptCommandModelSurface(activeApi as ExtensionAPI, ctx),
|
|
3208
|
+
notifyWarning: (message) => {
|
|
3209
|
+
notifyContextSafely(ctx as ExtensionContext, message, "error");
|
|
3210
|
+
},
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3044
3214
|
/**
|
|
3045
3215
|
* @brief Resolves the prompt execution plan targeted by `req-reset` recovery.
|
|
3046
3216
|
* @details Prefers the current in-memory active request, then the current in-memory pending request, then the process-scoped persisted prompt runtime state so the dedicated reset command can recover from same-host unclean prompt termination after session replacement. Runtime is O(1). No external state is mutated.
|
|
@@ -3091,6 +3261,10 @@ function registerReqResetCommand(
|
|
|
3091
3261
|
description: REQ_RESET_COMMAND_DESCRIPTION,
|
|
3092
3262
|
handler: async (_args, ctx) => {
|
|
3093
3263
|
const promptRequest = resolveReqResetPromptRequest(statusController);
|
|
3264
|
+
const resetModelSelection = promptRequest?.originalModel
|
|
3265
|
+
?? resolveCurrentModelSelection((ctx as unknown as PromptModelSelectionContextSurface).model);
|
|
3266
|
+
const resetThinkingLevel = promptRequest?.originalThinkingLevel
|
|
3267
|
+
?? resolveCurrentThinkingLevel(pi, ctx);
|
|
3094
3268
|
const commandCwd = resolveLiveBootstrapCwd(ctx.cwd);
|
|
3095
3269
|
syncContextCwdMirror(ctx, commandCwd);
|
|
3096
3270
|
const projectBase = path.resolve(promptRequest?.basePath ?? getProjectBase(commandCwd));
|
|
@@ -3109,6 +3283,12 @@ function registerReqResetCommand(
|
|
|
3109
3283
|
executionPlan = prepareReqResetCommandExecution(projectBase, config, promptRequest);
|
|
3110
3284
|
executionResult = await executeReqResetCommandExecution(executionPlan, ctx);
|
|
3111
3285
|
resetContext = (executionResult.activeContext ?? resetContext) as typeof ctx;
|
|
3286
|
+
await reapplyCapturedPromptModelSelection(
|
|
3287
|
+
resetModelSelection,
|
|
3288
|
+
resetThinkingLevel,
|
|
3289
|
+
pi,
|
|
3290
|
+
resetContext,
|
|
3291
|
+
);
|
|
3112
3292
|
} catch (error) {
|
|
3113
3293
|
const message = error instanceof Error ? error.message : String(error);
|
|
3114
3294
|
setPiUsereqWorkflowState(statusController, "error", resetContext);
|
|
@@ -3197,7 +3377,7 @@ function registerReqReferencesCommand(
|
|
|
3197
3377
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
3198
3378
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
3199
3379
|
* @return {void} No return value.
|
|
3200
|
-
* @satisfies REQ-004, REQ-067, REQ-068, REQ-169, REQ-200, REQ-201, REQ-202, REQ-203, REQ-206, REQ-207, REQ-219, REQ-220, REQ-221, REQ-224, REQ-225, REQ-226, REQ-227, REQ-245, REQ-246, REQ-247, REQ-277, REQ-281
|
|
3380
|
+
* @satisfies REQ-004, REQ-067, REQ-068, REQ-169, REQ-200, REQ-201, REQ-202, REQ-203, REQ-206, REQ-207, REQ-219, REQ-220, REQ-221, REQ-224, REQ-225, REQ-226, REQ-227, REQ-245, REQ-246, REQ-247, REQ-277, REQ-281, REQ-377
|
|
3201
3381
|
*/
|
|
3202
3382
|
function registerPromptCommands(
|
|
3203
3383
|
pi: ExtensionAPI,
|
|
@@ -3228,6 +3408,10 @@ function registerPromptCommands(
|
|
|
3228
3408
|
ensureBundledResourcesAccessible();
|
|
3229
3409
|
let executionPlan: PromptCommandExecutionPlan | undefined;
|
|
3230
3410
|
let promptContext = ctx;
|
|
3411
|
+
const originalModel = resolveCurrentModelSelection(
|
|
3412
|
+
(ctx as unknown as PromptModelSelectionContextSurface).model,
|
|
3413
|
+
);
|
|
3414
|
+
const originalThinkingLevel = resolveCurrentThinkingLevel(pi, ctx);
|
|
3231
3415
|
try {
|
|
3232
3416
|
executionPlan = preparePromptCommandExecution(
|
|
3233
3417
|
promptName,
|
|
@@ -3241,6 +3425,8 @@ function registerPromptCommands(
|
|
|
3241
3425
|
config,
|
|
3242
3426
|
workflowState: statusController.state.workflowState,
|
|
3243
3427
|
},
|
|
3428
|
+
originalModel,
|
|
3429
|
+
originalThinkingLevel,
|
|
3244
3430
|
);
|
|
3245
3431
|
const content = renderPrompt(
|
|
3246
3432
|
promptName,
|
|
@@ -3256,6 +3442,12 @@ function registerPromptCommands(
|
|
|
3256
3442
|
activePromptRequest: statusController.state.activePromptRequest,
|
|
3257
3443
|
});
|
|
3258
3444
|
promptContext = (await activatePromptCommandExecution(executionPlan, ctx) ?? ctx) as typeof ctx;
|
|
3445
|
+
await reapplyCapturedPromptModelSelection(
|
|
3446
|
+
executionPlan.originalModel,
|
|
3447
|
+
executionPlan.originalThinkingLevel,
|
|
3448
|
+
pi,
|
|
3449
|
+
promptContext,
|
|
3450
|
+
);
|
|
3259
3451
|
logPromptWorkflowEvent(
|
|
3260
3452
|
projectBase,
|
|
3261
3453
|
config,
|
|
@@ -3277,6 +3469,7 @@ function registerPromptCommands(
|
|
|
3277
3469
|
promptName,
|
|
3278
3470
|
args,
|
|
3279
3471
|
config,
|
|
3472
|
+
measureContextFileSizes(projectBase, config),
|
|
3280
3473
|
);
|
|
3281
3474
|
const promptDelivery = deliverPromptCommand(pi, content, commandSummary, promptContext);
|
|
3282
3475
|
transitionPromptWorkflowState(
|
|
@@ -4352,46 +4545,68 @@ async function configureStaticCheckMenu(
|
|
|
4352
4545
|
}
|
|
4353
4546
|
|
|
4354
4547
|
/**
|
|
4355
|
-
* @brief Summarizes the `Context Files` flag state for the top-level menu value column.
|
|
4356
|
-
* @details Renders the three context-file flags as compact `name:on|off` segments in the documented order so the top-level row
|
|
4548
|
+
* @brief Summarizes the `Context Files` flag state with measured sizes for the top-level menu value column.
|
|
4549
|
+
* @details Renders the three context-file flags as compact `name:on|off` segments in the documented order, appending `(<chars>c/<tokens>t)` measured size facts to every enabled segment so the top-level row exposes a runtime estimate of the injected context payload while disabled segments stay plain `name:off`. Runtime is O(1) in segment count. No external state is mutated.
|
|
4357
4550
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
4551
|
+
* @param[in] sizes {Record<ContextFileName, ContextFileSizeFacts>} Measured context-file size facts keyed by canonical file name.
|
|
4358
4552
|
* @return {string} Compact `Context Files` summary string.
|
|
4359
|
-
* @satisfies REQ-327
|
|
4553
|
+
* @satisfies REQ-327, REQ-376
|
|
4360
4554
|
*/
|
|
4361
|
-
function formatContextFilesSummary(
|
|
4362
|
-
|
|
4555
|
+
function formatContextFilesSummary(
|
|
4556
|
+
config: UseReqConfig,
|
|
4557
|
+
sizes: Record<ContextFileName, ContextFileSizeFacts>,
|
|
4558
|
+
): string {
|
|
4559
|
+
const segments = [
|
|
4560
|
+
`requirements:${config["context-files-requirements"] ? `on(${formatContextFileSize(sizes["REQUIREMENTS.md"])})` : "off"}`,
|
|
4561
|
+
`references:${config["context-files-references"] ? `on(${formatContextFileSize(sizes["REFERENCES.md"])})` : "off"}`,
|
|
4562
|
+
`workflow:${config["context-files-workflow"] ? `on(${formatContextFileSize(sizes["WORKFLOW.md"])})` : "off"}`,
|
|
4563
|
+
];
|
|
4564
|
+
return segments.join(" \u2022 ");
|
|
4363
4565
|
}
|
|
4364
4566
|
|
|
4365
4567
|
/**
|
|
4366
4568
|
* @brief Builds the shared settings-menu choices for the `Context Files` submenu.
|
|
4367
|
-
* @details Exposes one inline
|
|
4569
|
+
* @details Exposes one inline toggle row per context file in the documented `REQUIREMENTS.md`, `REFERENCES.md`, `WORKFLOW.md` order whose value renders `on|off • <chars>c/<tokens>t` measured size facts plus a value-less subtree-local `Reset defaults` row. Cycle values embed the same measured facts so inline toggling keeps the size estimate visible while persisting the on|off state. Runtime is O(1) in row count. No external state is mutated.
|
|
4368
4570
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
4571
|
+
* @param[in] sizes {Record<ContextFileName, ContextFileSizeFacts>} Measured context-file size facts keyed by canonical file name.
|
|
4369
4572
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered `Context Files` submenu choices.
|
|
4370
|
-
* @satisfies REQ-327, REQ-328, REQ-333
|
|
4573
|
+
* @satisfies REQ-327, REQ-328, REQ-333, REQ-375
|
|
4371
4574
|
*/
|
|
4372
|
-
function buildContextFilesMenuChoices(
|
|
4575
|
+
function buildContextFilesMenuChoices(
|
|
4576
|
+
config: UseReqConfig,
|
|
4577
|
+
sizes: Record<ContextFileName, ContextFileSizeFacts>,
|
|
4578
|
+
): PiUsereqSettingsMenuChoice[] {
|
|
4579
|
+
const buildRow = (
|
|
4580
|
+
id: "context-files-requirements" | "context-files-references" | "context-files-workflow",
|
|
4581
|
+
fileName: ContextFileName,
|
|
4582
|
+
description: string,
|
|
4583
|
+
): PiUsereqSettingsMenuChoice => {
|
|
4584
|
+
const sizedOn = `on \u2022 ${formatContextFileSize(sizes[fileName])}`;
|
|
4585
|
+
const sizedOff = `off \u2022 ${formatContextFileSize(sizes[fileName])}`;
|
|
4586
|
+
return {
|
|
4587
|
+
id,
|
|
4588
|
+
label: fileName,
|
|
4589
|
+
value: config[id] ? sizedOn : sizedOff,
|
|
4590
|
+
values: [sizedOn, sizedOff],
|
|
4591
|
+
description,
|
|
4592
|
+
};
|
|
4593
|
+
};
|
|
4373
4594
|
return [
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
id: "context-files-workflow",
|
|
4390
|
-
label: "WORKFLOW.md",
|
|
4391
|
-
value: config["context-files-workflow"] ? "on" : "off",
|
|
4392
|
-
values: ["on", "off"],
|
|
4393
|
-
description: "Toggle injection of WORKFLOW.md into the prompt context through `%%CONTEXT_FILES%%`.",
|
|
4394
|
-
},
|
|
4595
|
+
buildRow(
|
|
4596
|
+
"context-files-requirements",
|
|
4597
|
+
"REQUIREMENTS.md",
|
|
4598
|
+
"Toggle injection of REQUIREMENTS.md into the prompt context through `%%CONTEXT_FILES%%`.",
|
|
4599
|
+
),
|
|
4600
|
+
buildRow(
|
|
4601
|
+
"context-files-references",
|
|
4602
|
+
"REFERENCES.md",
|
|
4603
|
+
"Toggle injection of REFERENCES.md into the prompt context through `%%CONTEXT_FILES%%`.",
|
|
4604
|
+
),
|
|
4605
|
+
buildRow(
|
|
4606
|
+
"context-files-workflow",
|
|
4607
|
+
"WORKFLOW.md",
|
|
4608
|
+
"Toggle injection of WORKFLOW.md into the prompt context through `%%CONTEXT_FILES%%`.",
|
|
4609
|
+
),
|
|
4395
4610
|
...buildTerminalSettingsMenuChoices({
|
|
4396
4611
|
resetDefaultsDescription: "Restore the default Context Files configuration (all three files enabled).",
|
|
4397
4612
|
}),
|
|
@@ -4412,6 +4627,7 @@ async function configureContextFilesMenu(
|
|
|
4412
4627
|
config: UseReqConfig,
|
|
4413
4628
|
onConfigChange: () => void,
|
|
4414
4629
|
): Promise<void> {
|
|
4630
|
+
const contextFileSizes = measureContextFileSizes(getProjectBase(ctx.cwd), config);
|
|
4415
4631
|
const setFlag = (flagKey: "context-files-requirements" | "context-files-references" | "context-files-workflow", enabled: boolean): void => {
|
|
4416
4632
|
config[flagKey] = enabled;
|
|
4417
4633
|
onConfigChange();
|
|
@@ -4419,20 +4635,20 @@ async function configureContextFilesMenu(
|
|
|
4419
4635
|
};
|
|
4420
4636
|
let focusedChoiceId: string | undefined;
|
|
4421
4637
|
while (true) {
|
|
4422
|
-
const choice = await showPiUsereqSettingsMenu(ctx, "Context Files", buildContextFilesMenuChoices(config), {
|
|
4638
|
+
const choice = await showPiUsereqSettingsMenu(ctx, "Context Files", buildContextFilesMenuChoices(config, contextFileSizes), {
|
|
4423
4639
|
initialSelectedId: focusedChoiceId,
|
|
4424
|
-
getChoices: () => buildContextFilesMenuChoices(config),
|
|
4640
|
+
getChoices: () => buildContextFilesMenuChoices(config, contextFileSizes),
|
|
4425
4641
|
onChange: (choiceId, newValue) => {
|
|
4426
4642
|
if (choiceId === "context-files-requirements") {
|
|
4427
|
-
setFlag("context-files-requirements", newValue
|
|
4643
|
+
setFlag("context-files-requirements", newValue.startsWith("on"));
|
|
4428
4644
|
return;
|
|
4429
4645
|
}
|
|
4430
4646
|
if (choiceId === "context-files-references") {
|
|
4431
|
-
setFlag("context-files-references", newValue
|
|
4647
|
+
setFlag("context-files-references", newValue.startsWith("on"));
|
|
4432
4648
|
return;
|
|
4433
4649
|
}
|
|
4434
4650
|
if (choiceId === "context-files-workflow") {
|
|
4435
|
-
setFlag("context-files-workflow", newValue
|
|
4651
|
+
setFlag("context-files-workflow", newValue.startsWith("on"));
|
|
4436
4652
|
}
|
|
4437
4653
|
},
|
|
4438
4654
|
});
|
|
@@ -4473,7 +4689,7 @@ async function configureContextFilesMenu(
|
|
|
4473
4689
|
* @param[in] cwd {string} Current working directory.
|
|
4474
4690
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
4475
4691
|
* @return {PiUsereqSettingsMenuChoice[]} Ordered top-level menu choices.
|
|
4476
|
-
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-314, REQ-318, REQ-319, REQ-320, REQ-326
|
|
4692
|
+
* @satisfies REQ-006, REQ-031, REQ-137, REQ-150, REQ-151, REQ-152, REQ-162, REQ-190, REQ-191, REQ-197, REQ-204, REQ-205, REQ-212, REQ-215, REQ-216, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-314, REQ-318, REQ-319, REQ-320, REQ-326, REQ-376
|
|
4477
4693
|
*/
|
|
4478
4694
|
function buildPiUsereqMenuChoices(
|
|
4479
4695
|
cwd: string,
|
|
@@ -4506,7 +4722,7 @@ function buildPiUsereqMenuChoices(
|
|
|
4506
4722
|
{
|
|
4507
4723
|
id: "context-files",
|
|
4508
4724
|
label: "Context Files",
|
|
4509
|
-
value: formatContextFilesSummary(config),
|
|
4725
|
+
value: formatContextFilesSummary(config, measureContextFileSizes(cwd, config)),
|
|
4510
4726
|
description: "Toggle injection of REQUIREMENTS.md, REFERENCES.md, and WORKFLOW.md into prompt context through `%%CONTEXT_FILES%%`.",
|
|
4511
4727
|
},
|
|
4512
4728
|
{
|
|
@@ -4863,7 +5079,7 @@ async function configurePiUsereq(
|
|
|
4863
5079
|
{ label: "Document directory", previousValue: config["docs-dir"], nextValue: defaultConfig["docs-dir"] },
|
|
4864
5080
|
{ label: "Source-code directories", previousValue: config["src-dir"].join(", "), nextValue: defaultConfig["src-dir"].join(", ") },
|
|
4865
5081
|
{ label: "Unit tests directory", previousValue: config["tests-dir"], nextValue: defaultConfig["tests-dir"] },
|
|
4866
|
-
{ label: "Context Files", previousValue: formatContextFilesSummary(config), nextValue: formatContextFilesSummary(defaultConfig) },
|
|
5082
|
+
{ label: "Context Files", previousValue: formatContextFilesSummary(config, measureContextFileSizes(ctx.cwd, config)), nextValue: formatContextFilesSummary(defaultConfig, measureContextFileSizes(ctx.cwd, defaultConfig)) },
|
|
4867
5083
|
{ label: "Auto git commit", previousValue: config.AUTO_GIT_COMMIT, nextValue: defaultConfig.AUTO_GIT_COMMIT },
|
|
4868
5084
|
{ label: "Git worktree", previousValue: config.GIT_WORKTREE_ENABLED, nextValue: defaultConfig.GIT_WORKTREE_ENABLED },
|
|
4869
5085
|
{ label: "Worktree prefix", previousValue: config.GIT_WORKTREE_PREFIX, nextValue: defaultConfig.GIT_WORKTREE_PREFIX },
|
|
@@ -4921,12 +5137,13 @@ function registerConfigCommands(
|
|
|
4921
5137
|
|
|
4922
5138
|
/**
|
|
4923
5139
|
* @brief Registers the complete pi-usereq extension.
|
|
4924
|
-
* @details
|
|
5140
|
+
* @details Persists the live extension api into process-scoped storage on every bind so model re-application after session replacement binds to the api of the currently active session runner instead of an invalidated captured api, then validates installation-owned bundled resources, registers the specialized `req-reset` and `req-references` commands plus bundled prompt-backed commands and agent tools, conditionally registers config-gated debug tool wrapper commands when the current project enables them, registers configuration commands, registers the configurable notification-sound shortcut when the runtime supports shortcuts, and installs shared wrappers for all supported pi lifecycle hooks so status telemetry, context usage, prompt timing, cumulative runtime, prompt-specific Pushover metadata, tool-result debug logging, and prompt-orchestration effects remain synchronized with runtime events. Runtime is O(h) in hook count during registration. Side effects include filesystem reads, command/tool/shortcut registration, UI updates, active-tool changes, process-scoped extension-api persistence, optional debug-log writes, and timer scheduling.
|
|
4925
5141
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
4926
5142
|
* @return {void} No return value.
|
|
4927
5143
|
* @satisfies DES-002, DES-015, REQ-004, REQ-005, REQ-009, REQ-044, REQ-067, REQ-068, REQ-109, REQ-111, REQ-112, REQ-113, REQ-114, REQ-115, REQ-116, REQ-117, REQ-118, REQ-119, REQ-120, REQ-121, REQ-122, REQ-123, REQ-124, REQ-125, REQ-126, REQ-127, REQ-128, REQ-131, REQ-132, REQ-133, REQ-134, REQ-137, REQ-159, REQ-163, REQ-164, REQ-165, REQ-166, REQ-167, REQ-168, REQ-169, REQ-172, REQ-174, REQ-179, REQ-180, REQ-184, REQ-188, REQ-190, REQ-191, REQ-192, REQ-193, REQ-194, REQ-195, REQ-196, REQ-197, REQ-236, REQ-237, REQ-238, REQ-239, REQ-240, REQ-241, REQ-242, REQ-243, REQ-244, REQ-245, REQ-246, REQ-247, REQ-298, REQ-299, REQ-300, REQ-301, REQ-302, REQ-303, REQ-304, REQ-305, REQ-306, REQ-312, REQ-313, REQ-323, REQ-324, REQ-325, REQ-326, REQ-327
|
|
4928
5144
|
*/
|
|
4929
5145
|
export default function piUsereqExtension(pi: ExtensionAPI): void {
|
|
5146
|
+
writePersistedPromptCommandRuntimeApi(pi as unknown as PersistedPromptCommandRuntimeApi);
|
|
4930
5147
|
const statusController = createPiUsereqStatusController();
|
|
4931
5148
|
ensureBundledResourcesAccessible();
|
|
4932
5149
|
registerReqResetCommand(pi, statusController);
|