gentle-pi 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/README.md +340 -138
  2. package/assets/agents/gentle-ai-explore.md +1 -0
  3. package/assets/agents/gentle-ai-verify.md +6 -4
  4. package/assets/agents/gentle-ai-worker.md +7 -1
  5. package/assets/design-canvas/canvas.css +648 -0
  6. package/assets/design-canvas/canvas.js +1497 -0
  7. package/assets/design-canvas/index.html +88 -0
  8. package/assets/design-canvas/inspector.js +290 -0
  9. package/assets/orchestrator-delegation-reference.md +163 -0
  10. package/assets/orchestrator-delegation.md +25 -176
  11. package/assets/orchestrator-tracking.md +1 -1
  12. package/assets/orchestrator-verification.md +14 -8
  13. package/assets/orchestrator-writer.md +5 -1
  14. package/assets/orchestrator.md +11 -11
  15. package/bin/engram-guard +1 -1
  16. package/bin/gentle-shell.mjs +237 -139
  17. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  18. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
  19. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
  20. package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
  21. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
  22. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
  23. package/docs/assets/diagrams/agent-orchestration.svg +54 -15
  24. package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
  25. package/docs/assets/diagrams/measured-routing.svg +49 -0
  26. package/docs/assets/diagrams/native-review.svg +52 -12
  27. package/docs/assets/diagrams/odd-workflow.svg +128 -66
  28. package/docs/assets/diagrams/permission-modes.svg +51 -0
  29. package/docs/assets/features/audio-notifications.png +0 -0
  30. package/docs/assets/features/command-palette.png +0 -0
  31. package/docs/bundled-install.md +65 -30
  32. package/docs/delegated-verification.md +16 -8
  33. package/docs/gentle-agents-activity.md +16 -6
  34. package/docs/gentle-music.md +61 -0
  35. package/docs/gentle-shell-uninstall.md +8 -1
  36. package/docs/gentle-shell.md +80 -9
  37. package/docs/install-wizard.md +37 -6
  38. package/docs/permission-modes.md +8 -3
  39. package/docs/readme-reference.md +120 -28
  40. package/docs/session-profile-format.md +26 -0
  41. package/docs/voice.md +259 -0
  42. package/docs/yolo-mode.md +38 -10
  43. package/extensions/ask-user-choice.ts +4 -1
  44. package/extensions/codegraph-tools.ts +68 -6
  45. package/extensions/gentle-agents.ts +270 -48
  46. package/extensions/gentle-ai.ts +490 -113
  47. package/extensions/gentle-design.ts +158 -0
  48. package/extensions/gentle-herdr-activity.ts +2 -3
  49. package/extensions/gentle-music.ts +289 -0
  50. package/extensions/gentle-shell.ts +359 -85
  51. package/extensions/gentle-todo.ts +43 -9
  52. package/extensions/gentle-voice.ts +11 -0
  53. package/extensions/quiet-tools.ts +1 -1
  54. package/extensions/session-scratch.ts +384 -0
  55. package/extensions/skill-registry.ts +63 -8
  56. package/extensions/startup-banner.ts +2 -3
  57. package/lib/agent-home.ts +8 -3
  58. package/lib/agent-profiles.ts +26 -0
  59. package/lib/agents-config.ts +35 -0
  60. package/lib/agents-protocol.ts +60 -1
  61. package/lib/agents-runner.ts +107 -9
  62. package/lib/agents-session-transport.ts +101 -6
  63. package/lib/agents-view.ts +3 -1
  64. package/lib/agents-widget.ts +14 -1
  65. package/lib/auto-permission-memory.ts +48 -18
  66. package/lib/auto-permission-runtime.ts +21 -6
  67. package/lib/bounded-writer-admission.ts +51 -0
  68. package/lib/clipboard-image.ts +147 -0
  69. package/lib/command-palette-catalog.ts +2 -4
  70. package/lib/commit-slice-signal.ts +100 -0
  71. package/lib/context-backstop.ts +120 -0
  72. package/lib/delivered-test-lint.ts +80 -0
  73. package/lib/design-canvas-server.ts +947 -0
  74. package/lib/design-comments.ts +437 -0
  75. package/lib/design-handoffs.ts +313 -0
  76. package/lib/design-open-browser.ts +58 -0
  77. package/lib/design-sources.ts +120 -0
  78. package/lib/destructive-command-guard.ts +120 -9
  79. package/lib/external-editor.ts +31 -0
  80. package/lib/gauge-theme.ts +146 -0
  81. package/lib/gentle-ai-binary.ts +28 -7
  82. package/lib/gentle-design.ts +513 -0
  83. package/lib/gentle-shell-bundled.ts +146 -36
  84. package/lib/gentle-shell-import-pi.ts +233 -0
  85. package/lib/gentle-shell-launcher.ts +154 -15
  86. package/lib/gentle-shell-startup-update.ts +61 -9
  87. package/lib/gentle-shell-uninstall.ts +51 -2
  88. package/lib/herdr-activity.ts +24 -0
  89. package/lib/image-attachments.ts +150 -0
  90. package/lib/inprocess-reviewer.ts +33 -2
  91. package/lib/nan-provider.ts +3 -0
  92. package/lib/native-review-cli.ts +20 -5
  93. package/lib/notification-audio-native.ts +149 -13
  94. package/lib/notification-audio.ts +4 -0
  95. package/lib/notification-pulse-worker.ts +82 -18
  96. package/lib/notification-service.ts +5 -1
  97. package/lib/orchestrator-discovery.ts +5 -1
  98. package/lib/orchestrator-presence.ts +87 -8
  99. package/lib/orchestrator-scope.ts +31 -3
  100. package/lib/orchestrator-state.ts +11 -3
  101. package/lib/permission-mode-policy.ts +2 -2
  102. package/lib/provider-contract-bundle.ts +99 -53
  103. package/lib/review-candidate-view-owner.ts +29 -19
  104. package/lib/review-candidate-view.ts +36 -2
  105. package/lib/review-host-relay.ts +11 -2
  106. package/lib/review-reminder-receipt.ts +24 -1
  107. package/lib/review-risk-assessment.ts +43 -38
  108. package/lib/review-session-standing-permission.ts +3 -1
  109. package/lib/session-profile-append-controller.ts +713 -0
  110. package/lib/session-profile-disk-reader.ts +182 -0
  111. package/lib/session-profile-freeze.ts +30 -1
  112. package/lib/session-profile-persistence.ts +15 -7
  113. package/lib/session-scratch.ts +523 -0
  114. package/lib/session-worktree-registry.ts +7 -0
  115. package/lib/shell-bar.ts +29 -28
  116. package/lib/shell-changes-view.ts +80 -13
  117. package/lib/shell-changes.ts +81 -5
  118. package/lib/shell-gauge.ts +49 -6
  119. package/lib/shell-prompt.ts +4 -4
  120. package/lib/shell-sidebar-layout.ts +26 -6
  121. package/lib/shell-todo.ts +24 -5
  122. package/lib/shell-usage-view.ts +5 -1
  123. package/lib/shell-usage.ts +7 -7
  124. package/lib/verifier-admission.ts +30 -0
  125. package/lib/vim-editor-adapter.ts +11 -8
  126. package/lib/voice-artifacts.ts +188 -0
  127. package/lib/voice-command.ts +576 -0
  128. package/lib/voice-install.ts +455 -0
  129. package/lib/voice-platform.ts +99 -0
  130. package/lib/voice-push-to-talk.ts +262 -0
  131. package/lib/voice-session.ts +460 -0
  132. package/lib/voice-settings.ts +50 -0
  133. package/lib/voice-ui.ts +158 -0
  134. package/lib/yolo-session-policy.ts +58 -22
  135. package/lib/yolo-temp-scratch.ts +69 -0
  136. package/package.json +1 -2
  137. package/runtime/gentle-ai-binary.mjs +28 -7
  138. package/runtime/gentle-shell-bundled.mjs +145 -35
  139. package/runtime/gentle-shell-import-pi.mjs +234 -0
  140. package/runtime/gentle-shell-launcher.mjs +152 -13
  141. package/runtime/gentle-shell-startup-update.mjs +61 -9
  142. package/runtime/gentle-shell-uninstall.mjs +51 -2
  143. package/runtime/native-review-cli.mjs +19 -4
  144. package/runtime/review-risk-assessment.mjs +43 -38
  145. package/scripts/build-runtime-modules.mjs +1 -0
  146. package/scripts/bundled-engram-migration.mjs +119 -0
  147. package/scripts/gentle-ai-installer.mjs +10 -10
  148. package/scripts/installer-downloads.mjs +7 -7
  149. package/scripts/installer-preflight.mjs +5 -3
  150. package/scripts/installer-server.mjs +14 -4
  151. package/scripts/installer-windows.mjs +37 -7
  152. package/scripts/main-channel.mjs +94 -19
  153. package/scripts/pi-update.mjs +34 -6
  154. package/scripts/run-test-suite.mjs +5 -0
  155. package/scripts/verify-package-files.mjs +43 -13
  156. package/scripts/voice-worker.cjs +476 -0
  157. package/skills/design/SKILL.md +110 -0
  158. package/skills/gentle-ai/SKILL.md +3 -3
  159. package/skills/work-unit-commits/SKILL.md +5 -3
  160. package/tests/agent-profiles.test.ts +24 -0
  161. package/tests/agent-risk-escalation.test.ts +37 -10
  162. package/tests/agents-config.test.ts +61 -0
  163. package/tests/agents-fake-child.ts +8 -1
  164. package/tests/agents-protocol.test.ts +32 -0
  165. package/tests/agents-runner.test.ts +485 -7
  166. package/tests/agents-session-transport.test.ts +53 -3
  167. package/tests/ask-user-choice.test.ts +30 -0
  168. package/tests/auto-mode.test.ts +17 -0
  169. package/tests/auto-permission-memory-diagnostics.test.ts +56 -0
  170. package/tests/auto-permission-runtime.test.ts +85 -1
  171. package/tests/autonomous-guard.test.ts +142 -0
  172. package/tests/bounded-writer-admission.test.ts +11 -3
  173. package/tests/bundled-install.test.ts +3 -3
  174. package/tests/card-content-policy.test.ts +19 -9
  175. package/tests/child-safety.test.ts +39 -0
  176. package/tests/clipboard-image.test.ts +114 -0
  177. package/tests/codegraph-tools.test.ts +123 -2
  178. package/tests/command-palette.test.ts +15 -5
  179. package/tests/commit-slice-signal.test.ts +162 -0
  180. package/tests/context-backstop.test.ts +225 -0
  181. package/tests/delegate-for-reason-contract.test.ts +92 -24
  182. package/tests/delivered-test-lint.test.ts +105 -0
  183. package/tests/design-canvas-assets.test.ts +710 -0
  184. package/tests/design-canvas-server.test.ts +1096 -0
  185. package/tests/design-comments.test.ts +201 -0
  186. package/tests/design-handoffs.test.ts +282 -0
  187. package/tests/design-open-browser.test.ts +70 -0
  188. package/tests/design-sources.test.ts +132 -0
  189. package/tests/destructive-command-guard.test.ts +85 -1
  190. package/tests/engram-safety.test.ts +132 -57
  191. package/tests/external-editor.test.ts +84 -0
  192. package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
  193. package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
  194. package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
  195. package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
  196. package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
  197. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
  198. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
  199. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
  200. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
  201. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
  202. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
  203. package/tests/gauge-theme.test.ts +120 -0
  204. package/tests/gentle-agents.test.ts +645 -21
  205. package/tests/gentle-ai-binary.test.ts +1 -1
  206. package/tests/gentle-ai-child-guards.test.ts +61 -0
  207. package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
  208. package/tests/gentle-ai-installer.test.ts +51 -51
  209. package/tests/gentle-ai.test.ts +292 -13
  210. package/tests/gentle-design.test.ts +1313 -0
  211. package/tests/gentle-music.test.ts +335 -0
  212. package/tests/gentle-shell-bin.test.ts +163 -0
  213. package/tests/gentle-shell-bundled.test.ts +392 -176
  214. package/tests/gentle-shell-import-pi.test.ts +262 -0
  215. package/tests/gentle-shell-launcher.test.ts +107 -2
  216. package/tests/gentle-shell-startup-update.test.ts +77 -1
  217. package/tests/gentle-shell-uninstall.test.ts +89 -0
  218. package/tests/gentle-shell.test.ts +225 -17
  219. package/tests/gentle-todo.test.ts +190 -0
  220. package/tests/headless-delivery-question.test.ts +58 -0
  221. package/tests/herdr-activity.test.ts +34 -1
  222. package/tests/image-attachments.test.ts +119 -0
  223. package/tests/image-paste-chips.test.ts +236 -0
  224. package/tests/inprocess-reviewer.test.ts +55 -2
  225. package/tests/installer-bundles.test.ts +12 -6
  226. package/tests/installer-posix-bootstrap.test.ts +8 -8
  227. package/tests/installer-preflight.test.ts +16 -0
  228. package/tests/installer-server.test.ts +10 -2
  229. package/tests/installer-windows-bootstrap.test.ts +94 -7
  230. package/tests/installer-windows-diagnostics.test.ts +111 -0
  231. package/tests/main-channel-upgrade.test.ts +232 -2
  232. package/tests/main-channel.test.ts +140 -21
  233. package/tests/nan-provider.test.ts +36 -0
  234. package/tests/native-review-capability-contract.test.ts +17 -4
  235. package/tests/native-review-parity.test.ts +23 -2
  236. package/tests/no-self-pause-contract.test.ts +5 -2
  237. package/tests/notification-audio-native.test.ts +128 -9
  238. package/tests/notification-customize.test.ts +17 -5
  239. package/tests/odd-integration.test.ts +5 -1
  240. package/tests/odd-routing-canonical-ratchet.test.ts +128 -162
  241. package/tests/odd-routing-contract.test.ts +115 -30
  242. package/tests/orchestrator-budget.test.ts +22 -8
  243. package/tests/orchestrator-discovery.test.ts +29 -0
  244. package/tests/orchestrator-presence.test.ts +100 -2
  245. package/tests/orchestrator-rdd-ownership.test.ts +3 -1
  246. package/tests/orchestrator-scope.test.ts +80 -0
  247. package/tests/orchestrator-state.test.ts +29 -0
  248. package/tests/orchestrator-status-publication-contract.test.ts +12 -8
  249. package/tests/package-files.test.ts +34 -0
  250. package/tests/package-manifest.test.ts +24 -20
  251. package/tests/permission-mode-policy.test.ts +10 -1
  252. package/tests/pi-update-launcher.test.ts +21 -2
  253. package/tests/pi-update.test.ts +99 -4
  254. package/tests/primary-session-prompt.test.ts +101 -0
  255. package/tests/provider-contract-bundle.test.ts +118 -0
  256. package/tests/provider-contract-mirror.test.ts +24 -0
  257. package/tests/quiet-tool-rendering.test.ts +23 -0
  258. package/tests/rdd-aware-verification-contract.test.ts +29 -11
  259. package/tests/rdd-status-line.test.ts +79 -0
  260. package/tests/review-agent-end-preflight.test.ts +6 -0
  261. package/tests/review-candidate-view.test.ts +49 -0
  262. package/tests/review-contract-prompt.test.ts +5 -2
  263. package/tests/review-controller-native-routing.test.ts +14 -1
  264. package/tests/review-controller.test.ts +1 -1
  265. package/tests/review-host-relay-routing.test.ts +147 -3
  266. package/tests/review-host-relay.test.ts +9 -2
  267. package/tests/review-json-arguments.test.ts +20 -3
  268. package/tests/review-loadout-rdd-off-sdk.test.ts +258 -0
  269. package/tests/review-owner-identity.test.ts +132 -0
  270. package/tests/review-relay-transport-agent.test.ts +26 -0
  271. package/tests/review-reminder-receipt.test.ts +31 -1
  272. package/tests/review-risk-assessment.test.ts +163 -76
  273. package/tests/review-session-standing-permission-controller.test.ts +14 -1
  274. package/tests/review-session-standing-permission.test.ts +27 -1
  275. package/tests/run-test-suite.test.ts +15 -0
  276. package/tests/runtime-harness.mjs +16 -6
  277. package/tests/session-changes-shell.test.ts +1 -1
  278. package/tests/session-changes.test.ts +2 -1
  279. package/tests/session-profile-append-controller-fork.test.ts +458 -0
  280. package/tests/session-profile-append-controller-pi.test.ts +290 -0
  281. package/tests/session-profile-append-controller.test.ts +731 -0
  282. package/tests/session-profile-disk-reader-pi.test.ts +183 -0
  283. package/tests/session-profile-disk-reader.test.ts +311 -0
  284. package/tests/session-profile-freeze.test.ts +23 -0
  285. package/tests/session-scratch.test.ts +1011 -0
  286. package/tests/session-subject-routing-contract.test.ts +2 -1
  287. package/tests/session-worktree-registry.test.ts +13 -1
  288. package/tests/shell-bar.test.ts +21 -2
  289. package/tests/shell-changes-view.test.ts +137 -1
  290. package/tests/shell-changes.test.ts +148 -1
  291. package/tests/shell-gauge.test.ts +42 -0
  292. package/tests/shell-prompt.test.ts +3 -3
  293. package/tests/shell-render-cache.test.ts +134 -1
  294. package/tests/shell-sidebar-layout.test.ts +50 -0
  295. package/tests/short-check-routing-contract.test.ts +1 -1
  296. package/tests/skill-collision-prefixes.test.ts +3 -1
  297. package/tests/skill-registry-watcher.test.ts +17 -5
  298. package/tests/skill-registry.test.ts +47 -3
  299. package/tests/small-task-inline-contract.test.ts +37 -0
  300. package/tests/standalone-config-home.test.ts +324 -0
  301. package/tests/startup-banner.test.ts +5 -4
  302. package/tests/startup-home-repo.test.ts +34 -0
  303. package/tests/support/orchestrator-modules.ts +1 -0
  304. package/tests/task-size-routing-contract.test.ts +70 -10
  305. package/tests/test-scratch-contract.test.ts +102 -0
  306. package/tests/test-strength-contract.test.ts +36 -1
  307. package/tests/tool-batching-contract.test.ts +64 -0
  308. package/tests/verification-proportionality.test.ts +46 -0
  309. package/tests/verifier-admission.test.ts +154 -0
  310. package/tests/verify-agent-contract.test.ts +8 -8
  311. package/tests/voice-command.test.ts +773 -0
  312. package/tests/voice-install.test.ts +323 -0
  313. package/tests/voice-platform.test.ts +176 -0
  314. package/tests/voice-push-to-talk.test.ts +444 -0
  315. package/tests/voice-session.test.ts +434 -0
  316. package/tests/voice-settings.test.ts +49 -0
  317. package/tests/voice-ui.test.ts +97 -0
  318. package/tests/voice-worker.test.ts +445 -0
  319. package/tests/weak-model-economy-prompt.test.ts +32 -0
  320. package/tests/windows-hidden-processes.test.ts +30 -2
  321. package/tests/writer-edit-surface-scope.test.ts +3 -0
  322. package/tests/writer-feature-document-admission.test.ts +156 -0
  323. package/tests/yolo-customize.test.ts +5 -1
  324. package/tests/yolo-mode.test.ts +19 -1
  325. package/tests/yolo-session-policy-capture.test.ts +104 -0
  326. package/tests/yolo-temp-scratch.test.ts +71 -0
  327. package/scripts/mirror-odd-routing.mjs +0 -242
  328. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
  329. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
  330. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
  331. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  332. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
  333. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
  334. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -5,15 +5,17 @@ import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
5
5
  import { NATIVE_REVIEW_MODE_SOURCE, type NativeReviewCli } from "../lib/native-review-cli.ts";
6
6
  import {
7
7
  decodeAgentRiskEscalation,
8
- escalatedRisk,
9
8
  HIGH_RISK_ITEMS,
10
9
  REVIEW_ASSESSMENT_SCHEMA,
11
10
  VERIFICATION_TIER,
11
+ verificationRisk,
12
12
  } from "../lib/review-risk-assessment.ts";
13
13
 
14
14
  // gentle-shell#1494 S6: the agent may raise a candidate's risk to high by
15
15
  // citing one item of the shared high-risk list, never lower it. The raise
16
- // travels inside the existing assess call; no new operation.
16
+ // travels inside the existing assess call; no new operation. Verification by
17
+ // named risk: that escalation is now the only way to get the independent
18
+ // verifier; a native high or unassessable tier informs only.
17
19
 
18
20
  test("the high-risk list has exactly the six items of the always-on Task Size section", () => {
19
21
  assert.deepEqual(Object.keys(HIGH_RISK_ITEMS).map(Number), [1, 2, 3, 4, 5, 6]);
@@ -45,23 +47,25 @@ test("decodeAgentRiskEscalation rejects unknown items, empty or oversized reason
45
47
  }
46
48
  });
47
49
 
48
- test("escalatedRisk raises passive and medium to high and never lowers", () => {
50
+ // Replaces the escalatedRisk test: the plan risk is high only with an
51
+ // escalation; without one a native high or unassessable tier becomes medium.
52
+ test("verificationRisk makes every escalated tier high and maps the rest to passive or medium", () => {
49
53
  const escalation = { item: 3 as const, reason: "changes a published export" };
50
- assert.equal(escalatedRisk(VERIFICATION_TIER.PASSIVE, escalation), VERIFICATION_TIER.HIGH);
51
- assert.equal(escalatedRisk(VERIFICATION_TIER.MEDIUM, escalation), VERIFICATION_TIER.HIGH);
52
- assert.equal(escalatedRisk(VERIFICATION_TIER.HIGH, escalation), VERIFICATION_TIER.HIGH);
53
- assert.equal(escalatedRisk(VERIFICATION_TIER.UNASSESSABLE, escalation), VERIFICATION_TIER.UNASSESSABLE);
54
- for (const tier of Object.values(VERIFICATION_TIER)) assert.equal(escalatedRisk(tier, undefined), tier);
54
+ for (const tier of Object.values(VERIFICATION_TIER)) assert.equal(verificationRisk(tier, escalation), VERIFICATION_TIER.HIGH, tier);
55
+ assert.equal(verificationRisk(VERIFICATION_TIER.PASSIVE, undefined), VERIFICATION_TIER.PASSIVE);
56
+ for (const tier of [VERIFICATION_TIER.MEDIUM, VERIFICATION_TIER.HIGH, VERIFICATION_TIER.UNASSESSABLE]) {
57
+ assert.equal(verificationRisk(tier, undefined), VERIFICATION_TIER.MEDIUM, tier);
58
+ }
55
59
  });
56
60
 
57
- function assessTool(): { execute: (id: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: undefined, ctx: ExtensionContext) => Promise<{ details: unknown }> } {
61
+ function assessTool(nativeRisk: "passive" | "medium" | "high" = "medium"): { execute: (id: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: undefined, ctx: ExtensionContext) => Promise<{ details: unknown }> } {
58
62
  const tools = new Map<string, any>();
59
63
  const pi = { on() {}, registerCommand() {}, registerTool(tool: { name: string }) { tools.set(tool.name, tool); } } as unknown as ExtensionAPI;
60
64
  const nativeReviewCli: Partial<NativeReviewCli> = {
61
65
  reviewMode: async () => ({ operation: "status", scope: "clone", status: { global: "off", cloneLocal: "off", effective: "off", source: NATIVE_REVIEW_MODE_SOURCE.GLOBAL } }),
62
66
  assess: async () => ({
63
67
  schema: REVIEW_ASSESSMENT_SCHEMA,
64
- risk: "medium",
68
+ risk: nativeRisk,
65
69
  reasons: [],
66
70
  changedPaths: 1,
67
71
  changedLines: 3,
@@ -121,3 +125,26 @@ test("gentle_review assess: agentEscalation says whether the escalation changed
121
125
  const { details } = await tool.execute("esc-5", { operation: "assess", input }, undefined, undefined, ctx);
122
126
  assert.equal((details as { agentEscalation: { applied: boolean } }).agentEscalation.applied, true, "medium raised to high is applied");
123
127
  });
128
+
129
+ test("gentle_review assess: a native high without an escalation informs only and adds no verifier", async () => {
130
+ const tool = assessTool("high");
131
+ for (const writer of [{ writerModelId: "openai/gpt-6.1-sol", writerEffort: "high" }, { writerModelId: "openai/gpt-5-mini", writerEffort: "low" }]) {
132
+ const { details } = await tool.execute("esc-6", { operation: "assess", input: JSON.stringify(writer) }, undefined, undefined, ctx);
133
+ const plan = details as { risk: string; nativeRisk: string; plan: { independentVerifier: boolean; writerSelfVerification: boolean } };
134
+ assert.equal(plan.nativeRisk, VERIFICATION_TIER.HIGH);
135
+ assert.equal(plan.risk, VERIFICATION_TIER.MEDIUM, writer.writerModelId);
136
+ assert.equal(plan.plan.writerSelfVerification, true);
137
+ assert.equal(plan.plan.independentVerifier, false, `${writer.writerModelId}: neither a native high nor the writer's model adds a verifier`);
138
+ }
139
+ });
140
+
141
+ test("gentle_review assess: an escalation over a native high is what adds the verifier", async () => {
142
+ const tool = assessTool("high");
143
+ const input = JSON.stringify({ escalate: { item: 1, reason: "rewrites stored records" } });
144
+ const { details } = await tool.execute("esc-7", { operation: "assess", input }, undefined, undefined, ctx);
145
+ const plan = details as { risk: string; nativeRisk: string; agentEscalation: { applied: boolean }; plan: { independentVerifier: boolean } };
146
+ assert.equal(plan.nativeRisk, VERIFICATION_TIER.HIGH);
147
+ assert.equal(plan.risk, VERIFICATION_TIER.HIGH);
148
+ assert.equal(plan.agentEscalation.applied, true, "without the escalation the plan would be medium");
149
+ assert.equal(plan.plan.independentVerifier, true);
150
+ });
@@ -14,6 +14,7 @@ import {
14
14
  parseFrontmatter,
15
15
  parseModelRef,
16
16
  resolveAgentProfile,
17
+ unmatchedProfileKeys,
17
18
  withPinnedModelProfiles,
18
19
  } from "../lib/agents-config.ts";
19
20
  import { THINKING_LEVELS } from "../lib/model-routing-authority.ts";
@@ -247,3 +248,63 @@ test("a pinned profile replaces subagent routing and leaves every other default
247
248
  // repository can pin "everything inherits" without touching the global store.
248
249
  assert.deepEqual(withPinnedModelProfiles(global, {}).modelProfiles, {});
249
250
  });
251
+
252
+ // Issue #2196 rework (PR #2233 maintainer decision): role keys that name no
253
+ // discovered agent must be reported, with a did-you-mean suggestion, instead of
254
+ // being silently ignored while the child inherits the orchestrator model.
255
+ test("unmatchedProfileKeys reports profile keys that match no discovered agent", () => {
256
+ const worker = {
257
+ name: "gentle-ai-worker",
258
+ description: "",
259
+ filePath: "/x/worker.md",
260
+ scope: "global" as const,
261
+ instructions: "",
262
+ model: undefined,
263
+ thinking: undefined,
264
+ mode: undefined,
265
+ tools: [],
266
+ };
267
+ const explore = { ...worker, name: "gentle-ai-explore" };
268
+ const profiles = {
269
+ worker: { model: { provider: "nan", id: "glm5.3" }, thinking: undefined },
270
+ WORKER: { model: { provider: "nan", id: "glm5.3" }, thinking: undefined },
271
+ review_risk: { model: undefined, thinking: "high" as const },
272
+ explore: { model: undefined, thinking: undefined },
273
+ };
274
+ assert.deepEqual(unmatchedProfileKeys(profiles, [worker, explore]), [
275
+ { key: "worker", suggestion: "gentle-ai-worker" },
276
+ { key: "WORKER", suggestion: "gentle-ai-worker" },
277
+ { key: "review_risk", suggestion: undefined },
278
+ { key: "explore", suggestion: "gentle-ai-explore" },
279
+ ]);
280
+ // A key that exactly matches an agent name never reports, and an empty
281
+ // profile set reports nothing.
282
+ assert.deepEqual(unmatchedProfileKeys({ "gentle-ai-worker": { model: undefined, thinking: undefined } }, [worker]), []);
283
+ assert.deepEqual(unmatchedProfileKeys({}, [worker]), []);
284
+ });
285
+
286
+ // Cortex QA adversarial follow-up (R5 pin): the did-you-mean mapping exists to
287
+ // make a misconfiguration visible, never to resolve; a short key must keep
288
+ // yielding an unresolved profile so the role inherits frontmatter and default
289
+ // routing exactly as before the fix.
290
+ test("resolveAgentProfile never resolves through the suggestion mapping", () => {
291
+ const worker = {
292
+ name: "gentle-ai-worker",
293
+ description: "",
294
+ filePath: "/x/worker.md",
295
+ scope: "global" as const,
296
+ instructions: "",
297
+ model: { provider: "openai-codex", id: "gpt-5.6-terra" },
298
+ thinking: undefined,
299
+ mode: undefined,
300
+ tools: [],
301
+ };
302
+ const config = parseAgentsConfig(
303
+ { model_profiles: { worker: { model: "nan/glm5.3", effort: "high" } } },
304
+ undefined,
305
+ );
306
+ const resolved = resolveAgentProfile(worker, config);
307
+ assert.equal(resolved.model?.provider, "openai-codex", "the short key must not route; the agent frontmatter model wins");
308
+ assert.equal(resolved.source.model, "definition");
309
+ assert.ok(unmatchedProfileKeys(config.modelProfiles, [worker]).some((entry) => entry.key === "worker" && entry.suggestion === "gentle-ai-worker"), "the same short key is still reported with its suggestion");
310
+ });
@@ -17,7 +17,7 @@ export interface FakeChild {
17
17
  message(event: Record<string, unknown>): void;
18
18
  }
19
19
 
20
- export function fakeChild(options: { exitOnKill?: boolean; pid?: number } = {}): FakeChild {
20
+ export function fakeChild(options: { exitOnKill?: boolean; exitOnStdinEnd?: boolean; pid?: number } = {}): FakeChild {
21
21
  const emitter = new EventEmitter();
22
22
  const stdin = new PassThrough();
23
23
  const stdout = new PassThrough();
@@ -38,6 +38,13 @@ export function fakeChild(options: { exitOnKill?: boolean; pid?: number } = {}):
38
38
  stdout.write(`${JSON.stringify({ type: "response", id: command.id, command: command.type, success: true, data })}\n`);
39
39
  }
40
40
  });
41
+ const origEnd = stdin.end.bind(stdin);
42
+ stdin.end = ((...args: unknown[]) => {
43
+ const res = (origEnd as Function)(...args);
44
+ // Preserve existing fixtures while allowing EOF and signal exits to vary independently.
45
+ if (options.exitOnStdinEnd ?? options.exitOnKill ?? true) queueMicrotask(() => emitter.emit("exit", 0, null));
46
+ return res;
47
+ }) as typeof stdin.end;
41
48
  const child: ChildLike = {
42
49
  pid: options.pid,
43
50
  stdin,
@@ -10,7 +10,9 @@ import {
10
10
  TASK_STATUS,
11
11
  taskLabel,
12
12
  TaskStore,
13
+ TERMINAL_RETENTION,
13
14
  THREAD_ITEM,
15
+ isFinished,
14
16
  type TaskRecord,
15
17
  } from "../lib/agents-protocol.ts";
16
18
 
@@ -274,6 +276,36 @@ test("TaskStore notifies only the listeners of the task that changed and keeps s
274
276
  assert.deepEqual(store.thread("missing"), emptyThread());
275
277
  });
276
278
 
279
+ // Finished tasks must release their weight: a bounded window of the most
280
+ // recent ones stays readable, older ones leave the store entirely, and a
281
+ // terminal task's listeners go quiet after the one final transition.
282
+ test("TaskStore keeps a bounded window of finished tasks and silences terminal listeners", () => {
283
+ const store = new TaskStore();
284
+ const seen: string[] = [];
285
+ store.add(record({ id: "watched", status: TASK_STATUS.RUNNING, lastActivityAt: 1 }));
286
+ store.add(record({ id: "filler", status: TASK_STATUS.RUNNING, lastActivityAt: 2 }));
287
+ store.subscribe("watched", (task) => seen.push(task.status));
288
+ // `watched` completes the window once it finishes, so the next terminal
289
+ // task evicts exactly the oldest one.
290
+ for (let index = 0; index < TERMINAL_RETENTION - 1; index += 1) {
291
+ store.add(record({ id: `done-${index}`, status: TASK_STATUS.COMPLETED, lastActivityAt: 10 + index }));
292
+ }
293
+ assert.equal(store.list().filter((task) => isFinished(task.status)).length, TERMINAL_RETENTION - 1, "retained finished tasks stay at the cap");
294
+ assert.ok(store.get("watched") && store.get("filler"), "running tasks are never evicted");
295
+ store.update("watched", { status: TASK_STATUS.COMPLETED, endedAt: 50 });
296
+ assert.deepEqual(seen, ["completed"], "the terminal transition itself still notifies exactly once");
297
+ store.apply("watched", { type: TASK_EVENT.TEXT, text: "late" }, 60);
298
+ assert.deepEqual(seen, ["completed"], "a terminal task's listener stays quiet afterwards");
299
+ store.add(record({ id: "newest", status: TASK_STATUS.COMPLETED, lastActivityAt: 1_000 }));
300
+ assert.equal(store.get("done-0"), undefined, "the oldest finished task is evicted");
301
+ assert.ok(store.get("done-1") && store.get("newest"), "recent finished tasks remain readable");
302
+ assert.ok(store.get("filler"), "running tasks survive eviction");
303
+ assert.equal(store.list().filter((task) => isFinished(task.status)).length, TERMINAL_RETENTION);
304
+ assert.deepEqual(store.thread("done-0"), emptyThread(), "an evicted task's thread weight is released");
305
+ assert.ok(store.thread("done-1").limits.maxItems > 0, "a retained finished task stays renderable");
306
+ assert.equal(store.summary().finished, TERMINAL_RETENTION, "summaries reflect the bounded window");
307
+ });
308
+
277
309
  test("TaskStore.apply moves the task to waiting on ask, back to running on any later event, and counts turns", () => {
278
310
  const store = new TaskStore();
279
311
  store.add(record({ id: "a", status: TASK_STATUS.RUNNING }));