@nklisch/pi-enhanced 0.1.2 → 0.1.4

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.
Files changed (236) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +3 -2
  3. package/node_modules/@nklisch/pi-clearance/README.md +10 -5
  4. package/node_modules/@nklisch/pi-clearance/docs/ARCHITECTURE.md +12 -7
  5. package/node_modules/@nklisch/pi-clearance/docs/CONFIGURATION.md +25 -35
  6. package/node_modules/@nklisch/pi-clearance/docs/DEVELOPER_GUIDE.md +30 -30
  7. package/node_modules/@nklisch/pi-clearance/docs/PACK_AUTHORING.md +5 -4
  8. package/node_modules/@nklisch/pi-clearance/docs/PRINCIPLES.md +14 -10
  9. package/node_modules/@nklisch/pi-clearance/docs/REFERENCE_PATTERNS.md +1 -1
  10. package/node_modules/@nklisch/pi-clearance/docs/REVIEWER_PROMPTS.md +12 -8
  11. package/node_modules/@nklisch/pi-clearance/docs/RULE_PACKS.md +1 -1
  12. package/node_modules/@nklisch/pi-clearance/docs/SPEC.md +7 -6
  13. package/node_modules/@nklisch/pi-clearance/docs/TUNE.md +2 -2
  14. package/node_modules/@nklisch/pi-clearance/docs/USER_GUIDE.md +7 -3
  15. package/node_modules/@nklisch/pi-clearance/docs/VISION.md +3 -3
  16. package/node_modules/@nklisch/pi-clearance/native/clearance-core.darwin-arm64.node +0 -0
  17. package/node_modules/@nklisch/pi-clearance/native/clearance-core.darwin-x64.node +0 -0
  18. package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-arm64-gnu.node +0 -0
  19. package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-x64-gnu.node +0 -0
  20. package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-arm64-msvc.node +0 -0
  21. package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
  22. package/node_modules/@nklisch/pi-clearance/package.json +9 -5
  23. package/node_modules/@nklisch/pi-clearance/src/config/config-command-plans.ts +79 -3
  24. package/node_modules/@nklisch/pi-clearance/src/config/config-command-writer.ts +7 -1
  25. package/node_modules/@nklisch/pi-clearance/src/config/defaults.ts +42 -0
  26. package/node_modules/@nklisch/pi-clearance/src/config/gated-tools.ts +7 -0
  27. package/node_modules/@nklisch/pi-clearance/src/config/loader.ts +7 -1
  28. package/node_modules/@nklisch/pi-clearance/src/config/pack-enablement-writer.ts +1 -0
  29. package/node_modules/@nklisch/pi-clearance/src/config/paths.ts +14 -3
  30. package/node_modules/@nklisch/pi-clearance/src/config/persistence.ts +135 -0
  31. package/node_modules/@nklisch/pi-clearance/src/config/schema.ts +74 -26
  32. package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCase.ts +1 -1
  33. package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseCategory.ts +1 -1
  34. package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseExpectation.ts +1 -1
  35. package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseResult.ts +1 -1
  36. package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseResultOutcome.ts +1 -1
  37. package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialCaseSource.ts +1 -1
  38. package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialValidationReport.ts +1 -1
  39. package/node_modules/@nklisch/pi-clearance/src/contracts/AdversarialValidationStatus.ts +1 -1
  40. package/node_modules/@nklisch/pi-clearance/src/contracts/BashBlock.ts +1 -1
  41. package/node_modules/@nklisch/pi-clearance/src/contracts/BashCommandShape.ts +1 -1
  42. package/node_modules/@nklisch/pi-clearance/src/contracts/BashConditionalArm.ts +1 -1
  43. package/node_modules/@nklisch/pi-clearance/src/contracts/BashControlConstruct.ts +1 -1
  44. package/node_modules/@nklisch/pi-clearance/src/contracts/BashFlag.ts +1 -1
  45. package/node_modules/@nklisch/pi-clearance/src/contracts/BashForLoopKeywordSpans.ts +1 -1
  46. package/node_modules/@nklisch/pi-clearance/src/contracts/BashIteratorEntry.ts +1 -1
  47. package/node_modules/@nklisch/pi-clearance/src/contracts/BashIteratorEntryKind.ts +1 -1
  48. package/node_modules/@nklisch/pi-clearance/src/contracts/BashListOperator.ts +1 -1
  49. package/node_modules/@nklisch/pi-clearance/src/contracts/BashLoopVariableReference.ts +1 -1
  50. package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFact.ts +1 -1
  51. package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFactContext.ts +1 -1
  52. package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFactProvenance.ts +1 -1
  53. package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFactProvenanceEntry.ts +1 -1
  54. package/node_modules/@nklisch/pi-clearance/src/contracts/BashPathFacts.ts +1 -1
  55. package/node_modules/@nklisch/pi-clearance/src/contracts/BashPipeline.ts +1 -1
  56. package/node_modules/@nklisch/pi-clearance/src/contracts/BashStage.ts +1 -1
  57. package/node_modules/@nklisch/pi-clearance/src/contracts/BashStageProgram.ts +1 -1
  58. package/node_modules/@nklisch/pi-clearance/src/contracts/BlockOperator.ts +1 -1
  59. package/node_modules/@nklisch/pi-clearance/src/contracts/CapturedOutcome.ts +1 -1
  60. package/node_modules/@nklisch/pi-clearance/src/contracts/CommandFamilyKey.ts +1 -1
  61. package/node_modules/@nklisch/pi-clearance/src/contracts/CommandFamilySummary.ts +1 -1
  62. package/node_modules/@nklisch/pi-clearance/src/contracts/CompileError.ts +1 -1
  63. package/node_modules/@nklisch/pi-clearance/src/contracts/CompositionOperator.ts +1 -1
  64. package/node_modules/@nklisch/pi-clearance/src/contracts/CompoundBodyReason.ts +1 -1
  65. package/node_modules/@nklisch/pi-clearance/src/contracts/CompoundFeatureReason.ts +1 -1
  66. package/node_modules/@nklisch/pi-clearance/src/contracts/CompoundForm.ts +1 -1
  67. package/node_modules/@nklisch/pi-clearance/src/contracts/CompoundIteratorReason.ts +1 -1
  68. package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusEntry.ts +1 -1
  69. package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusExpectedLabel.ts +1 -1
  70. package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusFidelity.ts +1 -1
  71. package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusQueryModel.ts +1 -1
  72. package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusQuerySummary.ts +1 -1
  73. package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusQuerySummarySnapshot.ts +1 -1
  74. package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusRecord.ts +1 -1
  75. package/node_modules/@nklisch/pi-clearance/src/contracts/CorpusSource.ts +1 -1
  76. package/node_modules/@nklisch/pi-clearance/src/contracts/CountByLabel.ts +1 -1
  77. package/node_modules/@nklisch/pi-clearance/src/contracts/Decision.ts +1 -1
  78. package/node_modules/@nklisch/pi-clearance/src/contracts/DecisionEffect.ts +1 -1
  79. package/node_modules/@nklisch/pi-clearance/src/contracts/DecisionProvenance.ts +1 -1
  80. package/node_modules/@nklisch/pi-clearance/src/contracts/DecisionSource.ts +1 -1
  81. package/node_modules/@nklisch/pi-clearance/src/contracts/DiagnosticSeverity.ts +1 -1
  82. package/node_modules/@nklisch/pi-clearance/src/contracts/EffectivePolicy.ts +1 -1
  83. package/node_modules/@nklisch/pi-clearance/src/contracts/EmbeddedShellProjection.ts +1 -1
  84. package/node_modules/@nklisch/pi-clearance/src/contracts/EnvironmentAssignment.ts +1 -1
  85. package/node_modules/@nklisch/pi-clearance/src/contracts/IteratorSourceKind.ts +1 -1
  86. package/node_modules/@nklisch/pi-clearance/src/contracts/LoopQuoteKind.ts +1 -1
  87. package/node_modules/@nklisch/pi-clearance/src/contracts/LoopVariableUnknownReason.ts +1 -1
  88. package/node_modules/@nklisch/pi-clearance/src/contracts/MatcherExpr.ts +1 -1
  89. package/node_modules/@nklisch/pi-clearance/src/contracts/MutationShapeKind.ts +1 -1
  90. package/node_modules/@nklisch/pi-clearance/src/contracts/MutationTrustBoundaryClassification.ts +1 -1
  91. package/node_modules/@nklisch/pi-clearance/src/contracts/MutationTrustBoundaryKind.ts +1 -1
  92. package/node_modules/@nklisch/pi-clearance/src/contracts/PackCompileResult.ts +1 -1
  93. package/node_modules/@nklisch/pi-clearance/src/contracts/PackWarningLevel.ts +1 -1
  94. package/node_modules/@nklisch/pi-clearance/src/contracts/ParsedEvidence.ts +1 -1
  95. package/node_modules/@nklisch/pi-clearance/src/contracts/ParsedShapeSummary.ts +1 -1
  96. package/node_modules/@nklisch/pi-clearance/src/contracts/PathAccess.ts +1 -1
  97. package/node_modules/@nklisch/pi-clearance/src/contracts/PathFactProjectScope.ts +1 -1
  98. package/node_modules/@nklisch/pi-clearance/src/contracts/PathFactsRequirement.ts +1 -1
  99. package/node_modules/@nklisch/pi-clearance/src/contracts/PathFactsResolvedConfig.ts +1 -1
  100. package/node_modules/@nklisch/pi-clearance/src/contracts/PathNormalization.ts +1 -1
  101. package/node_modules/@nklisch/pi-clearance/src/contracts/PathScope.ts +1 -1
  102. package/node_modules/@nklisch/pi-clearance/src/contracts/PathScopeMatcherExpr.ts +1 -1
  103. package/node_modules/@nklisch/pi-clearance/src/contracts/PathScopeMatcherMode.ts +1 -1
  104. package/node_modules/@nklisch/pi-clearance/src/contracts/PathUnknownReason.ts +1 -1
  105. package/node_modules/@nklisch/pi-clearance/src/contracts/PathUsageKind.ts +1 -1
  106. package/node_modules/@nklisch/pi-clearance/src/contracts/PiBuiltinToolOperation.ts +1 -1
  107. package/node_modules/@nklisch/pi-clearance/src/contracts/PiBuiltinToolPathInput.ts +1 -1
  108. package/node_modules/@nklisch/pi-clearance/src/contracts/PiBuiltinToolShape.ts +1 -1
  109. package/node_modules/@nklisch/pi-clearance/src/contracts/PiBuiltinToolSpec.ts +1 -1
  110. package/node_modules/@nklisch/pi-clearance/src/contracts/PiFileMutationToolName.ts +1 -1
  111. package/node_modules/@nklisch/pi-clearance/src/contracts/PiToolMutationFacts.ts +1 -1
  112. package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPack.ts +1 -1
  113. package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPackDocLink.ts +1 -1
  114. package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPackExample.ts +1 -1
  115. package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPackMetadata.ts +1 -1
  116. package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyPackWarning.ts +1 -1
  117. package/node_modules/@nklisch/pi-clearance/src/contracts/PolicyRule.ts +1 -1
  118. package/node_modules/@nklisch/pi-clearance/src/contracts/ProposalNotRunReason.ts +1 -1
  119. package/node_modules/@nklisch/pi-clearance/src/contracts/ProposalNotRunSeverity.ts +1 -1
  120. package/node_modules/@nklisch/pi-clearance/src/contracts/QuoteKind.ts +1 -1
  121. package/node_modules/@nklisch/pi-clearance/src/contracts/Redirect.ts +1 -1
  122. package/node_modules/@nklisch/pi-clearance/src/contracts/RedirectStream.ts +1 -1
  123. package/node_modules/@nklisch/pi-clearance/src/contracts/RedirectTargetKind.ts +1 -1
  124. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayCorpus.ts +1 -1
  125. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDelta.ts +1 -1
  126. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaBlockedSummary.ts +1 -1
  127. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaChangedRecord.ts +1 -1
  128. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaFamily.ts +1 -1
  129. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaImprovement.ts +1 -1
  130. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaRegression.ts +1 -1
  131. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaStatus.ts +1 -1
  132. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayDeltaTransitionCount.ts +1 -1
  133. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayPathFacts.ts +1 -1
  134. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayReviewerMode.ts +1 -1
  135. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayReviewerOutcome.ts +1 -1
  136. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayStatus.ts +1 -1
  137. package/node_modules/@nklisch/pi-clearance/src/contracts/ReplayedDecision.ts +1 -1
  138. package/node_modules/@nklisch/pi-clearance/src/contracts/ShapeDiagnostic.ts +1 -1
  139. package/node_modules/@nklisch/pi-clearance/src/contracts/SourceFidelity.ts +1 -1
  140. package/node_modules/@nklisch/pi-clearance/src/contracts/SourceSpan.ts +1 -1
  141. package/node_modules/@nklisch/pi-clearance/src/contracts/Substitution.ts +1 -1
  142. package/node_modules/@nklisch/pi-clearance/src/contracts/SubstitutionKind.ts +1 -1
  143. package/node_modules/@nklisch/pi-clearance/src/contracts/ToolCallIdentity.ts +1 -1
  144. package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathAccess.ts +1 -1
  145. package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathFact.ts +1 -1
  146. package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathFactContext.ts +1 -1
  147. package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathFacts.ts +1 -1
  148. package/node_modules/@nklisch/pi-clearance/src/contracts/ToolPathUsage.ts +1 -1
  149. package/node_modules/@nklisch/pi-clearance/src/contracts/ToolShape.ts +1 -1
  150. package/node_modules/@nklisch/pi-clearance/src/contracts/UnknownPathBehavior.ts +1 -1
  151. package/node_modules/@nklisch/pi-clearance/src/contracts/UnknownToolShape.ts +1 -1
  152. package/node_modules/@nklisch/pi-clearance/src/contracts/index.ts +1 -1
  153. package/node_modules/@nklisch/pi-clearance/src/index.ts +33 -2
  154. package/node_modules/@nklisch/pi-clearance/src/native/loader.ts +29 -42
  155. package/node_modules/@nklisch/pi-clearance/src/replay/reviewer-config-proposals.ts +7 -4
  156. package/node_modules/@nklisch/pi-clearance/src/runtime/allow-request-message.ts +21 -0
  157. package/node_modules/@nklisch/pi-clearance/src/runtime/auto-reviewer-read-models.ts +165 -8
  158. package/node_modules/@nklisch/pi-clearance/src/runtime/command-registry.ts +1 -1
  159. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/allow.ts +26 -2
  160. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/packs.ts +6 -6
  161. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/scope.ts +5 -5
  162. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/actions.ts +11 -0
  163. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/dispatcher.ts +274 -1
  164. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/model-options.ts +52 -0
  165. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/native-ui.ts +54 -67
  166. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/panels/reviewer.ts +19 -3
  167. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/panels/scope.ts +2 -11
  168. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/panels.ts +10 -1
  169. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings/read-model.ts +47 -13
  170. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/settings.ts +32 -37
  171. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/status.ts +3 -0
  172. package/node_modules/@nklisch/pi-clearance/src/runtime/config-commands/types.ts +14 -5
  173. package/node_modules/@nklisch/pi-clearance/src/runtime/handler.ts +36 -0
  174. package/node_modules/@nklisch/pi-clearance/src/runtime/mode-prompt.ts +0 -1
  175. package/node_modules/@nklisch/pi-clearance/src/runtime/model-adapter.ts +5 -1
  176. package/node_modules/@nklisch/pi-clearance/src/runtime/reviewer-context.ts +73 -10
  177. package/node_modules/@nklisch/pi-clearance/src/runtime/reviewer-prompts.ts +186 -175
  178. package/node_modules/@nklisch/pi-clearance/src/runtime/reviewer.ts +26 -6
  179. package/node_modules/@nklisch/pi-plugins/dist/infrastructure/state/local-lock-filesystem.d.ts +23 -1
  180. package/node_modules/@nklisch/pi-plugins/dist/infrastructure/state/local-lock-filesystem.js +46 -50
  181. package/node_modules/@nklisch/pi-plugins/dist/infrastructure/state/local-lock-filesystem.js.map +1 -1
  182. package/node_modules/@nklisch/pi-plugins/dist/runtime/mcp/pi-mcp-adapter-package.js +2 -2
  183. package/node_modules/@nklisch/pi-plugins/dist/runtime/mcp/pi-mcp-adapter-runtime.js +2 -2
  184. package/node_modules/@nklisch/pi-plugins/dist/runtime/subagents/pi-subagents-lifecycle.js +1 -1
  185. package/node_modules/@nklisch/pi-plugins/dist/runtime/subagents/pi-subagents-package.js +2 -2
  186. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +41 -31
  187. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +2 -1
  188. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +17 -11
  189. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +14 -7
  190. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +11 -19
  191. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +58 -53
  192. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +2 -2
  193. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/agent-type-resolution.ts +43 -0
  194. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/agent-types.ts +11 -6
  195. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/custom-agents.ts +13 -6
  196. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/default-agents.ts +0 -52
  197. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/thinking-levels.ts +12 -0
  198. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/handlers/interrupt.ts +5 -2
  199. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/handlers/lifecycle.ts +3 -3
  200. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +7 -6
  201. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +7 -2
  202. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/parent-snapshot.ts +11 -3
  203. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +26 -56
  204. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +48 -9
  205. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +32 -0
  206. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +105 -17
  207. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/composite-subagent-observer.ts +4 -0
  208. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +50 -46
  209. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +1 -1
  210. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +15 -0
  211. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/runtime.ts +2 -0
  212. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +35 -6
  213. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service.ts +1 -0
  214. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/model-label.ts +19 -0
  215. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/prompts.ts +24 -1
  216. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/session-config.ts +11 -6
  217. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/settings.ts +70 -3
  218. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +27 -10
  219. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +6 -2
  220. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +12 -3
  221. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +17 -3
  222. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +13 -20
  223. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +6 -1
  224. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +16 -16
  225. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +12 -4
  226. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +2 -2
  227. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +24 -11
  228. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +10 -7
  229. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/glyphs.ts +7 -0
  230. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +34 -25
  231. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +30 -10
  232. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/subagents-settings.ts +58 -13
  233. package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +32 -19
  234. package/node_modules/@nklisch/pi-plugins/package.json +3 -3
  235. package/package.json +1 -1
  236. package/test/verify-bundle.mjs +28 -1
@@ -27,29 +27,11 @@ import type { WorkspaceProvider } from "#src/lifecycle/workspace";
27
27
  import type { RunConfig } from "#src/runtime";
28
28
  import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SubagentType, ThinkingLevel } from "#src/types";
29
29
 
30
- /**
31
- * A lightweight snapshot of a subagent evicted by the 10-minute cleanup sweep.
32
- *
33
- * The sweep frees the heavy in-memory session (its message history included);
34
- * this descriptor retains only the fields the session navigator needs to label
35
- * the agent in the picker, plus the persisted `outputFile` to source its
36
- * transcript from disk. Carries no messages, so memory stays bounded.
37
- */
38
- export interface EvictedSubagent {
39
- readonly id: string;
40
- readonly type: SubagentType;
41
- readonly description: string;
42
- readonly status: SubagentStatus;
43
- readonly startedAt: number;
44
- readonly completedAt: number | undefined;
45
- readonly toolUses: number;
46
- readonly outputFile: string;
47
- }
48
-
49
30
  /** Observer interface for agent lifecycle notifications. */
50
31
  export interface SubagentManagerObserver {
51
32
  onSubagentStarted(record: Subagent): void;
52
33
  onSubagentCompleted(record: Subagent): void;
34
+ onSubagentResumed?(record: Subagent): void;
53
35
  onSubagentCompacted(record: Subagent, info: CompactionInfo): void;
54
36
  /** Fires synchronously after a background agent record is created (before run). */
55
37
  onSubagentCreated(record: Subagent): void;
@@ -95,8 +77,6 @@ export interface AgentSpawnConfig {
95
77
 
96
78
  export class SubagentManager {
97
79
  private agents = new Map<string, Subagent>();
98
- /** Descriptors of agents removed by the cleanup sweep, keyed by id — navigable from disk. */
99
- private readonly evicted = new Map<string, EvictedSubagent>();
100
80
  private cleanupInterval: ReturnType<typeof setInterval>;
101
81
  private readonly observer?: SubagentManagerObserver;
102
82
  private readonly createSubagentSession: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
@@ -117,7 +97,7 @@ export class SubagentManager {
117
97
  this.baseCwd = options.baseCwd;
118
98
  this.observer = options.observer;
119
99
  this.getRunConfig = options.getRunConfig;
120
- // Cleanup completed agents after 10 minutes (but keep sessions for resume)
100
+ // Periodically release heavy terminal sessions according to retention policy.
121
101
  this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);
122
102
  this.cleanupInterval.unref();
123
103
  }
@@ -160,6 +140,9 @@ export class SubagentManager {
160
140
  try { this.observer?.onSubagentCompleted(agent); } catch (err) { debugLog("onSubagentCompleted observer", err); }
161
141
  }
162
142
  },
143
+ onResumedFinished: (agent) => {
144
+ try { this.observer?.onSubagentResumed?.(agent); } catch (err) { debugLog("onSubagentResumed observer", err); }
145
+ },
163
146
  onCompacted: (agent, info) => {
164
147
  this.observer?.onSubagentCompacted(agent, info);
165
148
  },
@@ -271,19 +254,14 @@ export class SubagentManager {
271
254
  );
272
255
  }
273
256
 
274
- /** Descriptors of agents evicted by the cleanup sweep, most recent first. */
275
- listEvicted(): EvictedSubagent[] {
276
- return [...this.evicted.values()].sort((a, b) => b.startedAt - a.startedAt);
277
- }
278
-
279
257
  abort(id: string): boolean {
280
258
  const record = this.agents.get(id);
281
259
  if (!record) return false;
282
260
 
283
- // A queued agent has not started; mark it stopped. Its scheduled thunk
284
- // becomes a no-op (status guard) when its slot finally opens.
261
+ // A queued agent has not started; terminate it through the observer funnel.
262
+ // Its scheduled thunk becomes a no-op when its slot eventually opens.
285
263
  if (record.status === "queued") {
286
- record.markStopped();
264
+ record.stopQueued();
287
265
  return true;
288
266
  }
289
267
 
@@ -297,14 +275,22 @@ export class SubagentManager {
297
275
  }
298
276
 
299
277
  private cleanup() {
300
- const cutoff = Date.now() - 10 * 60_000;
301
- for (const [id, record] of this.agents) {
302
- if (record.status === "running" || record.status === "queued") continue;
303
- if ((record.completedAt ?? 0) >= cutoff) continue;
304
- // Retain a navigable descriptor before freeing the heavy session. Only an
305
- // agent with a persisted file can be sourced from disk after eviction.
306
- if (record.outputFile) this.evicted.set(id, toEvictedSubagent(record, record.outputFile));
307
- this.removeRecord(id, record);
278
+ const now = Date.now();
279
+ const config = this.getRunConfig?.();
280
+ const consumedMinutes = config?.consumedSessionRetentionMinutes ?? 10;
281
+ const unconsumedMinutes = config?.unconsumedSessionRetentionMinutes ?? 720;
282
+
283
+ for (const record of this.agents.values()) {
284
+ if (record.isActive() || !record.isSessionReady()) continue;
285
+ const anchor = record.consumed
286
+ ? record.consumedAt ?? record.completedAt
287
+ : record.completedAt;
288
+ if (anchor == null) continue;
289
+ const retentionMinutes = record.consumed ? consumedMinutes : unconsumedMinutes;
290
+ if (anchor + retentionMinutes * 60_000 > now) continue;
291
+ // Keep the lightweight terminal record and result for the whole parent
292
+ // session; only release the heavy in-memory child session.
293
+ record.releaseSession();
308
294
  }
309
295
  }
310
296
 
@@ -317,8 +303,7 @@ export class SubagentManager {
317
303
  if (record.status === "running" || record.status === "queued") continue;
318
304
  this.removeRecord(id, record);
319
305
  }
320
- // Evicted descriptors belong to the session that swept them — a new session starts empty.
321
- this.evicted.clear();
306
+
322
307
  }
323
308
 
324
309
  /** Whether any agents are still running or queued. */
@@ -335,7 +320,7 @@ export class SubagentManager {
335
320
  let count = 0;
336
321
  for (const record of this.agents.values()) {
337
322
  if (record.status === "queued") {
338
- record.markStopped();
323
+ record.stopQueued();
339
324
  count++;
340
325
  } else if (record.abort()) {
341
326
  count++;
@@ -378,20 +363,5 @@ export class SubagentManager {
378
363
  record.disposeSession();
379
364
  }
380
365
  this.agents.clear();
381
- this.evicted.clear();
382
366
  }
383
367
  }
384
-
385
- /** Capture an evicted agent's navigable fields from its record. */
386
- function toEvictedSubagent(record: Subagent, outputFile: string): EvictedSubagent {
387
- return {
388
- id: record.id,
389
- type: record.type,
390
- description: record.description,
391
- status: record.status,
392
- startedAt: record.startedAt,
393
- completedAt: record.completedAt,
394
- toolUses: record.toolUses,
395
- outputFile,
396
- };
397
- }
@@ -29,6 +29,8 @@ import type { SessionMessage } from "#src/types";
29
29
  /** Outcome of one turn loop. */
30
30
  export interface TurnLoopResult {
31
31
  responseText: string;
32
+ /** Normally-resolved provider failure from the final assistant turn. */
33
+ failure?: string;
32
34
  /** True if the agent was hard-aborted (max turns + grace exceeded). */
33
35
  aborted: boolean;
34
36
  /** True if the agent was steered to wrap up (soft turn limit) but finished in time. */
@@ -121,6 +123,7 @@ export class SubagentSession {
121
123
  }
122
124
  });
123
125
 
126
+ const startIndex = session.messages.length;
124
127
  const collector = collectResponseText(session);
125
128
  const cleanupAbort = forwardAbortSignal(session, opts.signal);
126
129
 
@@ -135,13 +138,19 @@ export class SubagentSession {
135
138
  // published immediately after prompt resolution and before extraction.
136
139
  await session.prompt(effectivePrompt);
137
140
  this.publishCompleted(aborted, softLimitReached);
138
- const responseText = collector.getText().trim() || getLastAssistantText(session);
139
- return { responseText, aborted, steered: softLimitReached };
141
+ const responseText = collector.getText().trim() || getLastAssistantText(session, startIndex);
142
+ return {
143
+ responseText,
144
+ failure: finalTurnError(session, startIndex),
145
+ aborted,
146
+ steered: softLimitReached,
147
+ };
140
148
  }
141
149
  return await this.driveLifecycleTurns(
142
150
  effectivePrompt,
143
151
  opts.lifecycle,
144
- () => collector.getText().trim() || getLastAssistantText(session),
152
+ () => collector.getText().trim() || getLastAssistantText(session, startIndex),
153
+ () => finalTurnError(session, startIndex),
145
154
  () => ({ aborted, steered: softLimitReached }),
146
155
  );
147
156
  } finally {
@@ -152,14 +161,21 @@ export class SubagentSession {
152
161
  }
153
162
 
154
163
  /** Re-prompt the same session (resume); preserves the released no-provider path. */
155
- async resumeTurnLoop(prompt: string, signal?: AbortSignal): Promise<string> {
164
+ async resumeTurnLoop(
165
+ prompt: string,
166
+ signal?: AbortSignal,
167
+ ): Promise<{ text: string; failure?: string }> {
156
168
  const session = this._session;
169
+ const startIndex = session.messages.length;
157
170
  const collector = collectResponseText(session);
158
171
  const cleanupAbort = forwardAbortSignal(session, signal);
159
172
 
160
173
  try {
161
174
  await session.prompt(prompt);
162
- return collector.getText().trim() || getLastAssistantText(session);
175
+ return {
176
+ text: collector.getText().trim() || getLastAssistantText(session, startIndex),
177
+ failure: finalTurnError(session, startIndex),
178
+ };
163
179
  } finally {
164
180
  collector.unsubscribe();
165
181
  cleanupAbort();
@@ -175,13 +191,15 @@ export class SubagentSession {
175
191
  signal: AbortSignal | undefined,
176
192
  lifecycle: SubagentTurnLifecycle,
177
193
  ): Promise<TurnLoopResult> {
194
+ const startIndex = this._session.messages.length;
178
195
  const collector = collectResponseText(this._session);
179
196
  const cleanupAbort = forwardAbortSignal(this._session, signal);
180
197
  try {
181
198
  return await this.driveLifecycleTurns(
182
199
  prompt,
183
200
  lifecycle,
184
- () => collector.getText().trim() || getLastAssistantText(this._session),
201
+ () => collector.getText().trim() || getLastAssistantText(this._session, startIndex),
202
+ () => finalTurnError(this._session, startIndex),
185
203
  () => ({ aborted: false, steered: false }),
186
204
  );
187
205
  } finally {
@@ -194,6 +212,7 @@ export class SubagentSession {
194
212
  initialPrompt: string,
195
213
  lifecycle: SubagentTurnLifecycle,
196
214
  responseText: () => string,
215
+ failure: () => string | undefined,
197
216
  outcome: () => Readonly<{ aborted: boolean; steered: boolean }>,
198
217
  ): Promise<TurnLoopResult> {
199
218
  const start = await lifecycle.beforeStart(initialPrompt);
@@ -208,6 +227,10 @@ export class SubagentSession {
208
227
  await this._session.prompt(prompt);
209
228
  const turnOutcome = outcome();
210
229
  const proposedResult = responseText();
230
+ const turnFailure = failure();
231
+ if (turnFailure) {
232
+ return { responseText: proposedResult, failure: turnFailure, ...turnOutcome };
233
+ }
211
234
  const completion = await lifecycle.beforeComplete(
212
235
  proposedResult,
213
236
  toLifecycleOutcome(turnOutcome),
@@ -327,7 +350,7 @@ function toLifecycleOutcome(
327
350
  function collectResponseText(session: AgentSession) {
328
351
  let text = "";
329
352
  const unsubscribe = session.subscribe((event: AgentSessionEvent) => {
330
- if (event.type === "message_start") {
353
+ if (event.type === "message_start" && event.message?.role === "assistant") {
331
354
  text = "";
332
355
  }
333
356
  if (
@@ -341,8 +364,8 @@ function collectResponseText(session: AgentSession) {
341
364
  }
342
365
 
343
366
  /** Get the last assistant text from the completed session history. */
344
- function getLastAssistantText(session: AgentSession): string {
345
- for (let i = session.messages.length - 1; i >= 0; i--) {
367
+ function getLastAssistantText(session: AgentSession, startIndex = 0): string {
368
+ for (let i = session.messages.length - 1; i >= startIndex; i--) {
346
369
  const msg = session.messages[i];
347
370
  if (msg.role !== "assistant") continue;
348
371
  const text = extractText(msg.content).trim();
@@ -351,6 +374,22 @@ function getLastAssistantText(session: AgentSession): string {
351
374
  return "";
352
375
  }
353
376
 
377
+ /** Classify a normally-resolved failure from this invocation's final assistant turn. */
378
+ function finalTurnError(session: AgentSession, startIndex = 0): string | undefined {
379
+ for (let i = session.messages.length - 1; i >= startIndex; i--) {
380
+ const msg = session.messages[i];
381
+ if (msg.role !== "assistant") continue;
382
+ if (msg.stopReason === "error") {
383
+ return msg.errorMessage?.trim() || "provider error with no output";
384
+ }
385
+ if (msg.stopReason === "length" && !extractText(msg.content).trim()) {
386
+ return "run hit the output token limit before producing any text";
387
+ }
388
+ return undefined;
389
+ }
390
+ return undefined;
391
+ }
392
+
354
393
  /**
355
394
  * Wire an AbortSignal to abort a session.
356
395
  * Returns a cleanup function to remove the listener.
@@ -34,8 +34,12 @@ export interface SubagentStateInit {
34
34
  status?: SubagentStatus;
35
35
  result?: string;
36
36
  error?: string;
37
+ /** Whether the limiter never admitted this agent. */
38
+ stoppedWhileQueued?: boolean;
37
39
  startedAt?: number;
38
40
  completedAt?: number;
41
+ /** Time the parent first collected the terminal outcome. */
42
+ consumedAt?: number;
39
43
  }
40
44
 
41
45
  export class SubagentState {
@@ -55,6 +59,13 @@ export class SubagentState {
55
59
  private _completedAt?: number;
56
60
  get completedAt(): number | undefined { return this._completedAt; }
57
61
 
62
+ private _stoppedWhileQueued: boolean;
63
+ get stoppedWhileQueued(): boolean { return this._stoppedWhileQueued; }
64
+
65
+ private _consumedAt?: number;
66
+ get consumedAt(): number | undefined { return this._consumedAt; }
67
+ get consumed(): boolean { return this._consumedAt != null; }
68
+
58
69
  // Stats — accumulated via mutation methods, readable via getters
59
70
  private _toolUses = 0;
60
71
  get toolUses(): number { return this._toolUses; }
@@ -81,8 +92,10 @@ export class SubagentState {
81
92
  this._status = init.status ?? "queued";
82
93
  this._result = init.result;
83
94
  this._error = init.error;
95
+ this._stoppedWhileQueued = init.stoppedWhileQueued ?? false;
84
96
  this._startedAt = init.startedAt ?? Date.now();
85
97
  this._completedAt = init.completedAt;
98
+ this._consumedAt = init.consumedAt;
86
99
  }
87
100
 
88
101
  /** Increment tool use count. Called by record-observer on tool_execution_end. */
@@ -177,19 +190,36 @@ export class SubagentState {
177
190
  * Always sets error (formatted) and completedAt (??=). Only changes status if not stopped.
178
191
  */
179
192
  markError(error: unknown, completedAt?: number): void {
193
+ this.markFailed(error, undefined, completedAt);
194
+ }
195
+
196
+ /** Record a failure together with any partial output produced before it. */
197
+ markFailed(error: unknown, partialResult?: string, completedAt?: number): void {
180
198
  this._error = error instanceof Error ? error.message : String(error);
199
+ this._result = partialResult?.trim() ? partialResult : undefined;
181
200
  this._completedAt ??= completedAt ?? Date.now();
182
201
  if (this._status !== "stopped") {
183
202
  this._status = "error";
184
203
  }
185
204
  }
186
205
 
206
+ /** Record that the parent collected the outcome. Idempotent. */
207
+ markConsumed(at?: number): void {
208
+ this._consumedAt ??= at ?? Date.now();
209
+ }
210
+
187
211
  /** Transition to stopped state. Always valid — no guard. */
188
212
  markStopped(completedAt?: number): void {
189
213
  this._status = "stopped";
190
214
  this._completedAt = completedAt ?? Date.now();
191
215
  }
192
216
 
217
+ /** Stop an agent before the limiter admitted it. */
218
+ stopQueued(completedAt?: number): void {
219
+ if (this._status === "queued") this._stoppedWhileQueued = true;
220
+ this.markStopped(completedAt);
221
+ }
222
+
193
223
  /** Reset for resume: running status, new startedAt, clear completedAt/result/error. */
194
224
  resetForResume(startedAt: number): void {
195
225
  this._status = "running";
@@ -197,5 +227,7 @@ export class SubagentState {
197
227
  this._completedAt = undefined;
198
228
  this._result = undefined;
199
229
  this._error = undefined;
230
+ this._consumedAt = undefined;
231
+ this._stoppedWhileQueued = false;
200
232
  }
201
233
  }
@@ -25,6 +25,7 @@ import type { WorkspaceProvider } from "#src/lifecycle/workspace";
25
25
  import { WorkspaceBracket } from "#src/lifecycle/workspace-bracket";
26
26
  import { subscribeSubagentObserver } from "#src/observation/record-observer";
27
27
  import type { RunConfig } from "#src/runtime";
28
+ import { formatModelLabel } from "#src/session/model-label";
28
29
  import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SessionMessage, SubagentType, ThinkingLevel } from "#src/types";
29
30
 
30
31
  /** Per-subagent lifecycle observer — created by SubagentManager for each spawn. */
@@ -33,8 +34,10 @@ export interface SubagentLifecycleObserver {
33
34
  onStarted?(agent: Subagent): void;
34
35
  /** Fires once the session is created — the subagent's subagentSession is now available. */
35
36
  onSessionCreated?(agent: Subagent): void;
36
- /** Fires once when the run completes or fails (for concurrency drain). */
37
+ /** Fires once when the initial run completes or fails (for concurrency drain). */
37
38
  onRunFinished?(agent: Subagent): void;
39
+ /** Fires once when a resumed turn reaches a terminal state. */
40
+ onResumedFinished?(agent: Subagent): void;
38
41
  /** Fires on compaction events during the run. */
39
42
  onCompacted?(agent: Subagent, info: CompactionInfo): void;
40
43
  }
@@ -112,6 +115,9 @@ export class Subagent {
112
115
  get error(): string | undefined { return this.state.error; }
113
116
  get startedAt(): number { return this.state.startedAt; }
114
117
  get completedAt(): number | undefined { return this.state.completedAt; }
118
+ get stoppedWhileQueued(): boolean { return this.state.stoppedWhileQueued; }
119
+ get consumedAt(): number | undefined { return this.state.consumedAt; }
120
+ get consumed(): boolean { return this.state.consumed; }
115
121
  get toolUses(): number { return this.state.toolUses; }
116
122
  get lifetimeUsage(): Readonly<LifetimeUsage> { return this.state.lifetimeUsage; }
117
123
  get compactionCount(): number { return this.state.compactionCount; }
@@ -119,8 +125,10 @@ export class Subagent {
119
125
  get activeTools(): ReadonlyMap<string, string> { return this.state.activeTools; }
120
126
  get responseText(): string { return this.state.responseText; }
121
127
  get maxTurns(): number | undefined { return this.execution.maxTurns; }
128
+ /** Exact effective model label used by every operator-facing status surface. */
129
+ get modelLabel(): string { return formatModelLabel(this.execution.model ?? this.execution.snapshot.model); }
122
130
 
123
- readonly abortController: AbortController;
131
+ abortController: AbortController;
124
132
  private _promise?: Promise<void>;
125
133
  get promise(): Promise<void> | undefined { return this._promise; }
126
134
 
@@ -129,6 +137,9 @@ export class Subagent {
129
137
  private readonly workspaceBracket: WorkspaceBracket;
130
138
 
131
139
  subagentSession?: SubagentSession;
140
+ private releasedOutputFile?: string;
141
+ private _sessionReleased = false;
142
+ get sessionReleased(): boolean { return this._sessionReleased; }
132
143
 
133
144
  // Steer buffer — messages queued before the session is ready
134
145
  private _pendingSteers: string[] = [];
@@ -137,7 +148,7 @@ export class Subagent {
137
148
 
138
149
  /** Path to the agent's session JSONL file, or undefined if not yet available. */
139
150
  get outputFile(): string | undefined {
140
- return this.subagentSession?.outputFile;
151
+ return this.subagentSession?.outputFile ?? this.releasedOutputFile;
141
152
  }
142
153
 
143
154
  /** The tool call ID that spawned this background agent, if any. */
@@ -150,6 +161,14 @@ export class Subagent {
150
161
  return this.subagentSession != null;
151
162
  }
152
163
 
164
+ isActive(): boolean {
165
+ return this.status === "queued" || this.status === "running";
166
+ }
167
+
168
+ isRunning(): boolean {
169
+ return this.status === "running";
170
+ }
171
+
153
172
  /**
154
173
  * Steer a running agent, owning the non-running rejection rule.
155
174
  * Returns a `rejected` outcome (with the observed status) when the agent is
@@ -329,21 +348,35 @@ export class Subagent {
329
348
  *
330
349
  * Requires an existing SubagentSession (set when the original run created it).
331
350
  * The returned promise always resolves (errors are captured internally).
332
- * The parent signal flows straight through to resumeTurnLoop resume does not
333
- * route through this.abortController.
351
+ * Parent cancellation and manager abort both stop the resumed turn.
334
352
  */
335
- async resume(prompt: string, signal?: AbortSignal): Promise<void> {
353
+ resume(prompt: string, signal?: AbortSignal): Promise<void> {
336
354
  const subagentSession = this.subagentSession;
337
355
  if (!subagentSession) {
338
- throw new Error("Subagent not configured for resume — missing session");
356
+ return Promise.reject(new Error(
357
+ this.sessionReleased
358
+ ? "Subagent session was released and can no longer be resumed"
359
+ : "Subagent not configured for resume — missing session",
360
+ ));
339
361
  }
362
+ this._promise = this.runResume(subagentSession, prompt, signal);
363
+ return this._promise;
364
+ }
340
365
 
366
+ private async runResume(
367
+ subagentSession: SubagentSession,
368
+ prompt: string,
369
+ signal?: AbortSignal,
370
+ ): Promise<void> {
341
371
  this.resetForResume(Date.now());
372
+ const executionSignal = signal
373
+ ? AbortSignal.any([this.abortController.signal, signal])
374
+ : this.abortController.signal;
342
375
  this.listeners.attachObserver(subscribeSubagentObserver(subagentSession, this.state, {
343
376
  onCompact: (info) => this.execution.observer?.onCompacted?.(this, info),
344
377
  }));
345
378
 
346
- const lifecycle = this.createTurnLifecycle("resume", signal);
379
+ const lifecycle = this.createTurnLifecycle("resume", executionSignal);
347
380
  try {
348
381
  if (lifecycle) {
349
382
  const result = await subagentSession.resumeLifecycleTurnLoop(
@@ -351,20 +384,31 @@ export class Subagent {
351
384
  lifecycle.signal,
352
385
  lifecycle,
353
386
  );
354
- if (result.aborted) this.markAborted(result.responseText);
387
+ if (result.failure) this.markFailed(result.failure, result.responseText);
388
+ else if (result.aborted) this.markAborted(result.responseText);
355
389
  else if (result.steered) this.markSteered(result.responseText);
356
390
  else this.markCompleted(result.responseText);
357
391
  } else {
358
- const responseText = await subagentSession.resumeTurnLoop(prompt, signal);
359
- this.markCompleted(responseText);
392
+ const resumed = await subagentSession.resumeTurnLoop(prompt, executionSignal);
393
+ const outcome = typeof resumed === "string" ? { text: resumed } : resumed;
394
+ if (outcome.failure) this.markFailed(outcome.failure, outcome.text);
395
+ else this.markCompleted(outcome.text);
360
396
  }
361
397
  } catch (err) {
362
398
  this.markError(err);
363
399
  } finally {
364
400
  this.listeners.release();
401
+ this.execution.observer?.onResumedFinished?.(this);
365
402
  }
366
403
  }
367
404
 
405
+ /** Wait for the current queued, running, or resumed execution without cancelling it. */
406
+ async waitUntilSettled(signal: AbortSignal): Promise<void> {
407
+ const run = this._promise;
408
+ if (!run || !this.isActive() || signal.aborted) return;
409
+ await settleOrAbort(run, signal);
410
+ }
411
+
368
412
  /** Build a callback-only lifecycle bridge after the immutable child session exists. */
369
413
  private createTurnLifecycle(
370
414
  phase: "initial" | "resume",
@@ -449,11 +493,26 @@ export class Subagent {
449
493
  this.state.markError(error, completedAt);
450
494
  }
451
495
 
496
+ /** Record a provider failure and preserve any partial output. */
497
+ markFailed(error: unknown, partialResult?: string, completedAt?: number): void {
498
+ this.state.markFailed(error, partialResult, completedAt);
499
+ }
500
+
452
501
  /** Transition to stopped state. Always valid — no guard. */
453
502
  markStopped(completedAt?: number): void {
454
503
  this.state.markStopped(completedAt);
455
504
  }
456
505
 
506
+ markConsumed(at?: number): void {
507
+ this.state.markConsumed(at);
508
+ }
509
+
510
+ /** Stop a queued agent through the same terminal observer funnel as a run. */
511
+ stopQueued(): void {
512
+ this.state.stopQueued();
513
+ this.execution.observer?.onRunFinished?.(this);
514
+ }
515
+
457
516
  /**
458
517
  * Abort a running agent: fire AbortController and transition to stopped.
459
518
  * Returns false if the agent is not running.
@@ -488,6 +547,7 @@ export class Subagent {
488
547
 
489
548
  /** Reset for resume: running status, new startedAt, clear completedAt/result/error/listeners. */
490
549
  resetForResume(startedAt: number): void {
550
+ this.abortController = new AbortController();
491
551
  this.state.resetForResume(startedAt);
492
552
  this.listeners.release();
493
553
  }
@@ -496,16 +556,19 @@ export class Subagent {
496
556
  completeRun(result: TurnLoopResult): void {
497
557
  this.listeners.release();
498
558
 
499
- const finalStatus: SubagentStatus = result.aborted
500
- ? "aborted"
501
- : result.steered
502
- ? "steered"
503
- : "completed";
559
+ const finalStatus: SubagentStatus = result.failure
560
+ ? "error"
561
+ : result.aborted
562
+ ? "aborted"
563
+ : result.steered
564
+ ? "steered"
565
+ : "completed";
504
566
  const finalResult =
505
567
  result.responseText +
506
568
  this.workspaceBracket.dispose({ status: finalStatus, description: this.description });
507
569
 
508
- if (result.aborted) this.markAborted(finalResult);
570
+ if (result.failure) this.markFailed(result.failure, finalResult);
571
+ else if (result.aborted) this.markAborted(finalResult);
509
572
  else if (result.steered) this.markSteered(finalResult);
510
573
  else this.markCompleted(finalResult);
511
574
 
@@ -517,6 +580,16 @@ export class Subagent {
517
580
  this.subagentSession?.dispose();
518
581
  }
519
582
 
583
+ /** Release heavy session state while preserving the transcript pointer and record. */
584
+ releaseSession(): void {
585
+ const session = this.subagentSession;
586
+ if (!session) return;
587
+ this.releasedOutputFile = session.outputFile;
588
+ session.dispose();
589
+ this.subagentSession = undefined;
590
+ this._sessionReleased = true;
591
+ }
592
+
520
593
  /** Fail a run: mark error, release listeners, best-effort workspace dispose, notify observer. */
521
594
  failRun(err: unknown): void {
522
595
  this.markError(err);
@@ -529,3 +602,18 @@ export class Subagent {
529
602
  this.execution.observer?.onRunFinished?.(this);
530
603
  }
531
604
  }
605
+
606
+ /** Resolve when either the run settles or the caller's wait is interrupted. */
607
+ async function settleOrAbort(run: Promise<void>, signal: AbortSignal): Promise<void> {
608
+ await new Promise<void>((resolve) => {
609
+ let done = false;
610
+ const finish = (): void => {
611
+ if (done) return;
612
+ done = true;
613
+ signal.removeEventListener("abort", finish);
614
+ resolve();
615
+ };
616
+ signal.addEventListener("abort", finish, { once: true });
617
+ void run.then(finish, finish);
618
+ });
619
+ }
@@ -33,6 +33,10 @@ export class CompositeSubagentObserver implements SubagentManagerObserver {
33
33
  this.dispatch((o) => o.onSubagentCompleted(record), "onSubagentCompleted");
34
34
  }
35
35
 
36
+ onSubagentResumed(record: Subagent): void {
37
+ this.dispatch((o) => o.onSubagentResumed?.(record), "onSubagentResumed");
38
+ }
39
+
36
40
  onSubagentCompacted(record: Subagent, info: CompactionInfo): void {
37
41
  this.dispatch((o) => o.onSubagentCompacted(record, info), "onSubagentCompacted");
38
42
  }