pi-usereq 0.50.0 → 0.51.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.50.0)
1
+ # PI-useReq/pi-usereq (0.51.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.50.0",
3
+ "version": "0.51.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Ogekuri/PI-useReq.git"
@@ -14,7 +14,7 @@ import type {
14
14
  ThemeColor,
15
15
  } from "@mariozechner/pi-coding-agent";
16
16
  import type { UseReqConfig } from "./config.js";
17
- import type { PiNotifySoundLevel } from "./pi-notify.js";
17
+ import type { PiNotifyOutcome, PiNotifySoundLevel } from "./pi-notify.js";
18
18
  import type { PromptCommandExecutionPlan } from "./prompt-command-runtime.js";
19
19
  import {
20
20
  restorePersistedPromptCommandRuntimeStateForSession,
@@ -77,6 +77,7 @@ export const PI_USEREQ_STATUS_HOOK_NAMES = [
77
77
  "before_agent_start",
78
78
  "agent_start",
79
79
  "agent_end",
80
+ "agent_settled",
80
81
  "turn_start",
81
82
  "turn_end",
82
83
  "message_start",
@@ -124,6 +125,7 @@ export interface PiUsereqStatusState {
124
125
  runtimeSoundLevel: PiNotifySoundLevel | undefined;
125
126
  pendingPromptRequest: PiUsereqPromptRequest | undefined;
126
127
  activePromptRequest: PiUsereqPromptRequest | undefined;
128
+ pendingFinalizationOutcome: PiNotifyOutcome | undefined;
127
129
  }
128
130
 
129
131
  /**
@@ -680,6 +682,7 @@ export function createPiUsereqStatusController(): PiUsereqStatusController {
680
682
  runtimeSoundLevel: undefined,
681
683
  pendingPromptRequest: undefined,
682
684
  activePromptRequest: undefined,
685
+ pendingFinalizationOutcome: undefined,
683
686
  },
684
687
  tickHandle: undefined,
685
688
  };
@@ -802,7 +805,7 @@ export function setPiUsereqWorkflowState(
802
805
 
803
806
  /**
804
807
  * @brief Updates mutable status state for one intercepted lifecycle hook.
805
- * @details Refreshes stored context usage on every hook, resets or restores persisted elapsed counters during `session_start`, loads the active runtime sound level from persisted config during `session_start`, restores persisted prompt-command metadata when the active session matches a forked execution session, resynchronizes that metadata on later lifecycle hooks so post-switch workflow transitions performed by the initiating command handler become visible to the replacement-session runtime, resets workflow state to `idle` for documented session-start reasons, starts run timing on `agent_start`, promotes pending prompt-request metadata into the active run, captures non-aborted run duration on `agent_end`, accumulates successful runtime into `Σ`, preserves in-memory prompt-command state plus process-scoped persistence across switch-triggered `session_shutdown`, tolerates stale post-replacement render contexts, synchronizes the live ticker, and re-renders the status bar with the runtime extension identity prefix when configuration is available. Runtime is O(n) in `agent_end` message count and otherwise O(1). Side effects include in-memory state mutation, interval scheduling, process-scoped persistence mutation, and footer-status updates.
808
+ * @details Refreshes stored context usage on every hook, resets or restores persisted elapsed counters during `session_start`, loads the active runtime sound level from persisted config during `session_start`, restores persisted prompt-command metadata when the active session matches a forked execution session, resynchronizes that metadata on later lifecycle hooks so post-switch workflow transitions performed by the initiating command handler become visible to the replacement-session runtime, resets workflow state to `idle` for documented session-start reasons, starts run timing on `agent_start`, promotes pending prompt-request metadata into the active run, captures non-aborted run duration on `agent_end` or `agent_settled`, accumulates successful runtime into `Σ`, preserves in-memory prompt-command state plus process-scoped persistence across switch-triggered `session_shutdown`, tolerates stale post-replacement render contexts, synchronizes the live ticker, and re-renders the status bar with the runtime extension identity prefix when configuration is available. Runtime is O(n) in `agent_end` message count and otherwise O(1). Side effects include in-memory state mutation, interval scheduling, process-scoped persistence mutation, and footer-status updates.
806
809
  * @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
807
810
  * @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
808
811
  * @param[in] event {unknown} Hook payload forwarded from the wrapper.
@@ -851,9 +854,15 @@ export function updateExtensionStatus(
851
854
  controller.state.pendingPromptRequest = undefined;
852
855
  }
853
856
 
854
- if (hookName === "agent_end" && controller.state.runStartTimeMs !== undefined) {
857
+ if (
858
+ (hookName === "agent_end" || hookName === "agent_settled")
859
+ && controller.state.runStartTimeMs !== undefined
860
+ ) {
855
861
  const durationMs = nowMs - controller.state.runStartTimeMs;
856
- if (!didAgentEndAbort((event as AgentEndEvent).messages ?? [])) {
862
+ const agentEndMessages = hookName === "agent_end"
863
+ ? (event as AgentEndEvent).messages ?? []
864
+ : [];
865
+ if (!didAgentEndAbort(agentEndMessages)) {
857
866
  controller.state.lastRunDurationMs = durationMs;
858
867
  controller.state.totalRunDurationMs = controller.state.totalRunDurationMs === undefined
859
868
  ? durationMs
package/src/index.ts CHANGED
@@ -1249,7 +1249,7 @@ function applyConfiguredPiUsereqTools(pi: ExtensionAPI, config: UseReqConfig): v
1249
1249
 
1250
1250
  /**
1251
1251
  * @brief Handles one intercepted pi lifecycle hook for pi-usereq status updates.
1252
- * @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics, restores the original session-backed `base-path` for every matched worktree-backed completion by reusing persisted replacement-session command contexts when event contexts omit `switchSession()`, executes the stash-assisted merge-and-delete finalization path for every matched successful worktree-backed completion even when a later busy-command rejection already moved workflow state to `error`, emits a warning-only notification when restored `base-path` changes are reapplied after merge, tolerates stale replacement-session notification contexts after session replacement, retains the worktree plus notifies closure failure for interrupted or failed outcomes, logs selected prompt workflow transitions, and transitions workflow state through `merging`, `error`, and `idle` as required. On `session_shutdown`, captures pre-update prompt snapshots so workflow-shutdown diagnostics and same-runtime command continuation preserve the active prompt workflow state across switch-triggered rebinding, then disposes the shared controller. Runtime is dominated by configuration loading during `session_start` and git finalization during matched successful `agent_end` handling; all other hooks are O(1). Side effects include resource checks, active-tool mutation, active-session replacement, status updates, live-ticker disposal on shutdown, optional child-process spawning, outbound HTTPS requests, branch merges, worktree deletion, and optional debug-log writes.
1252
+ * @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics, classifies the prompt outcome, and for every matched successful worktree-backed completion defers the restore switch, stash-assisted merge, and worktree deletion to `agent_settled` because the pi 0.67.1+ `switchSession` implementation awaits the active agent run to become idle and would deadlock inside `agent_end`. On `agent_settled`, reuses persisted replacement-session command contexts when event contexts omit `switchSession()`, executes the deferred stash-assisted merge-and-delete finalization path, emits a warning-only notification when restored `base-path` changes are reapplied after merge, tolerates stale replacement-session notification contexts after session replacement, retains the worktree plus notifies closure failure for interrupted or failed outcomes, logs selected prompt workflow transitions, and transitions workflow state through `merging`, `error`, and `idle` as required. On `session_shutdown`, captures pre-update prompt snapshots so workflow-shutdown diagnostics and same-runtime command continuation preserve the active prompt workflow state across switch-triggered rebinding, then disposes the shared controller. Runtime is dominated by configuration loading during `session_start` and git finalization during matched successful `agent_settled` handling; all other hooks are O(1). Side effects include resource checks, active-tool mutation, active-session replacement, status updates, live-ticker disposal on shutdown, optional child-process spawning, outbound HTTPS requests, branch merges, worktree deletion, and optional debug-log writes.
1253
1253
  * @param[in] pi {ExtensionAPI} Active extension API instance.
1254
1254
  * @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
1255
1255
  * @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
@@ -1373,6 +1373,13 @@ async function handleExtensionStatusEvent(
1373
1373
  );
1374
1374
  }
1375
1375
  if (shouldFinalizeMatchedSuccess) {
1376
+ // Defer the restore switch, merge, and worktree deletion to
1377
+ // `agent_settled`. The pi 0.67.1+ `switchSession` implementation
1378
+ // awaits the active agent run to become idle before replacing the
1379
+ // session, and that idle transition only happens at `agent_settled`.
1380
+ // Calling `switchSession` here would deadlock the `agent_end` handler
1381
+ // and leave the workflow parked in `merging` forever.
1382
+ statusController.state.pendingFinalizationOutcome = outcome;
1376
1383
  if (debugConfig) {
1377
1384
  transitionPromptWorkflowState(
1378
1385
  statusController,
@@ -1385,75 +1392,6 @@ async function handleExtensionStatusEvent(
1385
1392
  } else {
1386
1393
  setPiUsereqWorkflowState(statusController, "merging", promptContext);
1387
1394
  }
1388
- let finalization:
1389
- | {
1390
- mergeAttempted: boolean;
1391
- mergeSucceeded: boolean;
1392
- cleanupSucceeded: boolean;
1393
- errorMessage?: string;
1394
- warningMessage?: string;
1395
- activeContext?: unknown;
1396
- }
1397
- | undefined;
1398
- try {
1399
- finalization = await finalizePromptCommandExecution(
1400
- activePromptRequest,
1401
- promptContext,
1402
- debugConfig
1403
- ? { config: debugConfig, workflowState: statusController.state.workflowState }
1404
- : undefined,
1405
- );
1406
- promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
1407
- } catch (error) {
1408
- promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
1409
- let errorMessage = error instanceof Error ? error.message : String(error);
1410
- let cleanupSucceeded = false;
1411
- try {
1412
- promptContext = (await restorePromptCommandExecution(
1413
- activePromptRequest,
1414
- promptContext,
1415
- debugConfig
1416
- ? { config: debugConfig, workflowState: statusController.state.workflowState }
1417
- : undefined,
1418
- ) ?? promptContext) as typeof ctx;
1419
- cleanupSucceeded = true;
1420
- } catch (restoreError) {
1421
- promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
1422
- errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
1423
- }
1424
- finalization = {
1425
- mergeAttempted: false,
1426
- mergeSucceeded: false,
1427
- cleanupSucceeded,
1428
- errorMessage,
1429
- };
1430
- }
1431
- if (
1432
- finalization.errorMessage
1433
- && (!finalization.cleanupSucceeded || !finalization.mergeSucceeded)
1434
- ) {
1435
- if (debugConfig) {
1436
- transitionPromptWorkflowState(
1437
- statusController,
1438
- promptContext,
1439
- activePromptRequest.basePath,
1440
- debugConfig,
1441
- activePromptRequest.promptName,
1442
- "error",
1443
- );
1444
- } else {
1445
- setPiUsereqWorkflowState(statusController, "error", promptContext);
1446
- }
1447
- notifyContextSafely(promptContext, finalization.errorMessage, "error");
1448
- }
1449
- if (
1450
- finalization.warningMessage
1451
- && finalization.cleanupSucceeded
1452
- && finalization.mergeSucceeded
1453
- && !finalization.errorMessage
1454
- ) {
1455
- notifyContextSafely(promptContext, finalization.warningMessage, "info");
1456
- }
1457
1395
  } else if (closureFailureMessage !== undefined) {
1458
1396
  // Worktree-backed run that ended interrupted, failed, aborted, or
1459
1397
  // incomplete (REQ-209): keep the worktree execution session visible,
@@ -1500,7 +1438,7 @@ async function handleExtensionStatusEvent(
1500
1438
  notifyContextSafely(promptContext, error instanceof Error ? error.message : String(error), "error");
1501
1439
  }
1502
1440
  }
1503
- if (closureFailureMessage === undefined) {
1441
+ if (closureFailureMessage === undefined && !shouldFinalizeMatchedSuccess) {
1504
1442
  statusController.state.pendingPromptRequest = undefined;
1505
1443
  statusController.state.activePromptRequest = undefined;
1506
1444
  if (debugConfig) {
@@ -1518,6 +1456,102 @@ async function handleExtensionStatusEvent(
1518
1456
  }
1519
1457
  }
1520
1458
  }
1459
+ if (hookName === "agent_settled") {
1460
+ const settledPromptRequest = statusController.state.activePromptRequest;
1461
+ const pendingOutcome = statusController.state.pendingFinalizationOutcome;
1462
+ if (
1463
+ settledPromptRequest !== undefined
1464
+ && settledPromptRequest.worktreeDir !== undefined
1465
+ && pendingOutcome === "completed"
1466
+ ) {
1467
+ const debugConfig = statusController.config;
1468
+ let promptContext = ctx;
1469
+ let finalization:
1470
+ | {
1471
+ mergeAttempted: boolean;
1472
+ mergeSucceeded: boolean;
1473
+ cleanupSucceeded: boolean;
1474
+ errorMessage?: string;
1475
+ warningMessage?: string;
1476
+ activeContext?: unknown;
1477
+ }
1478
+ | undefined;
1479
+ try {
1480
+ finalization = await finalizePromptCommandExecution(
1481
+ settledPromptRequest,
1482
+ promptContext,
1483
+ debugConfig
1484
+ ? { config: debugConfig, workflowState: statusController.state.workflowState }
1485
+ : undefined,
1486
+ );
1487
+ promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
1488
+ } catch (error) {
1489
+ promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
1490
+ let errorMessage = error instanceof Error ? error.message : String(error);
1491
+ let cleanupSucceeded = false;
1492
+ try {
1493
+ promptContext = (await restorePromptCommandExecution(
1494
+ settledPromptRequest,
1495
+ promptContext,
1496
+ debugConfig
1497
+ ? { config: debugConfig, workflowState: statusController.state.workflowState }
1498
+ : undefined,
1499
+ ) ?? promptContext) as typeof ctx;
1500
+ cleanupSucceeded = true;
1501
+ } catch (restoreError) {
1502
+ promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
1503
+ errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
1504
+ }
1505
+ finalization = {
1506
+ mergeAttempted: false,
1507
+ mergeSucceeded: false,
1508
+ cleanupSucceeded,
1509
+ errorMessage,
1510
+ };
1511
+ }
1512
+ if (
1513
+ finalization.errorMessage
1514
+ && (!finalization.cleanupSucceeded || !finalization.mergeSucceeded)
1515
+ ) {
1516
+ if (debugConfig) {
1517
+ transitionPromptWorkflowState(
1518
+ statusController,
1519
+ promptContext,
1520
+ settledPromptRequest.basePath,
1521
+ debugConfig,
1522
+ settledPromptRequest.promptName,
1523
+ "error",
1524
+ );
1525
+ } else {
1526
+ setPiUsereqWorkflowState(statusController, "error", promptContext);
1527
+ }
1528
+ notifyContextSafely(promptContext, finalization.errorMessage, "error");
1529
+ }
1530
+ if (
1531
+ finalization.warningMessage
1532
+ && finalization.cleanupSucceeded
1533
+ && finalization.mergeSucceeded
1534
+ && !finalization.errorMessage
1535
+ ) {
1536
+ notifyContextSafely(promptContext, finalization.warningMessage, "info");
1537
+ }
1538
+ statusController.state.pendingFinalizationOutcome = undefined;
1539
+ statusController.state.pendingPromptRequest = undefined;
1540
+ statusController.state.activePromptRequest = undefined;
1541
+ if (debugConfig) {
1542
+ transitionPromptWorkflowState(
1543
+ statusController,
1544
+ promptContext,
1545
+ settledPromptRequest.basePath,
1546
+ debugConfig,
1547
+ settledPromptRequest.promptName,
1548
+ "idle",
1549
+ );
1550
+ } else {
1551
+ setPiUsereqWorkflowState(statusController, "idle", promptContext);
1552
+ }
1553
+ }
1554
+ }
1521
1555
  if (hookName === "session_shutdown") {
1522
1556
  if (shutdownPromptRequest !== undefined && statusController.config) {
1523
1557
  logPromptWorkflowEvent(