@polderlabs/bizar 6.3.0 → 7.0.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.
- package/.claude/hooks/sessionend-recall.mjs +3 -3
- package/.claude/hooks/worker-suggest.mjs +110 -0
- package/.claude/settings.json +9 -0
- package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-CW6fWKds.js} +3 -3
- package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js.map → EnvVarsSection-CW6fWKds.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileChat-D7zRhU9K.js +1 -0
- package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-D7zRhU9K.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-K-sNsn_r.js +1 -0
- package/bizar-dash/dist/assets/{MobileSettings-CEQNJNLJ.js.map → MobileSettings-K-sNsn_r.js.map} +1 -1
- package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
- package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
- package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
- package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
- package/bizar-dash/dist/assets/main-C-Md3DaY.css +1 -0
- package/bizar-dash/dist/assets/main-CO8fIxQK.js +18 -0
- package/bizar-dash/dist/assets/main-CO8fIxQK.js.map +1 -0
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
- package/bizar-dash/dist/assets/{markdown-tOLaD6nm.js.map → markdown-w2bDDkAH.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-DXYCA0hC.js +1 -0
- package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DXYCA0hC.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js +2 -0
- package/bizar-dash/dist/assets/mobile-layout-DSmtNPja.js.map +1 -0
- package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
- package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js +2 -0
- package/bizar-dash/dist/assets/useSlashCommands-3DFLOfpk.js.map +1 -0
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
- package/bizar-dash/dist/index.html +12 -10
- package/bizar-dash/dist/mobile.html +7 -4
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/api.mjs +11 -0
- package/bizar-dash/src/server/memory-consolidator.mjs +218 -0
- package/bizar-dash/src/server/memory-distillation.mjs +296 -0
- package/bizar-dash/src/server/routes/distill.mjs +129 -0
- package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
- package/bizar-dash/src/web/App.tsx +4 -0
- package/bizar-dash/src/web/components/Tag.tsx +18 -5
- package/bizar-dash/src/web/components/Topbar.tsx +3 -0
- package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
- package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
- package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
- package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
- package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
- package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
- package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
- package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
- package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
- package/bizar-dash/src/web/lib/types.ts +25 -0
- package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
- package/bizar-dash/src/web/styles/main.css +251 -0
- package/bizar-dash/src/web/styles/tasks-redesign.css +231 -0
- package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
- package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
- package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
- package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
- package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
- package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
- package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
- package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
- package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
- package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
- package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
- package/bizar-dash/src/web/ui/controls/controls.css +454 -0
- package/bizar-dash/src/web/ui/controls/index.ts +33 -0
- package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
- package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
- package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
- package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
- package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
- package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
- package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
- package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
- package/bizar-dash/src/web/ui/data/data.css +535 -0
- package/bizar-dash/src/web/ui/data/index.ts +31 -0
- package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
- package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
- package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
- package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
- package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
- package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
- package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
- package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
- package/bizar-dash/src/web/ui/index.ts +56 -0
- package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
- package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
- package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
- package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
- package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
- package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
- package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
- package/bizar-dash/src/web/ui/layout/index.ts +33 -0
- package/bizar-dash/src/web/ui/layout/layout.css +413 -0
- package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
- package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
- package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
- package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
- package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
- package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
- package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
- package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
- package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
- package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
- package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
- package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
- package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
- package/bizar-dash/src/web/ui/styles/globals.css +86 -0
- package/bizar-dash/src/web/ui/styles/reset.css +125 -0
- package/bizar-dash/src/web/ui/styles/tokens.css +195 -0
- package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
- package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
- package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
- package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
- package/bizar-dash/src/web/views/Agents.tsx +506 -266
- package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
- package/bizar-dash/src/web/views/Overview.tsx +996 -448
- package/bizar-dash/src/web/views/Tasks.tsx +277 -273
- package/bizar-dash/tests/communication-log.test.tsx +67 -0
- package/bizar-dash/tests/dep-graph.test.tsx +40 -0
- package/bizar-dash/tests/event-log.test.tsx +61 -0
- package/bizar-dash/tests/goal-input.test.tsx +72 -0
- package/bizar-dash/tests/goap-planner.test.ts +68 -0
- package/bizar-dash/tests/quality-gates.test.tsx +59 -0
- package/bizar-dash/tests/setup.ts +31 -1
- package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
- package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
- package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
- package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
- package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
- package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
- package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
- package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
- package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
- package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
- package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
- package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
- package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
- package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
- package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
- package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
- package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
- package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
- package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
- package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
- package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
- package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
- package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
- package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
- package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
- package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
- package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
- package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
- package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
- package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
- package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
- package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
- package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
- package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
- package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
- package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
- package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
- package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
- package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
- package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
- package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
- package/bizar-dash/tests/views/Agents.test.tsx +359 -0
- package/bizar-dash/tests/views/Overview.test.tsx +413 -0
- package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
- package/bizar-dash/vitest.config.ts +3 -1
- package/cli/__tests__/cost-gate.test.mjs +301 -0
- package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
- package/cli/bin.mjs +38 -0
- package/cli/commands/claim.mjs +282 -0
- package/cli/commands/cost.mjs +279 -0
- package/cli/cost-gate.mjs +501 -0
- package/cli/feature-list-bridge.mjs +400 -0
- package/cli/memory-constants.mjs +17 -0
- package/cli/worker-dispatcher.mjs +259 -0
- package/cli/worker-dispatcher.test.mjs +204 -0
- package/config/trigger-patterns.json +186 -0
- package/package.json +2 -2
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/agent-registry.ts +304 -0
- package/packages/sdk/src/consensus/byzantine.ts +587 -0
- package/packages/sdk/src/consensus/index.ts +147 -0
- package/packages/sdk/src/consensus/queen.ts +173 -0
- package/packages/sdk/src/consensus/types.ts +199 -0
- package/packages/sdk/src/federation/audit.ts +156 -0
- package/packages/sdk/src/federation/budget.ts +269 -0
- package/packages/sdk/src/federation/envelope.ts +144 -0
- package/packages/sdk/src/federation/hmac.ts +160 -0
- package/packages/sdk/src/federation/index.ts +443 -0
- package/packages/sdk/src/federation/pii.ts +241 -0
- package/packages/sdk/src/federation/policy.ts +112 -0
- package/packages/sdk/src/federation/trust.ts +146 -0
- package/packages/sdk/src/index.ts +67 -0
- package/packages/sdk/src/mcp/server.ts +351 -1
- package/packages/sdk/src/router/codemod-intent.ts +125 -0
- package/packages/sdk/src/router/index.ts +247 -0
- package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
- package/packages/sdk/src/router/memory-distillation.ts +249 -0
- package/packages/sdk/src/router/model-router.ts +235 -0
- package/packages/sdk/src/router/q-learning-router.ts +304 -0
- package/packages/sdk/src/swarm-topology.ts +279 -0
- package/packages/sdk/tests/agent-registry.test.ts +285 -0
- package/packages/sdk/tests/consensus.test.ts +376 -0
- package/packages/sdk/tests/federation/audit.test.ts +164 -0
- package/packages/sdk/tests/federation/budget.test.ts +189 -0
- package/packages/sdk/tests/federation/envelope.test.ts +120 -0
- package/packages/sdk/tests/federation/hmac.test.ts +194 -0
- package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
- package/packages/sdk/tests/federation/pii.test.ts +106 -0
- package/packages/sdk/tests/federation/policy.test.ts +140 -0
- package/packages/sdk/tests/federation/trust.test.ts +124 -0
- package/packages/sdk/tests/mcp-tools.test.ts +204 -0
- package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
- package/packages/sdk/tests/model-router.test.mjs +108 -0
- package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
- package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
- package/packages/sdk/tests/router.test.mjs +91 -0
- package/packages/sdk/tests/sdk.test.mjs +26 -2
- package/packages/sdk/tests/swarm-topology.test.ts +258 -0
- package/scripts/bh-full-e2e.mjs +11 -15
- package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
- package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
- package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
- package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
- package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
- package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
- package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
- package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
- package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
- package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
- package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
- package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
- package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
- package/packages/sdk/package-lock.json +0 -32
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* router/index.ts — orchestrator for Bizar's self-learning router.
|
|
3
|
+
*
|
|
4
|
+
* v6.4.0 — F-033. Composes the three sub-routers
|
|
5
|
+
* (`codemod-intent`, `model-router`, `q-learning-router`) into a
|
|
6
|
+
* single precedence chain:
|
|
7
|
+
*
|
|
8
|
+
* explicit-route (caller-provided) >
|
|
9
|
+
* codemod-intent ($0 Tier-1 short-circuit) >
|
|
10
|
+
* q-learning-router (8-action agent pick) >
|
|
11
|
+
* model-router (Thompson bandit for tier) >
|
|
12
|
+
* default ("odin", "mid", confidence 0.5)
|
|
13
|
+
*
|
|
14
|
+
* The orchestrator is what the `hooks_route` MCP tool (and any
|
|
15
|
+
* in-process caller such as the Odin agent) calls to make a routing
|
|
16
|
+
* decision. It surfaces two human-readable tags on `stdout`-equivalent
|
|
17
|
+
* surfaces:
|
|
18
|
+
*
|
|
19
|
+
* - `[CODEMOD_AVAILABLE] <intent>` — printed BEFORE the model call
|
|
20
|
+
* would happen, telling the model that a deterministic $0 codemod
|
|
21
|
+
* could short-circuit the work. Honoured by the Odin prompt.
|
|
22
|
+
* - `[TASK_MODEL_RECOMMENDATION] <tier> (conf=<n>)` — appended to
|
|
23
|
+
* prompt-side telemetry so downstream model selection knows the
|
|
24
|
+
* bandit-learner thinks this task should be routed to `flash` /
|
|
25
|
+
* `mid` / `expensive`.
|
|
26
|
+
*
|
|
27
|
+
* Persistence: when constructed via `getRouter({ persist: true })`,
|
|
28
|
+
* the orchestrator holds a single shared `ModelRouter` +
|
|
29
|
+
* `QLearningRouter` whose state lives at `.harness/router-state.json`
|
|
30
|
+
* and `.harness/q-router-state.json` respectively. Callers can also
|
|
31
|
+
* pass `modelRouter` / `qRouter` for in-memory-only operation
|
|
32
|
+
* (preferred in tests).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
36
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
detectCodemodIntent,
|
|
40
|
+
type CodemodIntent,
|
|
41
|
+
CODEMOD_CONFIDENCE_THRESHOLD,
|
|
42
|
+
} from "./codemod-intent.js";
|
|
43
|
+
import {
|
|
44
|
+
ModelRouter,
|
|
45
|
+
type ModelTier,
|
|
46
|
+
type RouteDecision,
|
|
47
|
+
} from "./model-router.js";
|
|
48
|
+
import {
|
|
49
|
+
QLearningRouter,
|
|
50
|
+
AGENT_ACTIONS,
|
|
51
|
+
type AgentRouteDecision,
|
|
52
|
+
} from "./q-learning-router.js";
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Public types
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
export interface RouteInput {
|
|
59
|
+
task: string;
|
|
60
|
+
/** Caller-provided explicit override for the agent (highest precedence). */
|
|
61
|
+
explicitAgent?: string;
|
|
62
|
+
/** Optional embedding — currently ignored (reserved for future ANN
|
|
63
|
+
* lookup that could refine the state bucket). */
|
|
64
|
+
embedding?: number[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RouteDecisionOutput {
|
|
68
|
+
agent: string;
|
|
69
|
+
modelTier: ModelTier;
|
|
70
|
+
agentConfidence: number;
|
|
71
|
+
modelConfidence: number;
|
|
72
|
+
/** Null unless the prompt matched a Tier-1 codemod intent. */
|
|
73
|
+
codemodIntent: CodemodIntent | null;
|
|
74
|
+
/** `[CODEMOD_AVAILABLE] var-to-const` style surface for prompt pre-injection. */
|
|
75
|
+
surfacedTags: string[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface RouterBundle {
|
|
79
|
+
modelRouter: ModelRouter;
|
|
80
|
+
qRouter: QLearningRouter;
|
|
81
|
+
decideAgent(input: RouteInput): RouteDecisionOutput;
|
|
82
|
+
/** Persist both routers to `path`'s sibling state files. */
|
|
83
|
+
saveTo(path: string): void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Factory
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
export interface GetRouterOpts {
|
|
91
|
+
/** When provided, the orchestrator attempts to load priors + Q-table
|
|
92
|
+
* from `<basePath>.{model,q}` if those files exist; missing files
|
|
93
|
+
* fall back to defaults. */
|
|
94
|
+
basePath?: string;
|
|
95
|
+
/** In-memory overrides — useful for tests. */
|
|
96
|
+
modelRouter?: ModelRouter;
|
|
97
|
+
qRouter?: QLearningRouter;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Default state-file paths (kept beside the orchestrator's caller). */
|
|
101
|
+
export const DEFAULT_MODEL_STATE_PATH = ".harness/router-state.json";
|
|
102
|
+
export const DEFAULT_Q_STATE_PATH = ".harness/q-router-state.json";
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build (or rehydrate) a router bundle.
|
|
106
|
+
*
|
|
107
|
+
* Default state files are stored under `.harness/` in the current
|
|
108
|
+
* working directory. Tests typically pass `modelRouter` / `qRouter`
|
|
109
|
+
* to keep things in-process.
|
|
110
|
+
*/
|
|
111
|
+
export function getRouter(opts: GetRouterOpts = {}): RouterBundle {
|
|
112
|
+
const basePath = opts.basePath ?? join(process.cwd(), ".harness", "router-state");
|
|
113
|
+
const modelPath = `${basePath}.model`;
|
|
114
|
+
const qPath = `${basePath}.q`;
|
|
115
|
+
|
|
116
|
+
const modelRouter =
|
|
117
|
+
opts.modelRouter ??
|
|
118
|
+
(existsSync(modelPath) ? ModelRouter.loadFrom(modelPath) : new ModelRouter());
|
|
119
|
+
const qRouter =
|
|
120
|
+
opts.qRouter ??
|
|
121
|
+
(existsSync(qPath) ? QLearningRouter.loadFrom(qPath) : new QLearningRouter());
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
modelRouter,
|
|
125
|
+
qRouter,
|
|
126
|
+
decideAgent(input: RouteInput): RouteDecisionOutput {
|
|
127
|
+
return decideAgentWith(modelRouter, qRouter, input);
|
|
128
|
+
},
|
|
129
|
+
saveTo(path: string): void {
|
|
130
|
+
const fp = isAbsolute(path) ? path : resolve(path);
|
|
131
|
+
mkdirSync(dirname(fp), { recursive: true });
|
|
132
|
+
modelRouter.saveTo(modelPath);
|
|
133
|
+
qRouter.saveTo(qPath);
|
|
134
|
+
// And a tiny pointer file for callers that only know the bundle name.
|
|
135
|
+
writeFileSync(
|
|
136
|
+
fp,
|
|
137
|
+
JSON.stringify({ modelPath, qPath, version: 1 }, null, 2),
|
|
138
|
+
"utf-8",
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Pure, testable entry point — the actual precedence chain.
|
|
146
|
+
*
|
|
147
|
+
* Order:
|
|
148
|
+
* 1. caller-supplied `explicitAgent` wins outright.
|
|
149
|
+
* 2. Tier-1 codemod intent → agent="heimdall" (routine implementation),
|
|
150
|
+
* tier="flash" ($0), confidence 1.0, surface `[CODEMOD_AVAILABLE]`.
|
|
151
|
+
* 3. Q-learning agent pick (clamped to known agent names).
|
|
152
|
+
* 4. Thompson-bandit model-tier pick.
|
|
153
|
+
* 5. Defaults: agent="odin", tier="mid".
|
|
154
|
+
*/
|
|
155
|
+
export function decideAgentWith(
|
|
156
|
+
modelRouter: ModelRouter,
|
|
157
|
+
qRouter: QLearningRouter,
|
|
158
|
+
input: RouteInput,
|
|
159
|
+
): RouteDecisionOutput {
|
|
160
|
+
const task = String(input?.task ?? "");
|
|
161
|
+
const surfacedTags: string[] = [];
|
|
162
|
+
|
|
163
|
+
// 1. Explicit override — highest precedence.
|
|
164
|
+
if (input.explicitAgent && AGENT_ACTIONS.includes(input.explicitAgent)) {
|
|
165
|
+
// Still pick a model tier (bandit) so we can surface
|
|
166
|
+
// [TASK_MODEL_RECOMMENDATION] downstream.
|
|
167
|
+
const tier: RouteDecision = modelRouter.route(task);
|
|
168
|
+
surfacedTags.push(tierTag(tier));
|
|
169
|
+
return {
|
|
170
|
+
agent: input.explicitAgent,
|
|
171
|
+
modelTier: tier.tier,
|
|
172
|
+
agentConfidence: 1.0,
|
|
173
|
+
modelConfidence: tier.confidence,
|
|
174
|
+
codemodIntent: null,
|
|
175
|
+
surfacedTags,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 2. Codemod short-circuit (only when there's a task string).
|
|
180
|
+
const cm = task ? detectCodemodIntent(task) : null;
|
|
181
|
+
if (cm !== null) {
|
|
182
|
+
surfacedTags.push(codemodTag(cm.intent, cm.confidence));
|
|
183
|
+
// Tier-1 codemod: $0, model tier = "flash" but the call is
|
|
184
|
+
// supposed to be skipped (deterministic). We still set
|
|
185
|
+
// modelTier/Confidence so downstream telemetry has consistent
|
|
186
|
+
// shape.
|
|
187
|
+
surfacedTags.push(tierTag({ tier: "flash", confidence: 1.0 }));
|
|
188
|
+
return {
|
|
189
|
+
agent: "heimdall",
|
|
190
|
+
modelTier: "flash",
|
|
191
|
+
agentConfidence: 1.0,
|
|
192
|
+
modelConfidence: 1.0,
|
|
193
|
+
codemodIntent: cm.intent,
|
|
194
|
+
surfacedTags,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 3. Q-learning agent pick.
|
|
199
|
+
const agentDecision: AgentRouteDecision = qRouter.selectAgent(task);
|
|
200
|
+
// 4. Thompson bandit for model tier.
|
|
201
|
+
const tierDecision: RouteDecision = modelRouter.route(task);
|
|
202
|
+
surfacedTags.push(tierTag(tierDecision));
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
agent: agentDecision.agent,
|
|
206
|
+
modelTier: tierDecision.tier,
|
|
207
|
+
agentConfidence: agentDecision.confidence,
|
|
208
|
+
modelConfidence: tierDecision.confidence,
|
|
209
|
+
codemodIntent: null,
|
|
210
|
+
surfacedTags,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Tag formatters — the strings the spec asks us to surface to the prompt.
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
export function codemodTag(intent: CodemodIntent, confidence: number): string {
|
|
219
|
+
return `[CODEMOD_AVAILABLE] ${intent} (conf=${confidence.toFixed(2)} >= ${CODEMOD_CONFIDENCE_THRESHOLD.toFixed(2)})`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function tierTag(decision: RouteDecision): string {
|
|
223
|
+
return `[TASK_MODEL_RECOMMENDATION] ${decision.tier} (conf=${decision.confidence.toFixed(2)})`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Convenience re-exports so callers can `import { ... } from "..."/router"
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
export {
|
|
231
|
+
detectCodemodIntent,
|
|
232
|
+
CODEMOD_CONFIDENCE_THRESHOLD,
|
|
233
|
+
} from "./codemod-intent.js";
|
|
234
|
+
export type { CodemodIntent, CodemodIntentHit } from "./codemod-intent.js";
|
|
235
|
+
|
|
236
|
+
export {
|
|
237
|
+
ModelRouter,
|
|
238
|
+
type ModelTier,
|
|
239
|
+
type RouteDecision,
|
|
240
|
+
} from "./model-router.js";
|
|
241
|
+
|
|
242
|
+
export {
|
|
243
|
+
QLearningRouter,
|
|
244
|
+
AGENT_ACTIONS,
|
|
245
|
+
type AgentName,
|
|
246
|
+
type AgentRouteDecision,
|
|
247
|
+
} from "./q-learning-router.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* router/memory-distillation-shim.mjs — runtime-only ESM re-export of
|
|
3
|
+
* `memory-distillation.ts` so the `memory_distill` MCP tool can
|
|
4
|
+
* dynamically `import()` the pipeline without taking a static
|
|
5
|
+
* dependency on the heavier consolidation logic.
|
|
6
|
+
*
|
|
7
|
+
* v6.4.0 — F-033 (Self-Learning, ADR-174). Lives next to its sibling
|
|
8
|
+
* `memory-distillation.ts`; the parent keeps the strict TS surface,
|
|
9
|
+
* this shim keeps the runtime import ESM-clean for the MCP tool.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
runDistillation,
|
|
14
|
+
retrieve,
|
|
15
|
+
judge,
|
|
16
|
+
distill,
|
|
17
|
+
PROVENANCE_TIERS,
|
|
18
|
+
} from "./memory-distillation.js";
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* router/memory-distillation.ts — RETRIEVE → JUDGE → DISTILL →
|
|
3
|
+
* CONSOLIDATE (ADR-174) as a pure TypeScript module.
|
|
4
|
+
*
|
|
5
|
+
* v6.4.0 — ported from ruflo `@claude-flow/neural/src/reasoning-bank.ts`
|
|
6
|
+
* (the 4-step pipeline) + ADR-174 (provenance-tiered promote gate).
|
|
7
|
+
*
|
|
8
|
+
* This is the $0 default path — deterministic, no LLM, no I/O.
|
|
9
|
+
* Living inside the SDK (rather than the dashboard .mjs files) so
|
|
10
|
+
* the `memory_distill` MCP tool can call it via a static TypeScript
|
|
11
|
+
* import without crossing the ESM/CJS boundary at runtime.
|
|
12
|
+
*
|
|
13
|
+
* The dashboard's `bizar-dash/src/server/memory-{distillation,
|
|
14
|
+
* consolidator}.mjs` re-export the same semantics and add the
|
|
15
|
+
* vault I/O (read notes, write promoted patterns back). Both paths
|
|
16
|
+
* agree on the `Pattern` shape so the schema is one-of.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const PROVENANCE_TIERS = [
|
|
20
|
+
"oracle:test-exec",
|
|
21
|
+
"proxy:structural",
|
|
22
|
+
"judge:fable",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export type ProvenanceTier = (typeof PROVENANCE_TIERS)[number];
|
|
26
|
+
|
|
27
|
+
export interface MemoryEntryLike {
|
|
28
|
+
memory_id?: string;
|
|
29
|
+
id?: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
relPath?: string;
|
|
32
|
+
frontmatter?: Record<string, unknown>;
|
|
33
|
+
body?: string;
|
|
34
|
+
kind?: string;
|
|
35
|
+
type?: string;
|
|
36
|
+
created?: string;
|
|
37
|
+
createdAt?: string;
|
|
38
|
+
tags?: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Retrieval {
|
|
42
|
+
complexity: "simple" | "moderate" | "complex";
|
|
43
|
+
testExec: boolean;
|
|
44
|
+
hasCodeBlock: boolean;
|
|
45
|
+
length: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface Pattern {
|
|
49
|
+
id: string;
|
|
50
|
+
kind: "pattern";
|
|
51
|
+
summary: string;
|
|
52
|
+
uses: string[];
|
|
53
|
+
evidence: string[];
|
|
54
|
+
provenance_tier: ProvenanceTier;
|
|
55
|
+
promoted: boolean;
|
|
56
|
+
score: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface DistillationResult {
|
|
60
|
+
patterns: Pattern[];
|
|
61
|
+
promoted: Pattern[];
|
|
62
|
+
byTier: Record<ProvenanceTier, number>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const COMPLEXITY_SIMPLE_MAX = 600;
|
|
66
|
+
const COMPLEXITY_MODERATE_MAX = 2400;
|
|
67
|
+
|
|
68
|
+
function firstLine(s: string): string {
|
|
69
|
+
if (!s) return "";
|
|
70
|
+
const i = s.indexOf("\n");
|
|
71
|
+
return (i < 0 ? s : s.slice(0, i)).trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function snippet(body: string): string {
|
|
75
|
+
const cleaned = body.replace(/\s+/g, " ").trim();
|
|
76
|
+
return cleaned.length <= 200 ? cleaned : cleaned.slice(0, 200) + "…";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function shorten(s: string): string {
|
|
80
|
+
let h = 5381 >>> 0;
|
|
81
|
+
for (let i = 0; i < s.length; i++) {
|
|
82
|
+
h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
|
|
83
|
+
}
|
|
84
|
+
return h.toString(36).slice(0, 10);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function wordSet(s: string): Set<string> {
|
|
88
|
+
const out = new Set<string>();
|
|
89
|
+
const norm = (s || "").toLowerCase().replace(/[^a-z0-9\s]+/g, " ");
|
|
90
|
+
for (const w of norm.split(/\s+/)) {
|
|
91
|
+
if (w.length > 2) out.add(w);
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function jaccard(a: Set<string>, b: Set<string>): number {
|
|
97
|
+
if (a.size === 0 && b.size === 0) return 0;
|
|
98
|
+
let inter = 0;
|
|
99
|
+
for (const x of a) if (b.has(x)) inter += 1;
|
|
100
|
+
const union = a.size + b.size - inter;
|
|
101
|
+
return union === 0 ? 0 : inter / union;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function dedupe<T>(arr: T[]): T[] {
|
|
105
|
+
const seen = new Set<T>();
|
|
106
|
+
const out: T[] = [];
|
|
107
|
+
for (const x of arr) if (!seen.has(x)) { seen.add(x); out.push(x); }
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function retrieve(entry: MemoryEntryLike): Retrieval {
|
|
112
|
+
const body = String(entry?.body ?? "");
|
|
113
|
+
const fm = entry?.frontmatter ?? {};
|
|
114
|
+
const hasCodeBlock = /```/.test(body);
|
|
115
|
+
const tagsArr = Array.isArray(fm.tags) ? fm.tags : [];
|
|
116
|
+
const testExec =
|
|
117
|
+
Boolean(fm.test_exec) ||
|
|
118
|
+
Boolean(fm.testExec) ||
|
|
119
|
+
tagsArr.some((t) => String(t).includes("test-exec")) ||
|
|
120
|
+
String(fm.kind ?? "") === "feedback";
|
|
121
|
+
const len = body.length;
|
|
122
|
+
let complexity: Retrieval["complexity"];
|
|
123
|
+
if (hasCodeBlock || len > COMPLEXITY_MODERATE_MAX) complexity = "complex";
|
|
124
|
+
else if (len > COMPLEXITY_SIMPLE_MAX) complexity = "moderate";
|
|
125
|
+
else complexity = "simple";
|
|
126
|
+
return { complexity, testExec, hasCodeBlock, length: len };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function judge(_entry: MemoryEntryLike, retrieval: Retrieval): ProvenanceTier {
|
|
130
|
+
if (retrieval.testExec) return "oracle:test-exec";
|
|
131
|
+
return "proxy:structural";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function distill(entry: MemoryEntryLike): Omit<Pattern, "provenance_tier" | "promoted" | "score"> | null {
|
|
135
|
+
const fm = entry?.frontmatter ?? {};
|
|
136
|
+
const body = String(entry?.body ?? "").trim();
|
|
137
|
+
const id = String(entry?.memory_id ?? entry?.id ?? entry?.path ?? entry?.relPath ?? "");
|
|
138
|
+
const kindRaw = String(fm.kind ?? entry?.kind ?? entry?.type ?? "").toLowerCase();
|
|
139
|
+
|
|
140
|
+
if (kindRaw === "coding_convention" || kindRaw === "bug_pattern") {
|
|
141
|
+
return {
|
|
142
|
+
id: `pat_${shorten(id || body.slice(0, 24))}`,
|
|
143
|
+
kind: "pattern",
|
|
144
|
+
summary: firstLine(body) || "(empty)",
|
|
145
|
+
uses: id ? [id] : [],
|
|
146
|
+
evidence: [snippet(body)],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const head = firstLine(body).toLowerCase();
|
|
151
|
+
if (
|
|
152
|
+
kindRaw === "task_summary" ||
|
|
153
|
+
kindRaw === "session_summary" ||
|
|
154
|
+
/\bpattern\s*:/.test(head) ||
|
|
155
|
+
/^\s*(use|avoid|do|don't)\s*:/.test(head)
|
|
156
|
+
) {
|
|
157
|
+
return {
|
|
158
|
+
id: `pat_${shorten(id || head.replace(/\W+/g, " ").slice(0, 24))}`,
|
|
159
|
+
kind: "pattern",
|
|
160
|
+
summary: firstLine(body) || "(empty)",
|
|
161
|
+
uses: id ? [id] : [],
|
|
162
|
+
evidence: [snippet(body)],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Distillation candidates carry their tier separately; we shift it on
|
|
170
|
+
* to `provenance_tier` here and set the `promoted` flag.
|
|
171
|
+
*/
|
|
172
|
+
interface Candidate {
|
|
173
|
+
id: string;
|
|
174
|
+
kind: "pattern";
|
|
175
|
+
summary: string;
|
|
176
|
+
uses: string[];
|
|
177
|
+
evidence: string[];
|
|
178
|
+
_tier: ProvenanceTier;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function runDistillation(entries: MemoryEntryLike[]): DistillationResult {
|
|
182
|
+
const empty = {
|
|
183
|
+
patterns: [] as Pattern[],
|
|
184
|
+
promoted: [] as Pattern[],
|
|
185
|
+
byTier: { "oracle:test-exec": 0, "proxy:structural": 0, "judge:fable": 0 } as Record<ProvenanceTier, number>,
|
|
186
|
+
};
|
|
187
|
+
if (!Array.isArray(entries) || entries.length === 0) return empty;
|
|
188
|
+
|
|
189
|
+
const candidates: Candidate[] = [];
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
const retrieval = retrieve(entry);
|
|
192
|
+
const tier = judge(entry, retrieval);
|
|
193
|
+
const pattern = distill(entry);
|
|
194
|
+
if (!pattern) continue;
|
|
195
|
+
candidates.push({ ...pattern, _tier: tier });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Cluster by Jaccard similarity ≥ 0.5 over (summary + evidence).
|
|
199
|
+
const groups: { rep: Candidate; members: Candidate[] }[] = [];
|
|
200
|
+
for (const c of candidates) {
|
|
201
|
+
const ws = wordSet(c.summary + " " + c.evidence.join(" "));
|
|
202
|
+
let placed = false;
|
|
203
|
+
for (const g of groups) {
|
|
204
|
+
const gs = wordSet(g.rep.summary + " " + g.rep.evidence.join(" "));
|
|
205
|
+
if (jaccard(ws, gs) >= 0.5) {
|
|
206
|
+
g.members.push(c);
|
|
207
|
+
placed = true;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!placed) groups.push({ rep: c, members: [c] });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const byTier: Record<ProvenanceTier, number> = {
|
|
215
|
+
"oracle:test-exec": 0,
|
|
216
|
+
"proxy:structural": 0,
|
|
217
|
+
"judge:fable": 0,
|
|
218
|
+
};
|
|
219
|
+
const patterns: Pattern[] = [];
|
|
220
|
+
for (const g of groups) {
|
|
221
|
+
const usesSet = new Set<string>();
|
|
222
|
+
const evidence: string[] = [];
|
|
223
|
+
for (const m of g.members) {
|
|
224
|
+
for (const u of m.uses ?? []) usesSet.add(u);
|
|
225
|
+
for (const e of m.evidence ?? []) evidence.push(e);
|
|
226
|
+
}
|
|
227
|
+
const tiers = g.members.map((m) => m._tier);
|
|
228
|
+
let tier: ProvenanceTier = "proxy:structural";
|
|
229
|
+
if (tiers.includes("oracle:test-exec")) tier = "oracle:test-exec";
|
|
230
|
+
else if (tiers.includes("judge:fable")) tier = "judge:fable";
|
|
231
|
+
|
|
232
|
+
const promoted = usesSet.size >= 1 && (tier === "oracle:test-exec" || tier === "judge:fable");
|
|
233
|
+
const pattern: Pattern = {
|
|
234
|
+
id: g.rep.id,
|
|
235
|
+
kind: "pattern",
|
|
236
|
+
summary: g.rep.summary,
|
|
237
|
+
uses: Array.from(usesSet),
|
|
238
|
+
evidence: dedupe(evidence).slice(0, 5),
|
|
239
|
+
provenance_tier: tier,
|
|
240
|
+
promoted,
|
|
241
|
+
score: 0.5 + usesSet.size * 0.1,
|
|
242
|
+
};
|
|
243
|
+
patterns.push(pattern);
|
|
244
|
+
byTier[tier] += 1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const promoted = patterns.filter((p) => p.promoted);
|
|
248
|
+
return { patterns, promoted, byTier };
|
|
249
|
+
}
|