pi-usereq 0.75.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 CHANGED
@@ -1,4 +1,4 @@
1
- # PI-useReq/pi-usereq (0.75.0)
1
+ # PI-useReq/pi-usereq (0.76.0)
2
2
 
3
3
  <p align="center">
4
4
  <img src="https://img.shields.io/badge/python-3.11%2B-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python 3.11+">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-usereq",
3
- "version": "0.75.0",
3
+ "version": "0.76.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Ogekuri/PI-useReq.git"
@@ -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, and append-only JSON log writing for tool, prompt, and dedicated workflow debug events. 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.
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
+ }
@@ -74,6 +74,7 @@ export const PI_USEREQ_STATUS_HOOK_NAMES = [
74
74
  "session_tree",
75
75
  "context",
76
76
  "before_provider_request",
77
+ "after_provider_response",
77
78
  "before_agent_start",
78
79
  "agent_start",
79
80
  "agent_end",
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;