pi-usereq 0.74.0 → 0.76.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 +1 -1
- package/src/core/debug-runtime.ts +117 -1
- package/src/core/extension-status.ts +1 -0
- package/src/core/prompts.ts +5 -18
- package/src/index.ts +23 -0
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file
|
|
3
3
|
* @brief Declares debug inventories, normalizers, and JSON log persistence helpers.
|
|
4
|
-
* @details Centralizes debug-menu selector inventories, config-field normalization, workflow-status gating,
|
|
4
|
+
* @details Centralizes debug-menu selector inventories, config-field normalization, workflow-status gating, append-only JSON log writing for tool, prompt, and dedicated workflow debug events, process-scoped capture of the exact `before_provider_request` provider payload for active prompt runs, and error-stage provider payload dumps gated by `after_provider_response` error statuses. Runtime is dominated by JSON serialization plus filesystem I/O during log writes. Side effects include directory creation and file overwrite when debug entries are appended.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from "node:fs";
|
|
@@ -710,3 +710,119 @@ export function logDebugPromptContent(
|
|
|
710
710
|
return false;
|
|
711
711
|
}
|
|
712
712
|
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* @brief Describes one process-scoped captured provider request payload bound to an active prompt run.
|
|
716
|
+
* @details Pairs the bundled prompt name with the JSON serialization of the exact `before_provider_request` payload so a later `after_provider_response` error status can dump the failing request verbatim without retaining live provider objects. The interface is compile-time only and introduces no runtime cost.
|
|
717
|
+
*/
|
|
718
|
+
interface CapturedPromptErrorPayload {
|
|
719
|
+
promptName: PromptCommandName;
|
|
720
|
+
payloadText: string;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* @brief Describes the process-scoped provider error payload capture state persisted across extension rebinds.
|
|
725
|
+
* @details Stores the latest captured prompt error payload on `globalThis` because pi rebinds extension modules for `/new`, `/resume`, `/fork`, and `/reload` between prompt dispatch and provider execution, while the hosting process persists across those operations, so the capture survives the forked execution-session switch. The interface is compile-time only and introduces no runtime cost.
|
|
726
|
+
*/
|
|
727
|
+
interface ProcessScopedPromptErrorCaptureStore {
|
|
728
|
+
capturedPromptErrorPayload: CapturedPromptErrorPayload | undefined;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* @brief Returns the process-scoped provider error payload capture store.
|
|
733
|
+
* @details Lazily initializes one `globalThis` record so session rebinds never discard an already captured failing provider request. Runtime is O(1). Side effect: initializes process-scoped state on first access.
|
|
734
|
+
* @return {ProcessScopedPromptErrorCaptureStore} Mutable process-scoped capture store.
|
|
735
|
+
*/
|
|
736
|
+
function getProcessScopedPromptErrorCaptureStore(): ProcessScopedPromptErrorCaptureStore {
|
|
737
|
+
const globalScope = globalThis as typeof globalThis & { __piUsereqPromptErrorCaptureStore?: ProcessScopedPromptErrorCaptureStore };
|
|
738
|
+
if (!globalScope.__piUsereqPromptErrorCaptureStore) {
|
|
739
|
+
globalScope.__piUsereqPromptErrorCaptureStore = { capturedPromptErrorPayload: undefined };
|
|
740
|
+
}
|
|
741
|
+
return globalScope.__piUsereqPromptErrorCaptureStore;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* @brief Builds one provider error payload debug filename for one bundled prompt.
|
|
746
|
+
* @details Combines the write-time `YYYYMMDDHHMMSSmmm` timestamp, the invokable `req-*` command name, and the failing provider response status into the `<timestamp>-<req-command>-error-<status>` shape so every saved error-stage prompt payload stays attributable to the originating `/req-*` prompt and its session error code. Runtime is O(1). No external state is mutated.
|
|
747
|
+
* @param[in] promptName {PromptCommandName} Bundled prompt name.
|
|
748
|
+
* @param[in] errorCode {number} Failing provider response status, such as `400`.
|
|
749
|
+
* @param[in] date {Date} Write-time timestamp. Defaults to the current wall-clock time.
|
|
750
|
+
* @return {string} Provider error payload filename in the `<timestamp>-<req-command>-error-<status>` shape.
|
|
751
|
+
* @satisfies REQ-423
|
|
752
|
+
*/
|
|
753
|
+
export function formatPromptDebugErrorFileName(
|
|
754
|
+
promptName: PromptCommandName,
|
|
755
|
+
errorCode: number,
|
|
756
|
+
date = new Date(),
|
|
757
|
+
): string {
|
|
758
|
+
return `${formatPromptDebugTimestamp(date)}-${getDebugPromptName(promptName)}-error-${errorCode}`;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* @brief Captures one exact provider request payload for the active prompt run when prompt debug logging is enabled.
|
|
763
|
+
* @details Applies the identical global-debug, prompt-debug, and workflow-state gating used by dispatched prompt content files, serializes the exact `before_provider_request` payload as pretty-printed JSON, and stores it in the process-scoped capture store keyed by the bundled prompt name so the payload survives session replacement until an error response flushes it. Runtime is O(n) in serialized payload size when enabled and O(1) otherwise. Side effect: mutates the process-scoped capture store for enabled matching requests.
|
|
764
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
765
|
+
* @param[in] workflowState {DebugWorkflowState} Current workflow state.
|
|
766
|
+
* @param[in] promptName {PromptCommandName} Active bundled prompt name.
|
|
767
|
+
* @param[in] payload {unknown} Exact `before_provider_request` provider request payload.
|
|
768
|
+
* @return {void} No return value.
|
|
769
|
+
* @satisfies REQ-416, REQ-421
|
|
770
|
+
*/
|
|
771
|
+
export function capturePromptProviderRequestForDebug(
|
|
772
|
+
config: UseReqConfig,
|
|
773
|
+
workflowState: DebugWorkflowState,
|
|
774
|
+
promptName: PromptCommandName,
|
|
775
|
+
payload: unknown,
|
|
776
|
+
): void {
|
|
777
|
+
if (!shouldLogDebugPromptContent(config, workflowState, promptName)) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
let payloadText: string;
|
|
781
|
+
try {
|
|
782
|
+
payloadText = JSON.stringify(payload, null, 2) ?? "";
|
|
783
|
+
} catch {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
getProcessScopedPromptErrorCaptureStore().capturedPromptErrorPayload = { promptName, payloadText };
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* @brief Writes the captured provider request payload as one error-stage prompt debug file for the failing prompt.
|
|
791
|
+
* @details Consumes the process-scoped capture for the supplied prompt, discards it after every write attempt so stale payloads never reach later runs, applies the identical global-debug, prompt-debug, and workflow-state gating used by dispatched prompt content files, and writes the exact captured payload into one `<timestamp>-<req-command>-error-<status>` file beside the initial dispatched prompt file under the configured prompt log path. Runtime is dominated by one directory creation plus one file write when a matching capture exists and O(1) otherwise. Side effects include directory creation and file creation only for enabled matching failures.
|
|
792
|
+
* @param[in] projectBase {string} Absolute original project base path.
|
|
793
|
+
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
794
|
+
* @param[in] workflowState {DebugWorkflowState} Current workflow state.
|
|
795
|
+
* @param[in] promptName {PromptCommandName} Active bundled prompt name.
|
|
796
|
+
* @param[in] errorCode {number} Failing provider response status, such as `400`.
|
|
797
|
+
* @return {boolean} `true` when the provider error payload file is written; otherwise `false`.
|
|
798
|
+
* @satisfies REQ-422, REQ-423, REQ-424, REQ-425
|
|
799
|
+
*/
|
|
800
|
+
export function flushCapturedPromptErrorPayload(
|
|
801
|
+
projectBase: string,
|
|
802
|
+
config: UseReqConfig,
|
|
803
|
+
workflowState: DebugWorkflowState,
|
|
804
|
+
promptName: PromptCommandName,
|
|
805
|
+
errorCode: number,
|
|
806
|
+
): boolean {
|
|
807
|
+
const captureStore = getProcessScopedPromptErrorCaptureStore();
|
|
808
|
+
const captured = captureStore.capturedPromptErrorPayload;
|
|
809
|
+
captureStore.capturedPromptErrorPayload = undefined;
|
|
810
|
+
if (!captured || captured.promptName !== promptName) {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
if (!shouldLogDebugPromptContent(config, workflowState, promptName)) {
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
const logDirectory = resolveDebugPromptsLogPath(projectBase, config);
|
|
817
|
+
try {
|
|
818
|
+
fs.mkdirSync(logDirectory, { recursive: true });
|
|
819
|
+
fs.writeFileSync(
|
|
820
|
+
path.join(logDirectory, formatPromptDebugErrorFileName(promptName, errorCode)),
|
|
821
|
+
captured.payloadText,
|
|
822
|
+
"utf8",
|
|
823
|
+
);
|
|
824
|
+
return true;
|
|
825
|
+
} catch {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
}
|
package/src/core/prompts.ts
CHANGED
|
@@ -195,23 +195,6 @@ function buildContextFilesBlock(projectBase: string, config: UseReqConfig): stri
|
|
|
195
195
|
return sections.join("\n\n");
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
/**
|
|
199
|
-
* @brief Injects the context-files block in place of the dedicated trailing `%%CONTEXT_FILES%%` placeholder.
|
|
200
|
-
* @details Replaces only the last occurrence of the `%%CONTEXT_FILES%%` token, which every bundled prompt places as the dedicated injection point at the end of its `## Context Files` section, so inline backticked references to the token inside prompt prose stay verbatim and no prompt content is overwritten by the injected block. Returns the input unchanged when the token is absent. Runs after every other prompt replacement so literal `%%...%%` tokens inside context-file content are never substituted. Time complexity O(n) in prompt length. No external state is mutated.
|
|
201
|
-
* @param[in] text {string} Prompt markdown after every other replacement, block injection, and tool-reference rewrite.
|
|
202
|
-
* @param[in] block {string} Rendered context-files markdown block, or the empty string when no enabled context file exists.
|
|
203
|
-
* @return {string} Prompt markdown whose dedicated trailing placeholder is replaced by the supplied block while all earlier occurrences of the token remain untouched.
|
|
204
|
-
* @satisfies REQ-329, REQ-331, REQ-332
|
|
205
|
-
*/
|
|
206
|
-
function injectContextFilesBlock(text: string, block: string): string {
|
|
207
|
-
const token = "%%CONTEXT_FILES%%";
|
|
208
|
-
const lastIndex = text.lastIndexOf(token);
|
|
209
|
-
if (lastIndex === -1) {
|
|
210
|
-
return text;
|
|
211
|
-
}
|
|
212
|
-
return text.slice(0, lastIndex) + block + text.slice(lastIndex + token.length);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
198
|
/**
|
|
216
199
|
* @brief Rewrites bundled prompt tool references from legacy `req --...` syntax to internal tool names.
|
|
217
200
|
* @details Applies deterministic global regex replacements so prompt text matches the extension-registered tool surface instead of the standalone CLI spelling. Time complexity O(p*r) where p is pattern count and r is prompt length.
|
|
@@ -388,7 +371,11 @@ export function renderPrompt(
|
|
|
388
371
|
projectBase,
|
|
389
372
|
);
|
|
390
373
|
const adapted = adaptPromptForInternalTools(withPiDevConformance);
|
|
391
|
-
|
|
374
|
+
const contextFilesBlock = buildContextFilesBlock(projectBase, config);
|
|
375
|
+
if (contextFilesBlock) {
|
|
376
|
+
return adapted.split("%%CONTEXT_FILES%%").join(contextFilesBlock);
|
|
377
|
+
}
|
|
378
|
+
return adapted.split("%%CONTEXT_FILES%%").join("");
|
|
392
379
|
}
|
|
393
380
|
|
|
394
381
|
/**
|
package/src/index.ts
CHANGED
|
@@ -133,6 +133,8 @@ import {
|
|
|
133
133
|
DEFAULT_DEBUG_STATUS_CHANGES,
|
|
134
134
|
DEFAULT_DEBUG_TOOL_COMMANDS_ENABLED,
|
|
135
135
|
DEFAULT_DEBUG_WORKFLOW_EVENTS,
|
|
136
|
+
capturePromptProviderRequestForDebug,
|
|
137
|
+
flushCapturedPromptErrorPayload,
|
|
136
138
|
logDebugPromptContent,
|
|
137
139
|
logDebugPromptEvent,
|
|
138
140
|
logDebugPromptWorkflowEvent,
|
|
@@ -1509,6 +1511,27 @@ async function handleExtensionStatusEvent(
|
|
|
1509
1511
|
}
|
|
1510
1512
|
: undefined;
|
|
1511
1513
|
updateExtensionStatus(statusController, hookName, event, ctx);
|
|
1514
|
+
if (hookName === "before_provider_request" && activePromptRequest !== undefined && statusController.config) {
|
|
1515
|
+
const providerRequestEvent = event as { payload?: unknown };
|
|
1516
|
+
capturePromptProviderRequestForDebug(
|
|
1517
|
+
statusController.config,
|
|
1518
|
+
statusController.state.workflowState,
|
|
1519
|
+
activePromptRequest.promptName,
|
|
1520
|
+
providerRequestEvent.payload,
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
if (hookName === "after_provider_response" && activePromptRequest !== undefined && statusController.config) {
|
|
1524
|
+
const providerResponseEvent = event as { status?: unknown };
|
|
1525
|
+
if (typeof providerResponseEvent.status === "number" && providerResponseEvent.status >= 400) {
|
|
1526
|
+
flushCapturedPromptErrorPayload(
|
|
1527
|
+
activePromptRequest.basePath,
|
|
1528
|
+
statusController.config,
|
|
1529
|
+
statusController.state.workflowState,
|
|
1530
|
+
activePromptRequest.promptName,
|
|
1531
|
+
providerResponseEvent.status,
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1512
1535
|
if (hookName === "tool_result" && statusController.config) {
|
|
1513
1536
|
const toolEvent = event as {
|
|
1514
1537
|
toolName?: string;
|