ultimate-pi 0.18.1 → 0.19.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 (284) hide show
  1. package/.agents/skills/harness-debate-plan/SKILL.md +1 -1
  2. package/.agents/skills/harness-decisions/SKILL.md +1 -2
  3. package/.agents/skills/harness-governor/SKILL.md +6 -5
  4. package/.pi/PACKAGING.md +4 -4
  5. package/.pi/SYSTEM.md +54 -120
  6. package/.pi/agents/harness/incident-recorder.md +0 -1
  7. package/.pi/agents/harness/planning/decompose.md +0 -2
  8. package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
  9. package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
  10. package/.pi/agents/harness/planning/hypothesis.md +0 -2
  11. package/.pi/agents/harness/planning/implementation-researcher.md +0 -2
  12. package/.pi/agents/harness/planning/plan-adversary.md +0 -2
  13. package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
  14. package/.pi/agents/harness/planning/planning-context.md +0 -2
  15. package/.pi/agents/harness/planning/review-integrator.md +0 -2
  16. package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
  17. package/.pi/agents/harness/planning/stack-researcher.md +0 -2
  18. package/.pi/agents/harness/reviewing/adversary.md +0 -2
  19. package/.pi/agents/harness/reviewing/evaluator.md +0 -2
  20. package/.pi/agents/harness/reviewing/tie-breaker.md +0 -2
  21. package/.pi/agents/harness/running/executor.md +0 -2
  22. package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
  23. package/.pi/agents/harness/sentrux-steward.md +0 -2
  24. package/.pi/agents/harness/trace-librarian.md +0 -1
  25. package/.pi/extensions/00-posthog-network-bootstrap.ts +1 -1
  26. package/.pi/extensions/agt-kill-switch.ts +57 -0
  27. package/.pi/extensions/agt-prompt-guard.ts +32 -0
  28. package/.pi/extensions/custom-footer.ts +46 -145
  29. package/.pi/extensions/custom-header.ts +1 -1
  30. package/.pi/extensions/custom-system-prompt.ts +1 -1
  31. package/.pi/extensions/debate-orchestrator.ts +6 -6
  32. package/.pi/extensions/harness-ask-user.ts +7 -7
  33. package/.pi/extensions/harness-debate-tools.ts +26 -42
  34. package/.pi/extensions/harness-lens.ts +94 -0
  35. package/.pi/extensions/harness-plan-approval.ts +11 -11
  36. package/.pi/extensions/harness-run-context.ts +1070 -876
  37. package/.pi/extensions/harness-subagent-governance.ts +8 -0
  38. package/.pi/extensions/harness-subagent-submit.ts +34 -163
  39. package/.pi/extensions/harness-subagents.ts +3 -3
  40. package/.pi/extensions/harness-telemetry.ts +2 -2
  41. package/.pi/extensions/harness-web-tools.ts +2 -2
  42. package/.pi/extensions/policy-gate.ts +25 -5
  43. package/.pi/extensions/sentrux-rules-sync.ts +1 -1
  44. package/.pi/extensions/subagent-governance.ts +92 -0
  45. package/.pi/extensions/trace-recorder.ts +1 -1
  46. package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
  47. package/.pi/harness/README.md +6 -2
  48. package/.pi/harness/agents.manifest.json +22 -25
  49. package/.pi/harness/agents.policy.yaml +275 -0
  50. package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
  51. package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
  52. package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
  53. package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
  54. package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
  55. package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
  56. package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
  57. package/.pi/harness/docs/adrs/README.md +5 -0
  58. package/.pi/harness/evolution/README.md +1 -2
  59. package/.pi/harness/examples/agents.policy.project.yaml +19 -0
  60. package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
  61. package/.pi/harness/policies/bash-denylists.yaml +5 -0
  62. package/.pi/harness/policies/defaults.yaml +51 -0
  63. package/.pi/harness/policies/orchestrator.yaml +18 -0
  64. package/.pi/harness/policies/phases.yaml +10 -0
  65. package/.pi/harness/policies/roles.yaml +5 -0
  66. package/.pi/harness/policies/web-guard.yaml +5 -0
  67. package/.pi/harness/policies/workflow-sequences.yaml +9 -0
  68. package/.pi/harness/sentrux/architecture.manifest.json +26 -4
  69. package/.pi/harness/specs/observation.schema.json +2 -1
  70. package/.pi/lib/agents-policy.d.mts +70 -0
  71. package/.pi/lib/agents-policy.mjs +325 -0
  72. package/.pi/lib/agents-policy.ts +19 -0
  73. package/.pi/lib/agt/audit-run-sink.ts +52 -0
  74. package/.pi/lib/agt/build-evaluation-context.ts +285 -0
  75. package/.pi/lib/agt/config.ts +28 -0
  76. package/.pi/lib/agt/delegation.ts +69 -0
  77. package/.pi/lib/agt/evaluate-policy.ts +56 -0
  78. package/.pi/lib/agt/identity-registry.ts +41 -0
  79. package/.pi/lib/agt/index.ts +55 -0
  80. package/.pi/lib/agt/kill-switch-state.ts +11 -0
  81. package/.pi/lib/agt/legacy-evaluate.ts +101 -0
  82. package/.pi/lib/agt/policy-engine.ts +154 -0
  83. package/.pi/lib/agt/rings.ts +21 -0
  84. package/.pi/lib/agt/sre-hooks.ts +45 -0
  85. package/.pi/lib/agt/trust-run-store.ts +26 -0
  86. package/.pi/lib/agt/workflow-history.ts +29 -0
  87. package/.pi/lib/agt-governance-active.ts +14 -0
  88. package/.pi/lib/agt-tool-guard.ts +78 -0
  89. package/.pi/lib/ask-user/dialog.ts +314 -0
  90. package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
  91. package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
  92. package/.pi/{extensions/lib → lib}/extension-load-guard.ts +13 -2
  93. package/.pi/lib/harness-agt-tool-guard.ts +5 -0
  94. package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +1 -1
  95. package/.pi/lib/harness-debate-core-deps.ts +14 -0
  96. package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
  97. package/.pi/lib/harness-lens/.gitattributes +1 -0
  98. package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
  99. package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
  100. package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
  101. package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
  102. package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
  103. package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
  104. package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
  105. package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
  106. package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
  107. package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
  108. package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
  109. package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
  110. package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
  111. package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
  112. package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
  113. package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
  114. package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
  115. package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
  116. package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
  117. package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
  118. package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
  119. package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
  120. package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
  121. package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
  122. package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
  123. package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
  124. package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
  125. package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
  126. package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
  127. package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
  128. package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
  129. package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
  130. package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
  131. package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
  132. package/.pi/lib/harness-lens/clients/types.ts +59 -0
  133. package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
  134. package/.pi/lib/harness-lens/index.ts +532 -0
  135. package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
  136. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
  137. package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
  138. package/.pi/lib/harness-run-context-responses.ts +9 -0
  139. package/.pi/lib/harness-run-context.ts +0 -2
  140. package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +1 -0
  141. package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +1 -1
  142. package/.pi/lib/harness-subagent-auth.ts +51 -0
  143. package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +10 -7
  144. package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
  145. package/.pi/lib/harness-subagent-submit-register.ts +163 -0
  146. package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -37
  147. package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +53 -14
  148. package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
  149. package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
  150. package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
  151. package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
  152. package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
  153. package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
  154. package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
  155. package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
  156. package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
  157. package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
  158. package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
  159. package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
  160. package/.pi/prompts/harness-plan.md +1 -1
  161. package/.pi/prompts/harness-setup.md +37 -64
  162. package/.pi/scripts/README.md +2 -5
  163. package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
  164. package/.pi/scripts/harness-agents-manifest.mjs +60 -3
  165. package/.pi/scripts/harness-agt-doctor.ts +36 -0
  166. package/.pi/scripts/harness-cli-verify.sh +9 -2
  167. package/.pi/scripts/harness-verify.mjs +113 -39
  168. package/.pi/scripts/harness-web-policy-guard.mjs +2 -2
  169. package/.pi/scripts/validate-plan-dag.mjs +65 -74
  170. package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
  171. package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
  172. package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
  173. package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
  174. package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
  175. package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
  176. package/.pi/skills/architecture/layered/SKILL.md +68 -0
  177. package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
  178. package/.pi/skills/architecture/microservices/SKILL.md +64 -0
  179. package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
  180. package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
  181. package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
  182. package/.pi/skills/architecture/service-based/SKILL.md +64 -0
  183. package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
  184. package/.pi/skills/architecture/space-based/SKILL.md +60 -0
  185. package/.pi/skills/ast-grep/SKILL.md +40 -321
  186. package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
  187. package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
  188. package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
  189. package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
  190. package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
  191. package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
  192. package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
  193. package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
  194. package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
  195. package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
  196. package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
  197. package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
  198. package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
  199. package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
  200. package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
  201. package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
  202. package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
  203. package/.pi/skills/lsp-navigation/SKILL.md +89 -0
  204. package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
  205. package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
  206. package/.pi/skills/quality/security-review/SKILL.md +34 -0
  207. package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
  208. package/.pi/skills/quality/testability-design/SKILL.md +33 -0
  209. package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
  210. package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
  211. package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
  212. package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
  213. package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
  214. package/.sentrux/rules.toml +20 -4
  215. package/AGENTS.md +5 -0
  216. package/CHANGELOG.md +14 -0
  217. package/README.md +3 -12
  218. package/THIRD_PARTY_NOTICES.md +12 -21
  219. package/package.json +15 -7
  220. package/vendor/pi-subagents/src/agents.ts +45 -1
  221. package/vendor/pi-subagents/src/subagents.ts +866 -811
  222. package/vendor/pi-vcc/src/core/brief.ts +68 -99
  223. package/vendor/pi-vcc/src/core/settings.ts +2 -2
  224. package/.agents/skills/caveman/SKILL.md +0 -67
  225. package/.pi/agents/harness/meta-optimizer.md +0 -36
  226. package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
  227. package/.pi/extensions/lib/harness-subagent-auth.ts +0 -207
  228. package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
  229. package/.pi/extensions/pi-model-router-harness.ts +0 -42
  230. package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
  231. package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
  232. package/.pi/model-router.example.json +0 -36
  233. package/.pi/prompts/harness-critic.md +0 -10
  234. package/.pi/prompts/harness-eval.md +0 -10
  235. package/.pi/prompts/harness-router-tune.md +0 -52
  236. package/.pi/scripts/harness-generate-model-router.mjs +0 -327
  237. package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
  238. package/.pi/scripts/harness-sync-model-router.mjs +0 -97
  239. package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
  240. package/vendor/pi-model-router/.prettierignore +0 -4
  241. package/vendor/pi-model-router/.prettierrc +0 -5
  242. package/vendor/pi-model-router/AGENTS.md +0 -39
  243. package/vendor/pi-model-router/LICENSE +0 -21
  244. package/vendor/pi-model-router/README.md +0 -99
  245. package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
  246. package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
  247. package/vendor/pi-model-router/extensions/commands.ts +0 -720
  248. package/vendor/pi-model-router/extensions/config.ts +0 -348
  249. package/vendor/pi-model-router/extensions/constants.ts +0 -1
  250. package/vendor/pi-model-router/extensions/index.ts +0 -478
  251. package/vendor/pi-model-router/extensions/provider.ts +0 -580
  252. package/vendor/pi-model-router/extensions/routing.ts +0 -564
  253. package/vendor/pi-model-router/extensions/state.ts +0 -52
  254. package/vendor/pi-model-router/extensions/types.ts +0 -95
  255. package/vendor/pi-model-router/extensions/ui.ts +0 -144
  256. package/vendor/pi-model-router/model-router.example.json +0 -48
  257. package/vendor/pi-model-router/package.json +0 -48
  258. package/vendor/pi-model-router/tsconfig.json +0 -16
  259. /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
  260. /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
  261. /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
  262. /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
  263. /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
  264. /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
  265. /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
  266. /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
  267. /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
  268. /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
  269. /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
  270. /package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +0 -0
  271. /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
  272. /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
  273. /package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +0 -0
  274. /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
  275. /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
  276. /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
  277. /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
  278. /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
  279. /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
  280. /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
  281. /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
  282. /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
  283. /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
  284. /package/.pi/{extensions/lib → lib}/posthog-node.d.ts +0 -0
@@ -1,478 +0,0 @@
1
- import type {
2
- ExtensionAPI,
3
- ExtensionContext,
4
- } from '@earendil-works/pi-coding-agent';
5
- import {
6
- type RouterConfig,
7
- type RouterPersistedState,
8
- type RoutingDecision,
9
- type RouterPinByProfile,
10
- type RouterThinkingByProfile,
11
- type RouterTier,
12
- type CustomSessionEntry,
13
- type SessionLock,
14
- } from './types.js';
15
- import {
16
- FALLBACK_CONFIG,
17
- loadRouterConfig,
18
- profileNames,
19
- resolveProfileName,
20
- parseCanonicalModelRef,
21
- } from './config.js';
22
- import { MAX_DEBUG_HISTORY } from './constants.js';
23
- import { isRouterPersistedState, buildPersistedState } from './state.js';
24
- import { updateStatus, formatModelRef } from './ui.js';
25
- import { registerCommands } from './commands.js';
26
- import { registerRouterProvider } from './provider.js';
27
-
28
- const routerExtension = (pi: ExtensionAPI) => {
29
- let currentConfig: RouterConfig = FALLBACK_CONFIG;
30
- let currentModelRegistry: ExtensionContext['modelRegistry'] | undefined;
31
- let currentCwd = process.cwd();
32
- let lastDecision: RoutingDecision | undefined;
33
- let debugEnabled = false;
34
- let routerEnabled = false;
35
- let selectedProfile = resolveProfileName(
36
- FALLBACK_CONFIG,
37
- FALLBACK_CONFIG.defaultProfile,
38
- );
39
- let widgetEnabled = false;
40
- let lastRegisteredModels = '';
41
- let pinnedTierByProfile: RouterPinByProfile = {};
42
- let thinkingByProfile: RouterThinkingByProfile = {};
43
- let debugHistory: RoutingDecision[] = [];
44
- let lastNonRouterModel: string | undefined;
45
- let accumulatedCost = 0;
46
- let lastExtensionContext: ExtensionContext | undefined;
47
- let lastConfigWarnings: string[] = [];
48
- let lastPersistedSnapshot: string | undefined;
49
- let isInitialized = false;
50
- let isInternalModelSwitch = false;
51
- let sessionLock: SessionLock | undefined;
52
-
53
- const setModelInternally = async (
54
- model: NonNullable<ExtensionContext['model']>,
55
- ) => {
56
- isInternalModelSwitch = true;
57
- try {
58
- return await pi.setModel(model);
59
- } finally {
60
- isInternalModelSwitch = false;
61
- }
62
- };
63
-
64
- const getPinnedTierForProfile = (
65
- profileName: string,
66
- ): RouterTier | undefined => pinnedTierByProfile[profileName];
67
-
68
- const setPinnedTierForProfile = (
69
- profileName: string,
70
- tier: RouterTier | undefined,
71
- ) => {
72
- if (tier) {
73
- pinnedTierByProfile[profileName] = tier;
74
- } else {
75
- delete pinnedTierByProfile[profileName];
76
- }
77
- };
78
-
79
- const recordDebugDecision = (decision: RoutingDecision) => {
80
- debugHistory = [...debugHistory, decision].slice(-MAX_DEBUG_HISTORY);
81
- };
82
-
83
- const getThinkingOverride = (profileName: string, tier: RouterTier) => {
84
- return thinkingByProfile[profileName]?.[tier];
85
- };
86
-
87
- const persistState = () => {
88
- const state = buildPersistedState(
89
- routerEnabled,
90
- selectedProfile,
91
- pinnedTierByProfile,
92
- thinkingByProfile,
93
- debugEnabled,
94
- widgetEnabled,
95
- debugHistory,
96
- lastDecision,
97
- lastNonRouterModel,
98
- accumulatedCost,
99
- sessionLock,
100
- );
101
- const snapshot = JSON.stringify({
102
- ...state,
103
- timestamp: 0,
104
- lastDecision: state.lastDecision
105
- ? { ...state.lastDecision, timestamp: 0 }
106
- : undefined,
107
- debugHistory: state.debugHistory?.map((decision) => ({
108
- ...decision,
109
- timestamp: 0,
110
- })),
111
- });
112
- if (snapshot === lastPersistedSnapshot) {
113
- return;
114
- }
115
- pi.appendEntry('router-state', state);
116
- lastPersistedSnapshot = snapshot;
117
- };
118
-
119
- const actions = {
120
- persistState,
121
- updateStatus: (ctx: ExtensionContext) =>
122
- updateStatus(
123
- ctx,
124
- routerEnabled,
125
- selectedProfile,
126
- pinnedTierByProfile,
127
- thinkingByProfile,
128
- lastDecision,
129
- lastNonRouterModel,
130
- accumulatedCost,
131
- widgetEnabled,
132
- currentConfig,
133
- sessionLock,
134
- ),
135
- reloadConfig: (
136
- ctx?: ExtensionContext,
137
- options?: { preserveDebug?: boolean },
138
- ) => {
139
- const loaded = loadRouterConfig(currentCwd);
140
- currentConfig = loaded.config;
141
- lastConfigWarnings = loaded.warnings;
142
- if (!options?.preserveDebug) {
143
- debugEnabled = currentConfig.debug ?? false;
144
- }
145
- selectedProfile = resolveProfileName(currentConfig, selectedProfile);
146
- actions.registerRouterProvider();
147
- if (ctx) {
148
- actions.updateStatus(ctx);
149
- }
150
- },
151
- ensureValidActiveRouterProfile: async (ctx: ExtensionContext) => {
152
- if (ctx.model?.provider !== 'router') {
153
- return;
154
- }
155
- if (currentConfig.profiles[ctx.model.id]) {
156
- selectedProfile = ctx.model.id;
157
- routerEnabled = true;
158
- return;
159
- }
160
-
161
- const fallbackProfile = resolveProfileName(
162
- currentConfig,
163
- selectedProfile,
164
- );
165
- const routerModel = ctx.modelRegistry.find('router', fallbackProfile);
166
- selectedProfile = fallbackProfile;
167
- if (!routerModel) {
168
- ctx.ui.notify(
169
- `Router profile "${ctx.model.id}" is no longer configured.`,
170
- 'warning',
171
- );
172
- return;
173
- }
174
-
175
- await setModelInternally(routerModel);
176
- ctx.ui.notify(
177
- `Router profile "${ctx.model.id}" is no longer configured. Switched to router/${fallbackProfile}.`,
178
- 'warning',
179
- );
180
- },
181
- switchToRouterProfile: async (
182
- profileName: string,
183
- ctx: ExtensionContext,
184
- strict = true,
185
- ) => {
186
- if (strict && !currentConfig.profiles[profileName]) {
187
- ctx.ui.notify(`Unknown router profile: ${profileName}`, 'error');
188
- return false;
189
- }
190
- const resolvedProfile = resolveProfileName(currentConfig, profileName);
191
-
192
- // Ensure the provider is registered with current capacities for this profile
193
- actions.registerRouterProvider();
194
- await new Promise((resolve) => setTimeout(resolve, 50));
195
-
196
- const routerModel = ctx.modelRegistry.find('router', resolvedProfile);
197
- if (!routerModel) {
198
- ctx.ui.notify(`Unknown router profile: ${profileName}`, 'error');
199
- return false;
200
- }
201
- if (ctx.model && ctx.model.provider !== 'router') {
202
- lastNonRouterModel = `${ctx.model.provider}/${ctx.model.id}`;
203
- }
204
- const success = await setModelInternally(routerModel);
205
- if (!success) {
206
- ctx.ui.notify(`Failed to switch to router/${resolvedProfile}`, 'error');
207
- return false;
208
- }
209
- selectedProfile = resolvedProfile;
210
- routerEnabled = true;
211
- sessionLock = undefined;
212
- persistState();
213
- actions.updateStatus(ctx);
214
- return true;
215
- },
216
- registerRouterProvider: () => {
217
- registerRouterProvider(
218
- pi,
219
- {
220
- get lastRegisteredModels() {
221
- return lastRegisteredModels;
222
- },
223
- set lastRegisteredModels(v) {
224
- lastRegisteredModels = v;
225
- },
226
- get currentConfig() {
227
- return currentConfig;
228
- },
229
- get currentModelRegistry() {
230
- return currentModelRegistry;
231
- },
232
- get lastExtensionContext() {
233
- return lastExtensionContext;
234
- },
235
- get selectedProfile() {
236
- return selectedProfile;
237
- },
238
- set selectedProfile(v) {
239
- selectedProfile = v;
240
- },
241
- get routerEnabled() {
242
- return routerEnabled;
243
- },
244
- set routerEnabled(v) {
245
- routerEnabled = v;
246
- },
247
- get lastDecision() {
248
- return lastDecision;
249
- },
250
- set lastDecision(v) {
251
- lastDecision = v;
252
- },
253
- thinkingByProfile,
254
- pinnedTierByProfile,
255
- get accumulatedCost() {
256
- return accumulatedCost;
257
- },
258
- set accumulatedCost(v) {
259
- accumulatedCost = v;
260
- },
261
- get sessionLock() {
262
- return sessionLock;
263
- },
264
- set sessionLock(v) {
265
- sessionLock = v;
266
- },
267
- },
268
- {
269
- persistState,
270
- recordDebugDecision,
271
- getThinkingOverride,
272
- updateStatus: actions.updateStatus,
273
- },
274
- );
275
- },
276
- };
277
-
278
- actions.reloadConfig();
279
-
280
- const restoreStateFromSession = async (ctx: ExtensionContext) => {
281
- lastExtensionContext = ctx;
282
- currentModelRegistry = ctx.modelRegistry;
283
- currentCwd = ctx.cwd;
284
- actions.reloadConfig();
285
-
286
- // Give the registry a moment to synchronize after re-registration
287
- await new Promise((resolve) => setTimeout(resolve, 50));
288
-
289
- routerEnabled = ctx.model?.provider === 'router';
290
- selectedProfile = resolveProfileName(
291
- currentConfig,
292
- ctx.model?.provider === 'router' ? ctx.model.id : selectedProfile,
293
- );
294
- pinnedTierByProfile = {};
295
- thinkingByProfile = {};
296
- widgetEnabled = false;
297
- debugHistory = [];
298
- accumulatedCost = 0;
299
- lastNonRouterModel =
300
- ctx.model && ctx.model.provider !== 'router'
301
- ? `${ctx.model.provider}/${ctx.model.id}`
302
- : lastNonRouterModel;
303
- lastDecision = undefined;
304
- sessionLock = undefined;
305
-
306
- const entries = ctx.sessionManager.getBranch() as CustomSessionEntry[];
307
- const savedState = entries
308
- .filter(
309
- (entry) =>
310
- entry.type === 'custom' && entry.customType === 'router-state',
311
- )
312
- .map((entry) => entry.data)
313
- .findLast((data) => isRouterPersistedState(data));
314
-
315
- if (isRouterPersistedState(savedState)) {
316
- selectedProfile = resolveProfileName(
317
- currentConfig,
318
- savedState.selectedProfile,
319
- );
320
- routerEnabled = savedState.enabled;
321
- pinnedTierByProfile = savedState.pinByProfile
322
- ? { ...savedState.pinByProfile }
323
- : {};
324
- thinkingByProfile = savedState.thinkingByProfile
325
- ? { ...savedState.thinkingByProfile }
326
- : {};
327
- if (savedState.pinTier) {
328
- pinnedTierByProfile[selectedProfile] = savedState.pinTier;
329
- }
330
- debugEnabled = savedState.debugEnabled ?? debugEnabled;
331
- widgetEnabled = savedState.widgetEnabled ?? widgetEnabled;
332
- debugHistory = savedState.debugHistory
333
- ? [...savedState.debugHistory].slice(-MAX_DEBUG_HISTORY)
334
- : [];
335
- lastNonRouterModel = savedState.lastNonRouterModel ?? lastNonRouterModel;
336
- accumulatedCost = savedState.accumulatedCost ?? 0;
337
- if (
338
- savedState.sessionLock &&
339
- savedState.sessionLock.profile === selectedProfile
340
- ) {
341
- sessionLock = { ...savedState.sessionLock };
342
- }
343
- }
344
-
345
- await actions.ensureValidActiveRouterProfile(ctx);
346
-
347
- if (routerEnabled) {
348
- const routerModel = ctx.modelRegistry.find('router', selectedProfile);
349
- if (routerModel) {
350
- const success = await setModelInternally(routerModel);
351
- if (!success) {
352
- ctx.ui.notify(
353
- `Failed to restore router/${selectedProfile} after relaunch.`,
354
- 'warning',
355
- );
356
- routerEnabled = false;
357
- }
358
- } else {
359
- ctx.ui.notify(
360
- `Unable to restore router/${selectedProfile}; model is unavailable.`,
361
- 'warning',
362
- );
363
- routerEnabled = false;
364
- ctx.ui.setHiddenThinkingLabel?.();
365
- }
366
- } else {
367
- ctx.ui.setHiddenThinkingLabel?.();
368
- }
369
-
370
- persistState();
371
- actions.updateStatus(ctx);
372
- };
373
-
374
- registerCommands(
375
- pi,
376
- {
377
- get currentConfig() {
378
- return currentConfig;
379
- },
380
- get routerEnabled() {
381
- return routerEnabled;
382
- },
383
- set routerEnabled(v) {
384
- routerEnabled = v;
385
- },
386
- get selectedProfile() {
387
- return selectedProfile;
388
- },
389
- set selectedProfile(v) {
390
- selectedProfile = v;
391
- },
392
- pinnedTierByProfile,
393
- thinkingByProfile,
394
- get lastDecision() {
395
- return lastDecision;
396
- },
397
- get lastNonRouterModel() {
398
- return lastNonRouterModel;
399
- },
400
- set lastNonRouterModel(v) {
401
- lastNonRouterModel = v;
402
- },
403
- get accumulatedCost() {
404
- return accumulatedCost;
405
- },
406
- get debugEnabled() {
407
- return debugEnabled;
408
- },
409
- set debugEnabled(v) {
410
- debugEnabled = v;
411
- },
412
- get widgetEnabled() {
413
- return widgetEnabled;
414
- },
415
- set widgetEnabled(v) {
416
- widgetEnabled = v;
417
- },
418
- get debugHistory() {
419
- return debugHistory;
420
- },
421
- },
422
- actions,
423
- );
424
-
425
- pi.on('session_start', async (_event, ctx) => {
426
- isInitialized = true;
427
- await restoreStateFromSession(ctx);
428
- if (debugEnabled) {
429
- ctx.ui.notify(
430
- `Router initialized with profiles: ${profileNames(currentConfig).join(', ')}`,
431
- 'info',
432
- );
433
- }
434
- });
435
-
436
- pi.on('model_select', async (event, ctx) => {
437
- if (!isInitialized || isInternalModelSwitch) return;
438
- if (event.model.provider === 'router') {
439
- const profileName = resolveProfileName(currentConfig, event.model.id);
440
-
441
- // If the selected model has stale capacities (e.g. from the initial registration),
442
- // re-apply the model from the registry to force a TUI refresh.
443
- const registryModel = ctx.modelRegistry.find('router', profileName);
444
- if (
445
- registryModel &&
446
- (registryModel.contextWindow !== event.model.contextWindow ||
447
- registryModel.maxTokens !== event.model.maxTokens)
448
- ) {
449
- await setModelInternally(registryModel);
450
- }
451
-
452
- routerEnabled = true;
453
- if (selectedProfile !== profileName) {
454
- sessionLock = undefined;
455
- }
456
- selectedProfile = profileName;
457
- } else {
458
- routerEnabled = false;
459
- lastNonRouterModel = `${event.model.provider}/${event.model.id}`;
460
- ctx.ui.setHiddenThinkingLabel?.();
461
- }
462
- persistState();
463
- actions.updateStatus(ctx);
464
- });
465
-
466
- pi.on('turn_end', async (_event, ctx) => {
467
- if (routerEnabled && ctx.model?.provider !== 'router') {
468
- const routerModel = ctx.modelRegistry.find('router', selectedProfile);
469
- if (routerModel) {
470
- await setModelInternally(routerModel);
471
- }
472
- }
473
- persistState();
474
- actions.updateStatus(ctx);
475
- });
476
- };
477
-
478
- export default routerExtension;