@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
@@ -6,6 +6,7 @@ import type { Subagent } from "#src/types";
6
6
  export interface NotificationDetails {
7
7
  id: string;
8
8
  description: string;
9
+ modelLabel: string;
9
10
  status: string;
10
11
  toolUses: number;
11
12
  turnCount: number;
@@ -19,9 +20,14 @@ export interface NotificationDetails {
19
20
 
20
21
  // ---- Pure helpers (exported for unit testing) ----
21
22
 
22
- /** Escape XML special characters to prevent injection in structured notifications. */
23
+ /** Escape XML text and attribute delimiters in structured notifications. */
23
24
  export function escapeXml(s: string): string {
24
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
25
+ return s
26
+ .replace(/&/g, "&amp;")
27
+ .replace(/</g, "&lt;")
28
+ .replace(/>/g, "&gt;")
29
+ .replace(/"/g, "&quot;")
30
+ .replace(/'/g, "&apos;");
25
31
  }
26
32
 
27
33
  /** Human-readable status label for agent completion. */
@@ -49,7 +55,9 @@ export function formatTaskNotification(record: Subagent, resultMaxLen: number):
49
55
  const ctxXml = contextPercent !== null ? `<context_percent>${Math.round(contextPercent)}</context_percent>` : "";
50
56
  const compactXml = record.compactionCount ? `<compactions>${record.compactionCount}</compactions>` : "";
51
57
 
52
- const resultPreview = record.result
58
+ const resultPreview = record.stoppedWhileQueued
59
+ ? "Agent was stopped while queued and never started. No work was performed."
60
+ : record.result
53
61
  ? record.result.length > resultMaxLen
54
62
  ? record.result.slice(0, resultMaxLen) + "\n...(truncated, use get_subagent_result for full output)"
55
63
  : record.result
@@ -63,6 +71,7 @@ export function formatTaskNotification(record: Subagent, resultMaxLen: number):
63
71
  toolCallId ? `<tool-use-id>${escapeXml(toolCallId)}</tool-use-id>` : null,
64
72
  outputFile ? `<output-file>${escapeXml(outputFile)}</output-file>` : null,
65
73
  `<status>${escapeXml(status)}</status>`,
74
+ `<model>${escapeXml(record.modelLabel)}</model>`,
66
75
  `<summary>Subagent "${escapeXml(record.description)}" ${record.status}</summary>`,
67
76
  `<result>${escapeXml(resultPreview)}</result>`,
68
77
  `<usage><total_tokens>${totalTokens}</total_tokens><tool_uses>${record.toolUses}</tool_uses>${ctxXml}${compactXml}<duration_ms>${durationMs}</duration_ms></usage>`,
@@ -82,6 +91,7 @@ export function buildNotificationDetails(
82
91
  return {
83
92
  id: record.id,
84
93
  description: record.description,
94
+ modelLabel: record.modelLabel,
85
95
  status: record.status,
86
96
  toolUses: record.toolUses,
87
97
  turnCount: record.turnCount,
@@ -90,7 +100,9 @@ export function buildNotificationDetails(
90
100
  durationMs: record.completedAt ? record.completedAt - record.startedAt : 0,
91
101
  outputFile: record.outputFile,
92
102
  error: record.error,
93
- resultPreview: record.result
103
+ resultPreview: record.stoppedWhileQueued
104
+ ? "Agent was stopped while queued and never started. No work was performed."
105
+ : record.result
94
106
  ? record.result.length > resultMaxLen
95
107
  ? record.result.slice(0, resultMaxLen) + "…"
96
108
  : record.result
@@ -127,17 +139,12 @@ export interface NotificationSystem {
127
139
  dispose: () => void;
128
140
  }
129
141
 
130
- /** Delivery-consumption operation: get-result-tool's dependency on NotificationManager. */
131
- export interface ResultDelivery {
132
- /** Record the parent consumed this agent's result: suppress its completion nudge. */
133
- consume: (id: string) => void;
134
- }
135
-
136
- const NUDGE_HOLD_MS = 200;
137
-
138
- export class NotificationManager implements NotificationSystem, ResultDelivery {
139
- private pendingNudges = new Map<string, ReturnType<typeof setTimeout>>();
140
- private consumed = new Set<string>();
142
+ export class NotificationManager implements NotificationSystem {
143
+ // Pi cannot recall a queued followUp. Hold records while the parent run is
144
+ // active so consumption can be rechecked at the actual delivery boundary.
145
+ private pendingNudges = new Map<string, Subagent>();
146
+ private parentRunActive = false;
147
+ private disposed = false;
141
148
 
142
149
  constructor(
143
150
  private sendMessage: (
@@ -146,51 +153,46 @@ export class NotificationManager implements NotificationSystem, ResultDelivery {
146
153
  ) => void,
147
154
  ) {}
148
155
 
149
- consume(id: string): void {
150
- this.consumed.add(id);
151
- this.cancelNudge(id);
152
- }
153
-
154
156
  sendCompletion(record: Subagent): void {
155
- if (this.consumed.has(record.id)) return;
156
- this.scheduleNudge(record.id, () => this.emitIndividualNudge(record));
157
+ if (this.disposed || record.consumed) return;
158
+ if (this.parentRunActive) {
159
+ this.pendingNudges.set(record.id, record);
160
+ return;
161
+ }
162
+ this.emitIndividualNudge(record);
157
163
  }
158
164
 
159
- dispose(): void {
160
- for (const timer of this.pendingNudges.values()) clearTimeout(timer);
161
- this.pendingNudges.clear();
162
- this.consumed.clear();
165
+ onParentAgentStart(): void {
166
+ if (!this.disposed) this.parentRunActive = true;
163
167
  }
164
168
 
165
- private cancelNudge(key: string): void {
166
- const timer = this.pendingNudges.get(key);
167
- if (timer != null) {
168
- clearTimeout(timer);
169
- this.pendingNudges.delete(key);
169
+ onParentAgentSettled(): void {
170
+ if (this.disposed) return;
171
+ this.parentRunActive = false;
172
+ const withheld = [...this.pendingNudges.values()];
173
+ this.pendingNudges.clear();
174
+ for (const record of withheld) {
175
+ try {
176
+ this.emitIndividualNudge(record);
177
+ } catch (err) {
178
+ debugLog("notification render", err);
179
+ }
170
180
  }
171
181
  }
172
182
 
173
- private scheduleNudge(key: string, send: () => void, delay = NUDGE_HOLD_MS): void {
174
- this.cancelNudge(key);
175
- this.pendingNudges.set(
176
- key,
177
- setTimeout(() => {
178
- this.pendingNudges.delete(key);
179
- try {
180
- send();
181
- } catch (err) {
182
- debugLog("notification render", err);
183
- }
184
- }, delay),
185
- );
183
+ dispose(): void {
184
+ this.disposed = true;
185
+ this.pendingNudges.clear();
186
186
  }
187
187
 
188
188
  private emitIndividualNudge(record: Subagent): void {
189
- if (this.consumed.has(record.id)) return;
189
+ if (this.disposed || record.consumed) return;
190
190
 
191
191
  const notification = formatTaskNotification(record, 500);
192
192
  const outputFile = record.outputFile;
193
- const footer = outputFile ? `\nFull transcript available at: ${outputFile}` : "";
193
+ const footer = !record.stoppedWhileQueued && outputFile
194
+ ? `\nFull transcript available at: ${outputFile}`
195
+ : "";
194
196
 
195
197
  this.sendMessage(
196
198
  {
@@ -201,5 +203,7 @@ export class NotificationManager implements NotificationSystem, ResultDelivery {
201
203
  },
202
204
  { deliverAs: "followUp", triggerTurn: true },
203
205
  );
206
+ // Push delivery collected the terminal outcome just as surely as a pull.
207
+ record.markConsumed();
204
208
  }
205
209
  }
@@ -39,7 +39,7 @@ export function createNotificationRenderer() {
39
39
  let line = `${icon} ${theme.bold(d.description)} ${theme.fg("dim", statusText)}`;
40
40
 
41
41
  // Line 2: stats
42
- const parts: string[] = [];
42
+ const parts: string[] = [d.modelLabel];
43
43
  if (d.turnCount > 0) parts.push(formatTurns(d.turnCount, d.maxTurns));
44
44
  if (d.toolUses > 0) parts.push(`${d.toolUses} tool use${d.toolUses === 1 ? "" : "s"}`);
45
45
  if (d.totalTokens > 0) parts.push(formatTokens(d.totalTokens));
@@ -68,6 +68,21 @@ export class SubagentEventsObserver implements SubagentManagerObserver {
68
68
  this.notifications.sendCompletion(record);
69
69
  }
70
70
 
71
+ onSubagentResumed(record: Subagent): void {
72
+ const eventData = buildEventData(record);
73
+ this.emit("subagents:resumed", eventData);
74
+ this.appendEntry("subagents:record", {
75
+ id: record.id,
76
+ type: record.type,
77
+ description: record.description,
78
+ status: record.status,
79
+ result: record.result,
80
+ error: record.error,
81
+ startedAt: record.startedAt,
82
+ completedAt: record.completedAt,
83
+ });
84
+ }
85
+
71
86
  onSubagentCompacted(record: Subagent, info: CompactionInfo): void {
72
87
  // Emit compacted event when agent's session compacts (preserves count on record).
73
88
  this.emit("subagents:compacted", {
@@ -18,6 +18,8 @@ import type { SessionContext } from "#src/types";
18
18
  export interface RunConfig {
19
19
  readonly defaultMaxTurns: number | undefined;
20
20
  readonly graceTurns: number;
21
+ readonly consumedSessionRetentionMinutes?: number;
22
+ readonly unconsumedSessionRetentionMinutes?: number;
21
23
  }
22
24
 
23
25
  /**
@@ -6,6 +6,8 @@
6
6
  */
7
7
 
8
8
  import type { Model } from "@earendil-works/pi-ai";
9
+ import { resolveDispatchAgentType } from "#src/config/agent-type-resolution";
10
+ import type { AgentTypeRegistry } from "#src/config/agent-types";
9
11
  import type {
10
12
  SubagentLifecycleInterceptor,
11
13
  SubagentLifecycleRegistration,
@@ -13,8 +15,10 @@ import type {
13
15
  import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
14
16
  import type { WorkspaceProvider } from "#src/lifecycle/workspace";
15
17
  import type { SpawnOptions, SubagentRecord, SubagentsService } from "#src/service/service";
18
+ import { formatModelLabel } from "#src/session/model-label";
16
19
  import type { ModelRegistry } from "#src/session/model-resolver";
17
- import type { SessionContext, Subagent } from "#src/types";
20
+ import { resolveDefaultModel } from "#src/session/session-config";
21
+ import type { AgentInvocation, SessionContext, Subagent } from "#src/types";
18
22
 
19
23
  /** Narrow interface for the SubagentManager — avoids coupling to the concrete class. */
20
24
  export interface SubagentManagerLike {
@@ -46,6 +50,8 @@ export class SubagentsServiceAdapter implements SubagentsService {
46
50
  private readonly manager: SubagentManagerLike,
47
51
  private readonly resolveModel: (input: string, registry: ModelRegistry) => Model<any> | string,
48
52
  private readonly runtime: ServiceRuntimeLike,
53
+ private readonly agentRegistry?: AgentTypeRegistry,
54
+ private readonly settings?: { readonly fallbackSubagent?: string | false },
49
55
  ) {}
50
56
 
51
57
  spawn(type: string, prompt: string, options?: SpawnOptions): string {
@@ -53,13 +59,28 @@ export class SubagentsServiceAdapter implements SubagentsService {
53
59
  throw new Error("No active session — cannot spawn agents outside a session.");
54
60
  }
55
61
 
56
- const model = this.resolveModelOption(options?.model);
62
+ this.agentRegistry?.reload();
63
+ const resolution = this.agentRegistry
64
+ ? resolveDispatchAgentType(type, this.agentRegistry, this.settings?.fallbackSubagent)
65
+ : { type, requestedType: type, fellBack: false };
66
+ if ("error" in resolution) throw new Error(resolution.error);
67
+ const resolvedType = resolution.type;
68
+
69
+ const model = options?.model
70
+ ? this.resolveModelOption(options.model)
71
+ : this.resolveTypeDefaultModel(resolvedType);
57
72
  const description = options?.description ?? prompt.slice(0, 80);
58
73
  const isBackground = !(options?.foreground ?? false);
74
+ const invocation: AgentInvocation = {
75
+ modelName: formatModelLabel(model),
76
+ maxTurns: options?.maxTurns,
77
+ inheritContext: options?.inheritContext,
78
+ runInBackground: isBackground,
79
+ };
59
80
 
60
81
  const snapshot = this.runtime.buildSnapshot(options?.inheritContext ?? false);
61
82
  const parent = this.runtime.getSessionInfo();
62
- return this.manager.spawn(snapshot, type, prompt, {
83
+ return this.manager.spawn(snapshot, resolvedType, prompt, {
63
84
  description,
64
85
  model,
65
86
  maxTurns: options?.maxTurns,
@@ -68,6 +89,7 @@ export class SubagentsServiceAdapter implements SubagentsService {
68
89
  bypassQueue: options?.bypassQueue,
69
90
  isBackground,
70
91
  origin: "service",
92
+ invocation,
71
93
  lifecycleParentSession: parent.parentSessionId
72
94
  ? {
73
95
  parentSessionFile: parent.parentSessionFile || undefined,
@@ -117,9 +139,8 @@ export class SubagentsServiceAdapter implements SubagentsService {
117
139
  return this.manager.registerLifecycleInterceptor(interceptor);
118
140
  }
119
141
 
120
- /** Resolve an optional model-string override against the current session's registry. */
121
- private resolveModelOption(modelInput: string | undefined): Model<any> | undefined {
122
- if (!modelInput) return undefined;
142
+ /** Resolve a model-string override against the current session's registry. */
143
+ private resolveModelOption(modelInput: string): Model<any> {
123
144
  const registry = this.runtime.currentCtx?.modelRegistry;
124
145
  if (!registry) {
125
146
  throw new Error("No model registry available.");
@@ -130,6 +151,14 @@ export class SubagentsServiceAdapter implements SubagentsService {
130
151
  }
131
152
  return resolved;
132
153
  }
154
+
155
+ /** Resolve the agent type's configured model with the same fallback used by session assembly. */
156
+ private resolveTypeDefaultModel(type: string): Model<any> {
157
+ const ctx = this.runtime.currentCtx!;
158
+ const configured = this.agentRegistry?.resolveAgentConfig(type).model;
159
+ if (!ctx.modelRegistry) return ctx.model as Model<any>;
160
+ return resolveDefaultModel(ctx.model, ctx.modelRegistry, configured) as Model<any>;
161
+ }
133
162
  }
134
163
 
135
164
  /**
@@ -125,6 +125,7 @@ export interface SubagentsService {
125
125
  export const SUBAGENT_EVENTS = {
126
126
  STARTED: "subagents:started",
127
127
  COMPLETED: "subagents:completed",
128
+ RESUMED: "subagents:resumed",
128
129
  FAILED: "subagents:failed",
129
130
  COMPACTED: "subagents:compacted",
130
131
  CREATED: "subagents:created",
@@ -0,0 +1,19 @@
1
+ import type { Model } from "@earendil-works/pi-ai";
2
+
3
+ /**
4
+ * Return the exact display identity for a resolved model.
5
+ *
6
+ * Pi identifies models by provider and id. The human-readable `name` is not
7
+ * unique across providers, so operator status surfaces use `provider/id`.
8
+ */
9
+ export function formatModelLabel(model: unknown): string {
10
+ if (isModelIdentity(model)) return `${model.provider}/${model.id}`;
11
+ return "unknown model";
12
+ }
13
+
14
+ function isModelIdentity(value: unknown): value is Pick<Model<any>, "provider" | "id"> {
15
+ if (typeof value !== "object" || value === null) return false;
16
+ const candidate = value as { provider?: unknown; id?: unknown };
17
+ return typeof candidate.provider === "string" && candidate.provider.length > 0
18
+ && typeof candidate.id === "string" && candidate.id.length > 0;
19
+ }
@@ -27,12 +27,14 @@ import type { AgentPromptConfig } from "#src/types";
27
27
  * The tag follows the cacheable parent prefix in both modes.
28
28
  *
29
29
  * @param parentSystemPrompt The parent agent's effective system prompt.
30
+ * @param parentCwd Parent cwd used to remove a contradictory Pi footer.
30
31
  */
31
32
  export function buildAgentPrompt(
32
33
  config: AgentPromptConfig,
33
34
  cwd: string,
34
35
  env: EnvInfo,
35
36
  parentSystemPrompt?: string,
37
+ parentCwd?: string,
36
38
  ): string {
37
39
  const activeAgentTag = `<active_agent name="${config.name}"/>\n\n`;
38
40
 
@@ -41,7 +43,9 @@ Working directory: ${cwd}
41
43
  ${env.isGitRepo ? `Git repository: yes\nBranch: ${env.branch}` : "Not a git repository"}
42
44
  Platform: ${env.platform}`;
43
45
 
44
- const identity = parentSystemPrompt ?? genericBase;
46
+ const identity = parentSystemPrompt
47
+ ? withoutContradictoryCwdFooter(parentSystemPrompt, parentCwd, cwd)
48
+ : genericBase;
45
49
 
46
50
  if (config.promptMode === "append") {
47
51
 
@@ -85,6 +89,25 @@ You are operating as a sub-agent invoked to handle a specific task.
85
89
  return identity + "\n\n" + activeAgentTag + envBlock + "\n\n" + config.systemPrompt;
86
90
  }
87
91
 
92
+ /**
93
+ * Pi appends an authoritative cwd line to the parent prompt. A workspace-backed
94
+ * child appends its own environment block, so retain that footer only when both
95
+ * sessions actually share a directory.
96
+ */
97
+ function withoutContradictoryCwdFooter(
98
+ prompt: string,
99
+ parentCwd: string | undefined,
100
+ childCwd: string,
101
+ ): string {
102
+ if (!parentCwd || toPromptPath(parentCwd) === toPromptPath(childCwd)) return prompt;
103
+ const footerLine = `Current working directory: ${toPromptPath(parentCwd)}`;
104
+ return prompt.split("\n").filter((line) => line !== footerLine).join("\n");
105
+ }
106
+
107
+ function toPromptPath(cwd: string): string {
108
+ return cwd.replaceAll("\\", "/");
109
+ }
110
+
88
111
  /** Fallback base prompt when parent system prompt is unavailable (both modes). */
89
112
  const genericBase = `# Role
90
113
  You are a general-purpose coding agent for complex, multi-step tasks.
@@ -10,7 +10,7 @@
10
10
  * before invoking this function, keeping the assembler synchronous.
11
11
  */
12
12
 
13
- import type { AgentConfigLookup } from "#src/config/agent-types";
13
+ import { type AgentConfigLookup, BUILTIN_TOOL_NAMES } from "#src/config/agent-types";
14
14
  import type { EnvInfo } from "#src/session/env";
15
15
  import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types";
16
16
 
@@ -30,6 +30,7 @@ export interface AssemblerIO {
30
30
  cwd: string,
31
31
  env: EnvInfo,
32
32
  parentPrompt?: string,
33
+ parentCwd?: string,
33
34
  ) => string;
34
35
  }
35
36
 
@@ -79,8 +80,8 @@ export interface SessionConfig {
79
80
  effectiveCwd: string;
80
81
  /** Fully-assembled system prompt string (ready for `systemPromptOverride`). */
81
82
  systemPrompt: string;
82
- /** Built-in tool name allowlist for this agent type. */
83
- toolNames: string[];
83
+ /** Built-in tools denied to this agent; extension tools remain inheritable. */
84
+ excludedBuiltinToolNames: string[];
84
85
  /**
85
86
  * Resolved model instance (undefined → use parent model as passed to SDK).
86
87
  * Opaque handle — the assembler passes it through without inspection.
@@ -102,7 +103,7 @@ export interface SessionConfig {
102
103
  * string that resolves against the registry AND is in the available set, return
103
104
  * that model instead.
104
105
  */
105
- function resolveDefaultModel(
106
+ export function resolveDefaultModel(
106
107
  parentModel: unknown,
107
108
  registry: AssemblerContext["modelRegistry"],
108
109
  configModel?: string,
@@ -155,7 +156,10 @@ export function assembleSessionConfig(
155
156
 
156
157
  const effectiveCwd = options.cwd ?? ctx.cwd;
157
158
 
158
- const toolNames = registry.getToolNamesForType(type);
159
+ const enabledBuiltinTools = new Set(registry.getToolNamesForType(type));
160
+ const excludedBuiltinToolNames = BUILTIN_TOOL_NAMES.filter(
161
+ (name) => !enabledBuiltinTools.has(name),
162
+ );
159
163
 
160
164
  // Build system prompt from the resolved agent config
161
165
  const systemPrompt = io.buildAgentPrompt(
@@ -163,6 +167,7 @@ export function assembleSessionConfig(
163
167
  effectiveCwd,
164
168
  env,
165
169
  ctx.parentSystemPrompt,
170
+ ctx.cwd,
166
171
  );
167
172
 
168
173
  // Model resolution: explicit option > config model string > parent model
@@ -179,7 +184,7 @@ export function assembleSessionConfig(
179
184
  return {
180
185
  effectiveCwd,
181
186
  systemPrompt,
182
- toolNames,
187
+ excludedBuiltinToolNames,
183
188
  model,
184
189
  thinkingLevel,
185
190
  agentMaxTurns,
@@ -1,6 +1,6 @@
1
1
  // Persistence for pi-subagents operational settings.
2
2
  // - Global: ~/.pi/agent/subagents.json (agentDir injected at construction) — manual defaults, never written here
3
- // - Project: <cwd>/.pi/subagents.json — written by /agents → Settings; overrides global on load
3
+ // - Project: <cwd>/.pi/subagents.json — written by /subagents:settings; overrides global on load
4
4
 
5
5
  import { mkdirSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join } from "node:path";
@@ -14,6 +14,11 @@ export interface SubagentsSettings {
14
14
  */
15
15
  defaultMaxTurns?: number;
16
16
  graceTurns?: number;
17
+ consumedSessionRetentionMinutes?: number;
18
+ unconsumedSessionRetentionMinutes?: number;
19
+ abortAllOnInterrupt?: boolean;
20
+ /** false or "none" fails closed; an agent name selects an explicit fallback. */
21
+ fallbackSubagent?: string | false;
17
22
  }
18
23
 
19
24
 
@@ -22,15 +27,21 @@ export type SettingsEmit = (event: string, payload: unknown) => void;
22
27
 
23
28
  const DEFAULT_MAX_CONCURRENT = 4;
24
29
  const DEFAULT_GRACE_TURNS = 5;
30
+ const DEFAULT_CONSUMED_RETENTION_MINUTES = 10;
31
+ const DEFAULT_UNCONSUMED_RETENTION_MINUTES = 720;
25
32
 
26
33
  /**
27
- * Owns all three in-memory settings values and their load/save/persist cycle.
34
+ * Owns runtime settings and their load/save/persist cycle.
28
35
  * Replaces the scattered free-function + SettingsAppliers callback pattern.
29
36
  */
30
37
  export class SettingsManager {
31
38
  private _defaultMaxTurns: number | undefined = undefined;
32
39
  private _graceTurns: number = DEFAULT_GRACE_TURNS;
33
40
  private _maxConcurrent: number = DEFAULT_MAX_CONCURRENT;
41
+ private _consumedSessionRetentionMinutes = DEFAULT_CONSUMED_RETENTION_MINUTES;
42
+ private _unconsumedSessionRetentionMinutes = DEFAULT_UNCONSUMED_RETENTION_MINUTES;
43
+ private _abortAllOnInterrupt = true;
44
+ private _fallbackSubagent: string | false | undefined;
34
45
 
35
46
  private readonly emit: SettingsEmit;
36
47
  private readonly cwd: string;
@@ -78,6 +89,22 @@ export class SettingsManager {
78
89
  this._maxConcurrent = Math.max(1, n);
79
90
  }
80
91
 
92
+ get consumedSessionRetentionMinutes(): number { return this._consumedSessionRetentionMinutes; }
93
+ set consumedSessionRetentionMinutes(n: number) { this._consumedSessionRetentionMinutes = Math.max(0, n); }
94
+
95
+ get unconsumedSessionRetentionMinutes(): number { return this._unconsumedSessionRetentionMinutes; }
96
+ set unconsumedSessionRetentionMinutes(n: number) { this._unconsumedSessionRetentionMinutes = Math.max(0, n); }
97
+
98
+ get abortAllOnInterrupt(): boolean { return this._abortAllOnInterrupt; }
99
+ set abortAllOnInterrupt(value: boolean) { this._abortAllOnInterrupt = value; }
100
+
101
+ get fallbackSubagent(): string | false | undefined { return this._fallbackSubagent; }
102
+ set fallbackSubagent(value: string | false | undefined) {
103
+ this._fallbackSubagent = value === false || value === "none"
104
+ ? false
105
+ : typeof value === "string" && value.trim() ? value.trim() : undefined;
106
+ }
107
+
81
108
  // ── Lifecycle methods ──
82
109
 
83
110
  /**
@@ -90,6 +117,10 @@ export class SettingsManager {
90
117
  if (typeof settings.maxConcurrent === "number") this.maxConcurrent = settings.maxConcurrent;
91
118
  if (typeof settings.defaultMaxTurns === "number") this.defaultMaxTurns = settings.defaultMaxTurns;
92
119
  if (typeof settings.graceTurns === "number") this.graceTurns = settings.graceTurns;
120
+ if (typeof settings.consumedSessionRetentionMinutes === "number") this.consumedSessionRetentionMinutes = settings.consumedSessionRetentionMinutes;
121
+ if (typeof settings.unconsumedSessionRetentionMinutes === "number") this.unconsumedSessionRetentionMinutes = settings.unconsumedSessionRetentionMinutes;
122
+ if (typeof settings.abortAllOnInterrupt === "boolean") this.abortAllOnInterrupt = settings.abortAllOnInterrupt;
123
+ if (settings.fallbackSubagent !== undefined) this.fallbackSubagent = settings.fallbackSubagent;
93
124
  this.emit("subagents:settings_loaded", { settings });
94
125
  return settings;
95
126
  }
@@ -98,11 +129,15 @@ export class SettingsManager {
98
129
  * Snapshot current in-memory values for persistence.
99
130
  * `defaultMaxTurns` uses 0 as the on-disk marker for unlimited (undefined).
100
131
  */
101
- snapshot(): { maxConcurrent: number; defaultMaxTurns: number; graceTurns: number } {
132
+ snapshot(): Required<SubagentsSettings> {
102
133
  return {
103
134
  maxConcurrent: this._maxConcurrent,
104
135
  defaultMaxTurns: this._defaultMaxTurns ?? 0,
105
136
  graceTurns: this._graceTurns,
137
+ consumedSessionRetentionMinutes: this._consumedSessionRetentionMinutes,
138
+ unconsumedSessionRetentionMinutes: this._unconsumedSessionRetentionMinutes,
139
+ abortAllOnInterrupt: this._abortAllOnInterrupt,
140
+ fallbackSubagent: this._fallbackSubagent ?? "general-purpose",
106
141
  };
107
142
  }
108
143
 
@@ -134,6 +169,27 @@ export class SettingsManager {
134
169
  return this.saveAndNotify(`Grace turns set to ${this.graceTurns}`);
135
170
  }
136
171
 
172
+ applyConsumedSessionRetention(n: number): { message: string; level: "info" | "warning" } {
173
+ this.consumedSessionRetentionMinutes = n;
174
+ return this.saveAndNotify(`Consumed session retention set to ${this.consumedSessionRetentionMinutes} minutes`);
175
+ }
176
+
177
+ applyUnconsumedSessionRetention(n: number): { message: string; level: "info" | "warning" } {
178
+ this.unconsumedSessionRetentionMinutes = n;
179
+ return this.saveAndNotify(`Unconsumed session retention set to ${this.unconsumedSessionRetentionMinutes} minutes`);
180
+ }
181
+
182
+ applyAbortAllOnInterrupt(value: boolean): { message: string; level: "info" | "warning" } {
183
+ this.abortAllOnInterrupt = value;
184
+ return this.saveAndNotify(`Abort all subagents on ESC ${value ? "enabled" : "disabled"}`);
185
+ }
186
+
187
+ applyFallbackSubagent(value: string | false | undefined): { message: string; level: "info" | "warning" } {
188
+ this.fallbackSubagent = value;
189
+ const label = this.fallbackSubagent === false ? "none (fail closed)" : this.fallbackSubagent ?? "general-purpose";
190
+ return this.saveAndNotify(`Unknown agent fallback set to ${label}`);
191
+ }
192
+
137
193
  /**
138
194
  * Persist the current snapshot, emit `subagents:settings_changed`,
139
195
  * and return the toast the UI should display.
@@ -152,6 +208,7 @@ export class SettingsManager {
152
208
  const MAX_CONCURRENT_CEILING = 1024;
153
209
  const MAX_TURNS_CEILING = 10_000;
154
210
  const GRACE_TURNS_CEILING = 1_000;
211
+ const RETENTION_MINUTES_CEILING = 60 * 24 * 365;
155
212
 
156
213
  /** Drop fields that don't match the expected shape. Silent — garbage becomes absent. */
157
214
  function sanitize(raw: unknown): SubagentsSettings {
@@ -179,6 +236,16 @@ function sanitize(raw: unknown): SubagentsSettings {
179
236
  ) {
180
237
  out.graceTurns = r.graceTurns as number;
181
238
  }
239
+ for (const key of ["consumedSessionRetentionMinutes", "unconsumedSessionRetentionMinutes"] as const) {
240
+ if (Number.isInteger(r[key]) && (r[key] as number) >= 0 && (r[key] as number) <= RETENTION_MINUTES_CEILING) {
241
+ out[key] = r[key] as number;
242
+ }
243
+ }
244
+ if (typeof r.abortAllOnInterrupt === "boolean") out.abortAllOnInterrupt = r.abortAllOnInterrupt;
245
+ if (r.fallbackSubagent === false || r.fallbackSubagent === "none") out.fallbackSubagent = false;
246
+ else if (typeof r.fallbackSubagent === "string" && r.fallbackSubagent.trim()) {
247
+ out.fallbackSubagent = r.fallbackSubagent.trim();
248
+ }
182
249
  return out;
183
250
  }
184
251