@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,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/distill.mjs
|
|
3
|
+
*
|
|
4
|
+
* v6.4.0 — F-033 / ADR-174.
|
|
5
|
+
*
|
|
6
|
+
* REST surface for the ReasoningBank distillation pipeline.
|
|
7
|
+
*
|
|
8
|
+
* POST /api/distill
|
|
9
|
+
* Triggers a single RETRIEVE → JUDGE → DISTILL → CONSOLIDATE run
|
|
10
|
+
* over the current vault. Optional JSON body:
|
|
11
|
+
* { "since": "<iso8601>" }
|
|
12
|
+
* Returns the consolidation result (same shape as the SDK
|
|
13
|
+
* `memory_distill` MCP tool plus the in-file path).
|
|
14
|
+
*
|
|
15
|
+
* GET /api/distill/patterns
|
|
16
|
+
* Reads the most recent `.bizar/distilled-patterns.json` and
|
|
17
|
+
* returns its `patterns` array (filtered by `?tier=` and
|
|
18
|
+
* `?promoted=` query params). 404 if the file does not yet
|
|
19
|
+
* exist (a distillation run hasn't happened).
|
|
20
|
+
*
|
|
21
|
+
* GET /api/distill/status
|
|
22
|
+
* Returns a small status object:
|
|
23
|
+
* { file: "<path or null>", patternCount, promotedCount, byTier, generatedAt }
|
|
24
|
+
*
|
|
25
|
+
* Mounted at /api/distill/*. The route itself is `Router()`-style so
|
|
26
|
+
* it composes with the rest of the v3 dashboard API.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { Router } from 'express';
|
|
30
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
31
|
+
import { dirname, join } from 'node:path';
|
|
32
|
+
|
|
33
|
+
const SERVER_ROOT = join(dirname(new URL(import.meta.url).pathname), '..');
|
|
34
|
+
const memoryConsolidator = await import(`${SERVER_ROOT}/memory-consolidator.mjs`).then((m) => m);
|
|
35
|
+
|
|
36
|
+
const { wrap } = await import('./_shared.mjs').then((m) => m);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build the distill router. `projectRoot` is the Bizar repo root — it
|
|
40
|
+
* defaults to `process.cwd()` so that `bizar dashboard --dev` "just
|
|
41
|
+
* works" when launched from inside a Bizar project.
|
|
42
|
+
*/
|
|
43
|
+
export function createDistillRouter({ projectRoot } = {}) {
|
|
44
|
+
const root = projectRoot ?? process.cwd();
|
|
45
|
+
const router = Router();
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------
|
|
48
|
+
// POST /api/distill — run the pipeline.
|
|
49
|
+
// ---------------------------------------------------------------------
|
|
50
|
+
router.post('/distill', wrap(async (req, res) => {
|
|
51
|
+
const since = typeof req.body?.since === 'string' ? req.body.since : undefined;
|
|
52
|
+
const result = memoryConsolidator.runConsolidation({ projectRoot: root, since });
|
|
53
|
+
|
|
54
|
+
// Side-effect: write the machine-readable log so the GET route
|
|
55
|
+
// can serve it without re-running the pipeline.
|
|
56
|
+
const filePath = memoryConsolidator.writeDistilledPatternsFile(result, {
|
|
57
|
+
projectRoot: root,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
res.json({
|
|
61
|
+
distilled: result.distilled,
|
|
62
|
+
promoted: result.promoted,
|
|
63
|
+
patterns: result.patterns,
|
|
64
|
+
byTier: result.byTier,
|
|
65
|
+
written: result.written,
|
|
66
|
+
file: filePath,
|
|
67
|
+
});
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------
|
|
71
|
+
// GET /api/distill/patterns — return the patterns.
|
|
72
|
+
// ---------------------------------------------------------------------
|
|
73
|
+
router.get('/distill/patterns', wrap(async (req, res) => {
|
|
74
|
+
const fp = join(root, memoryConsolidator.DISTILLED_PATTERNS_FILE);
|
|
75
|
+
if (!existsSync(fp)) {
|
|
76
|
+
res.status(404).json({ error: 'not_distilled_yet', file: fp });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const raw = readFileSync(fp, 'utf-8');
|
|
80
|
+
let payload;
|
|
81
|
+
try { payload = JSON.parse(raw); }
|
|
82
|
+
catch (e) { res.status(500).json({ error: 'corrupt_distill_file', message: String(e) }); return; }
|
|
83
|
+
|
|
84
|
+
let patterns = Array.isArray(payload.patterns) ? payload.patterns : [];
|
|
85
|
+
|
|
86
|
+
const tier = typeof req.query.tier === 'string' ? req.query.tier : null;
|
|
87
|
+
if (tier) patterns = patterns.filter((p) => p.provenance_tier === tier);
|
|
88
|
+
|
|
89
|
+
const promoted = req.query.promoted === 'true'
|
|
90
|
+
? true
|
|
91
|
+
: req.query.promoted === 'false'
|
|
92
|
+
? false
|
|
93
|
+
: null;
|
|
94
|
+
if (promoted !== null) patterns = patterns.filter((p) => Boolean(p.promoted) === promoted);
|
|
95
|
+
|
|
96
|
+
res.json({
|
|
97
|
+
generatedAt: payload.generatedAt,
|
|
98
|
+
byTier: payload.byTier,
|
|
99
|
+
patterns,
|
|
100
|
+
count: patterns.length,
|
|
101
|
+
});
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------
|
|
105
|
+
// GET /api/distill/status — small status object.
|
|
106
|
+
// ---------------------------------------------------------------------
|
|
107
|
+
router.get('/distill/status', wrap(async (_req, res) => {
|
|
108
|
+
const fp = join(root, memoryConsolidator.DISTILLED_PATTERNS_FILE);
|
|
109
|
+
if (!existsSync(fp)) {
|
|
110
|
+
res.json({ file: null, patternCount: 0, promotedCount: 0, byTier: null, generatedAt: null });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const payload = JSON.parse(readFileSync(fp, 'utf-8'));
|
|
115
|
+
const patterns = Array.isArray(payload.patterns) ? payload.patterns : [];
|
|
116
|
+
res.json({
|
|
117
|
+
file: fp,
|
|
118
|
+
patternCount: patterns.length,
|
|
119
|
+
promotedCount: patterns.filter((p) => Boolean(p.promoted)).length,
|
|
120
|
+
byTier: payload.byTier,
|
|
121
|
+
generatedAt: payload.generatedAt,
|
|
122
|
+
});
|
|
123
|
+
} catch (e) {
|
|
124
|
+
res.status(500).json({ error: 'corrupt_distill_file', message: String(e) });
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
return router;
|
|
129
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/routes/goal-planner.mjs
|
|
3
|
+
*
|
|
4
|
+
* v6.4.0 — F-036 (Goal Planner UI).
|
|
5
|
+
*
|
|
6
|
+
* Single endpoint:
|
|
7
|
+
* POST /api/goal-planner/plan { goal } -> Plan
|
|
8
|
+
*
|
|
9
|
+
* The planner logic is duplicated here in pure JS so the route
|
|
10
|
+
* runs without a Vite build step. The canonical implementation
|
|
11
|
+
* lives in `bizar-dash/src/web/lib/goapPlanner.ts` and produces
|
|
12
|
+
* the same shape.
|
|
13
|
+
*/
|
|
14
|
+
import { Router } from 'express';
|
|
15
|
+
import { wrap } from './_shared.mjs';
|
|
16
|
+
|
|
17
|
+
const VERB_TO_ACTION = {
|
|
18
|
+
research: { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Gather context, examples, and prior art.' },
|
|
19
|
+
investigate: { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Probe the unknown regions of the problem.' },
|
|
20
|
+
explore: { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Map the solution space.' },
|
|
21
|
+
find: { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Locate references and prior work.' },
|
|
22
|
+
analyze: { name: 'analyze', cost: 1, agent: 'Odin', effect: 'goal_analyzed', preconditions: ['context_gathered'], description: 'Decompose the goal into measurable sub-objectives.' },
|
|
23
|
+
plan: { name: 'plan', cost: 1, agent: 'Odin', effect: 'plan_drafted', preconditions: ['goal_analyzed'], description: 'Sequence the work into milestones.' },
|
|
24
|
+
design: { name: 'design', cost: 2, agent: 'Tyr', effect: 'design_drafted', preconditions: ['plan_drafted'], description: 'Sketch the architecture and contracts.' },
|
|
25
|
+
architect: { name: 'design', cost: 2, agent: 'Tyr', effect: 'design_drafted', preconditions: ['plan_drafted'], description: 'Lay out the system structure.' },
|
|
26
|
+
build: { name: 'build', cost: 3, agent: 'Thor', effect: 'artifact_built', preconditions: ['design_drafted'], description: 'Construct the artifact end-to-end.' },
|
|
27
|
+
implement: { name: 'implement', cost: 3, agent: 'Thor', effect: 'artifact_built', preconditions: ['design_drafted'], description: 'Wire up the code path.' },
|
|
28
|
+
create: { name: 'implement', cost: 3, agent: 'Thor', effect: 'artifact_built', preconditions: ['design_drafted'], description: 'Stand up the new module.' },
|
|
29
|
+
add: { name: 'implement', cost: 2, agent: 'Thor', effect: 'artifact_built', preconditions: ['design_drafted'], description: 'Layer the new capability onto an existing surface.' },
|
|
30
|
+
write: { name: 'implement', cost: 2, agent: 'Thor', effect: 'artifact_built', description: 'Author the requested artifact.' },
|
|
31
|
+
fix: { name: 'implement', cost: 2, agent: 'Thor', effect: 'artifact_built', description: 'Repair the reported defect.' },
|
|
32
|
+
refactor: { name: 'implement', cost: 2, agent: 'Thor', effect: 'artifact_built', description: 'Improve the structure without changing behavior.' },
|
|
33
|
+
test: { name: 'test', cost: 2, agent: 'Forseti', effect: 'tests_passing', preconditions: ['artifact_built'], description: 'Exercise the artifact against acceptance criteria.' },
|
|
34
|
+
verify: { name: 'test', cost: 1, agent: 'Forseti', effect: 'tests_passing', preconditions: ['artifact_built'], description: 'Validate against the original goal statement.' },
|
|
35
|
+
validate: { name: 'test', cost: 1, agent: 'Forseti', effect: 'tests_passing', preconditions: ['artifact_built'], description: 'Cross-check the result against acceptance criteria.' },
|
|
36
|
+
review: { name: 'review', cost: 1, agent: 'Forseti', effect: 'review_passed', preconditions: ['artifact_built'], description: 'Audit the change for quality and regressions.' },
|
|
37
|
+
document: { name: 'document', cost: 1, agent: 'Bragi', effect: 'docs_written', preconditions: ['artifact_built'], description: 'Capture the new knowledge in docs.' },
|
|
38
|
+
deploy: { name: 'deploy', cost: 2, agent: 'Heimdall', effect: 'deployed', preconditions: ['tests_passing', 'review_passed'], description: 'Promote the artifact to production.' },
|
|
39
|
+
ship: { name: 'deploy', cost: 2, agent: 'Heimdall', effect: 'deployed', preconditions: ['tests_passing', 'review_passed'], description: 'Release the change to users.' },
|
|
40
|
+
release: { name: 'deploy', cost: 2, agent: 'Heimdall', effect: 'deployed', preconditions: ['tests_passing', 'review_passed'], description: 'Tag and publish a new version.' },
|
|
41
|
+
publish: { name: 'deploy', cost: 1, agent: 'Heimdall', effect: 'deployed', preconditions: ['tests_passing'], description: 'Make the artifact externally available.' },
|
|
42
|
+
commit: { name: 'commit', cost: 1, agent: 'Hermod', effect: 'committed', description: 'Land the change in version control.' },
|
|
43
|
+
save: { name: 'commit', cost: 1, agent: 'Hermod', effect: 'committed', description: 'Persist the work.' },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const DURATION_MS = {
|
|
47
|
+
research: 30000, analyze: 20000, plan: 25000, design: 60000,
|
|
48
|
+
build: 120000, implement: 90000, test: 60000, review: 30000,
|
|
49
|
+
document: 30000, deploy: 60000, commit: 10000,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const FALLBACK = { name: 'research', cost: 1, agent: 'Odin', effect: 'context_gathered', description: 'Investigate the goal.' };
|
|
53
|
+
|
|
54
|
+
function splitClauses(text) {
|
|
55
|
+
// Split on terminal punctuation, conjunctions (then/after/before/and
|
|
56
|
+
// then), AND commas / the word "and" so that "Design, build, test,
|
|
57
|
+
// and deploy" produces 4 clauses.
|
|
58
|
+
const parts = text.split(/[.;!?\n]|\bthen\b|\bafter that\b|\bafter\b|\bbefore\b|\band then\b|\s*,\s*|\band\b/gi);
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const part of parts) {
|
|
61
|
+
const t = part.trim();
|
|
62
|
+
if (!t) continue;
|
|
63
|
+
if (!/^[a-zA-Z]+\b/.test(t) && t.includes(',')) {
|
|
64
|
+
for (const sub of t.split(',')) {
|
|
65
|
+
const s = sub.trim();
|
|
66
|
+
if (s) out.push(s);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
out.push(t);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function stripPolite(body) {
|
|
76
|
+
let out = body;
|
|
77
|
+
for (let i = 0; i < 4; i++) {
|
|
78
|
+
const before = out;
|
|
79
|
+
out = out.replace(/^(?:please\s+|kindly\s+)/i, '');
|
|
80
|
+
out = out.replace(/^(?:we should|let's|lets|i want to|i need to|i'd like to|i would like to|can you|could you|would you|help me|help us|make sure to|please)\s+/i, '');
|
|
81
|
+
if (out === before) break;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractVerb(clause) {
|
|
87
|
+
const body = stripPolite(clause.trim());
|
|
88
|
+
const first = (body.split(/\s+/)[0] || '').toLowerCase().replace(/[^a-z]/g, '');
|
|
89
|
+
return first;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function clauseSubject(clause) {
|
|
93
|
+
const body = stripPolite(clause.trim());
|
|
94
|
+
const stripped = body.replace(/^[a-zA-Z]+\s+/, '').trim();
|
|
95
|
+
return stripped.length > 0 ? stripped : clause;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function aStar(actions, goals) {
|
|
99
|
+
const goalSet = new Set(goals);
|
|
100
|
+
if (goalSet.size === 0) return actions.slice(0, 1);
|
|
101
|
+
const heur = (s) => {
|
|
102
|
+
let u = 0;
|
|
103
|
+
for (const g of goalSet) if (!s.has(g)) u++;
|
|
104
|
+
return u;
|
|
105
|
+
};
|
|
106
|
+
const keyOf = (s) => Array.from(s).sort().join('|') || 'empty';
|
|
107
|
+
const start = { sat: new Set(), path: [], cost: 0 };
|
|
108
|
+
const open = [start];
|
|
109
|
+
const closed = new Set();
|
|
110
|
+
let best = start;
|
|
111
|
+
while (open.length) {
|
|
112
|
+
open.sort((a, b) => (a.cost + heur(a.sat)) - (b.cost + heur(b.sat)));
|
|
113
|
+
const node = open.shift();
|
|
114
|
+
if (heur(node.sat) === 0) return node.path;
|
|
115
|
+
if (heur(node.sat) < heur(best.sat)) best = node;
|
|
116
|
+
const k = keyOf(node.sat);
|
|
117
|
+
if (closed.has(k)) continue;
|
|
118
|
+
closed.add(k);
|
|
119
|
+
for (const a of actions) {
|
|
120
|
+
const pre = a.preconditions || [];
|
|
121
|
+
if (!pre.every((p) => node.sat.has(p))) continue;
|
|
122
|
+
const next = new Set(node.sat);
|
|
123
|
+
next.add(a.effect);
|
|
124
|
+
open.push({ sat: next, path: [...node.path, a], cost: node.cost + a.cost });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return best.path;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function inlinePlan(goalText) {
|
|
131
|
+
const id = `plan_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
132
|
+
const clauses = splitClauses(goalText);
|
|
133
|
+
const effective = clauses.length > 0 ? clauses : [(goalText || 'Investigate the goal').trim()];
|
|
134
|
+
const rawTemplates = effective.map((c) => VERB_TO_ACTION[extractVerb(c)] || FALLBACK);
|
|
135
|
+
const subjects = effective.map(clauseSubject);
|
|
136
|
+
const seen = new Set();
|
|
137
|
+
const dedup = [];
|
|
138
|
+
for (let i = 0; i < rawTemplates.length; i++) {
|
|
139
|
+
const t = rawTemplates[i];
|
|
140
|
+
const s = subjects[i] || `step ${i + 1}`;
|
|
141
|
+
const sig = `${t.name}::${s.toLowerCase().slice(0, 40)}`;
|
|
142
|
+
if (seen.has(sig)) continue;
|
|
143
|
+
seen.add(sig);
|
|
144
|
+
dedup.push({ template: t, subject: s });
|
|
145
|
+
}
|
|
146
|
+
const trimmed = dedup.slice(0, 8);
|
|
147
|
+
const goalEffects = trimmed.map((d) => d.template.effect);
|
|
148
|
+
const sequence = aStar(trimmed.map((d) => d.template), goalEffects);
|
|
149
|
+
const idByEffect = new Map();
|
|
150
|
+
const steps = [];
|
|
151
|
+
let n = 0;
|
|
152
|
+
for (const tpl of sequence) {
|
|
153
|
+
const match = trimmed.find((s) => s.template === tpl);
|
|
154
|
+
if (!match) continue;
|
|
155
|
+
n += 1;
|
|
156
|
+
const sid = `s${n}`;
|
|
157
|
+
const deps = [];
|
|
158
|
+
for (const p of tpl.preconditions || []) {
|
|
159
|
+
const pid = idByEffect.get(p);
|
|
160
|
+
if (pid) deps.push(pid);
|
|
161
|
+
}
|
|
162
|
+
const title = match.subject.charAt(0).toUpperCase() + match.subject.slice(1);
|
|
163
|
+
steps.push({
|
|
164
|
+
id: sid,
|
|
165
|
+
action: tpl.name,
|
|
166
|
+
title: title.length > 0 ? title : tpl.name,
|
|
167
|
+
agent: tpl.agent,
|
|
168
|
+
description: tpl.description,
|
|
169
|
+
effects: [tpl.effect],
|
|
170
|
+
deps,
|
|
171
|
+
status: 'pending',
|
|
172
|
+
estimatedCost: tpl.cost,
|
|
173
|
+
estimatedDurationMs: DURATION_MS[tpl.name] || 30000,
|
|
174
|
+
});
|
|
175
|
+
idByEffect.set(tpl.effect, sid);
|
|
176
|
+
}
|
|
177
|
+
if (steps.length === 0 && trimmed.length > 0) {
|
|
178
|
+
let idx = 0;
|
|
179
|
+
for (const { template, subject } of trimmed) {
|
|
180
|
+
idx += 1;
|
|
181
|
+
const sid = `s${idx}`;
|
|
182
|
+
const deps = [];
|
|
183
|
+
for (const p of template.preconditions || []) {
|
|
184
|
+
const pid = idByEffect.get(p);
|
|
185
|
+
if (pid) deps.push(pid);
|
|
186
|
+
}
|
|
187
|
+
const title = subject.charAt(0).toUpperCase() + subject.slice(1);
|
|
188
|
+
steps.push({
|
|
189
|
+
id: sid,
|
|
190
|
+
action: template.name,
|
|
191
|
+
title: title.length > 0 ? title : template.name,
|
|
192
|
+
agent: template.agent,
|
|
193
|
+
description: template.description,
|
|
194
|
+
effects: [template.effect],
|
|
195
|
+
deps,
|
|
196
|
+
status: 'pending',
|
|
197
|
+
estimatedCost: template.cost,
|
|
198
|
+
estimatedDurationMs: DURATION_MS[template.name] || 30000,
|
|
199
|
+
});
|
|
200
|
+
idByEffect.set(template.effect, sid);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const totalCost = steps.reduce((sum, s) => sum + s.estimatedCost, 0);
|
|
204
|
+
const totalDurationMs = steps.reduce((sum, s) => sum + s.estimatedDurationMs, 0);
|
|
205
|
+
return { id, goal: goalText, steps, totalCost, totalDurationMs, goalEffects };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function readGoal(body) {
|
|
209
|
+
const raw = body && body.goal;
|
|
210
|
+
if (typeof raw !== 'string' || !raw.trim()) {
|
|
211
|
+
const err = new Error('goal (non-empty string) is required');
|
|
212
|
+
err.status = 400;
|
|
213
|
+
err.code = 'bad_request';
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
if (raw.length > 4000) {
|
|
217
|
+
const err = new Error('goal must be 4000 chars or fewer');
|
|
218
|
+
err.status = 400;
|
|
219
|
+
err.code = 'bad_request';
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
return raw.trim();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function createGoalPlannerRouter({ broadcast } = {}) {
|
|
226
|
+
const router = Router();
|
|
227
|
+
router.post('/goal-planner/plan', wrap(async (req, res) => {
|
|
228
|
+
const goal = readGoal(req.body);
|
|
229
|
+
const plan = inlinePlan(goal);
|
|
230
|
+
if (broadcast) {
|
|
231
|
+
try { broadcast({ type: 'goal:planned', planId: plan.id, goal }); } catch { /* best-effort */ }
|
|
232
|
+
}
|
|
233
|
+
res.json(plan);
|
|
234
|
+
}));
|
|
235
|
+
return router;
|
|
236
|
+
}
|
|
@@ -42,6 +42,8 @@ import { Doctor } from './views/Doctor';
|
|
|
42
42
|
import { Harness } from './views/Harness';
|
|
43
43
|
import { Eval } from './views/Eval';
|
|
44
44
|
import { EvalReport } from './views/EvalReport';
|
|
45
|
+
// v6.4.0 — F-036 Goal Planner UI.
|
|
46
|
+
import { GoalPlanner } from './views/GoalPlanner';
|
|
45
47
|
import { SettingsNav } from './components/SettingsNav';
|
|
46
48
|
import { Spinner } from './components/Spinner';
|
|
47
49
|
import { Button } from './components/Button';
|
|
@@ -108,6 +110,8 @@ const VIEW_MAP: Record<string, (p: ViewProps) => React.ReactNode> = {
|
|
|
108
110
|
// Eval view's "Open Eval Report" button (full report + diff).
|
|
109
111
|
eval: Eval,
|
|
110
112
|
evalReport: EvalReport,
|
|
113
|
+
// v6.4.0 — F-036 Goal Planner UI. Plain-English goal → A* plan.
|
|
114
|
+
'goal-planner': GoalPlanner,
|
|
111
115
|
};
|
|
112
116
|
|
|
113
117
|
const VERSION = 'v6.0.0';
|
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
// src/components/Tag.tsx — pill for tags.
|
|
2
2
|
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
3
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
4
4
|
import { cn } from '../lib/utils';
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type TagVariant =
|
|
7
|
+
| "neutral"
|
|
8
|
+
| "info"
|
|
9
|
+
| "accent"
|
|
10
|
+
| "primary"
|
|
11
|
+
| "success"
|
|
12
|
+
| "warning"
|
|
13
|
+
| "error";
|
|
14
|
+
|
|
15
|
+
export type TagProps = HTMLAttributes<HTMLSpanElement> & {
|
|
7
16
|
children: ReactNode;
|
|
8
17
|
className?: string;
|
|
18
|
+
variant?: TagVariant;
|
|
9
19
|
onRemove?: () => void;
|
|
10
20
|
};
|
|
11
21
|
|
|
12
|
-
export function Tag({ children, className, onRemove }: TagProps) {
|
|
22
|
+
export function Tag({ children, className, variant, onRemove, ...rest }: TagProps) {
|
|
13
23
|
return (
|
|
14
|
-
<span
|
|
24
|
+
<span
|
|
25
|
+
className={cn('tag', variant ? `tag-${variant}` : undefined, className)}
|
|
26
|
+
{...rest}
|
|
27
|
+
>
|
|
15
28
|
{children}
|
|
16
29
|
{onRemove && (
|
|
17
30
|
<button
|
|
@@ -25,4 +38,4 @@ export function Tag({ children, className, onRemove }: TagProps) {
|
|
|
25
38
|
)}
|
|
26
39
|
</span>
|
|
27
40
|
);
|
|
28
|
-
}
|
|
41
|
+
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
Brain,
|
|
27
27
|
Stethoscope,
|
|
28
28
|
ClipboardCheck,
|
|
29
|
+
Target,
|
|
29
30
|
type LucideIcon,
|
|
30
31
|
} from 'lucide-react';
|
|
31
32
|
import { cn } from '../lib/utils';
|
|
@@ -66,6 +67,8 @@ export const TABS: TabDef[] = [
|
|
|
66
67
|
{ id: 'doctor', label: 'Doctor', icon: Stethoscope },
|
|
67
68
|
// v6.0.0 — Harness engineering dashboard.
|
|
68
69
|
{ id: 'harness', label: 'Harness', icon: Shield },
|
|
70
|
+
// v6.4.0 — F-036 Goal Planner UI. Plain-English → A* plan.
|
|
71
|
+
{ id: 'goal-planner', label: 'Goals', icon: Target },
|
|
69
72
|
{ id: 'settings', label: 'Settings', icon: Sliders },
|
|
70
73
|
];
|
|
71
74
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/web/components/agents/CommunicationLog.tsx — inter-agent message bus trace.
|
|
2
|
+
//
|
|
3
|
+
// v6.4.0 — F-036 (Goal Planner UI). Subscribes to ws.ts for any
|
|
4
|
+
// event whose type starts with `agent:` and renders the last 200
|
|
5
|
+
// messages with color coding by type. Bounded virtual-list window
|
|
6
|
+
// so a runaway stream can't blow up the page.
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
8
|
+
import { MessageSquare, ArrowRight } from 'lucide-react';
|
|
9
|
+
import { Ws } from '../../lib/ws';
|
|
10
|
+
import { Card, CardTitle, CardMeta } from '../Card';
|
|
11
|
+
import { Tag } from '../Tag';
|
|
12
|
+
|
|
13
|
+
const MAX_ROWS = 200;
|
|
14
|
+
|
|
15
|
+
type Entry = {
|
|
16
|
+
id: string;
|
|
17
|
+
ts: number;
|
|
18
|
+
from: string;
|
|
19
|
+
to: string;
|
|
20
|
+
message: string;
|
|
21
|
+
kind: 'info' | 'request' | 'success' | 'warning' | 'error' | 'status' | 'restarted' | 'stuck' | 'message';
|
|
22
|
+
raw: unknown;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function classify(t: string): Entry['kind'] {
|
|
26
|
+
if (t === 'agent:status') return 'status';
|
|
27
|
+
if (t === 'agent:restarted') return 'restarted';
|
|
28
|
+
if (t === 'agent:stuck') return 'stuck';
|
|
29
|
+
if (t === 'agent:message') return 'message';
|
|
30
|
+
return 'info';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deriveEntry(t: string, payload: Record<string, unknown> | undefined, ts: number): Entry {
|
|
34
|
+
const kind = classify(t);
|
|
35
|
+
const id = `${ts}-${Math.random().toString(36).slice(2, 7)}`;
|
|
36
|
+
if (t === 'agent:message') {
|
|
37
|
+
const m = (payload?.message || payload) as Record<string, unknown>;
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
ts,
|
|
41
|
+
from: String(m?.from || 'unknown'),
|
|
42
|
+
to: String(m?.to || 'unknown'),
|
|
43
|
+
message: String(m?.text || m?.message || JSON.stringify(m).slice(0, 160)),
|
|
44
|
+
kind,
|
|
45
|
+
raw: payload,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const agent = (payload?.agent || payload) as Record<string, unknown>;
|
|
49
|
+
const name = String(agent?.name || 'agent');
|
|
50
|
+
return {
|
|
51
|
+
id,
|
|
52
|
+
ts,
|
|
53
|
+
from: name,
|
|
54
|
+
to: kind === 'stuck' ? 'orchestrator' : 'broadcast',
|
|
55
|
+
message: `${t} ${agent?.status ? `→ ${agent.status}` : ''}`.trim(),
|
|
56
|
+
kind,
|
|
57
|
+
raw: payload,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface Props {
|
|
62
|
+
/** Override the max row count (default 200). */
|
|
63
|
+
maxRows?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function CommunicationLog({ maxRows = MAX_ROWS }: Props) {
|
|
67
|
+
const [rows, setRows] = useState<Entry[]>([]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const ws = new Ws();
|
|
71
|
+
const off = ws.on((msg) => {
|
|
72
|
+
const t = (msg as { type?: string }).type;
|
|
73
|
+
if (!t || !t.startsWith('agent:')) return;
|
|
74
|
+
const m = msg as { type: string; ts?: number } & Record<string, unknown>;
|
|
75
|
+
const ts = Number(m.ts) || Date.now();
|
|
76
|
+
setRows((prev) => {
|
|
77
|
+
const next = [...prev, deriveEntry(t, m as Record<string, unknown>, ts)];
|
|
78
|
+
return next.length > maxRows ? next.slice(next.length - maxRows) : next;
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return () => {
|
|
82
|
+
off();
|
|
83
|
+
ws.close();
|
|
84
|
+
};
|
|
85
|
+
}, [maxRows]);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Card className="comm-log" data-testid="communication-log">
|
|
89
|
+
<div className="comm-log-header">
|
|
90
|
+
<CardTitle>
|
|
91
|
+
<MessageSquare size={14} aria-hidden /> Communication log
|
|
92
|
+
</CardTitle>
|
|
93
|
+
<Tag variant="neutral" data-testid="communication-log-count">{rows.length}</Tag>
|
|
94
|
+
</div>
|
|
95
|
+
<CardMeta>Inter-agent message bus trace · bounded to {maxRows} rows</CardMeta>
|
|
96
|
+
<ul className="comm-log-list" data-testid="communication-log-list">
|
|
97
|
+
{rows.length === 0 && (
|
|
98
|
+
<li className="comm-log-empty" data-testid="communication-log-empty">
|
|
99
|
+
No agent messages yet. Stream will appear here live.
|
|
100
|
+
</li>
|
|
101
|
+
)}
|
|
102
|
+
{rows.slice().reverse().map((row) => (
|
|
103
|
+
<li
|
|
104
|
+
key={row.id}
|
|
105
|
+
className={`comm-log-row is-${row.kind}`}
|
|
106
|
+
data-testid="communication-log-row"
|
|
107
|
+
data-kind={row.kind}
|
|
108
|
+
>
|
|
109
|
+
<div className="comm-log-row-meta">
|
|
110
|
+
<span className="comm-log-from">{row.from}</span>
|
|
111
|
+
<ArrowRight size={12} aria-hidden />
|
|
112
|
+
<span className="comm-log-to">{row.to}</span>
|
|
113
|
+
<Tag variant="neutral" className={`comm-log-kind is-${row.kind}`}>{row.kind}</Tag>
|
|
114
|
+
<time className="comm-log-time" dateTime={new Date(row.ts).toISOString()}>
|
|
115
|
+
{new Date(row.ts).toLocaleTimeString()}
|
|
116
|
+
</time>
|
|
117
|
+
</div>
|
|
118
|
+
<div className="comm-log-message">{row.message}</div>
|
|
119
|
+
</li>
|
|
120
|
+
))}
|
|
121
|
+
</ul>
|
|
122
|
+
</Card>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default CommunicationLog;
|