memorix 1.2.0 → 1.2.2
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/CHANGELOG.md +30 -1
- package/README.md +18 -4
- package/README.zh-CN.md +18 -4
- package/TEAM.md +86 -86
- package/dist/cli/index.js +15919 -14055
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1997 -1021
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8481 -8005
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +30 -1
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +2022 -1024
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +49 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
- package/docs/API_REFERENCE.md +27 -5
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/DEVELOPMENT.md +4 -0
- package/docs/README.md +1 -1
- package/docs/SETUP.md +7 -1
- package/docs/dev-log/progress.txt +91 -11
- package/docs/knowledge/workflows/memorix-release.md +57 -0
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/command-guide.ts +192 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +5 -1
- package/src/cli/commands/codegraph.ts +17 -8
- package/src/cli/commands/context.ts +3 -2
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/explain.ts +9 -3
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +75 -61
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/identity.ts +116 -0
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +71 -69
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/knowledge.ts +40 -0
- package/src/cli/commands/lock.ts +93 -92
- package/src/cli/commands/memory.ts +58 -21
- package/src/cli/commands/message.ts +123 -118
- package/src/cli/commands/operator-shared.ts +98 -3
- package/src/cli/commands/poll.ts +74 -64
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +135 -121
- package/src/cli/commands/retention.ts +9 -4
- package/src/cli/commands/serve-http.ts +22 -43
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/session.ts +29 -3
- package/src/cli/commands/setup.ts +9 -3
- package/src/cli/commands/skills.ts +124 -119
- package/src/cli/commands/status.ts +4 -3
- package/src/cli/commands/task.ts +193 -184
- package/src/cli/commands/team.ts +14 -10
- package/src/cli/commands/transfer.ts +108 -55
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/identity.ts +89 -0
- package/src/cli/index.ts +96 -19
- package/src/cli/invocation.ts +115 -0
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +41 -18
- package/src/cli/tui/data.ts +23 -44
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/operator-context.ts +60 -0
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/tui/views/MemoryView.tsx +10 -8
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/auto-context.ts +34 -17
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/current-facts.ts +19 -1
- package/src/codegraph/project-context.ts +2 -0
- package/src/codegraph/task-lens.ts +49 -5
- package/src/compact/engine.ts +26 -10
- package/src/compact/index-format.ts +25 -2
- package/src/compact/token-budget.ts +74 -74
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/server.ts +58 -52
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/admission.ts +117 -0
- package/src/hooks/handler.ts +98 -91
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/significance-filter.ts +250 -250
- package/src/knowledge/claims.ts +51 -1
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/types.ts +1 -0
- package/src/knowledge/workflows.ts +34 -3
- package/src/knowledge/workset.ts +179 -10
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/admission.ts +57 -0
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/auto-relations.ts +21 -0
- package/src/memory/consolidation.ts +13 -2
- package/src/memory/disclosure-policy.ts +140 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/export-import.ts +11 -3
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph-context.ts +8 -2
- package/src/memory/graph-scope.ts +46 -0
- package/src/memory/graph.ts +197 -197
- package/src/memory/observations.ts +162 -4
- package/src/memory/quality-audit.ts +2 -0
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +22 -2
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +5 -2
- package/src/memory/visibility.ts +80 -0
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +378 -340
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/verify-gate.ts +33 -10
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +18 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/maintenance-runner.ts +2 -0
- package/src/runtime/project-maintenance.ts +89 -0
- package/src/sdk.ts +334 -304
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +334 -93
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/orama-store.ts +61 -6
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/sqlite-db.ts +23 -1
- package/src/store/sqlite-store.ts +12 -2
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +168 -161
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/types.ts +51 -0
- package/src/wiki/generator.ts +2 -0
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
package/src/cli/tui/Sidebar.tsx
CHANGED
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sidebar right panel with quick action hints and health snapshot.
|
|
3
|
-
*
|
|
4
|
-
* Modern design: Unicode separators, status dots, active indicator
|
|
5
|
-
* with arrow symbol, brand-colored section headers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { Box, Text, useInput } from 'ink';
|
|
10
|
-
import { COLORS, SEP, STATUS_DOTS, SYMBOLS } from './theme.js';
|
|
11
|
-
import type { HealthInfo, BackgroundInfo } from './data.js';
|
|
12
|
-
import type { ViewType } from './theme.js';
|
|
13
|
-
|
|
14
|
-
interface SidebarProps {
|
|
15
|
-
health: HealthInfo;
|
|
16
|
-
background: BackgroundInfo;
|
|
17
|
-
onAction: (cmd: string) => void;
|
|
18
|
-
activeView: ViewType;
|
|
19
|
-
/** When true, Sidebar captures shortcut keys and drives navigation. */
|
|
20
|
-
isFocused?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const ACTIONS = [
|
|
24
|
-
{ key: 's', label: 'Search', cmd: '/search' },
|
|
25
|
-
{ key: 'r', label: 'Remember', cmd: '/remember' },
|
|
26
|
-
{ key: 'v', label: 'Recent', cmd: '/recent' },
|
|
27
|
-
{ key: 'd', label: 'Doctor', cmd: '/doctor' },
|
|
28
|
-
{ key: 'b', label: 'Background', cmd: '/background' },
|
|
29
|
-
{ key: 'w', label: 'Dashboard', cmd: '/dashboard' },
|
|
30
|
-
{ key: 'p', label: 'Project', cmd: '/project' },
|
|
31
|
-
{ key: 'c', label: 'Configure', cmd: '/configure' },
|
|
32
|
-
{ key: 'i', label: 'Integrate', cmd: '/integrate' },
|
|
33
|
-
{ key: 'h', label: 'Home', cmd: '/home' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const SIDEBAR_WIDTH = 32;
|
|
37
|
-
const INNER_WIDTH = SIDEBAR_WIDTH - 4;
|
|
38
|
-
const ACTION_LABEL_WIDTH = INNER_WIDTH - 2;
|
|
39
|
-
const VALUE_WIDTH = INNER_WIDTH - 8;
|
|
40
|
-
|
|
41
|
-
function separator(width: number): string {
|
|
42
|
-
return SEP.thin.repeat(width);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function colorForMode(mode: string): string {
|
|
46
|
-
const normalized = mode.toLowerCase();
|
|
47
|
-
if (normalized.includes('hybrid')) return COLORS.success;
|
|
48
|
-
if (normalized.includes('vector')) return COLORS.accent;
|
|
49
|
-
return COLORS.warning;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function charWidth(ch: string): number {
|
|
53
|
-
const cp = ch.codePointAt(0)!;
|
|
54
|
-
return ((cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
|
|
55
|
-
(cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xFF01 && cp <= 0xFF60) ||
|
|
56
|
-
(cp >= 0x3400 && cp <= 0x4DBF)) ? 2 : 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function fit(text: string, max: number): string {
|
|
60
|
-
if (max <= 0) return '';
|
|
61
|
-
let width = 0;
|
|
62
|
-
for (const ch of text) {
|
|
63
|
-
width += charWidth(ch);
|
|
64
|
-
if (width > max) break;
|
|
65
|
-
}
|
|
66
|
-
if (width <= max) return text.padEnd(text.length + (max - width));
|
|
67
|
-
if (max === 1) return '…';
|
|
68
|
-
let result = '';
|
|
69
|
-
let used = 0;
|
|
70
|
-
for (const ch of text) {
|
|
71
|
-
const cw = charWidth(ch);
|
|
72
|
-
if (used + cw > max - 1) break;
|
|
73
|
-
result += ch;
|
|
74
|
-
used += cw;
|
|
75
|
-
}
|
|
76
|
-
const truncated = `${result}…`;
|
|
77
|
-
return truncated.padEnd(truncated.length + Math.max(0, max - (used + 1)));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Build a key→cmd lookup from ACTIONS for O(1) dispatch
|
|
81
|
-
const KEY_TO_CMD: Record<string, string> = {};
|
|
82
|
-
for (const a of ACTIONS) KEY_TO_CMD[a.key] = a.cmd;
|
|
83
|
-
|
|
84
|
-
export function Sidebar({ health, background, onAction, activeView, isFocused = false }: SidebarProps): React.ReactElement {
|
|
85
|
-
// ── Interactive navigation: Sidebar owns shortcut key dispatch ──
|
|
86
|
-
useInput((ch, key) => {
|
|
87
|
-
// Esc: return home from any secondary view
|
|
88
|
-
if (key.escape && activeView !== 'home') {
|
|
89
|
-
onAction('/home');
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const cmd = KEY_TO_CMD[ch];
|
|
93
|
-
if (cmd) {
|
|
94
|
-
onAction(cmd);
|
|
95
|
-
}
|
|
96
|
-
}, { isActive: isFocused });
|
|
97
|
-
|
|
98
|
-
// Map view types to sidebar action commands for highlight
|
|
99
|
-
const activeCmd = ACTIONS.find(a => {
|
|
100
|
-
const viewMap: Record<string, string> = {
|
|
101
|
-
'/search': 'search', '/recent': 'recent', '/doctor': 'doctor',
|
|
102
|
-
'/background': 'background', '/dashboard': 'dashboard',
|
|
103
|
-
'/project': 'project', '/configure': 'configure',
|
|
104
|
-
'/integrate': 'integrate', '/home': 'home',
|
|
105
|
-
};
|
|
106
|
-
return viewMap[a.cmd] === activeView;
|
|
107
|
-
})?.cmd;
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<Box
|
|
111
|
-
flexDirection="column"
|
|
112
|
-
width={SIDEBAR_WIDTH}
|
|
113
|
-
borderStyle="single"
|
|
114
|
-
borderColor={COLORS.border}
|
|
115
|
-
paddingX={1}
|
|
116
|
-
>
|
|
117
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
118
|
-
<Text color={COLORS.brand} bold>Quick Actions</Text>
|
|
119
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
120
|
-
{ACTIONS.map((action) => {
|
|
121
|
-
const isActive = action.cmd === activeCmd;
|
|
122
|
-
return (
|
|
123
|
-
<Box key={action.key}>
|
|
124
|
-
<Text color={isActive ? COLORS.brand : COLORS.muted}>
|
|
125
|
-
{`${isActive ? SYMBOLS.arrow : action.key} `}
|
|
126
|
-
</Text>
|
|
127
|
-
<Text color={isActive ? COLORS.accent : COLORS.text} bold={isActive}>{fit(action.label, ACTION_LABEL_WIDTH)}</Text>
|
|
128
|
-
</Box>
|
|
129
|
-
);
|
|
130
|
-
})}
|
|
131
|
-
</Box>
|
|
132
|
-
|
|
133
|
-
<Box flexDirection="column">
|
|
134
|
-
<Text color={COLORS.brand} bold>Health</Text>
|
|
135
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
136
|
-
|
|
137
|
-
<Box>
|
|
138
|
-
<Text color={COLORS.muted}>{'Embed'.padEnd(8)}</Text>
|
|
139
|
-
<Text color={
|
|
140
|
-
health.embeddingProvider === 'ready' ? COLORS.success
|
|
141
|
-
: health.embeddingProvider === 'unavailable' ? COLORS.warning
|
|
142
|
-
: COLORS.muted
|
|
143
|
-
}>
|
|
144
|
-
{fit(`${health.embeddingProvider === 'ready' ? STATUS_DOTS.ok : health.embeddingProvider === 'unavailable' ? STATUS_DOTS.warn : STATUS_DOTS.off} ${health.embeddingLabel}`, VALUE_WIDTH)}
|
|
145
|
-
</Text>
|
|
146
|
-
</Box>
|
|
147
|
-
<Box>
|
|
148
|
-
<Text color={COLORS.muted}>{'Search'.padEnd(8)}</Text>
|
|
149
|
-
<Text color={colorForMode(health.searchModeLabel)}>{fit(`${STATUS_DOTS.ok} ${health.searchModeLabel}`, VALUE_WIDTH)}</Text>
|
|
150
|
-
</Box>
|
|
151
|
-
<Box>
|
|
152
|
-
<Text color={COLORS.muted}>{'Sess'.padEnd(8)}</Text>
|
|
153
|
-
<Text color={COLORS.text}>{fit(`${health.sessions}`, VALUE_WIDTH)}</Text>
|
|
154
|
-
</Box>
|
|
155
|
-
|
|
156
|
-
<Box marginTop={1} flexDirection="column">
|
|
157
|
-
<Text color={COLORS.brand} bold>Background</Text>
|
|
158
|
-
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
159
|
-
<Box>
|
|
160
|
-
<Text color={COLORS.muted}>{'Status'.padEnd(8)}</Text>
|
|
161
|
-
<Text color={background.healthy ? COLORS.success : background.running ? COLORS.warning : COLORS.muted}>
|
|
162
|
-
{fit(`${background.healthy ? STATUS_DOTS.running : background.running ? STATUS_DOTS.warn : STATUS_DOTS.stopped} ${background.healthy ? 'Running' : background.running ? 'Warning' : 'Stopped'}`, VALUE_WIDTH)}
|
|
163
|
-
</Text>
|
|
164
|
-
</Box>
|
|
165
|
-
{background.port && (
|
|
166
|
-
<Box>
|
|
167
|
-
<Text color={COLORS.muted}>{'Port'.padEnd(8)}</Text>
|
|
168
|
-
<Text color={COLORS.text}>{fit(`${background.port}`, VALUE_WIDTH)}</Text>
|
|
169
|
-
</Box>
|
|
170
|
-
)}
|
|
171
|
-
</Box>
|
|
172
|
-
</Box>
|
|
173
|
-
|
|
174
|
-
<Box marginTop={1}>
|
|
175
|
-
<Text color={COLORS.muted} italic>Keys or /cmd</Text>
|
|
176
|
-
</Box>
|
|
177
|
-
</Box>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar right panel with quick action hints and health snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Modern design: Unicode separators, status dots, active indicator
|
|
5
|
+
* with arrow symbol, brand-colored section headers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Box, Text, useInput } from 'ink';
|
|
10
|
+
import { COLORS, SEP, STATUS_DOTS, SYMBOLS } from './theme.js';
|
|
11
|
+
import type { HealthInfo, BackgroundInfo } from './data.js';
|
|
12
|
+
import type { ViewType } from './theme.js';
|
|
13
|
+
|
|
14
|
+
interface SidebarProps {
|
|
15
|
+
health: HealthInfo;
|
|
16
|
+
background: BackgroundInfo;
|
|
17
|
+
onAction: (cmd: string) => void;
|
|
18
|
+
activeView: ViewType;
|
|
19
|
+
/** When true, Sidebar captures shortcut keys and drives navigation. */
|
|
20
|
+
isFocused?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ACTIONS = [
|
|
24
|
+
{ key: 's', label: 'Search', cmd: '/search' },
|
|
25
|
+
{ key: 'r', label: 'Remember', cmd: '/remember' },
|
|
26
|
+
{ key: 'v', label: 'Recent', cmd: '/recent' },
|
|
27
|
+
{ key: 'd', label: 'Doctor', cmd: '/doctor' },
|
|
28
|
+
{ key: 'b', label: 'Background', cmd: '/background' },
|
|
29
|
+
{ key: 'w', label: 'Dashboard', cmd: '/dashboard' },
|
|
30
|
+
{ key: 'p', label: 'Project', cmd: '/project' },
|
|
31
|
+
{ key: 'c', label: 'Configure', cmd: '/configure' },
|
|
32
|
+
{ key: 'i', label: 'Integrate', cmd: '/integrate' },
|
|
33
|
+
{ key: 'h', label: 'Home', cmd: '/home' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const SIDEBAR_WIDTH = 32;
|
|
37
|
+
const INNER_WIDTH = SIDEBAR_WIDTH - 4;
|
|
38
|
+
const ACTION_LABEL_WIDTH = INNER_WIDTH - 2;
|
|
39
|
+
const VALUE_WIDTH = INNER_WIDTH - 8;
|
|
40
|
+
|
|
41
|
+
function separator(width: number): string {
|
|
42
|
+
return SEP.thin.repeat(width);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function colorForMode(mode: string): string {
|
|
46
|
+
const normalized = mode.toLowerCase();
|
|
47
|
+
if (normalized.includes('hybrid')) return COLORS.success;
|
|
48
|
+
if (normalized.includes('vector')) return COLORS.accent;
|
|
49
|
+
return COLORS.warning;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function charWidth(ch: string): number {
|
|
53
|
+
const cp = ch.codePointAt(0)!;
|
|
54
|
+
return ((cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
|
|
55
|
+
(cp >= 0xAC00 && cp <= 0xD7AF) || (cp >= 0xFF01 && cp <= 0xFF60) ||
|
|
56
|
+
(cp >= 0x3400 && cp <= 0x4DBF)) ? 2 : 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fit(text: string, max: number): string {
|
|
60
|
+
if (max <= 0) return '';
|
|
61
|
+
let width = 0;
|
|
62
|
+
for (const ch of text) {
|
|
63
|
+
width += charWidth(ch);
|
|
64
|
+
if (width > max) break;
|
|
65
|
+
}
|
|
66
|
+
if (width <= max) return text.padEnd(text.length + (max - width));
|
|
67
|
+
if (max === 1) return '…';
|
|
68
|
+
let result = '';
|
|
69
|
+
let used = 0;
|
|
70
|
+
for (const ch of text) {
|
|
71
|
+
const cw = charWidth(ch);
|
|
72
|
+
if (used + cw > max - 1) break;
|
|
73
|
+
result += ch;
|
|
74
|
+
used += cw;
|
|
75
|
+
}
|
|
76
|
+
const truncated = `${result}…`;
|
|
77
|
+
return truncated.padEnd(truncated.length + Math.max(0, max - (used + 1)));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Build a key→cmd lookup from ACTIONS for O(1) dispatch
|
|
81
|
+
const KEY_TO_CMD: Record<string, string> = {};
|
|
82
|
+
for (const a of ACTIONS) KEY_TO_CMD[a.key] = a.cmd;
|
|
83
|
+
|
|
84
|
+
export function Sidebar({ health, background, onAction, activeView, isFocused = false }: SidebarProps): React.ReactElement {
|
|
85
|
+
// ── Interactive navigation: Sidebar owns shortcut key dispatch ──
|
|
86
|
+
useInput((ch, key) => {
|
|
87
|
+
// Esc: return home from any secondary view
|
|
88
|
+
if (key.escape && activeView !== 'home') {
|
|
89
|
+
onAction('/home');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const cmd = KEY_TO_CMD[ch];
|
|
93
|
+
if (cmd) {
|
|
94
|
+
onAction(cmd);
|
|
95
|
+
}
|
|
96
|
+
}, { isActive: isFocused });
|
|
97
|
+
|
|
98
|
+
// Map view types to sidebar action commands for highlight
|
|
99
|
+
const activeCmd = ACTIONS.find(a => {
|
|
100
|
+
const viewMap: Record<string, string> = {
|
|
101
|
+
'/search': 'search', '/recent': 'recent', '/doctor': 'doctor',
|
|
102
|
+
'/background': 'background', '/dashboard': 'dashboard',
|
|
103
|
+
'/project': 'project', '/configure': 'configure',
|
|
104
|
+
'/integrate': 'integrate', '/home': 'home',
|
|
105
|
+
};
|
|
106
|
+
return viewMap[a.cmd] === activeView;
|
|
107
|
+
})?.cmd;
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Box
|
|
111
|
+
flexDirection="column"
|
|
112
|
+
width={SIDEBAR_WIDTH}
|
|
113
|
+
borderStyle="single"
|
|
114
|
+
borderColor={COLORS.border}
|
|
115
|
+
paddingX={1}
|
|
116
|
+
>
|
|
117
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
118
|
+
<Text color={COLORS.brand} bold>Quick Actions</Text>
|
|
119
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
120
|
+
{ACTIONS.map((action) => {
|
|
121
|
+
const isActive = action.cmd === activeCmd;
|
|
122
|
+
return (
|
|
123
|
+
<Box key={action.key}>
|
|
124
|
+
<Text color={isActive ? COLORS.brand : COLORS.muted}>
|
|
125
|
+
{`${isActive ? SYMBOLS.arrow : action.key} `}
|
|
126
|
+
</Text>
|
|
127
|
+
<Text color={isActive ? COLORS.accent : COLORS.text} bold={isActive}>{fit(action.label, ACTION_LABEL_WIDTH)}</Text>
|
|
128
|
+
</Box>
|
|
129
|
+
);
|
|
130
|
+
})}
|
|
131
|
+
</Box>
|
|
132
|
+
|
|
133
|
+
<Box flexDirection="column">
|
|
134
|
+
<Text color={COLORS.brand} bold>Health</Text>
|
|
135
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
136
|
+
|
|
137
|
+
<Box>
|
|
138
|
+
<Text color={COLORS.muted}>{'Embed'.padEnd(8)}</Text>
|
|
139
|
+
<Text color={
|
|
140
|
+
health.embeddingProvider === 'ready' ? COLORS.success
|
|
141
|
+
: health.embeddingProvider === 'unavailable' ? COLORS.warning
|
|
142
|
+
: COLORS.muted
|
|
143
|
+
}>
|
|
144
|
+
{fit(`${health.embeddingProvider === 'ready' ? STATUS_DOTS.ok : health.embeddingProvider === 'unavailable' ? STATUS_DOTS.warn : STATUS_DOTS.off} ${health.embeddingLabel}`, VALUE_WIDTH)}
|
|
145
|
+
</Text>
|
|
146
|
+
</Box>
|
|
147
|
+
<Box>
|
|
148
|
+
<Text color={COLORS.muted}>{'Search'.padEnd(8)}</Text>
|
|
149
|
+
<Text color={colorForMode(health.searchModeLabel)}>{fit(`${STATUS_DOTS.ok} ${health.searchModeLabel}`, VALUE_WIDTH)}</Text>
|
|
150
|
+
</Box>
|
|
151
|
+
<Box>
|
|
152
|
+
<Text color={COLORS.muted}>{'Sess'.padEnd(8)}</Text>
|
|
153
|
+
<Text color={COLORS.text}>{fit(`${health.sessions}`, VALUE_WIDTH)}</Text>
|
|
154
|
+
</Box>
|
|
155
|
+
|
|
156
|
+
<Box marginTop={1} flexDirection="column">
|
|
157
|
+
<Text color={COLORS.brand} bold>Background</Text>
|
|
158
|
+
<Text color={COLORS.border}>{separator(INNER_WIDTH)}</Text>
|
|
159
|
+
<Box>
|
|
160
|
+
<Text color={COLORS.muted}>{'Status'.padEnd(8)}</Text>
|
|
161
|
+
<Text color={background.healthy ? COLORS.success : background.running ? COLORS.warning : COLORS.muted}>
|
|
162
|
+
{fit(`${background.healthy ? STATUS_DOTS.running : background.running ? STATUS_DOTS.warn : STATUS_DOTS.stopped} ${background.healthy ? 'Running' : background.running ? 'Warning' : 'Stopped'}`, VALUE_WIDTH)}
|
|
163
|
+
</Text>
|
|
164
|
+
</Box>
|
|
165
|
+
{background.port && (
|
|
166
|
+
<Box>
|
|
167
|
+
<Text color={COLORS.muted}>{'Port'.padEnd(8)}</Text>
|
|
168
|
+
<Text color={COLORS.text}>{fit(`${background.port}`, VALUE_WIDTH)}</Text>
|
|
169
|
+
</Box>
|
|
170
|
+
)}
|
|
171
|
+
</Box>
|
|
172
|
+
</Box>
|
|
173
|
+
|
|
174
|
+
<Box marginTop={1}>
|
|
175
|
+
<Text color={COLORS.muted} italic>Keys or /cmd</Text>
|
|
176
|
+
</Box>
|
|
177
|
+
</Box>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -3,11 +3,11 @@ import { loadDotenv } from '../../config/dotenv-loader.js';
|
|
|
3
3
|
import { initLLM, isLLMEnabled, getLLMConfig, callLLMWithTools } from '../../llm/provider.js';
|
|
4
4
|
import type { ChatMessage, ToolDefinition, ToolCall } from '../../llm/provider.js';
|
|
5
5
|
import { initObservations, prepareSearchIndex, storeObservation, resolveObservations, getObservation, getAllObservations } from '../../memory/observations.js';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import { canManageObservation, filterReadableObservations } from '../../memory/visibility.js';
|
|
7
|
+
import type { ObservationReader, ObservationType } from '../../types.js';
|
|
8
8
|
import { getLastSearchMode } from '../../store/orama-store.js';
|
|
9
|
-
import { getProjectDataDir } from '../../store/persistence.js';
|
|
10
9
|
import type { MemorixDocument } from '../../types.js';
|
|
10
|
+
import { getTuiOperatorContext } from './operator-context.js';
|
|
11
11
|
|
|
12
12
|
export interface ChatHistoryTurn {
|
|
13
13
|
role: 'user' | 'assistant';
|
|
@@ -208,19 +208,29 @@ async function prepareProjectSearch(projectId: string, dataDir: string): Promise
|
|
|
208
208
|
|
|
209
209
|
interface ToolExecutionContext {
|
|
210
210
|
projectId: string;
|
|
211
|
+
reader: ObservationReader;
|
|
212
|
+
writerAgentId?: string;
|
|
211
213
|
collectedSources: ChatSource[];
|
|
212
214
|
}
|
|
213
215
|
|
|
216
|
+
async function resolveTuiContext() {
|
|
217
|
+
try {
|
|
218
|
+
return await getTuiOperatorContext();
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
214
224
|
function executeSearchMemories(args: { query: string; limit?: number }, ctx: ToolExecutionContext): Promise<string> {
|
|
215
225
|
const limit = Math.min(args.limit ?? SEARCH_LIMIT, 10);
|
|
216
|
-
return compactSearch({ query: args.query, limit, projectId: ctx.projectId, status: 'active' })
|
|
226
|
+
return compactSearch({ query: args.query, limit, projectId: ctx.projectId, status: 'active', reader: ctx.reader })
|
|
217
227
|
.then((result) => {
|
|
218
228
|
const entries = result.entries.slice(0, DETAIL_LIMIT);
|
|
219
229
|
if (entries.length === 0) return 'No memories found for that query.';
|
|
220
230
|
|
|
221
231
|
// Collect sources for citation tracking
|
|
222
232
|
const refs = entries.map((e) => ({ id: e.id, projectId: ctx.projectId }));
|
|
223
|
-
return compactDetail(refs).then((detail) => {
|
|
233
|
+
return compactDetail(refs, { reader: ctx.reader }).then((detail) => {
|
|
224
234
|
for (let i = 0; i < detail.documents.length; i++) {
|
|
225
235
|
const doc = detail.documents[i];
|
|
226
236
|
const score = entries[i]?.score ?? 0;
|
|
@@ -235,7 +245,7 @@ function executeSearchMemories(args: { query: string; limit?: number }, ctx: Too
|
|
|
235
245
|
}
|
|
236
246
|
|
|
237
247
|
function executeGetMemoryDetail(args: { id: number }, ctx: ToolExecutionContext): Promise<string> {
|
|
238
|
-
return compactDetail([{ id: args.id, projectId: ctx.projectId }])
|
|
248
|
+
return compactDetail([{ id: args.id, projectId: ctx.projectId }], { reader: ctx.reader })
|
|
239
249
|
.then((detail) => {
|
|
240
250
|
if (detail.documents.length === 0) return `Observation ${args.id} not found.`;
|
|
241
251
|
const doc = detail.documents[0];
|
|
@@ -295,6 +305,8 @@ function executeStoreMemory(
|
|
|
295
305
|
facts: args.facts,
|
|
296
306
|
projectId: ctx.projectId,
|
|
297
307
|
source: 'agent',
|
|
308
|
+
visibilityReader: ctx.reader,
|
|
309
|
+
...(ctx.writerAgentId ? { createdByAgentId: ctx.writerAgentId } : {}),
|
|
298
310
|
}).then((result) => {
|
|
299
311
|
const obs = result.observation;
|
|
300
312
|
ctx.collectedSources.push({
|
|
@@ -314,7 +326,7 @@ function executeUpdateMemory(
|
|
|
314
326
|
ctx: ToolExecutionContext,
|
|
315
327
|
): string {
|
|
316
328
|
const obs = getObservation(args.id, ctx.projectId);
|
|
317
|
-
if (!obs) return `Observation ${args.id} not found.`;
|
|
329
|
+
if (!obs || !canManageObservation(obs, ctx.reader)) return `Observation ${args.id} not found.`;
|
|
318
330
|
|
|
319
331
|
const shouldAppend = args.append !== false; // default true
|
|
320
332
|
const newNarrative = shouldAppend
|
|
@@ -334,6 +346,7 @@ function executeUpdateMemory(
|
|
|
334
346
|
projectId: ctx.projectId,
|
|
335
347
|
topicKey: obs.topicKey,
|
|
336
348
|
source: 'agent',
|
|
349
|
+
visibilityReader: ctx.reader,
|
|
337
350
|
}).catch(() => {/* non-blocking */});
|
|
338
351
|
|
|
339
352
|
return `Updated observation [obs:${args.id}] — ${shouldAppend ? 'appended to' : 'replaced'} narrative${args.facts?.length ? ` and added ${args.facts.length} facts` : ''}. Changes will be persisted asynchronously.`;
|
|
@@ -344,7 +357,7 @@ function executeDeleteMemory(
|
|
|
344
357
|
ctx: ToolExecutionContext,
|
|
345
358
|
): Promise<string> {
|
|
346
359
|
const obs = getObservation(args.id, ctx.projectId);
|
|
347
|
-
if (!obs) return Promise.resolve(`Observation ${args.id} not found.`);
|
|
360
|
+
if (!obs || !canManageObservation(obs, ctx.reader)) return Promise.resolve(`Observation ${args.id} not found.`);
|
|
348
361
|
|
|
349
362
|
return resolveObservations([args.id], 'resolved')
|
|
350
363
|
.then((result) => {
|
|
@@ -361,8 +374,10 @@ function executeListRecentMemories(
|
|
|
361
374
|
ctx: ToolExecutionContext,
|
|
362
375
|
): string {
|
|
363
376
|
const limit = Math.min(args.limit ?? 10, 20);
|
|
364
|
-
let allObs =
|
|
365
|
-
.filter(o => o.projectId === ctx.projectId && o.status !== 'archived' && o.status !== 'resolved')
|
|
377
|
+
let allObs = filterReadableObservations(
|
|
378
|
+
getAllObservations().filter(o => o.projectId === ctx.projectId && o.status !== 'archived' && o.status !== 'resolved'),
|
|
379
|
+
ctx.reader,
|
|
380
|
+
);
|
|
366
381
|
|
|
367
382
|
if (args.type) {
|
|
368
383
|
allObs = allObs.filter(o => o.type === args.type);
|
|
@@ -399,10 +414,10 @@ export async function askMemoryQuestion(
|
|
|
399
414
|
};
|
|
400
415
|
}
|
|
401
416
|
|
|
402
|
-
const
|
|
417
|
+
const operatorContext = await resolveTuiContext();
|
|
403
418
|
|
|
404
419
|
// No project + no LLM → dead end
|
|
405
|
-
if (!
|
|
420
|
+
if (!operatorContext) {
|
|
406
421
|
loadDotenv(process.cwd());
|
|
407
422
|
initLLM({ scope: 'agent' });
|
|
408
423
|
if (!isLLMEnabled()) {
|
|
@@ -438,10 +453,11 @@ export async function askMemoryQuestion(
|
|
|
438
453
|
};
|
|
439
454
|
}
|
|
440
455
|
|
|
456
|
+
const { project, dataDir, reader, identity } = operatorContext;
|
|
457
|
+
|
|
441
458
|
loadDotenv(project.rootPath);
|
|
442
459
|
initLLM({ scope: 'agent' });
|
|
443
460
|
|
|
444
|
-
const dataDir = await getProjectDataDir(project.id);
|
|
445
461
|
await prepareProjectSearch(project.id, dataDir);
|
|
446
462
|
|
|
447
463
|
const searchMode = normalizeSearchMode(getLastSearchMode(project.id) || 'fulltext');
|
|
@@ -453,11 +469,12 @@ export async function askMemoryQuestion(
|
|
|
453
469
|
limit: SEARCH_LIMIT,
|
|
454
470
|
projectId: project.id,
|
|
455
471
|
status: 'active',
|
|
472
|
+
reader,
|
|
456
473
|
});
|
|
457
474
|
const topEntries = searchResult.entries.slice(0, DETAIL_LIMIT);
|
|
458
475
|
const detailRefs = topEntries.map((entry) => ({ id: entry.id, projectId: project.id }));
|
|
459
476
|
const detailResult = detailRefs.length > 0
|
|
460
|
-
? await compactDetail(detailRefs)
|
|
477
|
+
? await compactDetail(detailRefs, { reader })
|
|
461
478
|
: { documents: [], formatted: '', totalTokens: 0 };
|
|
462
479
|
const sources = detailResult.documents.map((doc, index) => toSource(doc, topEntries[index]?.score ?? 0));
|
|
463
480
|
|
|
@@ -488,6 +505,8 @@ export async function askMemoryQuestion(
|
|
|
488
505
|
|
|
489
506
|
const ctx: ToolExecutionContext = {
|
|
490
507
|
projectId: project.id,
|
|
508
|
+
reader,
|
|
509
|
+
...(identity ? { writerAgentId: identity.agentId } : {}),
|
|
491
510
|
collectedSources: [],
|
|
492
511
|
};
|
|
493
512
|
|
|
@@ -584,10 +603,10 @@ export async function askMemoryQuestionStream(
|
|
|
584
603
|
};
|
|
585
604
|
}
|
|
586
605
|
|
|
587
|
-
const
|
|
606
|
+
const operatorContext = await resolveTuiContext();
|
|
588
607
|
|
|
589
608
|
// No project + no LLM → dead end
|
|
590
|
-
if (!
|
|
609
|
+
if (!operatorContext) {
|
|
591
610
|
loadDotenv(process.cwd());
|
|
592
611
|
initLLM({ scope: 'agent' });
|
|
593
612
|
if (!isLLMEnabled()) {
|
|
@@ -626,10 +645,11 @@ export async function askMemoryQuestionStream(
|
|
|
626
645
|
};
|
|
627
646
|
}
|
|
628
647
|
|
|
648
|
+
const { project, dataDir, reader, identity } = operatorContext;
|
|
649
|
+
|
|
629
650
|
loadDotenv(project.rootPath);
|
|
630
651
|
initLLM({ scope: 'agent' });
|
|
631
652
|
|
|
632
|
-
const dataDir = await getProjectDataDir(project.id);
|
|
633
653
|
await prepareProjectSearch(project.id, dataDir);
|
|
634
654
|
|
|
635
655
|
const searchMode = normalizeSearchMode(getLastSearchMode(project.id) || 'fulltext');
|
|
@@ -641,11 +661,12 @@ export async function askMemoryQuestionStream(
|
|
|
641
661
|
limit: SEARCH_LIMIT,
|
|
642
662
|
projectId: project.id,
|
|
643
663
|
status: 'active',
|
|
664
|
+
reader,
|
|
644
665
|
});
|
|
645
666
|
const topEntries = searchResult.entries.slice(0, DETAIL_LIMIT);
|
|
646
667
|
const detailRefs = topEntries.map((entry) => ({ id: entry.id, projectId: project.id }));
|
|
647
668
|
const detailResult = detailRefs.length > 0
|
|
648
|
-
? await compactDetail(detailRefs)
|
|
669
|
+
? await compactDetail(detailRefs, { reader })
|
|
649
670
|
: { documents: [], formatted: '', totalTokens: 0 };
|
|
650
671
|
const sources = detailResult.documents.map((doc, index) => toSource(doc, topEntries[index]?.score ?? 0));
|
|
651
672
|
|
|
@@ -672,6 +693,8 @@ export async function askMemoryQuestionStream(
|
|
|
672
693
|
|
|
673
694
|
const ctx: ToolExecutionContext = {
|
|
674
695
|
projectId: project.id,
|
|
696
|
+
reader,
|
|
697
|
+
...(identity ? { writerAgentId: identity.agentId } : {}),
|
|
675
698
|
collectedSources: [],
|
|
676
699
|
};
|
|
677
700
|
|