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,564 +0,0 @@
1
- import { streamSimple, type Context, type Message } from '@earendil-works/pi-ai';
2
- import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
3
- import type {
4
- RouterTier,
5
- RouterPhase,
6
- RouterProfile,
7
- RoutingDecision,
8
- RoutingRule,
9
- RouterThinkingByTier,
10
- SessionLock,
11
- } from './types.js';
12
- import { parseCanonicalModelRef, isRouterTier } from './config.js';
13
-
14
- export const extractTextFromContent = (
15
- content: string | Message['content'],
16
- ): string => {
17
- if (typeof content === 'string') {
18
- return content;
19
- }
20
- return content
21
- .map((part) => {
22
- if (part.type === 'text') return part.text;
23
- if (part.type === 'thinking') return part.thinking;
24
- if (part.type === 'toolCall')
25
- return `${part.name} ${JSON.stringify(part.arguments)}`;
26
- return '';
27
- })
28
- .filter(Boolean)
29
- .join('\n');
30
- };
31
-
32
- export const getLastUserText = (context: Context): string => {
33
- for (let i = context.messages.length - 1; i >= 0; i--) {
34
- const message = context.messages[i];
35
- if (message.role === 'user') {
36
- return extractTextFromContent(message.content).trim();
37
- }
38
- }
39
- return '';
40
- };
41
-
42
- export const getFirstUserText = (context: Context): string => {
43
- for (const message of context.messages) {
44
- if (message.role === 'user') {
45
- return extractTextFromContent(message.content).trim();
46
- }
47
- }
48
- return '';
49
- };
50
-
51
- /** Context for one-shot session model lock: system prompt + first user message only. */
52
- export const buildSessionLockContext = (context: Context): Context => {
53
- const firstUser = context.messages.find((message) => message.role === 'user');
54
- const messages = firstUser ? [firstUser] : context.messages.slice(0, 1);
55
- return { ...context, messages };
56
- };
57
-
58
- export const sessionLockToRoutingDecision = (
59
- lock: SessionLock,
60
- profile: RouterProfile,
61
- thinkingOverrides?: RouterThinkingByTier,
62
- ): RoutingDecision => {
63
- const routed = profile[lock.tier];
64
- const { provider, modelId } = parseCanonicalModelRef(lock.modelRef);
65
- const baseThinking =
66
- routed.thinking ??
67
- (lock.tier === 'high' ? 'high' : lock.tier === 'low' ? 'low' : 'medium');
68
- const effectiveThinking = thinkingOverrides?.[lock.tier] ?? baseThinking;
69
- return {
70
- profile: lock.profile,
71
- tier: lock.tier,
72
- phase: phaseForTier(lock.tier),
73
- targetProvider: provider,
74
- targetModelId: modelId,
75
- targetLabel: lock.modelRef,
76
- reasoning: lock.reasoning,
77
- thinking: effectiveThinking,
78
- timestamp: Date.now(),
79
- };
80
- };
81
-
82
- export const routingDecisionToSessionLock = (
83
- decision: RoutingDecision,
84
- ): SessionLock => ({
85
- profile: decision.profile,
86
- tier: decision.tier,
87
- modelRef: decision.targetLabel,
88
- reasoning: decision.reasoning,
89
- });
90
-
91
- /** Text used to score initial session complexity (system prompt + first user message). */
92
- export const getSessionLockAnalysisText = (context: Context): string => {
93
- const lockContext = buildSessionLockContext(context);
94
- const system = (context.systemPrompt ?? '').trim();
95
- const firstUser = getFirstUserText(lockContext);
96
- if (system && firstUser) return `${system}\n\n${firstUser}`;
97
- return system || firstUser || '';
98
- };
99
-
100
- /**
101
- * One-shot tier pick for session model lock (no prior-turn stickiness).
102
- */
103
- export const decideSessionLock = (
104
- context: Context,
105
- profileName: string,
106
- profile: RouterProfile,
107
- pinnedTier?: RouterTier,
108
- thinkingOverrides?: RouterThinkingByTier,
109
- phaseBias = 0.5,
110
- rules?: RoutingRule[],
111
- ): RoutingDecision => {
112
- const analysisText = getSessionLockAnalysisText(context);
113
- const synthetic: Context = {
114
- systemPrompt: context.systemPrompt,
115
- messages: analysisText
116
- ? [{ role: 'user', content: analysisText, timestamp: Date.now() }]
117
- : [],
118
- };
119
- return decideRouting(
120
- synthetic,
121
- profileName,
122
- profile,
123
- undefined,
124
- pinnedTier,
125
- thinkingOverrides,
126
- phaseBias,
127
- rules,
128
- false,
129
- );
130
- };
131
-
132
- /** Per-turn thinking tier merged onto a session-locked model decision. */
133
- export const applyThinkingToDecision = (
134
- locked: RoutingDecision,
135
- thinkingDecision: RoutingDecision,
136
- profile: RouterProfile,
137
- thinkingOverrides?: RouterThinkingByTier,
138
- ): RoutingDecision => {
139
- const tier = thinkingDecision.tier;
140
- const routed = profile[tier];
141
- const baseThinking =
142
- routed.thinking ??
143
- (tier === 'high' ? 'high' : tier === 'low' ? 'low' : 'medium');
144
- const effectiveThinking = thinkingOverrides?.[tier] ?? baseThinking;
145
-
146
- return {
147
- profile: locked.profile,
148
- tier,
149
- phase: thinkingDecision.phase,
150
- targetProvider: locked.targetProvider,
151
- targetModelId: locked.targetModelId,
152
- targetLabel: locked.targetLabel,
153
- reasoning: `${locked.reasoning} | Thinking: ${thinkingDecision.reasoning}`,
154
- thinking: effectiveThinking,
155
- timestamp: Date.now(),
156
- isClassifier: thinkingDecision.isClassifier,
157
- isRuleMatched: thinkingDecision.isRuleMatched,
158
- isBudgetForced: thinkingDecision.isBudgetForced,
159
- isContextTriggered: thinkingDecision.isContextTriggered,
160
- };
161
- };
162
-
163
- /** Harness subagents: tier from system prompt + optional first user line. */
164
- export const resolveTierFromPrompt = (
165
- systemPrompt: string,
166
- userText: string,
167
- profileName: string,
168
- profile: RouterProfile,
169
- rules?: RoutingRule[],
170
- phaseBias = 0.5,
171
- ): RouterTier => {
172
- const context: Context = {
173
- systemPrompt,
174
- messages: userText
175
- ? [{ role: 'user', content: userText, timestamp: Date.now() }]
176
- : [],
177
- };
178
- return decideSessionLock(
179
- context,
180
- profileName,
181
- profile,
182
- undefined,
183
- undefined,
184
- phaseBias,
185
- rules,
186
- ).tier;
187
- };
188
-
189
- export const getRecentConversationText = (
190
- context: Context,
191
- limit = 6,
192
- ): string => {
193
- return context.messages
194
- .slice(-limit)
195
- .map((message) => extractTextFromContent(message.content).trim())
196
- .filter(Boolean)
197
- .join('\n')
198
- .toLowerCase();
199
- };
200
-
201
- export const countToolResults = (context: Context): number => {
202
- return context.messages.filter((message) => message.role === 'toolResult')
203
- .length;
204
- };
205
-
206
- export const countWords = (text: string): number => {
207
- return text.split(/\s+/).filter(Boolean).length;
208
- };
209
-
210
- export const hasImageAttachment = (context: Context): boolean => {
211
- return context.messages.some(
212
- (message) =>
213
- Array.isArray(message.content) &&
214
- message.content.some((part) => part.type === 'image'),
215
- );
216
- };
217
-
218
- export const containsAny = (text: string, keywords: string[]): boolean => {
219
- return keywords.some((keyword) => text.includes(keyword));
220
- };
221
-
222
- export const phaseForTier = (tier: RouterTier): RouterPhase => {
223
- if (tier === 'high') return 'planning';
224
- if (tier === 'medium') return 'implementation';
225
- return 'lightweight';
226
- };
227
-
228
- export const buildRoutingDecision = (
229
- profileName: string,
230
- profile: RouterProfile,
231
- tier: RouterTier,
232
- phase: RouterPhase,
233
- reasoning: string,
234
- thinkingOverrides?: RouterThinkingByTier,
235
- isClassifier?: boolean,
236
- ): RoutingDecision => {
237
- const routed = profile[tier];
238
- const { provider, modelId } = parseCanonicalModelRef(routed.model);
239
- const baseThinking =
240
- routed.thinking ??
241
- (tier === 'high' ? 'high' : tier === 'low' ? 'low' : 'medium');
242
- const effectiveThinking = thinkingOverrides?.[tier] ?? baseThinking;
243
-
244
- return {
245
- profile: profileName,
246
- tier,
247
- phase,
248
- targetProvider: provider,
249
- targetModelId: modelId,
250
- targetLabel: routed.model,
251
- reasoning,
252
- thinking: effectiveThinking,
253
- timestamp: Date.now(),
254
- isClassifier,
255
- };
256
- };
257
-
258
- export const decideRouting = (
259
- context: Context,
260
- profileName: string,
261
- profile: RouterProfile,
262
- previousDecision: RoutingDecision | undefined,
263
- pinnedTier?: RouterTier,
264
- thinkingOverrides?: RouterThinkingByTier,
265
- phaseBias = 0.5,
266
- rules?: RoutingRule[],
267
- isBudgetExceeded = false,
268
- ): RoutingDecision => {
269
- const prompt = getLastUserText(context).toLowerCase();
270
- const recentConversation = getRecentConversationText(context);
271
- const toolResultCount = countToolResults(context);
272
- const wordCount = countWords(prompt);
273
- const multiLinePrompt = prompt.split('\n').length >= 4;
274
-
275
- const explicitHighHints = [
276
- 'best',
277
- 'deep',
278
- 'deeply',
279
- 'carefully',
280
- 'thoroughly',
281
- 'robust',
282
- 'comprehensive',
283
- 'step by step',
284
- 'think hard',
285
- 'highest quality',
286
- ];
287
- const explicitLowHints = [
288
- 'fast',
289
- 'cheap',
290
- 'quick',
291
- 'quickly',
292
- 'brief',
293
- 'briefly',
294
- 'one sentence',
295
- 'one line',
296
- 'tiny',
297
- 'small',
298
- ];
299
- const planningKeywords = [
300
- 'plan',
301
- 'planning',
302
- 'architecture',
303
- 'architect',
304
- 'design',
305
- 'tradeoff',
306
- 'trade-off',
307
- 'research',
308
- 'investigate',
309
- 'root cause',
310
- 'analyze',
311
- 'analysis',
312
- 'migration',
313
- 'strategy',
314
- 'compare',
315
- 'options',
316
- 'approach',
317
- ];
318
- const summaryKeywords = [
319
- 'summarize',
320
- 'summary',
321
- 'changelog',
322
- 'rewrite',
323
- 'reformat',
324
- 'format',
325
- 'rename',
326
- 'explain briefly',
327
- 'recap',
328
- 'tl;dr',
329
- ];
330
- const implementationKeywords = [
331
- 'implement',
332
- 'code',
333
- 'fix',
334
- 'update',
335
- 'edit',
336
- 'write',
337
- 'refactor',
338
- 'add tests',
339
- 'patch',
340
- 'change',
341
- 'apply',
342
- 'continue',
343
- 'resume',
344
- 'make the changes',
345
- 'go ahead',
346
- ];
347
- const lookupKeywords = [
348
- 'where is',
349
- 'which file',
350
- 'show me',
351
- 'list',
352
- 'what files',
353
- 'find',
354
- 'grep',
355
- ];
356
-
357
- let phase: RouterPhase = previousDecision?.phase ?? 'implementation';
358
- let tier: RouterTier = 'medium';
359
- let reasoning = 'Defaulted to medium tier for general coding work.';
360
- let isRuleMatched = false;
361
-
362
- if (pinnedTier) {
363
- phase = phaseForTier(pinnedTier);
364
- tier = pinnedTier;
365
- reasoning = `Pinned to ${pinnedTier} tier via /router-pin.`;
366
- } else {
367
- // Check custom rules first
368
- if (rules) {
369
- for (const rule of rules) {
370
- const matches = Array.isArray(rule.matches)
371
- ? rule.matches
372
- : [rule.matches];
373
- if (containsAny(prompt, matches)) {
374
- tier = rule.tier;
375
- phase = phaseForTier(tier);
376
- reasoning =
377
- rule.reason ??
378
- `Matched custom routing rule for: ${matches.join(', ')}`;
379
- isRuleMatched = true;
380
- break;
381
- }
382
- }
383
- }
384
-
385
- if (!isRuleMatched) {
386
- // Sticky phase adjustments
387
- const highThreshold = Math.max(
388
- 40,
389
- 120 - (previousDecision?.phase === 'planning' ? phaseBias * 80 : 0),
390
- );
391
- const lowThreshold = Math.max(
392
- 4,
393
- 12 -
394
- (previousDecision?.phase === 'implementation' ||
395
- previousDecision?.phase === 'planning'
396
- ? phaseBias * 8
397
- : 0),
398
- );
399
-
400
- if (containsAny(prompt, explicitHighHints)) {
401
- phase = 'planning';
402
- tier = 'high';
403
- reasoning =
404
- 'Detected an explicit request for deeper or higher-quality reasoning.';
405
- } else if (containsAny(prompt, explicitLowHints)) {
406
- phase = 'lightweight';
407
- tier = 'low';
408
- reasoning =
409
- 'Detected an explicit request for a faster or lighter response.';
410
- } else if (containsAny(prompt, summaryKeywords)) {
411
- phase = 'lightweight';
412
- tier = 'low';
413
- reasoning = 'Detected summary or lightweight transformation keywords.';
414
- } else if (
415
- containsAny(prompt, planningKeywords) ||
416
- prompt.startsWith('why ') ||
417
- wordCount >= highThreshold ||
418
- multiLinePrompt
419
- ) {
420
- phase = 'planning';
421
- tier = 'high';
422
- reasoning =
423
- previousDecision?.phase === 'planning'
424
- ? 'Continued planning phase based on complexity or keywords.'
425
- : 'Detected planning, broad analysis, or a high-complexity request.';
426
- } else if (containsAny(prompt, implementationKeywords)) {
427
- phase = 'implementation';
428
- tier = 'medium';
429
- reasoning =
430
- 'Detected implementation-oriented work with bounded execution scope.';
431
- } else if (
432
- containsAny(prompt, lookupKeywords) &&
433
- wordCount <= 24 &&
434
- toolResultCount === 0
435
- ) {
436
- phase = 'lightweight';
437
- tier = 'low';
438
- reasoning = 'Detected a short read-only lookup request.';
439
- } else if (
440
- previousDecision?.phase === 'planning' &&
441
- toolResultCount === 0 &&
442
- wordCount > lowThreshold
443
- ) {
444
- phase = 'planning';
445
- tier = 'high';
446
- reasoning =
447
- 'Kept the planning-phase bias because the conversation still looks exploratory.';
448
- } else if (
449
- toolResultCount > 0 ||
450
- previousDecision?.phase === 'implementation' ||
451
- recentConversation.includes('plan:')
452
- ) {
453
- phase = 'implementation';
454
- tier = 'medium';
455
- reasoning =
456
- 'Detected active implementation work from prior tools or recent plan execution context.';
457
- } else if (wordCount <= lowThreshold) {
458
- phase = 'lightweight';
459
- tier = 'low';
460
- reasoning = 'Detected a short bounded request.';
461
- }
462
- }
463
- }
464
-
465
- let isBudgetForced = false;
466
- if (isBudgetExceeded && tier === 'high') {
467
- tier = 'medium';
468
- phase = 'implementation';
469
- reasoning = `Budget exceeded. Downgraded from high to medium tier. (Original: ${reasoning})`;
470
- isBudgetForced = true;
471
- }
472
-
473
- const decision = buildRoutingDecision(
474
- profileName,
475
- profile,
476
- tier,
477
- phase,
478
- reasoning,
479
- thinkingOverrides,
480
- false,
481
- );
482
- decision.isRuleMatched = isRuleMatched;
483
- decision.isBudgetForced = isBudgetForced;
484
- return decision;
485
- };
486
-
487
- export const runClassifier = async (
488
- classifierModelRef: string,
489
- modelRegistry: ExtensionContext['modelRegistry'],
490
- context: Context,
491
- currentPhase?: RouterPhase,
492
- ): Promise<{ tier: RouterTier; reasoning: string } | undefined> => {
493
- try {
494
- const { provider, modelId } = parseCanonicalModelRef(classifierModelRef);
495
- const model = modelRegistry.find(provider, modelId);
496
- if (!model) return undefined;
497
-
498
- const auth = await modelRegistry.getApiKeyAndHeaders(model);
499
- if (!auth.ok || !auth.apiKey) return undefined;
500
- const apiKey = auth.apiKey;
501
- const headers = auth.headers;
502
-
503
- const promptText = getLastUserText(context);
504
- const historyText = getRecentConversationText(context, 4);
505
-
506
- const classifierPrompt = `You are a model router classifier. Your job is to categorize the user's latest request into one of three tiers: "high", "medium", or "low".
507
-
508
- Tiers:
509
- - high: Architecture, design, planning, tradeoff analysis, broad debugging, large refactors, codebase research.
510
- - medium: Implementation of a known plan, multi-file edits, normal coding work, focused debugging, tests/fixes.
511
- - low: Summaries, changelogs, formatting, quick explanations, small bounded transforms, simple read-only lookup.
512
-
513
- ${currentPhase ? `Current conversation phase: ${currentPhase}\n` : ''}
514
- Recent history:
515
- ${historyText}
516
-
517
- Latest user message:
518
- ${promptText}
519
-
520
- Return your decision in exactly two lines:
521
- Tier: [high|medium|low]
522
- Reasoning: [one short sentence]
523
-
524
- ${currentPhase === 'planning' ? 'Consider that the conversation is currently in a planning phase. Bias toward "high" unless the request is clearly a simple implementation or summary.' : ''}
525
- ${currentPhase === 'implementation' ? 'Consider that the conversation is currently in an implementation phase. Bias toward "medium" unless the request is clearly planning or a simple summary.' : ''}`;
526
-
527
- const classifierContext: Context = {
528
- ...context,
529
- messages: [{ role: 'user', content: classifierPrompt, timestamp: Date.now() }],
530
- };
531
-
532
- const stream = streamSimple(model, classifierContext, { apiKey, headers });
533
- let fullText = '';
534
- for await (const event of stream) {
535
- if (
536
- event.type === 'text_delta' &&
537
- typeof (event as any).delta === 'string'
538
- ) {
539
- fullText += (event as any).delta;
540
- }
541
- }
542
-
543
- const lines = fullText.trim().split('\n');
544
- const tierLine = lines.find((l) => l.toLowerCase().startsWith('tier:'));
545
- const reasoningLine = lines.find((l) =>
546
- l.toLowerCase().startsWith('reasoning:'),
547
- );
548
-
549
- if (tierLine) {
550
- const tierValue = tierLine.split(':')[1].trim().toLowerCase();
551
- if (isRouterTier(tierValue)) {
552
- return {
553
- tier: tierValue,
554
- reasoning: reasoningLine
555
- ? reasoningLine.split(':')[1].trim()
556
- : 'Classifier decision.',
557
- };
558
- }
559
- }
560
- } catch (error) {
561
- // Ignore classifier errors and fall back to heuristics
562
- }
563
- return undefined;
564
- };
@@ -1,52 +0,0 @@
1
- import type {
2
- RouterPinByProfile,
3
- RouterThinkingByProfile,
4
- RoutingDecision,
5
- RouterPersistedState,
6
- SessionLock,
7
- } from './types.js';
8
-
9
- export const isRouterPersistedState = (
10
- value: unknown,
11
- ): value is RouterPersistedState => {
12
- if (typeof value !== 'object' || value === null) {
13
- return false;
14
- }
15
- const v = value as any;
16
- return (
17
- typeof v.enabled === 'boolean' &&
18
- typeof v.selectedProfile === 'string' &&
19
- typeof v.timestamp === 'number'
20
- );
21
- };
22
-
23
- export const buildPersistedState = (
24
- routerEnabled: boolean,
25
- selectedProfile: string,
26
- pinnedTierByProfile: RouterPinByProfile,
27
- thinkingByProfile: RouterThinkingByProfile,
28
- debugEnabled: boolean,
29
- widgetEnabled: boolean,
30
- debugHistory: RoutingDecision[],
31
- lastDecision: RoutingDecision | undefined,
32
- lastNonRouterModel: string | undefined,
33
- accumulatedCost: number,
34
- sessionLock: SessionLock | undefined,
35
- ): RouterPersistedState => {
36
- return {
37
- enabled: routerEnabled,
38
- selectedProfile,
39
- pinTier: pinnedTierByProfile[selectedProfile],
40
- pinByProfile: { ...pinnedTierByProfile },
41
- thinkingByProfile: { ...thinkingByProfile },
42
- sessionLock,
43
- debugEnabled,
44
- widgetEnabled,
45
- debugHistory,
46
- lastPhase: lastDecision?.phase,
47
- lastDecision,
48
- lastNonRouterModel,
49
- accumulatedCost,
50
- timestamp: Date.now(),
51
- };
52
- };
@@ -1,95 +0,0 @@
1
- import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
2
-
3
- export type RouterTier = 'high' | 'medium' | 'low';
4
- export type RouterPin = RouterTier | 'auto';
5
- export type RouterPhase = 'planning' | 'implementation' | 'lightweight';
6
- export type RouterPinByProfile = Partial<Record<string, RouterTier>>;
7
- export type RouterThinkingByTier = Partial<Record<RouterTier, ThinkingLevel>>;
8
- export type RouterThinkingByProfile = Record<string, RouterThinkingByTier>;
9
-
10
- export interface RoutingRule {
11
- matches: string | string[];
12
- tier: RouterTier;
13
- reason?: string;
14
- }
15
-
16
- export interface RoutedTierConfig {
17
- model: string;
18
- thinking?: ThinkingLevel;
19
- fallbacks?: string[];
20
- }
21
-
22
- export interface RouterProfile {
23
- high: RoutedTierConfig;
24
- medium: RoutedTierConfig;
25
- low: RoutedTierConfig;
26
- }
27
-
28
- export interface RouterConfig {
29
- defaultProfile?: string;
30
- debug?: boolean;
31
- classifierModel?: string;
32
- phaseBias?: number;
33
- largeContextThreshold?: number;
34
- maxSessionBudget?: number;
35
- rules?: RoutingRule[];
36
- profiles: Record<string, RouterProfile>;
37
- }
38
-
39
- export interface RoutingDecision {
40
- profile: string;
41
- tier: RouterTier;
42
- phase: RouterPhase;
43
- targetProvider: string;
44
- targetModelId: string;
45
- targetLabel: string;
46
- reasoning: string;
47
- thinking: ThinkingLevel;
48
- timestamp: number;
49
- isClassifier?: boolean;
50
- isFallback?: boolean;
51
- isContextTriggered?: boolean;
52
- isBudgetForced?: boolean;
53
- isRuleMatched?: boolean;
54
- }
55
-
56
- /** Fixed model for a router profile for the lifetime of a session (or until profile switch). */
57
- export interface SessionLock {
58
- profile: string;
59
- tier: RouterTier;
60
- modelRef: string;
61
- reasoning: string;
62
- }
63
-
64
- export interface RouterPersistedState {
65
- enabled: boolean;
66
- selectedProfile: string;
67
- pinTier?: RouterTier;
68
- pinByProfile?: RouterPinByProfile;
69
- thinkingByProfile?: RouterThinkingByProfile;
70
- sessionLock?: SessionLock;
71
- debugEnabled?: boolean;
72
- widgetEnabled?: boolean;
73
- debugHistory?: RoutingDecision[];
74
- lastPhase?: RouterPhase;
75
- lastDecision?: RoutingDecision;
76
- lastNonRouterModel?: string;
77
- accumulatedCost?: number;
78
- timestamp: number;
79
- }
80
-
81
- export interface ConfigLoadResult {
82
- config: RouterConfig;
83
- warnings: string[];
84
- }
85
-
86
- export interface ParsedConfigFile {
87
- config: Partial<RouterConfig>;
88
- warnings: string[];
89
- }
90
-
91
- export interface CustomSessionEntry {
92
- type: string;
93
- customType?: string;
94
- data?: unknown;
95
- }