total-recall-brain 3.41.4 → 3.41.6
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/bin/total-recall.mjs +10 -6
- package/docs/reference/cli-reference.md +18 -0
- package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
- package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +11 -0
- package/frontend/src/App.tsx +47 -122
- package/frontend/src/components/plugins/plugin-frame.ts +1 -0
- package/frontend/src/index.css +152 -86
- package/frontend/src/pages/HelpPage.tsx +13 -10
- package/frontend/src/pages/MemoryPage.tsx +28 -30
- package/frontend/src/pages/NetworkPage.tsx +3 -3
- package/frontend/src/pages/PluginsPage.tsx +6 -5
- package/frontend/src/pages/RulesPage.tsx +16 -12
- package/package.json +4 -3
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3127 -49
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +304 -46
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3097 -81
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +4 -1
- package/src/cli/agent.mjs +12 -1
- package/src/cli/compact.mjs +108 -0
- package/src/cli/connect.mjs +56 -1
- package/src/cli/context.mjs +55 -23
- package/src/cli/doctor.mjs +5 -2
- package/src/cli/forget.mjs +2 -0
- package/src/cli/harness.mjs +62 -50
- package/src/cli/memory-entry.mjs +2 -2
- package/src/cli/preflight.mjs +152 -16
- package/src/cli/release.mjs +386 -0
- package/src/cli/remember.mjs +19 -1
- package/src/cli/rules.mjs +54 -12
- package/src/cli/startup.mjs +1 -1
- package/src/cli/status.mjs +12 -2
- package/src/core/claude-hooks.mjs +41 -3
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-semantic-index.mjs +3 -2
- package/src/core/conflict-detector.mjs +23 -12
- package/src/core/context-compactor.mjs +145 -0
- package/src/core/context-policy.mjs +20 -0
- package/src/core/duplicate-guard.mjs +350 -0
- package/src/core/meta-harness.mjs +31 -11
- package/src/core/preflight-gate.mjs +2 -2
- package/src/core/preflight-receipt.mjs +29 -1
- package/src/core/scaffold-plugins.mjs +7 -0
- package/src/core/stdio-flush.mjs +23 -0
- package/src/core/surface.mjs +33 -10
- package/src/server/invoke-durability.mjs +39 -0
- package/src/server/invoke-write-child.mjs +25 -0
- package/src/server/routes/invoke.mjs +7 -0
- package/src/server/routes/plugins.mjs +2 -2
- package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
- package/frontend/dist/assets/index-CRzjQLu7.js +0 -5631
- package/frontend/src/components/DaemonLogsTab.tsx +0 -54
- package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
- package/frontend/src/components/TaskQueueTab.tsx +0 -193
- package/frontend/src/components/VoiceInput.tsx +0 -112
- package/frontend/src/pages/ChatPage.tsx +0 -1059
- package/frontend/src/pages/CollabPage.tsx +0 -470
- package/frontend/src/pages/DesignDocsPage.tsx +0 -621
- package/frontend/src/pages/FilesPage.tsx +0 -393
- package/frontend/src/pages/HealthPage.tsx +0 -359
- package/frontend/src/pages/InboxPage.tsx +0 -83
- package/frontend/src/pages/InstructionsPage.tsx +0 -583
- package/frontend/src/pages/IntegrationsPage.tsx +0 -761
- package/frontend/src/pages/MeshPage.css +0 -58
- package/frontend/src/pages/MeshPage.tsx +0 -1219
- package/frontend/src/pages/OkfPage.tsx +0 -535
- package/frontend/src/pages/SandboxPage.tsx +0 -113
- package/frontend/src/pages/SkillsPage.tsx +0 -1994
- package/frontend/src/pages/TasksPage.tsx +0 -427
- package/frontend/src/pages/UsagePage.tsx +0 -702
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
|
@@ -23,7 +23,9 @@ export const HARNESS_SPECS = {
|
|
|
23
23
|
category: 'frontier_reasoning',
|
|
24
24
|
defaultFlags: ['--output-format', 'json', '-p'],
|
|
25
25
|
execType: 'flag_last',
|
|
26
|
-
description: 'Frontier reasoning and research on Google AI Ultra plan.'
|
|
26
|
+
description: 'Frontier reasoning and research on Google AI Ultra plan.',
|
|
27
|
+
enforcement: 'enforced',
|
|
28
|
+
enforcementEvidence: 'PreToolUse deny hook verified live'
|
|
27
29
|
},
|
|
28
30
|
claude: {
|
|
29
31
|
id: 'claude',
|
|
@@ -38,7 +40,9 @@ export const HARNESS_SPECS = {
|
|
|
38
40
|
// Headless subscription auth (`claude setup-token`), read from the secret
|
|
39
41
|
// store when the caller's environment does not already carry it.
|
|
40
42
|
authSecrets: ['CLAUDE_CODE_OAUTH_TOKEN'],
|
|
41
|
-
description: 'Deep codebase editing, refactoring, and Unix command execution.'
|
|
43
|
+
description: 'Deep codebase editing, refactoring, and Unix command execution.',
|
|
44
|
+
enforcement: 'enforced',
|
|
45
|
+
enforcementEvidence: 'PreToolUse deny hook verified live'
|
|
42
46
|
},
|
|
43
47
|
codex: {
|
|
44
48
|
id: 'codex',
|
|
@@ -47,7 +51,9 @@ export const HARNESS_SPECS = {
|
|
|
47
51
|
category: 'code_synthesis',
|
|
48
52
|
defaultFlags: ['exec', '--sandbox', 'workspace-write', '--json', '--skip-git-repo-check'],
|
|
49
53
|
execType: 'subcommand',
|
|
50
|
-
description: 'Autonomous program synthesis and sandbox execution.'
|
|
54
|
+
description: 'Autonomous program synthesis and sandbox execution.',
|
|
55
|
+
enforcement: 'enforced',
|
|
56
|
+
enforcementEvidence: 'hooks.json PreToolUse block hook verified live'
|
|
51
57
|
},
|
|
52
58
|
ollama: {
|
|
53
59
|
id: 'ollama',
|
|
@@ -56,7 +62,9 @@ export const HARNESS_SPECS = {
|
|
|
56
62
|
category: 'local_neural',
|
|
57
63
|
defaultFlags: ['run', process.env.TR_OLLAMA_MODEL || 'gemma4:latest'],
|
|
58
64
|
execType: 'pipe_stdin',
|
|
59
|
-
description: 'Local neural inference, offline reasoning, zero API cost.'
|
|
65
|
+
description: 'Local neural inference, offline reasoning, zero API cost.',
|
|
66
|
+
enforcement: 'dispatch-only',
|
|
67
|
+
enforcementEvidence: 'No tool hooks; preflight checked before dispatch'
|
|
60
68
|
},
|
|
61
69
|
grok: {
|
|
62
70
|
id: 'grok',
|
|
@@ -65,7 +73,9 @@ export const HARNESS_SPECS = {
|
|
|
65
73
|
category: 'code_engineering',
|
|
66
74
|
defaultFlags: ['-p'],
|
|
67
75
|
execType: 'flag_last',
|
|
68
|
-
description: 'Autonomous coding, repo analysis, and shell execution via xAI Grok.'
|
|
76
|
+
description: 'Autonomous coding, repo analysis, and shell execution via xAI Grok.',
|
|
77
|
+
enforcement: 'enforced (unverified live)',
|
|
78
|
+
enforcementEvidence: 'PreToolUse hook adapter specification verified offline'
|
|
69
79
|
},
|
|
70
80
|
hermes: {
|
|
71
81
|
id: 'hermes',
|
|
@@ -74,7 +84,9 @@ export const HARNESS_SPECS = {
|
|
|
74
84
|
category: 'autonomous_agent',
|
|
75
85
|
defaultFlags: ['chat', '-p'],
|
|
76
86
|
execType: 'flag_last',
|
|
77
|
-
description: 'Supervised messaging gateway and multi-channel autonomous agent runtime.'
|
|
87
|
+
description: 'Supervised messaging gateway and multi-channel autonomous agent runtime.',
|
|
88
|
+
enforcement: 'enforced',
|
|
89
|
+
enforcementEvidence: 'pre_tool_call block hook verified live'
|
|
78
90
|
},
|
|
79
91
|
dsh: {
|
|
80
92
|
id: 'dsh',
|
|
@@ -83,7 +95,9 @@ export const HARNESS_SPECS = {
|
|
|
83
95
|
category: 'reasoning_engine',
|
|
84
96
|
defaultFlags: ['-p'],
|
|
85
97
|
execType: 'flag_last',
|
|
86
|
-
description: 'High-throughput DeepSeek reasoning and tool execution harness.'
|
|
98
|
+
description: 'High-throughput DeepSeek reasoning and tool execution harness.',
|
|
99
|
+
enforcement: 'enforced',
|
|
100
|
+
enforcementEvidence: 'PreToolUse deny hook verified live'
|
|
87
101
|
},
|
|
88
102
|
openclaw: {
|
|
89
103
|
id: 'openclaw',
|
|
@@ -92,7 +106,9 @@ export const HARNESS_SPECS = {
|
|
|
92
106
|
category: 'multichannel_bridge',
|
|
93
107
|
defaultFlags: ['status', '--json'],
|
|
94
108
|
execType: 'flag_last',
|
|
95
|
-
description: 'Multi-channel messaging bridge (WhatsApp, Telegram, Discord, Slack) and agent execution.'
|
|
109
|
+
description: 'Multi-channel messaging bridge (WhatsApp, Telegram, Discord, Slack) and agent execution.',
|
|
110
|
+
enforcement: 'enforced',
|
|
111
|
+
enforcementEvidence: 'before_tool_call block hook verified live'
|
|
96
112
|
}
|
|
97
113
|
};
|
|
98
114
|
|
|
@@ -567,6 +583,7 @@ export async function queryHarnessUsage() {
|
|
|
567
583
|
const reports = [];
|
|
568
584
|
|
|
569
585
|
for (const h of harnesses) {
|
|
586
|
+
const spec = HARNESS_SPECS[h.id];
|
|
570
587
|
if (!h.available) {
|
|
571
588
|
reports.push({
|
|
572
589
|
id: h.id,
|
|
@@ -582,7 +599,9 @@ export async function queryHarnessUsage() {
|
|
|
582
599
|
plan: '—',
|
|
583
600
|
rolling5hRemainingPct: '—',
|
|
584
601
|
weeklyRemainingPct: '—',
|
|
585
|
-
quotaRemaining: 'not installed'
|
|
602
|
+
quotaRemaining: 'not installed',
|
|
603
|
+
enforcement: spec?.enforcement || 'advisory',
|
|
604
|
+
enforcementEvidence: spec?.enforcementEvidence || null
|
|
586
605
|
});
|
|
587
606
|
continue;
|
|
588
607
|
}
|
|
@@ -610,7 +629,6 @@ export async function queryHarnessUsage() {
|
|
|
610
629
|
const acc = JSON.parse(fs.readFileSync(accPath, 'utf8'));
|
|
611
630
|
if (acc.active) identity = acc.active;
|
|
612
631
|
}
|
|
613
|
-
plan = 'Google AI Ultra / Pro';
|
|
614
632
|
} catch {}
|
|
615
633
|
} else if (h.id === 'claude') {
|
|
616
634
|
try {
|
|
@@ -722,7 +740,9 @@ export async function queryHarnessUsage() {
|
|
|
722
740
|
plan,
|
|
723
741
|
rolling5hRemainingPct,
|
|
724
742
|
weeklyRemainingPct,
|
|
725
|
-
quotaRemaining
|
|
743
|
+
quotaRemaining,
|
|
744
|
+
enforcement: spec?.enforcement || 'advisory',
|
|
745
|
+
enforcementEvidence: spec?.enforcementEvidence || null
|
|
726
746
|
});
|
|
727
747
|
}
|
|
728
748
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Rules, in order:
|
|
7
7
|
* 1. Tools that only display or discover (`meta`, `ui`) are always open.
|
|
8
8
|
* 2. Reads of skill files are open: reading them is how the preflight is earned.
|
|
9
|
-
* 3. A single plain Total Recall preflight, context, recall or help command is
|
|
9
|
+
* 3. A single plain Total Recall preflight, context, compact, recall, rules or help command is
|
|
10
10
|
* open, so the gate can always be satisfied or repaired. Chaining, piping,
|
|
11
11
|
* substitution and redirects are not plain, so the exemption cannot carry
|
|
12
12
|
* other work and a capsule cannot be truncated through a pipe.
|
|
@@ -21,7 +21,7 @@ export const TOOL_CLASSES = ['shell', 'read', 'write', 'search', 'web', 'mcp', '
|
|
|
21
21
|
export const SWEEP_CONSULT_MS = 15 * 60 * 1000;
|
|
22
22
|
|
|
23
23
|
const SKILL_PATH = /\/\.(agent|claude)\/skills\//;
|
|
24
|
-
const SATISFYING = /^(cd\s+\S+\s*&&\s*)?(npx\s+)?(total-recall|node\s+\S*bin\/total-recall\.mjs)\s+(tr-preflight|preflight|context|recall|help|--help)(\s+[^;&|`<>$\n]*)?$/;
|
|
24
|
+
const SATISFYING = /^(cd\s+\S+\s*&&\s*)?(npx\s+)?(total-recall|node\s+\S*bin\/total-recall\.mjs)\s+(tr-preflight|preflight|context|compact|recall|rules|help|--help)(\s+[^;&|`<>$\n]*)?$/;
|
|
25
25
|
|
|
26
26
|
/** True when the whole command is one plain Total Recall invocation that satisfies or repairs the gate. */
|
|
27
27
|
export function isSatisfyingCommand(command) {
|
|
@@ -98,7 +98,7 @@ export function acknowledge({ root, task = '', actions = 'read,edit,test', maxAg
|
|
|
98
98
|
const context = run(['context', task || '(no task given)', '--action', actions], root);
|
|
99
99
|
if (!/ready:true/.test(context.out)) {
|
|
100
100
|
return { ok: false, problems: ['total-recall context did not return ready:true',
|
|
101
|
-
context.code === 2 ? 'exit 2: capsule overflow;
|
|
101
|
+
context.code === 2 ? 'exit 2: capsule overflow; the budget cannot hold every rule even compacted, so retry with the required_budget it reports' : `context exit=${context.code ?? '?'}`] };
|
|
102
102
|
}
|
|
103
103
|
const version = (context.out.match(/version:([0-9a-f]+)/) || [])[1] || null;
|
|
104
104
|
const rules = run(['rules', 'verify'], root);
|
|
@@ -121,3 +121,31 @@ export function touchConsult(root, { now = Date.now(), home = os.homedir() } = {
|
|
|
121
121
|
export function lastConsultMs(root, { home = os.homedir() } = {}) {
|
|
122
122
|
try { return Number(fs.readFileSync(consultPath(root, home), 'utf8')) || 0; } catch { return 0; }
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
export const decisionsLogPath = (home = os.homedir()) => path.join(stateDir(home), 'decisions.jsonl');
|
|
126
|
+
|
|
127
|
+
/** Append a real gate decision to the persistent decisions log (ENFORCE-R5). */
|
|
128
|
+
export function recordDecision(entry, { home = os.homedir(), now = Date.now() } = {}) {
|
|
129
|
+
try {
|
|
130
|
+
const file = decisionsLogPath(home);
|
|
131
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
132
|
+
const record = { at: new Date(now).toISOString(), ...entry };
|
|
133
|
+
fs.appendFileSync(file, `${JSON.stringify(record)}\n`, { mode: 0o600 });
|
|
134
|
+
return record;
|
|
135
|
+
} catch { return null; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Read recent gate decisions for compliance reporting. */
|
|
139
|
+
export function readDecisions({ home = os.homedir(), limit = 1000 } = {}) {
|
|
140
|
+
try {
|
|
141
|
+
const file = decisionsLogPath(home);
|
|
142
|
+
if (!fs.existsSync(file)) return [];
|
|
143
|
+
const lines = fs.readFileSync(file, 'utf8').trim().split('\n').filter(Boolean);
|
|
144
|
+
const start = Math.max(0, lines.length - limit);
|
|
145
|
+
const records = [];
|
|
146
|
+
for (let i = start; i < lines.length; i++) {
|
|
147
|
+
try { records.push(JSON.parse(lines[i])); } catch {}
|
|
148
|
+
}
|
|
149
|
+
return records;
|
|
150
|
+
} catch { return []; }
|
|
151
|
+
}
|
|
@@ -84,6 +84,13 @@ export function syncScaffoldPlugins({ root, ids, write = false, run = spawnSync
|
|
|
84
84
|
};
|
|
85
85
|
walk(target);
|
|
86
86
|
}
|
|
87
|
+
// The release gate reads the pin, so a stale pin is drift even when every copied file matches its source.
|
|
88
|
+
const lockFile = path.join(root, 'scripts', 'scaffold-plugins.json');
|
|
89
|
+
if (!write && fs.existsSync(lockFile)) {
|
|
90
|
+
const pinned = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
|
|
91
|
+
const stale = [...new Set([...Object.keys(pinned), ...Object.keys(lock)])].some(key => pinned[key] !== lock[key]);
|
|
92
|
+
if (stale) changes.push({ file: 'scripts/scaffold-plugins.json', change: 'stale pin' });
|
|
93
|
+
}
|
|
87
94
|
if (write) fs.writeFileSync(path.join(root, 'scripts', 'scaffold-plugins.json'), `${JSON.stringify(Object.fromEntries(Object.entries(lock).sort()), null, 2)}\n`);
|
|
88
95
|
return { changes, files: Object.keys(lock).length };
|
|
89
96
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit once everything already written to stdout and stderr has left the process.
|
|
3
|
+
*
|
|
4
|
+
* process.exit() drops whatever is still queued. On macOS a pipe write is
|
|
5
|
+
* asynchronous and the pipe takes 64 KiB at once, so `write(big); process.exit()`
|
|
6
|
+
* loses the rest with exit status 0, while a redirect to a file looks fine.
|
|
7
|
+
*
|
|
8
|
+
* The returned promise never settles, so `await exitAfterFlush(code)` stops the
|
|
9
|
+
* caller the way process.exit() does. A reader that has gone away (`| head`) is
|
|
10
|
+
* not an error here: the unwritten output has nowhere to go.
|
|
11
|
+
*/
|
|
12
|
+
function drained(stream) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
stream.on('error', resolve);
|
|
15
|
+
if (stream.destroyed || !stream.writable || stream.writableLength === 0) return resolve();
|
|
16
|
+
stream.write('', resolve);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function exitAfterFlush(code = process.exitCode ?? 0) {
|
|
21
|
+
Promise.all([drained(process.stdout), drained(process.stderr)]).then(() => process.exit(code));
|
|
22
|
+
return new Promise(() => {});
|
|
23
|
+
}
|
package/src/core/surface.mjs
CHANGED
|
@@ -390,17 +390,18 @@ export function buildInstalledPluginsSummary(projectRoot = process.cwd()) {
|
|
|
390
390
|
const plugins = discoverPlugins(projectRoot).filter((p) => p.valid);
|
|
391
391
|
if (!plugins.length) return '';
|
|
392
392
|
const lines = [
|
|
393
|
-
'## Installed Plugins
|
|
394
|
-
'
|
|
393
|
+
'## Mandatory Plugin Enforcement Table (Installed Plugins)\n',
|
|
394
|
+
'All tasks within these domains MUST use their owning Total Recall plugin CLI (`total-recall <namespace>`). Hand-rolling curl, ad-hoc scripts, raw bash, direct ssh, or manual file edits to bypass these plugins is a strict protocol violation. Discover operations with `total-recall plugin library --json` or run `total-recall <namespace> --help`.\n',
|
|
395
395
|
'Namespaces below run as `total-recall <namespace> [command]`; declarations do not prove provider readiness.\n'
|
|
396
396
|
];
|
|
397
|
-
if (plugins.some(plugin => plugin.id === 'composable-cli'))
|
|
397
|
+
if (plugins.some(plugin => plugin.id === 'composable-cli')) {
|
|
398
|
+
lines.push('Composed commands: run `total-recall composable-cli library --json` or `total-recall <command> --help`.\n');
|
|
399
|
+
}
|
|
398
400
|
for (const p of plugins) {
|
|
399
401
|
const cmd = p.manifest.cli?.command || p.id;
|
|
400
|
-
const desc = (p.manifest.description || 'Plugin capability').replace(/\s+/g, ' ').
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
lines.push(`- **${p.id}** v${p.manifest.version || 'unknown'} · ${pluginActivation(p, { projectRoot }).state} · \`total-recall ${cmd}\` ${shown ? `[${shown}]` : '(no operations)'}: ${desc}`);
|
|
402
|
+
const desc = (p.manifest.description || 'Plugin capability').replace(/\s+/g, ' ').trim();
|
|
403
|
+
const state = pluginActivation(p, { projectRoot }).state;
|
|
404
|
+
lines.push(`- **${p.id}** v${p.manifest.version || 'unknown'} · ${state} · \`total-recall ${cmd}\`: ${desc}`);
|
|
404
405
|
}
|
|
405
406
|
return lines.join('\n') + '\n';
|
|
406
407
|
} catch {
|
|
@@ -418,6 +419,19 @@ export async function buildRulesBlock(skillsDir, nodes = [], {
|
|
|
418
419
|
const contributions = [];
|
|
419
420
|
const counts = category => rules.filter(n => n.category === category).length;
|
|
420
421
|
const commands = buildCommandsSection(listSurfaceCommands(commandDirsFor(skillsDir)));
|
|
422
|
+
|
|
423
|
+
// Tier 1 Absolute Invariants & Top Operational Directives lead lines 1-50 so they are never truncated
|
|
424
|
+
const tier1Rules = rules.filter(r => r.priority === 'absolute' || r.immutable === true || r.slug === 'invariants-8cd098fd' || r.slug === 'invariants-dc116371');
|
|
425
|
+
const otherRules = rules.filter(r => !tier1Rules.includes(r));
|
|
426
|
+
|
|
427
|
+
for (const node of tier1Rules) {
|
|
428
|
+
contributions.push({
|
|
429
|
+
id: `${node._layer || 'project'}:${node.slug}`,
|
|
430
|
+
required: true,
|
|
431
|
+
text: node._directive ? `[${node.slug}] ${node._directive}` : `### ${node.title || node.slug} [${node.slug}]\n\n${node.body || node.content || ''}`
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
421
435
|
const header = bootstrap ? `${CLI_QUICK_REFERENCE}\n${buildInstalledPluginsSummary(root)}\n${commands}\n# 🔴 MANDATORY FIRST STEP: TOTAL RECALL CLI & TASK ROUTING
|
|
422
436
|
|
|
423
437
|
1. RUN TOTAL RECALL CLI HELP FIRST:
|
|
@@ -427,7 +441,7 @@ Inspect all core capabilities, installed plugins, and commands.
|
|
|
427
441
|
|
|
428
442
|
2. RUN TOTAL RECALL CONTEXT BEFORE ACTING:
|
|
429
443
|
\`total-recall context "task description" --action <actions>\`
|
|
430
|
-
Read the
|
|
444
|
+
Read the returned capsule and require ready:true. Rules of unknown applicability are required. Over budget it compacts; every listed rule still applies: expand one with \`total-recall compact "task" --expand <slug>\` before relying on it. If it exits 2, retry with the required_budget it reports.
|
|
431
445
|
|
|
432
446
|
Total Recall is the agent's working memory, toolset, and single source of truth across all decisions. Refresh at every changed task, action or project, and after a memory/skill edit. Do not infer applicability from semantic similarity. CLI retrieval is local and works without the server. Skills have compact entrypoints; load only relevant references. Canonical vault writes use the validated CLI.
|
|
433
447
|
|
|
@@ -435,13 +449,22 @@ DO NOT SKIP THIS. Run Total Recall CLI help and context first before doing anyth
|
|
|
435
449
|
|
|
436
450
|
` :
|
|
437
451
|
`## Active Rules: ${counts('invariants')} invariants, ${counts('preferences')} preferences, ${counts('anti-patterns')} corrections`;
|
|
452
|
+
|
|
438
453
|
contributions.push({ id: 'routing', text: header, required: true });
|
|
454
|
+
|
|
439
455
|
const pluginsSummary = buildInstalledPluginsSummary(root);
|
|
440
456
|
if (pluginsSummary && !bootstrap) {
|
|
441
457
|
contributions.push({ id: 'installed-plugins', text: pluginsSummary, required: true });
|
|
442
458
|
}
|
|
443
|
-
|
|
444
|
-
|
|
459
|
+
|
|
460
|
+
for (const node of otherRules) {
|
|
461
|
+
contributions.push({
|
|
462
|
+
id: `${node._layer || 'project'}:${node.slug}`,
|
|
463
|
+
required: true,
|
|
464
|
+
text: node._directive ? `[${node.slug}] ${node._directive}` : `### ${node.title || node.slug} [${node.slug}]\n\n${node.body || node.content || ''}`
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
445
468
|
if (!bootstrap) contributions.push({ id: 'commands', text: commands });
|
|
446
469
|
if (!bootstrap) {
|
|
447
470
|
if (skillsDir) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The restricted write child runs under Node's permission model, which disables
|
|
3
|
+
* fsync. Durability is therefore the unconfined parent's job: before a write is
|
|
4
|
+
* reported as successful, it flushes every file the child changed and the
|
|
5
|
+
* directories that hold them.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
// File timestamps can round down, so a change made at the start of the run still counts.
|
|
11
|
+
const MTIME_SLACK_MS = 2000;
|
|
12
|
+
|
|
13
|
+
function flush(target) {
|
|
14
|
+
const fd = fs.openSync(target, 'r');
|
|
15
|
+
try { fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Flush files under `roots` modified since `sinceMs`, then their directories, and return how many files were flushed. */
|
|
19
|
+
export function syncChangedFiles(roots, sinceMs) {
|
|
20
|
+
let files = 0;
|
|
21
|
+
const walk = dir => {
|
|
22
|
+
let changed = false;
|
|
23
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
if (entry.isSymbolicLink()) continue;
|
|
25
|
+
const target = path.join(dir, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
if (walk(target)) changed = true;
|
|
28
|
+
} else if (entry.isFile() && fs.statSync(target).mtimeMs >= sinceMs - MTIME_SLACK_MS) {
|
|
29
|
+
flush(target);
|
|
30
|
+
files++;
|
|
31
|
+
changed = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (changed) flush(dir);
|
|
35
|
+
return changed;
|
|
36
|
+
};
|
|
37
|
+
for (const root of roots) if (fs.existsSync(root)) walk(root);
|
|
38
|
+
return files;
|
|
39
|
+
}
|
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
/** Structured trusted mutation entrypoint. Never accepts executable code. */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
// Node.js Permission Model disables fsync/fsyncSync APIs, so this child cannot
|
|
5
|
+
// flush its own writes. The unconfined parent flushes them before reporting
|
|
6
|
+
// success (invoke-durability.mjs); without that step these writes would not survive a crash.
|
|
7
|
+
if (fs.fsyncSync) {
|
|
8
|
+
const origFsyncSync = fs.fsyncSync;
|
|
9
|
+
fs.fsyncSync = (fd) => {
|
|
10
|
+
try { return origFsyncSync(fd); }
|
|
11
|
+
catch (err) {
|
|
12
|
+
if (err.message && err.message.includes('fsync API is disabled when Permission Model is enabled')) return;
|
|
13
|
+
throw err;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (fs.fsync) {
|
|
18
|
+
const origFsync = fs.fsync;
|
|
19
|
+
fs.fsync = (fd, cb) => {
|
|
20
|
+
origFsync(fd, (err) => {
|
|
21
|
+
if (err && err.message && err.message.includes('fsync API is disabled when Permission Model is enabled')) return cb?.(null);
|
|
22
|
+
return cb?.(err);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
2
27
|
import { createConfinedFact } from '../core/confined-memory.mjs';
|
|
3
28
|
|
|
4
29
|
try {
|
|
@@ -15,6 +15,7 @@ import { composedCommandDirectories } from '../../core/composed-command-resoluti
|
|
|
15
15
|
import { invokeAuthority, authorizeInvoke, operationAllowed } from '../invoke-policy.mjs';
|
|
16
16
|
import { restrictedBrainTargets } from '../invoke-targets.mjs';
|
|
17
17
|
import { INVOKE_LIMITS, InvokeError, executeInvokeChild, requireSuccessfulRun, invokeEnvironment, absoluteTarget } from '../invoke-execution.mjs';
|
|
18
|
+
import { syncChangedFiles } from '../invoke-durability.mjs';
|
|
18
19
|
|
|
19
20
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
20
21
|
// Share across router instances in this server; do not race projection refreshes.
|
|
@@ -214,6 +215,7 @@ const HANDLERS = {
|
|
|
214
215
|
const { category, title, content, tags, scope, timeoutMs } = params;
|
|
215
216
|
if (context.authority.mode === 'restricted') {
|
|
216
217
|
const brain = context.selectedBrain;
|
|
218
|
+
const startedAt = Date.now();
|
|
217
219
|
const run = await context.run({ entry: path.join(root, 'src/server/invoke-write-child.mjs'),
|
|
218
220
|
cwd: root, env: invokeEnvironment(false), timeoutMs, nodeArgs: ['--max-old-space-size=128', '--permission',
|
|
219
221
|
`--allow-fs-read=${path.join(root, 'src')}`, `--allow-fs-read=${path.join(root, 'node_modules')}`,
|
|
@@ -231,6 +233,11 @@ const HANDLERS = {
|
|
|
231
233
|
result || { committed: 'unknown', indexed: false });
|
|
232
234
|
}
|
|
233
235
|
if (!result?.committed || !result?.indexed) throw new InvokeError(502, 'invalid-owner-output', 'Mutation returned invalid output.', { committed: 'unknown', indexed: false });
|
|
236
|
+
try {
|
|
237
|
+
syncChangedFiles([path.join(brain, 'memory-vault'), path.join(brain, 'memory-derived')], startedAt);
|
|
238
|
+
} catch {
|
|
239
|
+
throw new InvokeError(502, 'durability-unconfirmed', 'Fact was written but could not be flushed to disk; check it before retrying.', result);
|
|
240
|
+
}
|
|
234
241
|
return result;
|
|
235
242
|
}
|
|
236
243
|
if (!content || typeof content !== 'string') throw new Error('ssss.write needs content.');
|
|
@@ -35,7 +35,7 @@ const projectRoot = () => process.cwd();
|
|
|
35
35
|
router.get('/api/plugins/execution-targets', requireAuth, requireScope('config:read'), async (req, res) => {
|
|
36
36
|
try {
|
|
37
37
|
const { loadPluginModule } = await import('../../core/plugin-runtime.mjs');
|
|
38
|
-
const owner = await loadPluginModule('
|
|
38
|
+
const owner = await loadPluginModule('meta-harness', 'command-executor', { activeRequired: true });
|
|
39
39
|
const result = req.query.node ? await owner.inspectNode(String(req.query.node)) : await owner.listTargets();
|
|
40
40
|
res.json({ success: true, ...result });
|
|
41
41
|
} catch (err) { badRequest(res, err.message); }
|
|
@@ -338,7 +338,7 @@ router.post("/api/plugins/:id/run", requireAuth, (req, res, next) => {
|
|
|
338
338
|
if (stdin !== undefined) return badRequest(res, 'Credential operations require direct local execution');
|
|
339
339
|
if (!pluginActivation(plugin, { projectRoot: projectRoot() }).enabled && !['on', 'off', 'status', 'health', 'jobs', '--help', 'help'].includes(subcommand)) return badRequest(res, `Plugin ${plugin.id} is inactive`);
|
|
340
340
|
const { loadPluginModule } = await import('../../core/plugin-runtime.mjs');
|
|
341
|
-
const owner = await loadPluginModule('
|
|
341
|
+
const owner = await loadPluginModule('meta-harness', 'command-executor', { activeRequired: true });
|
|
342
342
|
const result = await owner.executeCommand({ plugin, subcommand, args, execution, projectRoot: projectRoot() });
|
|
343
343
|
return res.json({ success: result.ok, pluginId: plugin.id, ...result });
|
|
344
344
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--bg-primary:#070b14;--bg-secondary:#0c1220;--bg-tertiary:#121a2b;--bg-elevated:#172033;--bg-hover:#1c2740;--bg-active:#243352;--accent:#3b82f6;--accent-deep:#1e3a8a;--accent-glow:#3b82f647;--accent-hover:#60a5fa;--accent-muted:#3b82f624;--accent-soft:#93c5fd1f;--purple-primary:var(--accent);--purple-border:var(--border-accent);--success:#34d399;--success-muted:#34d3991f;--warning:#fbbf24;--warning-muted:#fbbf241f;--error:#f87171;--error-muted:#f871711f;--text-primary:#f1f5f9;--text-secondary:#94a3b8;--text-tertiary:#64748b;--text-inverse:#070b14;--border:#94a3b81a;--border-hover:#94a3b838;--border-accent:#3b82f659;--glass:#0c1220b8;--glass-border:#94a3b81f;--mesh-1:#1e3a8a59;--mesh-2:#3b82f61f;--mesh-3:#0f172ae6;--font-sans:"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono:"JetBrains Mono", "SF Mono", ui-monospace, monospace;--sidebar-width:252px;--radius-sm:8px;--radius-md:12px;--radius-lg:18px;--radius-xl:24px;--shadow-sm:0 1px 2px #00000059;--shadow-md:0 8px 24px #00000059;--shadow-lg:0 24px 64px #00000073;--shadow-glow:0 0 40px #3b82f62e;--transition-fast:.14s cubic-bezier(.23, 1, .32, 1);--transition-normal:.22s cubic-bezier(.23, 1, .32, 1)}*,:before,:after{box-sizing:border-box;margin:0;padding:0}html,body,#root{background:var(--bg-primary);height:100%;color:var(--text-primary);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizelegibility;font-size:14px;line-height:1.6}body{background:radial-gradient(1200px 600px at 10% -10%, var(--mesh-1), transparent 55%), radial-gradient(900px 500px at 100% 0%, var(--mesh-2), transparent 50%), radial-gradient(800px 400px at 50% 100%, #1e3a8a33, transparent 60%), var(--bg-primary)}a{color:var(--accent);text-decoration:none}a:hover{color:var(--accent-hover)}button{font-family:var(--font-sans);cursor:pointer;border:none;outline:none}code,pre{font-family:var(--font-mono)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--bg-hover);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-tertiary)}.app-layout{height:100vh;display:flex;position:relative;overflow:hidden}.sidebar{width:var(--sidebar-width);min-width:var(--sidebar-width);background:var(--glass);-webkit-backdrop-filter:blur(20px)saturate(1.4);border-right:1px solid var(--glass-border);transition:transform var(--transition-normal);z-index:100;flex-direction:column;padding:14px 0 12px;display:flex;box-shadow:4px 0 40px #00000040}.sidebar-brand{border-bottom:1px solid var(--border);flex-direction:column;gap:10px;margin-bottom:10px;padding:4px 16px 18px;display:flex}.sidebar-brand-row{align-items:center;gap:12px;display:flex}.sidebar-brand h1{color:var(--text-primary);letter-spacing:-.03em;font-size:15px;font-weight:700;line-height:1.2}.sidebar-brand span{color:var(--text-tertiary);letter-spacing:.02em;font-size:11px;font-weight:500}.sidebar-brand-lockup{background:linear-gradient(#f8fafc 0%,#e8eef9 100%);border-radius:12px;width:100%;padding:10px 12px;overflow:hidden;box-shadow:inset 0 1px #ffffffb3,0 8px 20px #0f172a59,0 0 0 1px #94a3b833}.sidebar-brand-lockup img{object-fit:contain;width:100%;height:auto;display:block}.sidebar-nav{scrollbar-width:thin;flex-direction:column;flex:1;gap:2px;padding:0 10px;display:flex;overflow-y:auto}.nav-section-label{text-transform:uppercase;letter-spacing:.08em;color:var(--text-tertiary);opacity:.85;padding:12px 12px 6px;font-size:10px;font-weight:600}.nav-link{color:var(--text-secondary);transition:all var(--transition-fast);border:1px solid #0000;border-radius:10px;align-items:center;gap:10px;padding:9px 12px;font-size:13px;font-weight:500;text-decoration:none;display:flex;position:relative}.nav-link:hover{color:var(--text-primary);background:#94a3b814}.nav-link.active{color:#dbeafe;border-color:var(--border-accent);background:linear-gradient(135deg,#3b82f62e,#1e3a8a38);box-shadow:0 0 0 1px #3b82f614,inset 0 1px #ffffff0a}.nav-link.active:before{content:"";background:linear-gradient(#60a5fa,#2563eb);border-radius:0 3px 3px 0;width:3px;position:absolute;top:20%;bottom:20%;left:0;box-shadow:0 0 12px #3b82f699}.nav-link svg{opacity:.72;flex-shrink:0;width:18px;height:18px}.nav-link.active svg{opacity:1;color:#93c5fd}.nav-link:hover svg{opacity:.95}.sidebar-footer{border-top:1px solid var(--border);background:linear-gradient(#0000,#0f172a66);margin-top:8px;padding:12px 14px}.main-content{background:0 0;flex-direction:column;flex:1;min-width:0;display:flex;overflow:hidden}.page{flex:1;padding:28px 32px 40px;overflow-y:auto}.page-header{margin-bottom:28px}.page-header h1{letter-spacing:-.03em;background:linear-gradient(135deg,#f8fafc 0%,#93c5fd 100%);-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text;font-size:24px;font-weight:700}.page-header p{color:var(--text-secondary);max-width:56ch;margin-top:6px;font-size:13.5px}.card{border:1px solid var(--border);border-radius:var(--radius-md);transition:border-color var(--transition-normal), box-shadow var(--transition-normal), transform var(--transition-normal);box-shadow:var(--shadow-sm);background:linear-gradient(165deg,#172033e6,#0c1220f2);padding:20px}.card:hover{border-color:var(--border-hover);box-shadow:var(--shadow-md)}.card-grid{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px;display:grid}.btn{transition:all var(--transition-fast);letter-spacing:-.01em;border-radius:10px;align-items:center;gap:6px;padding:9px 16px;font-size:13px;font-weight:600;display:inline-flex}.btn-primary{color:#fff;background:linear-gradient(135deg,#3b82f6 0%,#2563eb 55%,#1d4ed8 100%);box-shadow:inset 0 1px #ffffff26,0 8px 20px #2563eb47}.btn-primary:hover{box-shadow:0 0 28px var(--accent-glow), 0 8px 24px #2563eb59;background:linear-gradient(135deg,#60a5fa 0%,#3b82f6 50%,#2563eb 100%);transform:translateY(-1px)}.btn-ghost{color:var(--text-secondary);border:1px solid var(--border);background:#94a3b80f}.btn-ghost:hover{color:var(--text-primary);border-color:var(--border-hover);background:#94a3b81f}.btn-sm{padding:6px 11px;font-size:12px}.alert{border-radius:var(--radius-sm);border:1px solid #0000;padding:12px 16px;font-size:13px;font-weight:500}.alert-error{background:var(--error-muted);color:var(--error);border-color:#f8717140}.alert-success{background:var(--success-muted);color:var(--success);border-color:#34d39940}.alert-warning{background:var(--warning-muted);color:var(--warning);border-color:#fbbf2440}.input,.select{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text-primary);font-family:var(--font-sans);transition:border-color var(--transition-fast), box-shadow var(--transition-fast);outline:none;padding:9px 12px;font-size:13px}.input:focus,.select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-muted)}.input::placeholder{color:var(--text-tertiary)}.select{cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");background-position:right 10px center;background-repeat:no-repeat;padding-right:32px}.data-table{border-collapse:collapse;width:100%}.data-table th{text-align:left;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);background:var(--bg-secondary);padding:12px 16px;font-size:12px;font-weight:500}.data-table td{border-bottom:1px solid var(--border);color:var(--text-primary);padding:14px 16px;font-size:13px}.data-table tbody tr{transition:background var(--transition-fast)}.data-table tbody tr:hover{background:var(--bg-hover)}.data-table tr.is-leader td{background:var(--accent-soft)}.page-container{max-width:1200px;margin:0 auto;padding:32px}.page-header{justify-content:space-between;align-items:center;margin-bottom:32px;display:flex}.page-header h1{color:var(--text-primary);font-size:24px;font-weight:600}.badge{letter-spacing:.03em;border-radius:4px;align-items:center;padding:3px 8px;font-size:11px;font-weight:600;display:inline-flex}.badge-online,.badge-active{background:var(--success-muted);color:var(--success)}.badge-offline,.badge-inactive{background:var(--error-muted);color:var(--error)}.badge-leader{color:var(--accent-hover);background:#3b82f626}.badge-follower{background:var(--bg-hover);color:var(--text-secondary)}.surface-premium{border:1px solid var(--glass-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-md), inset 0 1px 0 #ffffff0a;background:linear-gradient(165deg,#172033eb,#0c1220f5)}.login-shell{justify-content:center;align-items:center;min-height:100vh;padding:24px;display:flex;position:relative;overflow:hidden}.login-shell:before{content:"";pointer-events:none;background:radial-gradient(50% 40% at 20% 20%,#2563eb47,#0000 55%),radial-gradient(45% 35% at 80% 70%,#1e3a8a59,#0000 50%),radial-gradient(30% 25%,#60a5fa14,#0000 60%);animation:18s ease-in-out infinite alternate meshDrift;position:absolute;inset:-20%}@keyframes meshDrift{0%{transform:translate(0,0)scale(1)}to{transform:translate(-2%,2%)scale(1.05)}}.login-card{z-index:1;border-radius:var(--radius-xl);-webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);background:linear-gradient(165deg,#121a2beb,#080c16f5);border:1px solid #94a3b824;flex-direction:column;gap:28px;width:min(400px,100%);padding:40px 36px;display:flex;position:relative;box-shadow:0 0 0 1px #3b82f614,0 32px 80px #0000008c,0 0 80px #2563eb1f}.onboarding-shell{max-width:680px;margin:0 auto;padding:36px 22px 72px}.onboarding-progress{background:#94a3b81a;border-radius:99px;height:6px;overflow:hidden;box-shadow:inset 0 1px 2px #0000004d}.onboarding-progress>div{background:linear-gradient(90deg,#1e40af,#3b82f6,#93c5fd);border-radius:99px;height:100%;transition:width .3s cubic-bezier(.23,1,.32,1);box-shadow:0 0 16px #3b82f680}.loop-card{border:1px solid var(--border-accent);border-radius:var(--radius-md);font-family:var(--font-mono);color:#cbd5e1;background:linear-gradient(145deg,#1e3a8a40,#0c1220cc);padding:18px 20px;font-size:12.5px;line-height:1.85;box-shadow:inset 0 1px #ffffff0a}.badge{text-transform:uppercase;letter-spacing:.04em;border-radius:99px;align-items:center;gap:4px;padding:3px 8px;font-size:11px;font-weight:600;display:inline-flex}.badge-success{background:var(--success-muted);color:var(--success)}.badge-warning{background:var(--warning-muted);color:var(--warning)}.badge-error{background:var(--error-muted);color:var(--error)}.badge-accent{background:var(--accent-muted);color:var(--accent)}.chat-container{flex-direction:column;flex:1;height:calc(100vh - 1px);display:flex;overflow:hidden}.chat-messages{flex-direction:column;flex:1;gap:16px;padding:24px 32px;display:flex;overflow-y:auto}.chat-empty{color:var(--text-tertiary);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:12px;display:flex}.chat-empty svg{opacity:.3;width:48px;height:48px}.chat-empty h2{color:var(--text-secondary);font-size:18px;font-weight:600}.chat-empty p{font-size:13px}.message{border-radius:var(--radius-md);white-space:pre-wrap;word-break:break-word;max-width:720px;padding:14px 18px;font-size:14px;line-height:1.7;animation:.2s msgIn}@keyframes msgIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.message-user{color:#fff;background:linear-gradient(135deg,#3b82f6 0%,#2563eb 100%);border-bottom-right-radius:4px;align-self:flex-end;box-shadow:0 8px 24px #2563eb38}.message-assistant{border:1px solid var(--border);box-shadow:var(--shadow-sm);background:linear-gradient(165deg,#172033f2,#0c1220fa);border-bottom-left-radius:4px;align-self:flex-start}.message-assistant code{background:var(--bg-primary);border-radius:4px;padding:1px 5px;font-size:12.5px}.chat-input-bar{border-top:1px solid var(--border);background:var(--bg-secondary);padding:16px 32px 20px}.chat-input-wrapper{align-items:flex-end;gap:8px;max-width:800px;margin:0 auto;display:flex}.chat-input-wrapper textarea{resize:none;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:var(--radius-md);color:var(--text-primary);font-family:var(--font-sans);min-height:44px;max-height:160px;transition:border-color var(--transition-fast);flex:1;padding:12px 16px;font-size:14px;line-height:1.5}.chat-input-wrapper textarea:focus{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-glow);outline:none}.chat-input-wrapper textarea::placeholder{color:var(--text-tertiary)}.chat-send-btn{border-radius:var(--radius-md);color:#fff;width:44px;height:44px;transition:all var(--transition-fast);background:linear-gradient(135deg,#3b82f6,#1d4ed8);flex-shrink:0;justify-content:center;align-items:center;display:flex;box-shadow:0 4px 14px #2563eb59}.chat-send-btn:hover{box-shadow:0 0 24px var(--accent-glow);background:linear-gradient(135deg,#60a5fa,#2563eb);transform:translateY(-1px)}.chat-send-btn:disabled{opacity:.4;cursor:not-allowed;box-shadow:none}.chat-send-btn svg{width:18px;height:18px}.memory-layout{grid-template-columns:220px 1fr;gap:20px;height:calc(100vh - 120px);display:grid}.memory-sidebar{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);padding:12px;overflow-y:auto}.memory-sidebar h3{text-transform:uppercase;letter-spacing:.06em;color:var(--text-tertiary);padding:8px 8px 6px;font-size:11px}.category-btn{border-radius:var(--radius-sm);width:100%;color:var(--text-secondary);text-align:left;transition:all var(--transition-fast);background:0 0;justify-content:space-between;align-items:center;padding:8px 10px;font-size:13px;display:flex}.category-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.category-btn.active{background:var(--accent-muted);color:var(--accent)}.category-count{background:var(--bg-tertiary);color:var(--text-tertiary);border-radius:99px;padding:1px 6px;font-size:11px}.memory-main{flex-direction:column;gap:12px;display:flex;overflow:hidden}.memory-search{gap:8px;display:flex}.memory-search input{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);color:var(--text-primary);font-size:13px;font-family:var(--font-sans);transition:border-color var(--transition-fast);flex:1;padding:10px 14px}.memory-search input:focus{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-glow);outline:none}.memory-search input::placeholder{color:var(--text-tertiary)}.memory-list{flex-direction:column;flex:1;gap:8px;display:flex;overflow-y:auto}.memory-node-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-fast);padding:14px 16px}.memory-node-card:hover{border-color:var(--accent);background:var(--bg-tertiary)}.memory-node-card.selected{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent-glow)}.memory-node-card h4{margin-bottom:4px;font-size:14px;font-weight:600}.memory-node-card .meta{color:var(--text-tertiary);gap:12px;font-size:12px;display:flex}.memory-detail{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);padding:24px;overflow-y:auto}.memory-detail h2{margin-bottom:12px;font-size:18px}.memory-detail pre{background:var(--bg-primary);border:1px solid var(--border);border-radius:var(--radius-sm);white-space:pre-wrap;color:var(--text-secondary);padding:16px;font-size:12px;line-height:1.7;overflow-x:auto}.memory-detail .frontmatter{background:var(--bg-tertiary);border-radius:var(--radius-sm);grid-template-columns:auto 1fr;gap:4px 16px;margin-bottom:16px;padding:12px 16px;font-size:13px;display:grid}.frontmatter dt{color:var(--text-tertiary);font-weight:500}.frontmatter dd{color:var(--text-primary)}.health-grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:24px;display:grid}.stat-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);text-align:center;transition:border-color var(--transition-normal);padding:20px}.stat-card:hover{border-color:var(--border-hover)}.stat-card .value{letter-spacing:-.02em;font-size:28px;font-weight:700}.stat-card .label{color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.04em;margin-top:4px;font-size:12px}.stat-card.healthy .value{color:var(--success)}.stat-card.warning .value{color:var(--warning)}.stat-card.error .value{color:var(--error)}.health-log{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);padding:20px}.health-log h3{margin-bottom:12px;font-size:14px;font-weight:600}.health-log table{border-collapse:collapse;width:100%;font-size:13px}.health-log th{text-align:left;color:var(--text-tertiary);border-bottom:1px solid var(--border);padding:8px 12px;font-weight:500}.health-log td{border-bottom:1px solid var(--border);color:var(--text-secondary);padding:8px 12px}.sandbox-layout{grid-template-rows:1fr auto 1fr;gap:0;height:calc(100vh - 120px);display:grid}.sandbox-editor{flex-direction:column;display:flex;overflow:hidden}.sandbox-editor-header{background:var(--bg-tertiary);border:1px solid var(--border);border-radius:var(--radius-md) var(--radius-md) 0 0;color:var(--text-tertiary);justify-content:space-between;align-items:center;padding:8px 12px;font-size:12px;display:flex}.sandbox-editor textarea{resize:none;background:var(--bg-secondary);border:1px solid var(--border);border-radius:0 0 var(--radius-md) var(--radius-md);width:100%;color:var(--text-primary);font-family:var(--font-mono);tab-size:2;border-top:none;flex:1;padding:16px;font-size:13px;line-height:1.6}.sandbox-editor textarea:focus{border-color:var(--accent);outline:none}.sandbox-toolbar{justify-content:center;align-items:center;padding:8px;display:flex}.sandbox-output{background:var(--bg-primary);border:1px solid var(--border);border-radius:var(--radius-md);font-family:var(--font-mono);color:var(--success);white-space:pre-wrap;padding:16px;font-size:13px;line-height:1.6;overflow-y:auto}.sandbox-output.error{color:var(--error)}.pulse{background:var(--success);border-radius:50%;width:8px;height:8px;animation:2s infinite pulseAnim;display:inline-block}@keyframes pulseAnim{0%,to{box-shadow:0 0 #00cec966}50%{box-shadow:0 0 0 6px #00cec900}}.skeleton{background:linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-hover) 50%, var(--bg-tertiary) 75%);border-radius:var(--radius-sm);background-size:200% 100%;animation:1.5s infinite shimmer}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@media (width<=768px){.sidebar{position:fixed;top:0;bottom:0;left:0;transform:translate(-100%);box-shadow:0 0 40px #00000080}.sidebar.open{transform:translate(0)}.memory-layout{grid-template-columns:1fr;height:auto}.page{padding:16px}.btn,.nav-link,.category-btn{touch-action:manipulation;min-height:44px}.chat-input-wrapper textarea,.memory-search input,.sandbox-editor textarea{font-size:16px}.chat-input-bar{padding:12px 16px 16px}}.chat-layout{width:100%;height:100%;display:flex;overflow:hidden}.chat-threads-sidebar{background:var(--bg-secondary);border-right:1px solid var(--border);flex-direction:column;flex-shrink:0;width:260px;height:100%;display:flex;overflow:hidden}.sidebar-header{border-bottom:1px solid var(--border);padding:16px}.new-chat-btn{background:var(--accent);color:#fff;border-radius:var(--radius-sm);width:100%;transition:all var(--transition-fast);cursor:pointer;border:none;justify-content:center;align-items:center;gap:8px;padding:10px 16px;font-size:13px;font-weight:500;display:flex}.new-chat-btn:hover{background:var(--accent-hover);box-shadow:0 0 16px var(--accent-glow)}.thread-list{flex-direction:column;flex:1;gap:6px;padding:16px 12px;display:flex;overflow-y:auto}.thread-item{border-radius:var(--radius-sm);cursor:pointer;transition:all var(--transition-fast);border:1px solid #0000;justify-content:space-between;align-items:center;padding:10px 12px;display:flex;position:relative}.thread-item:hover{background:var(--bg-hover)}.thread-item.active{border-color:var(--border);background:#ffffff0d}.thread-info{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.thread-title{color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:500;overflow:hidden}.thread-meta{color:var(--text-tertiary);align-items:center;gap:8px;font-size:11px;display:flex}.thread-delete-btn{color:var(--text-tertiary);cursor:pointer;opacity:0;transition:all var(--transition-fast);background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;padding:4px;display:flex}.thread-item:hover .thread-delete-btn{opacity:1}.thread-delete-btn:hover{color:var(--error);background:var(--error-muted)}.chat-suggestions-container{width:100%;max-width:900px;margin-top:32px;animation:.4s ease-out fadeIn}.chat-suggestions-container h3{text-transform:uppercase;letter-spacing:.08em;color:var(--text-tertiary);text-align:center;margin-bottom:16px;font-size:12px}.chat-suggestions-deck{grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px;width:100%;display:grid}.suggestion-card{-webkit-backdrop-filter:blur(12px);border-radius:var(--radius-md);cursor:pointer;text-align:left;transition:all var(--transition-normal);background:#12121a73;border:1px solid #ffffff0d;gap:14px;padding:16px;display:flex;position:relative;overflow:hidden}.suggestion-card:before{content:"";opacity:.8;width:4px;height:100%;transition:width var(--transition-fast) ease;position:absolute;top:0;left:0}.suggestion-card.card-fact:before{background:var(--success)}.suggestion-card.card-concept:before{background:var(--accent)}.suggestion-card.card-question:before{background:var(--warning)}.suggestion-card:hover{background:#1a1a2699;border-color:#ffffff1f;transform:translateY(-2px);box-shadow:0 8px 24px #0003}.suggestion-card:hover:before{width:6px}.suggestion-card.card-fact:hover{border-color:#00cec940;box-shadow:0 8px 24px #00cec90f}.suggestion-card.card-concept:hover{border-color:#6c5ce740;box-shadow:0 8px 24px #6c5ce70f}.suggestion-card.card-question:hover{border-color:#fdcb6e40;box-shadow:0 8px 24px #fdcb6e0f}.suggestion-card-icon{-webkit-user-select:none;user-select:none;align-items:flex-start;padding-top:2px;font-size:22px;display:flex}.suggestion-card-content{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.suggestion-card-content h4{color:var(--text-primary);font-size:13.5px;font-weight:600;line-height:1.4}.suggestion-card-content p{color:var(--text-secondary);font-size:12.5px;line-height:1.5}.suggestion-card-nodes{flex-wrap:wrap;gap:6px;margin-top:8px;display:flex}.suggestion-node-pill{background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);white-space:nowrap;text-overflow:ellipsis;border-radius:4px;max-width:140px;padding:2px 6px;font-size:10px;font-weight:500;overflow:hidden}.grounding-pills{border:1px solid var(--border);border-radius:var(--radius-md) var(--radius-md) 0 0;background:#12121a66;border-bottom:none;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 12px;display:flex}.grounding-pill{background:var(--accent-muted);color:var(--text-primary);transition:all var(--transition-fast);border:1px solid #6c5ce74d;border-radius:99px;align-items:center;gap:6px;padding:4px 10px;font-size:12px;font-weight:500;display:inline-flex}.grounding-pill:hover{background:#6c5ce733;border-color:#6c5ce780}.grounding-pill svg{color:var(--accent);flex-shrink:0}.grounding-pill-title{text-overflow:ellipsis;white-space:nowrap;max-width:150px;overflow:hidden}.grounding-pill-remove{color:var(--text-tertiary);cursor:pointer;transition:color var(--transition-fast);background:0 0;border:none;justify-content:center;align-items:center;padding:0 2px;font-size:14px;line-height:1;display:inline-flex}.grounding-pill-remove:hover{color:var(--error)}.grounding-count-badge{background:var(--accent);color:#fff;border:1.5px solid var(--bg-secondary);min-width:16px;height:16px;box-shadow:0 0 8px var(--accent-glow);border-radius:50%;justify-content:center;align-items:center;font-size:9px;font-weight:700;animation:.2s cubic-bezier(.175,.885,.32,1.275) popIn;display:flex;position:absolute;top:-4px;right:-4px}.node-selector-popover{-webkit-backdrop-filter:blur(20px);border-radius:var(--radius-lg);z-index:50;background:#12121ad9;border:1px solid #ffffff14;flex-direction:column;width:320px;max-height:380px;animation:.25s cubic-bezier(.16,1,.3,1) slideUp;display:flex;position:absolute;bottom:calc(100% + 12px);left:0;overflow:hidden;box-shadow:0 12px 32px #0006}.node-selector-header{border-bottom:1px solid var(--border);flex-direction:column;gap:8px;padding:14px 16px 10px;display:flex}.node-selector-header h4{color:var(--text-primary);letter-spacing:.02em;font-size:13px;font-weight:600}.node-search-input{background:var(--bg-tertiary);border:1px solid var(--border);border-radius:var(--radius-sm);width:100%;color:var(--text-primary);font-size:12px;font-family:var(--font-sans);transition:all var(--transition-fast);padding:6px 10px}.node-search-input:focus{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-glow);outline:none}.node-selector-list{flex-direction:column;flex:1;gap:2px;padding:8px;display:flex;overflow-y:auto}.node-selector-empty{text-align:center;color:var(--text-tertiary);padding:24px;font-size:12px}.node-selector-item{border-radius:var(--radius-sm);cursor:pointer;transition:all var(--transition-fast);align-items:center;gap:10px;padding:8px 12px;display:flex}.node-selector-item:hover{background:var(--bg-hover)}.node-selector-item.selected{background:var(--accent-muted)}.node-selector-item-checkbox{border:1px solid var(--text-tertiary);width:16px;height:16px;color:var(--accent);transition:all var(--transition-fast);background:var(--bg-tertiary);border-radius:3px;flex-shrink:0;justify-content:center;align-items:center;font-size:11px;font-weight:700;display:flex}.node-selector-item.selected .node-selector-item-checkbox{background:var(--accent);border-color:var(--accent);color:#fff}.node-selector-item-info{flex-direction:column;flex:1;min-width:0;display:flex}.node-selector-item-title{color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;font-size:13px;font-weight:500;overflow:hidden}.node-selector-item-meta{color:var(--text-tertiary);text-transform:capitalize;font-size:11px}.animate-fade-in{animation:.2s ease-out fadeIn}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideUp{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes popIn{0%{opacity:0;transform:scale(.6)}to{opacity:1;transform:scale(1)}}.graph-3d-wrapper{-webkit-user-select:none;user-select:none;background:radial-gradient(circle,#0a0d16 0%,#020408 100%);width:100%;height:100%;position:relative;overflow:hidden}.node-detail-drawer{-webkit-backdrop-filter:blur(25px);border-radius:var(--radius-lg);z-index:100;width:350px;color:var(--text-primary);background:#0a0f1ebf;border:1px solid #ffffff14;flex-direction:column;display:flex;position:absolute;top:20px;bottom:20px;right:20px;overflow:hidden;box-shadow:0 16px 48px #00000080,inset 0 0 0 1px #ffffff0d}.animate-slide-in{animation:.4s cubic-bezier(.16,1,.3,1) forwards slideInRight}@keyframes slideInRight{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}.drawer-header{border-bottom:1px solid #ffffff0f;justify-content:space-between;align-items:center;padding:16px 20px;display:flex}.drawer-badge{text-transform:uppercase;letter-spacing:.05em;border-radius:4px;padding:4px 8px;font-size:10px;font-weight:700}.drawer-badge.badge-memory{color:#c084fc;background:#8b5cf626;border:1px solid #8b5cf64d}.drawer-badge.badge-thread{color:#22d3ee;background:#06b6d426;border:1px solid #06b6d44d}.drawer-badge.badge-research{color:#fbbf24;background:#f59e0b26;border:1px solid #f59e0b4d}.drawer-close-btn{color:var(--text-tertiary);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:4px;font-size:20px;transition:color .15s;display:flex}.drawer-close-btn:hover{color:#fff}.drawer-body{flex-direction:column;flex:1;gap:12px;padding:20px;display:flex;overflow-y:auto}.drawer-title{color:#fff;margin:0;font-size:18px;font-weight:700;line-height:1.35}.drawer-subtitle{color:var(--text-tertiary);margin-top:-4px;font-size:11px}.drawer-status{background:#ffffff08;border:1px solid #ffffff0a;border-radius:6px;align-items:center;gap:8px;width:fit-content;padding:6px 12px;font-size:12px;display:flex}.drawer-status .label{color:var(--text-tertiary)}.drawer-status .value{text-transform:capitalize;font-weight:600}.drawer-status .value.status-completed,.drawer-status .value.status-done,.drawer-status .value.status-active{color:#10b981}.drawer-status .value.status-in-progress{color:#f59e0b}.drawer-status .value.status-pending{color:#3b82f6}.drawer-excerpt{color:var(--text-secondary);white-space:pre-wrap;margin-top:8px;font-size:13.5px;line-height:1.6}.drawer-footer{background:#00000026;border-top:1px solid #ffffff0f;padding:16px 20px}.drawer-action-btn{border-radius:var(--radius-sm);cursor:pointer;width:100%;transition:all var(--transition-fast);border:none;justify-content:center;align-items:center;padding:10px 16px;font-size:13px;font-weight:600;display:flex}.drawer-action-btn.btn-cyan{color:#fff;background:#06b6d4}.drawer-action-btn.btn-cyan:hover{background:#0891b2;box-shadow:0 0 16px #06b6d466}.drawer-action-btn.btn-purple{color:#fff;background:#8b5cf6}.drawer-action-btn.btn-purple:hover{background:#7c3aed;box-shadow:0 0 16px #8b5cf666}@media (width<=768px){.node-detail-drawer{width:auto;height:300px;bottom:20px;left:20px}}.chat-messages:has(.graph-3d-wrapper){padding:0;overflow:hidden}.chat-header{-webkit-backdrop-filter:blur(16px);border-bottom:1px solid var(--border);z-index:40;background:#12121aeb;flex-shrink:0;justify-content:space-between;align-items:center;padding:12px 24px;display:flex;position:relative;overflow:visible}.chat-header-title{align-items:center;gap:10px;display:flex}.chat-header-title h2{color:var(--text-primary);letter-spacing:-.01em;font-size:15px;font-weight:600}.chat-header-model-selector{align-items:center;gap:8px;display:flex}.chat-header-model-selector span.selector-label{color:var(--text-tertiary);font-size:12px;font-weight:500}.model-selector-dropdown-wrapper{position:relative}.model-selector-dropdown-btn{border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text-secondary);transition:all var(--transition-fast);cursor:pointer;background:#ffffff0a;align-items:center;gap:8px;padding:6px 12px;font-size:13px;font-weight:500;display:flex}.model-selector-dropdown-btn:hover{border-color:var(--border-hover);color:var(--text-primary);background:#ffffff14}.model-selector-dropdown-btn.active{border-color:var(--accent);color:var(--text-primary);background:var(--accent-muted)}.model-selector-menu{-webkit-backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:var(--radius-md);z-index:200;pointer-events:auto;background:#12121afa;flex-direction:column;gap:2px;width:240px;margin-top:6px;padding:6px;animation:.15s ease-out popoverIn;display:flex;position:absolute;top:100%;right:0;box-shadow:0 12px 32px #0000008c}@keyframes popoverIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.model-selector-item{border-radius:var(--radius-sm);cursor:pointer;transition:all var(--transition-fast);text-align:left;justify-content:space-between;align-items:center;padding:8px 12px;display:flex}.model-selector-item:hover{background:var(--bg-hover)}.model-selector-item.selected{background:var(--accent-muted)}.model-selector-item-info{flex-direction:column;gap:2px;display:flex}.model-selector-item-name{color:var(--text-primary);font-size:13px;font-weight:500}.model-selector-item.selected .model-selector-item-name{color:var(--accent)}.model-selector-item-desc{color:var(--text-tertiary);font-size:11px}.model-selector-item-badge{background:var(--bg-tertiary);color:var(--text-tertiary);text-transform:uppercase;border-radius:99px;padding:2px 6px;font-size:9px;font-weight:600}.model-selector-item.selected .model-selector-item-badge{background:var(--accent-muted);color:var(--accent)}@media (width<=768px){button,a,[role=button]{min-width:44px;min-height:44px}.chat-input-area{bottom:0;bottom:env(safe-area-inset-bottom,0);position:sticky}.app-container{padding-left:env(safe-area-inset-left,0);padding-right:env(safe-area-inset-right,0);padding-bottom:env(safe-area-inset-bottom,0)}}@media (display-mode:standalone){body{padding-top:env(safe-area-inset-top,0)}}.network-page{animation:.35s ease-out page-fade-in}@keyframes page-fade-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.stat-grid{grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:32px;display:grid}.stat-card{text-align:center;flex-direction:column;align-items:center;padding:24px 16px;display:flex}.stat-value{color:var(--text-primary);font-variant-numeric:tabular-nums;font-size:32px;font-weight:700;line-height:1.2;transition:color .3s}.stat-value.error{color:var(--error)}.stat-value.success{color:var(--success)}.stat-value.accent{color:var(--accent)}.stat-label{color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.06em;margin-top:8px;font-size:12px;font-weight:500}.network-sections{grid-template-columns:1fr 1fr;gap:24px;margin-bottom:32px;display:grid}.network-section-full{margin-bottom:32px}.section-title{color:var(--text-primary);align-items:center;gap:8px;margin-bottom:16px;font-size:15px;font-weight:600;display:flex}.blocklist-items{max-height:240px;margin:0 0 12px;padding:0;list-style:none;overflow-y:auto}.blocklist-item{border-bottom:1px solid var(--border);font-family:var(--font-mono);color:var(--text-primary);transition:background var(--transition-fast);justify-content:space-between;align-items:center;padding:8px 12px;font-size:13px;display:flex}.blocklist-item:hover{background:var(--bg-hover)}.blocklist-item:last-child{border-bottom:none}.blocklist-remove{color:var(--error);opacity:.6;transition:opacity var(--transition-fast), background var(--transition-fast);background:0 0;border-radius:4px;padding:4px 8px;font-size:12px}.blocklist-remove:hover{opacity:1;background:var(--error-muted)}.blocklist-add{gap:8px;display:flex}.blocklist-add .input{flex:1}.limits-form{flex-direction:column;gap:12px;display:flex}.limits-row{justify-content:space-between;align-items:center;gap:12px;display:flex}.limits-label{color:var(--text-secondary);flex-shrink:0;font-size:13px}.limits-input{text-align:right;width:100px}.limits-actions{justify-content:flex-end;margin-top:8px;display:flex}.audit-filters{flex-wrap:wrap;gap:12px;margin-bottom:16px;display:flex}.audit-filters .input,.audit-filters .select{flex:1;min-width:140px}.audit-status-badge{border-radius:3px;align-items:center;padding:2px 6px;font-size:11px;font-weight:600;display:inline-flex}.audit-status-badge.success{background:var(--success-muted);color:var(--success)}.audit-status-badge.error{background:var(--error-muted);color:var(--error)}.audit-status-badge.timeout{background:var(--warning-muted);color:var(--warning)}.toggle-row{align-items:center;gap:12px;margin-bottom:16px;display:flex}.toggle-switch{background:var(--bg-hover);cursor:pointer;width:44px;height:24px;transition:background var(--transition-fast);border:1px solid var(--border);border-radius:12px;flex-shrink:0;position:relative}.toggle-switch.active{background:var(--accent);border-color:var(--accent)}.toggle-switch:after{content:"";width:18px;height:18px;transition:transform var(--transition-fast);background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;box-shadow:0 1px 3px #0003}.toggle-switch.active:after{transform:translate(20px)}.toggle-label{color:var(--text-secondary);font-size:13px}.refresh-controls{align-items:center;gap:8px;display:flex}.refresh-controls .select{width:auto;padding:6px 28px 6px 10px;font-size:12px}.empty-state{text-align:center;color:var(--text-tertiary);padding:32px 16px;font-size:13px;font-style:italic}@media (width<=768px){.stat-grid,.network-sections{grid-template-columns:1fr}}.webhooks-page{animation:.35s ease-out page-fade-in}.wizard-modal{width:100%;max-width:600px}.wizard-steps{justify-content:space-between;margin-bottom:24px;display:flex;position:relative}.wizard-steps:before{content:"";z-index:0;background:#ffffff1a;height:2px;position:absolute;top:12px;left:0;right:0}.wizard-step{background:var(--bg-surface);color:var(--text-muted);z-index:1;border:2px solid #ffffff1a;border-radius:50%;justify-content:center;align-items:center;width:28px;height:28px;font-size:14px;font-weight:600;transition:all .3s;display:flex}.wizard-step.active{background:var(--primary);color:#fff;border-color:var(--primary);box-shadow:0 0 12px #6366f180}.wizard-step.completed{background:var(--success);color:#fff;border-color:var(--success)}.json-viewer{color:#a5b4fc;background:#0003;border:1px solid #ffffff0d;border-radius:8px;margin-top:8px;padding:12px;font-family:Fira Code,monospace;font-size:13px;overflow-x:auto}.json-toggle{color:var(--primary);cursor:pointer;background:0 0;border:none;margin-top:4px;padding:0;font-size:12px}.json-toggle:hover{text-decoration:underline}.webhook-stats{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:16px;margin-bottom:24px;display:grid}.stat-box{text-align:center;background:#ffffff08;border:1px solid #ffffff0d;border-radius:12px;padding:16px}.stat-box .label{color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;font-size:12px}.stat-box .value{color:var(--text-primary);font-size:24px;font-weight:700}.rotate-btn{color:var(--text-primary);cursor:pointer;background:#ffffff0d;border:1px solid #ffffff1a;border-radius:4px;padding:4px 8px;font-size:12px;transition:all .2s}.rotate-btn:hover{background:#ffffff1a}.notifications-page{animation:.35s ease-out notif-fade-in}@keyframes notif-fade-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.notif-rules-card{padding:0;overflow:hidden}.notif-rules-actions{gap:6px;display:flex}.priority-badge{text-transform:uppercase;letter-spacing:.04em;border-radius:4px;align-items:center;padding:2px 8px;font-size:11px;font-weight:600;display:inline-flex}.priority-badge.critical{background:var(--error-muted);color:var(--error)}.priority-badge.high{background:var(--warning-muted);color:var(--warning)}.priority-badge.low{background:var(--success-muted);color:var(--success)}.notif-toggle{background:var(--bg-hover);cursor:pointer;width:40px;height:22px;transition:background var(--transition-fast);border:1px solid var(--border);border-radius:11px;flex-shrink:0;position:relative}.notif-toggle.active{background:var(--accent);border-color:var(--accent)}.notif-toggle:after{content:"";width:16px;height:16px;transition:transform var(--transition-fast);background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;box-shadow:0 1px 3px #0003}.notif-toggle.active:after{transform:translate(18px)}.notif-history-header{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.notif-history-header h2{color:var(--text-primary);font-size:15px;font-weight:600}.notif-history-list{flex-direction:column;gap:10px;max-height:420px;display:flex;overflow-y:auto}.notif-history-entry{background:var(--bg-hover);border-radius:var(--radius-sm);transition:background var(--transition-fast);justify-content:space-between;align-items:flex-start;padding:14px 16px;display:flex}.notif-history-entry:hover{background:var(--bg-secondary)}.notif-entry-content{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.notif-entry-title{color:var(--text-primary);font-size:14px;font-weight:500}.notif-entry-message{color:var(--text-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:13px;overflow:hidden}.notif-entry-meta{flex-direction:column;flex-shrink:0;align-items:flex-end;gap:4px;margin-left:16px;display:flex}.notif-entry-time{color:var(--text-tertiary);white-space:nowrap;font-size:12px}.notif-status-badge{border-radius:3px;align-items:center;padding:2px 6px;font-size:11px;font-weight:600;display:inline-flex}.notif-status-badge.delivered{background:var(--success-muted);color:var(--success)}.notif-status-badge.failed{background:var(--error-muted);color:var(--error)}.notif-quiet-hours-row{align-items:center;gap:10px;display:flex}.notif-quiet-hours-label{color:var(--text-secondary);cursor:pointer;font-size:13px}.notif-empty{text-align:center;color:var(--text-tertiary);padding:32px 16px;font-size:13px;font-style:italic}@media (width<=768px){.notif-history-entry{flex-direction:column;gap:8px}.notif-entry-meta{flex-direction:row;align-items:center;margin-left:0}}
|