clideck 1.31.0 → 1.31.4
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/README.md +1 -1
- package/agent-presets.json +1 -1
- package/bin/codex-hook.js +35 -0
- package/codex-config.js +8 -5
- package/codex-hooks.js +94 -0
- package/config.js +1 -43
- package/handlers.js +31 -10
- package/package.json +1 -1
- package/plugin-loader.js +2 -3
- package/public/js/app.js +17 -6
- package/public/js/hotkeys.js +12 -0
- package/public/js/terminals.js +45 -0
- package/public/tailwind.css +1 -1
- package/server.js +28 -16
- package/sessions.js +2 -41
- package/telemetry-receiver.js +18 -1
- package/public/js/roles.js +0 -112
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ the main problem with using multiple agents is not starting them. it is managing
|
|
|
26
26
|
|
|
27
27
|
Terminal multiplexers are great at panes. clideck is about conversations.
|
|
28
28
|
|
|
29
|
-
A pane grid is flat. agent work usually is not. projects,
|
|
29
|
+
A pane grid is flat. agent work usually is not. projects, previews, timestamps, notifications, resume, and sometimes a bit of routing between specialists all fit more naturally into a chat app layout. it also maps naturally to mobile, so the same mental model works on desktop and phone.
|
|
30
30
|
|
|
31
31
|
## Quick start
|
|
32
32
|
|
package/agent-presets.json
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:{{port}}",
|
|
44
44
|
"OTEL_LOGS_EXPORT_INTERVAL": "2000"
|
|
45
45
|
},
|
|
46
|
-
"telemetrySetup": "
|
|
46
|
+
"telemetrySetup": "Required for working/idle status, Autopilot, notifications, mobile remote, and resume. CliDeck will add OpenTelemetry config plus 2 silent Codex hooks. If Codex shows \"2 hooks need review\", open /hooks in Codex and approve the CliDeck hooks once.",
|
|
47
47
|
"telemetryAutoSetup": {
|
|
48
48
|
"label": "Configure automatically"
|
|
49
49
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Silent Codex lifecycle hook for CliDeck.
|
|
3
|
+
// Reads Codex hook JSON from stdin, posts to CliDeck, and intentionally prints nothing.
|
|
4
|
+
|
|
5
|
+
const http = require('http');
|
|
6
|
+
|
|
7
|
+
const port = parseInt(process.argv[2], 10);
|
|
8
|
+
const route = String(process.argv[3] || '').replace(/[^a-z]/g, '');
|
|
9
|
+
const clideckId = process.env.CLIDECK_SESSION_ID || '';
|
|
10
|
+
if (!port || !route) process.exit(0);
|
|
11
|
+
|
|
12
|
+
let body = '';
|
|
13
|
+
process.stdin.setEncoding('utf8');
|
|
14
|
+
process.stdin.on('data', chunk => {
|
|
15
|
+
body += chunk;
|
|
16
|
+
if (body.length > 1e6) process.exit(0);
|
|
17
|
+
});
|
|
18
|
+
process.stdin.on('end', () => {
|
|
19
|
+
let payload = {};
|
|
20
|
+
try { payload = body.trim() ? JSON.parse(body) : {}; } catch {}
|
|
21
|
+
payload.clideck_id = clideckId || undefined;
|
|
22
|
+
payload.source = 'hook';
|
|
23
|
+
|
|
24
|
+
const req = http.request({
|
|
25
|
+
hostname: 'localhost',
|
|
26
|
+
port,
|
|
27
|
+
path: `/hook/codex/${route}`,
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
timeout: 2000,
|
|
31
|
+
});
|
|
32
|
+
req.on('error', () => {});
|
|
33
|
+
req.on('timeout', () => req.destroy());
|
|
34
|
+
req.end(JSON.stringify(payload));
|
|
35
|
+
});
|
package/codex-config.js
CHANGED
|
@@ -39,11 +39,14 @@ function upsertCodexConfig(content, nodePath, notifyHelperPath, port) {
|
|
|
39
39
|
section.lines = section.lines.filter(line => !/^\s*notify\s*=/.test(line));
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
let hasFeatures = false;
|
|
43
|
+
const keptSections = sections.map(section => {
|
|
44
|
+
if (section.header !== '[features]') return section;
|
|
45
|
+
hasFeatures = true;
|
|
46
|
+
const lines = trimBlankEdges(section.lines.filter(line => !/^\s*(codex_hooks|hooks)\s*=/.test(line)));
|
|
47
|
+
return { ...section, lines: trimBlankEdges([...lines, 'hooks = true']) };
|
|
48
|
+
});
|
|
49
|
+
if (!hasFeatures) keptSections.push({ header: '[features]', lines: ['hooks = true'] });
|
|
47
50
|
|
|
48
51
|
const otelHeader = '[otel]';
|
|
49
52
|
const otelBody = [`exporter = { otlp-http = { endpoint = "http://localhost:${port}", protocol = "json" } }`];
|
package/codex-hooks.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } = require('fs');
|
|
2
|
+
const { dirname, join } = require('path');
|
|
3
|
+
|
|
4
|
+
const EVENTS = {
|
|
5
|
+
UserPromptSubmit: 'start',
|
|
6
|
+
Stop: 'stop',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function commandFor(nodePath, helperPath, port, route) {
|
|
10
|
+
return `"${nodePath}" "${helperPath}" ${port} ${route}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isClideckHook(hook) {
|
|
14
|
+
return typeof hook?.command === 'string' && hook.command.includes('codex-hook.js');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readHooksFile(path) {
|
|
18
|
+
if (!existsSync(path)) return { hooks: {} };
|
|
19
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
20
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
21
|
+
throw new Error('Invalid ~/.codex/hooks.json shape');
|
|
22
|
+
}
|
|
23
|
+
if (!parsed.hooks || typeof parsed.hooks !== 'object' || Array.isArray(parsed.hooks)) parsed.hooks = {};
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function stripClideckHooks(groups) {
|
|
28
|
+
return (Array.isArray(groups) ? groups : [])
|
|
29
|
+
.map(group => {
|
|
30
|
+
const hooks = (Array.isArray(group?.hooks) ? group.hooks : []).filter(hook => !isClideckHook(hook));
|
|
31
|
+
return { ...group, hooks };
|
|
32
|
+
})
|
|
33
|
+
.filter(group => group.hooks.length);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function installCodexHooks(home, nodePath, helperPath, port) {
|
|
37
|
+
const hooksPath = join(home, '.codex', 'hooks.json');
|
|
38
|
+
const doc = readHooksFile(hooksPath);
|
|
39
|
+
|
|
40
|
+
for (const [event, route] of Object.entries(EVENTS)) {
|
|
41
|
+
const groups = stripClideckHooks(doc.hooks[event]);
|
|
42
|
+
groups.push({
|
|
43
|
+
hooks: [{
|
|
44
|
+
type: 'command',
|
|
45
|
+
command: commandFor(nodePath, helperPath, port, route),
|
|
46
|
+
timeout: 5,
|
|
47
|
+
}],
|
|
48
|
+
});
|
|
49
|
+
doc.hooks[event] = groups;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
mkdirSync(dirname(hooksPath), { recursive: true });
|
|
53
|
+
writeFileSync(hooksPath, JSON.stringify(doc, null, 2) + '\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function removeCodexHooks(home) {
|
|
57
|
+
const hooksPath = join(home, '.codex', 'hooks.json');
|
|
58
|
+
if (!existsSync(hooksPath)) return;
|
|
59
|
+
const doc = readHooksFile(hooksPath);
|
|
60
|
+
|
|
61
|
+
for (const event of Object.keys(EVENTS)) {
|
|
62
|
+
const groups = stripClideckHooks(doc.hooks[event]);
|
|
63
|
+
if (groups.length) doc.hooks[event] = groups;
|
|
64
|
+
else delete doc.hooks[event];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (Object.keys(doc.hooks || {}).length) {
|
|
68
|
+
writeFileSync(hooksPath, JSON.stringify(doc, null, 2) + '\n');
|
|
69
|
+
} else {
|
|
70
|
+
delete doc.hooks;
|
|
71
|
+
if (Object.keys(doc).length) writeFileSync(hooksPath, JSON.stringify(doc, null, 2) + '\n');
|
|
72
|
+
else unlinkSync(hooksPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function codexHooksHealthy(home, helperPath, port) {
|
|
77
|
+
try {
|
|
78
|
+
const doc = readHooksFile(join(home, '.codex', 'hooks.json'));
|
|
79
|
+
for (const [event, route] of Object.entries(EVENTS)) {
|
|
80
|
+
const groups = Array.isArray(doc.hooks?.[event]) ? doc.hooks[event] : [];
|
|
81
|
+
const expected = commandFor(process.execPath.replace(/\\/g, '/'), helperPath, port, route);
|
|
82
|
+
const found = groups.some(group => (group.hooks || []).some(hook => {
|
|
83
|
+
if (!isClideckHook(hook)) return false;
|
|
84
|
+
return hook.command === expected || (hook.command.includes(helperPath) && hook.command.includes(` ${port} ${route}`));
|
|
85
|
+
}));
|
|
86
|
+
if (!found) return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { installCodexHooks, removeCodexHooks, codexHooksHealthy };
|
package/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { readFileSync, writeFileSync, existsSync } = require('fs');
|
|
2
2
|
const { join } = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
3
4
|
const os = require('os');
|
|
4
5
|
const { DATA_DIR } = require('./paths');
|
|
5
6
|
const { defaultShell, binName } = require('./utils');
|
|
@@ -31,46 +32,6 @@ list your fidings please.`,
|
|
|
31
32
|
},
|
|
32
33
|
];
|
|
33
34
|
|
|
34
|
-
const STARTER_ROLES = [
|
|
35
|
-
{
|
|
36
|
-
id: 'starter-role-programmer',
|
|
37
|
-
name: 'Programmer',
|
|
38
|
-
instructions: `You are the main programmer of this project.
|
|
39
|
-
Do you not apply workarounds or bandaids, prefer pure solutions.
|
|
40
|
-
NEVER use plan tool/mode, start to build immediatly and ask questions along the way if any.
|
|
41
|
-
Check if any external findings are valid before applying changes, the reviewer doesnt always updated with the full scope.
|
|
42
|
-
When you done with changes, list concisely what you did.
|
|
43
|
-
|
|
44
|
-
Learn the project quickly if exist. Go over the structure, code and functionality.
|
|
45
|
-
Let me know when you are ready.`,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: 'starter-role-reviewer',
|
|
49
|
-
name: 'Reviewer',
|
|
50
|
-
instructions: `You are the code reviewer in this project.
|
|
51
|
-
Your task is check the coder output and list critical / logical design flow issues, ugly workarounds or functionalty you just dont understand why its there. Do not waste time and list insignificunt findings.
|
|
52
|
-
|
|
53
|
-
If you didnt find anything, response with no findings.
|
|
54
|
-
|
|
55
|
-
You never write code!.
|
|
56
|
-
|
|
57
|
-
Quickly learn the project if exist. Go over the structure, code and functionality and let me know when you are ready.`,
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
id: 'starter-role-product-manager',
|
|
61
|
-
name: 'Product manager',
|
|
62
|
-
instructions: `You are the product manager of this project, you should understand why we do what we do and what is the best way to do it. You dont care about technical limitations or directions, the only thing matter to you is the user UI/UX and how this agents team will ship a top notch, professional deliveries.
|
|
63
|
-
Do not allow the team to round angles and skip small stuff that will basly impact the user.
|
|
64
|
-
|
|
65
|
-
You never write code!
|
|
66
|
-
You dont use your plan tool/mode - instead you are planning immediatly as you go.
|
|
67
|
-
|
|
68
|
-
Go over the project if exist and understand from the code, documentations and readme what is it and why we do it.
|
|
69
|
-
|
|
70
|
-
Let me know when you are ready`,
|
|
71
|
-
},
|
|
72
|
-
];
|
|
73
|
-
|
|
74
35
|
const DEFAULTS = {
|
|
75
36
|
defaultPath: join(os.homedir(), 'Documents'),
|
|
76
37
|
commands: [
|
|
@@ -87,7 +48,6 @@ const DEFAULTS = {
|
|
|
87
48
|
defaultTheme: 'catppuccin-mocha',
|
|
88
49
|
defaultShell,
|
|
89
50
|
prompts: [],
|
|
90
|
-
roles: [],
|
|
91
51
|
projects: [],
|
|
92
52
|
};
|
|
93
53
|
|
|
@@ -184,7 +144,6 @@ function migrate(cfg) {
|
|
|
184
144
|
}
|
|
185
145
|
}
|
|
186
146
|
if (!cfg.projects) cfg.projects = [];
|
|
187
|
-
if (!cfg.roles) cfg.roles = [];
|
|
188
147
|
return cfg;
|
|
189
148
|
}
|
|
190
149
|
|
|
@@ -193,7 +152,6 @@ function load() {
|
|
|
193
152
|
return {
|
|
194
153
|
...deepCopy(DEFAULTS),
|
|
195
154
|
prompts: deepCopy(STARTER_PROMPTS),
|
|
196
|
-
roles: deepCopy(STARTER_ROLES),
|
|
197
155
|
};
|
|
198
156
|
}
|
|
199
157
|
try {
|
package/handlers.js
CHANGED
|
@@ -24,6 +24,7 @@ function filterClientCommands(commands) {
|
|
|
24
24
|
const transcript = require('./transcript');
|
|
25
25
|
const plugins = require('./plugin-loader');
|
|
26
26
|
const { upsertCodexConfig, validateCodexConfigToml } = require('./codex-config');
|
|
27
|
+
const { installCodexHooks, removeCodexHooks, codexHooksHealthy } = require('./codex-hooks');
|
|
27
28
|
|
|
28
29
|
const opencodePluginDir = join(
|
|
29
30
|
process.platform === 'win32' ? (process.env.APPDATA || join(os.homedir(), 'AppData', 'Roaming')) : join(os.homedir(), '.config'),
|
|
@@ -140,8 +141,24 @@ function hasExistingHook(arr, hookFile, route) {
|
|
|
140
141
|
}));
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
function codexHooksFeatureEnabled(content) {
|
|
145
|
+
let inFeatures = false;
|
|
146
|
+
for (const line of String(content || '').split(/\r?\n/)) {
|
|
147
|
+
const trimmed = line.trim();
|
|
148
|
+
if (/^\[.*\]$/.test(trimmed)) {
|
|
149
|
+
inFeatures = trimmed === '[features]';
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (inFeatures && /^\s*hooks\s*=\s*true\s*$/.test(line)) return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
143
157
|
function codexConfigLooksHealthy(content, port) {
|
|
144
158
|
if (!content.includes('[otel]') || !content.includes(`localhost:${port}`)) return false;
|
|
159
|
+
const codexHookPath = join(__dirname, 'bin', 'codex-hook.js').replace(/\\/g, '/');
|
|
160
|
+
if (!codexHooksFeatureEnabled(content)) return false;
|
|
161
|
+
if (!codexHooksHealthy(os.homedir(), codexHookPath, port)) return false;
|
|
145
162
|
const notifyLine = content.match(/^\s*notify\s*=\s*\[(.+)\]\s*$/m)?.[1] || '';
|
|
146
163
|
if (!notifyLine.includes('notify-helper')) return false;
|
|
147
164
|
const quoted = [...notifyLine.matchAll(/"([^"]+)"/g)].map(m => m[1]);
|
|
@@ -226,6 +243,10 @@ function configForClient() {
|
|
|
226
243
|
return { ...cfg, commands: filterClientCommands(cfg.commands), pluginsDir: plugins.PLUGINS_DIR, version: appVersion };
|
|
227
244
|
}
|
|
228
245
|
|
|
246
|
+
function remoteCliEnv() {
|
|
247
|
+
return { ...process.env, CLIDECK_PORT: String(PORT) };
|
|
248
|
+
}
|
|
249
|
+
|
|
229
250
|
function onConnection(ws) {
|
|
230
251
|
sessions.clients.add(ws);
|
|
231
252
|
|
|
@@ -498,7 +519,7 @@ function onConnection(ws) {
|
|
|
498
519
|
let installed = false;
|
|
499
520
|
try { execFileSync(whichCmd, ['clideck-remote'], { stdio: 'ignore' }); installed = true; } catch {}
|
|
500
521
|
if (!installed) { ws.send(JSON.stringify({ type: 'remote.status', installed: false })); break; }
|
|
501
|
-
require('child_process').execFile('clideck-remote', ['status', '--json'], { timeout: 5000, shell: process.platform === 'win32' }, (err, stdout) => {
|
|
522
|
+
require('child_process').execFile('clideck-remote', ['status', '--json'], { timeout: 5000, shell: process.platform === 'win32', env: remoteCliEnv() }, (err, stdout) => {
|
|
502
523
|
if (err) { ws.send(JSON.stringify({ type: 'remote.status', installed: true })); return; }
|
|
503
524
|
try { ws.send(JSON.stringify({ type: 'remote.status', installed: true, ...JSON.parse(stdout) })); }
|
|
504
525
|
catch { ws.send(JSON.stringify({ type: 'remote.status', installed: true })); }
|
|
@@ -508,7 +529,7 @@ function onConnection(ws) {
|
|
|
508
529
|
}
|
|
509
530
|
|
|
510
531
|
case 'remote.pair': {
|
|
511
|
-
require('child_process').execFile('clideck-remote', ['pair', '--json'], { timeout: 15000, shell: process.platform === 'win32' }, (err, stdout) => {
|
|
532
|
+
require('child_process').execFile('clideck-remote', ['pair', '--json'], { timeout: 15000, shell: process.platform === 'win32', env: remoteCliEnv() }, (err, stdout) => {
|
|
512
533
|
if (err) { ws.send(JSON.stringify({ type: 'remote.error', error: err.message })); return; }
|
|
513
534
|
try { ws.send(JSON.stringify({ type: 'remote.paired', ...JSON.parse(stdout) })); }
|
|
514
535
|
catch { ws.send(JSON.stringify({ type: 'remote.error', error: 'Invalid response from clideck-remote' })); }
|
|
@@ -517,7 +538,7 @@ function onConnection(ws) {
|
|
|
517
538
|
}
|
|
518
539
|
|
|
519
540
|
case 'remote.unpair': {
|
|
520
|
-
require('child_process').execFile('clideck-remote', ['unpair', '--json'], { timeout: 5000, shell: process.platform === 'win32' }, (err) => {
|
|
541
|
+
require('child_process').execFile('clideck-remote', ['unpair', '--json'], { timeout: 5000, shell: process.platform === 'win32', env: remoteCliEnv() }, (err) => {
|
|
521
542
|
if (err) {
|
|
522
543
|
ws.send(JSON.stringify({ type: 'remote.error', error: err.message }));
|
|
523
544
|
} else {
|
|
@@ -592,14 +613,15 @@ function applyTelemetryConfig(preset) {
|
|
|
592
613
|
|
|
593
614
|
if (preset.presetId === 'codex') {
|
|
594
615
|
const configPath = join(home, '.codex', 'config.toml');
|
|
595
|
-
const hooksPath = join(home, '.codex', 'hooks.json');
|
|
596
616
|
let content = '';
|
|
597
617
|
if (existsSync(configPath)) content = readFileSync(configPath, 'utf8');
|
|
598
618
|
const hasOtel = content.includes('[otel]');
|
|
599
619
|
const hasCurrentOtel = content.includes(`localhost:${port}`);
|
|
600
620
|
const hasNotify = /^\s*notify\s*=.*notify-helper/m.test(content);
|
|
601
621
|
const hasWrongOtel = content.includes(`endpoint = "http://localhost:${port}/v1/logs"`);
|
|
602
|
-
|
|
622
|
+
const codexHookPath = join(__dirname, 'bin', 'codex-hook.js').replace(/\\/g, '/');
|
|
623
|
+
const hasHooks = codexHooksFeatureEnabled(content) && codexHooksHealthy(home, codexHookPath, port);
|
|
624
|
+
if (hasOtel && hasCurrentOtel && hasNotify && !hasWrongOtel && hasHooks) {
|
|
603
625
|
return { success: true, message: 'Already configured' };
|
|
604
626
|
}
|
|
605
627
|
const notifyHelperPath = join(__dirname, 'bin', 'notify-helper.js').replace(/\\/g, '/');
|
|
@@ -608,8 +630,8 @@ function applyTelemetryConfig(preset) {
|
|
|
608
630
|
if (!valid.ok) return { success: false, message: valid.error };
|
|
609
631
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
610
632
|
writeFileSync(configPath, nextContent);
|
|
611
|
-
|
|
612
|
-
return { success: true, message: '
|
|
633
|
+
installCodexHooks(home, process.execPath.replace(/\\/g, '/'), codexHookPath, port);
|
|
634
|
+
return { success: true, message: 'Configured. If Codex shows "2 hooks need review", open /hooks and approve the CliDeck hooks once.' };
|
|
613
635
|
}
|
|
614
636
|
|
|
615
637
|
if (preset.presetId === 'gemini-cli') {
|
|
@@ -691,9 +713,8 @@ function removeTelemetryConfig(preset) {
|
|
|
691
713
|
content = content.replace(/\n?notify\s*=\s*\[.*?notify-helper.*?\]\s*/g, '');
|
|
692
714
|
content = content.replace(/\n?codex_hooks\s*=\s*(true|false)\s*/g, '\n');
|
|
693
715
|
writeFileSync(configPath, content.trimEnd() + '\n');
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
return { success: true, message: 'Removed otel + notify from ~/.codex config' };
|
|
716
|
+
removeCodexHooks(home);
|
|
717
|
+
return { success: true, message: 'Removed otel + CliDeck hooks from ~/.codex config' };
|
|
697
718
|
}
|
|
698
719
|
|
|
699
720
|
if (preset.presetId === 'gemini-cli') {
|
package/package.json
CHANGED
package/plugin-loader.js
CHANGED
|
@@ -207,13 +207,13 @@ function buildApi(pluginId, pluginDir, state) {
|
|
|
207
207
|
const s = sessionsFn?.()?.get(id);
|
|
208
208
|
if (!s) return null;
|
|
209
209
|
const state = sessionStatus.get(id) || '';
|
|
210
|
-
return { id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId,
|
|
210
|
+
return { id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, working: state.startsWith('1:') };
|
|
211
211
|
},
|
|
212
212
|
getSessions() {
|
|
213
213
|
const sessions = sessionsFn?.();
|
|
214
214
|
if (!sessions) return [];
|
|
215
215
|
return [...sessions].map(([id, s]) => ({
|
|
216
|
-
id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId,
|
|
216
|
+
id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, working: (sessionStatus.get(id) || '').startsWith('1:'),
|
|
217
217
|
}));
|
|
218
218
|
},
|
|
219
219
|
|
|
@@ -232,7 +232,6 @@ function buildApi(pluginId, pluginDir, state) {
|
|
|
232
232
|
inputToSession(id, data) { inputFn?.({ id, data }); },
|
|
233
233
|
setAutoApproveMenu(id, enabled) { enabled ? autoApproveMenus.add(id) : autoApproveMenus.delete(id); },
|
|
234
234
|
|
|
235
|
-
getRoles() { return JSON.parse(JSON.stringify(getConfigFn?.()?.roles || [])); },
|
|
236
235
|
getProjects() { return JSON.parse(JSON.stringify(getConfigFn?.()?.projects || [])); },
|
|
237
236
|
getTranscript(id, n, order) { return transcript.getTurns(id, n || 20, order || 'end'); },
|
|
238
237
|
detectMenu(lines, presetId) { return transcript.detectMenu(lines, presetId); },
|
package/public/js/app.js
CHANGED
|
@@ -17,7 +17,8 @@ const shownAgentHealthToasts = new Set();
|
|
|
17
17
|
let reconnectReplaySkip = null;
|
|
18
18
|
|
|
19
19
|
function connect() {
|
|
20
|
-
|
|
20
|
+
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
21
|
+
state.ws = new WebSocket(`${wsProtocol}//${location.host}`);
|
|
21
22
|
|
|
22
23
|
state.ws.onopen = () => {
|
|
23
24
|
reconnectReplaySkip = new Set(state.terms.keys());
|
|
@@ -215,13 +216,23 @@ function connect() {
|
|
|
215
216
|
const sid = (toast.dataset.sessionId && toast.dataset.sessionId !== 'null' && toast.dataset.sessionId !== 'undefined')
|
|
216
217
|
? toast.dataset.sessionId
|
|
217
218
|
: '';
|
|
219
|
+
const reviewNote = msg.presetId === 'codex'
|
|
220
|
+
? `<div class="w-full px-3 py-2 rounded-lg bg-amber-500/10 border border-amber-400/20 text-[11px] leading-relaxed text-amber-200/90">
|
|
221
|
+
Codex may show <span class="font-semibold text-amber-100">2 hooks need review</span>. Open <code class="px-1 py-0.5 rounded bg-slate-950/50 text-amber-100">/hooks</code> in Codex and approve the CliDeck hooks once.
|
|
222
|
+
</div>`
|
|
223
|
+
: '';
|
|
224
|
+
actionsEl.className = 'setup-actions px-4 pb-3.5 flex flex-col gap-2';
|
|
218
225
|
actionsEl.innerHTML = `
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
226
|
+
${reviewNote}
|
|
227
|
+
<div class="w-full flex items-center gap-2">
|
|
228
|
+
<div class="flex-1 flex items-center gap-1.5 text-xs text-emerald-400">
|
|
229
|
+
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg>
|
|
230
|
+
Configured
|
|
231
|
+
</div>
|
|
232
|
+
${sid ? `<button class="restart-btn px-3 py-2 text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-colors">Restart Session</button>` : ''}
|
|
233
|
+
<button class="dismiss-btn px-3 py-2 text-xs text-slate-500 hover:text-slate-300 transition-colors">Dismiss</button>
|
|
222
234
|
</div>
|
|
223
|
-
|
|
224
|
-
<button class="dismiss-btn px-3 py-2 text-xs text-slate-500 hover:text-slate-300 transition-colors">Dismiss</button>`;
|
|
235
|
+
`;
|
|
225
236
|
actionsEl.querySelector('.dismiss-btn').onclick = () => toast.remove();
|
|
226
237
|
if (sid) actionsEl.querySelector('.restart-btn').onclick = () => {
|
|
227
238
|
const entry = state.terms.get(sid);
|
package/public/js/hotkeys.js
CHANGED
|
@@ -88,6 +88,18 @@ export function unregisterAllForPlugin(pluginId) {
|
|
|
88
88
|
// Prompt autocomplete (// trigger) runs first, then hotkey dispatch.
|
|
89
89
|
export function attachToTerminal(term, presetId) {
|
|
90
90
|
term.attachCustomKeyEventHandler((e) => {
|
|
91
|
+
if (e.type === 'keydown'
|
|
92
|
+
&& e.ctrlKey
|
|
93
|
+
&& !e.metaKey
|
|
94
|
+
&& !e.altKey
|
|
95
|
+
&& !e.shiftKey
|
|
96
|
+
&& e.code === 'KeyC'
|
|
97
|
+
&& term.hasSelection()) {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
navigator.clipboard?.writeText(term.getSelection()).catch(() => {});
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
// Claude Code uses Shift+Enter for multiline input, but xterm emits the
|
|
92
104
|
// same "\r" as plain Enter. Scope CSI-u translation to Claude only so
|
|
93
105
|
// other terminals keep their existing Enter behavior unchanged.
|
package/public/js/terminals.js
CHANGED
|
@@ -40,6 +40,50 @@ const MIN_CONTRAST_RATIO = 4.5;
|
|
|
40
40
|
const DARK_BALLS = ['#00e5ff', '#5df0d6', '#9b8cff'];
|
|
41
41
|
const LIGHT_BALLS = ['#0891b2', '#059669', '#7c3aed'];
|
|
42
42
|
|
|
43
|
+
const URL_RE = /\bhttps?:\/\/[^\s<>"'`]+/g;
|
|
44
|
+
|
|
45
|
+
function cleanUrlMatch(text, index) {
|
|
46
|
+
let url = text;
|
|
47
|
+
while (/[),.;:!?\\\]}]+$/.test(url)) url = url.slice(0, -1);
|
|
48
|
+
if (!url) return null;
|
|
49
|
+
try {
|
|
50
|
+
const parsed = new URL(url);
|
|
51
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return { text: url, index };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function openTerminalLink(url) {
|
|
59
|
+
const win = window.open(url, '_blank', 'noopener,noreferrer');
|
|
60
|
+
if (win) win.opener = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function addLinkProvider(term) {
|
|
64
|
+
term.registerLinkProvider({
|
|
65
|
+
provideLinks(y, callback) {
|
|
66
|
+
const line = term.buffer.active.getLine(y - 1);
|
|
67
|
+
if (!line) return callback(undefined);
|
|
68
|
+
const text = line.translateToString(true);
|
|
69
|
+
const links = [];
|
|
70
|
+
for (const match of text.matchAll(URL_RE)) {
|
|
71
|
+
const cleaned = cleanUrlMatch(match[0], match.index || 0);
|
|
72
|
+
if (!cleaned) continue;
|
|
73
|
+
links.push({
|
|
74
|
+
text: cleaned.text,
|
|
75
|
+
range: {
|
|
76
|
+
start: { x: cleaned.index + 1, y },
|
|
77
|
+
end: { x: cleaned.index + cleaned.text.length, y },
|
|
78
|
+
},
|
|
79
|
+
activate: (_event, linkText) => openTerminalLink(linkText),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
callback(links.length ? links : undefined);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
43
87
|
function startBounce(container) {
|
|
44
88
|
const isDark = !document.documentElement.classList.contains('light');
|
|
45
89
|
const colors = isDark ? DARK_BALLS : LIGHT_BALLS;
|
|
@@ -386,6 +430,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
386
430
|
});
|
|
387
431
|
const fit = new FitAddon.FitAddon();
|
|
388
432
|
term.loadAddon(fit);
|
|
433
|
+
addLinkProvider(term);
|
|
389
434
|
term.onData(data => send({ type: 'input', id, data }));
|
|
390
435
|
|
|
391
436
|
// [TRANSCRIPT-CAPTURE] initial settled capture plus one delayed idle save
|
package/public/tailwind.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-0{bottom:0}.bottom-5{bottom:1.25rem}.left-2\.5{left:.625rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.z-10{z-index:10}.z-\[200\]{z-index:200}.z-\[250\]{z-index:250}.z-\[260\]{z-index:260}.z-\[300\]{z-index:300}.z-\[400\]{z-index:400}.z-\[500\]{z-index:500}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mt-2{margin-top:-.5rem}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-0\.5{margin-left:.125rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[160px\]{max-height:160px}.max-h-\[400px\]{max-height:400px}.max-h-\[460px\]{max-height:460px}.min-h-0{min-height:0}.min-h-\[200px\]{min-height:200px}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[180px\]{width:180px}.w-\[18px\]{width:18px}.w-\[340px\]{width:340px}.w-\[354px\]{width:354px}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[16px\]{min-width:16px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[354px\]{min-width:354px}.max-w-2xl{max-width:42rem}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-px>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1px*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-blue-400{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.border-red-500\/30{border-color:rgba(239,68,68,.3)}.border-slate-600{--tw-border-opacity:1;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.border-slate-700{--tw-border-opacity:1;border-color:rgb(51 65 85/var(--tw-border-opacity,1))}.border-slate-700\/30{border-color:rgba(51,65,85,.3)}.border-slate-700\/40{border-color:rgba(51,65,85,.4)}.border-slate-700\/50{border-color:rgba(51,65,85,.5)}.border-slate-700\/60{border-color:rgba(51,65,85,.6)}.border-slate-900{--tw-border-opacity:1;border-color:rgb(15 23 42/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-500\/10{background-color:rgba(59,130,246,.1)}.bg-blue-500\/15{background-color:rgba(59,130,246,.15)}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-emerald-600\/20{background-color:rgba(5,150,105,.2)}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-red-600\/20{background-color:rgba(220,38,38,.2)}.bg-rose-500\/10{background-color:rgba(244,63,94,.1)}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.bg-slate-700\/50{background-color:rgba(51,65,85,.5)}.bg-slate-700\/60{background-color:rgba(51,65,85,.6)}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.bg-slate-800\/30{background-color:rgba(30,41,59,.3)}.bg-slate-800\/40{background-color:rgba(30,41,59,.4)}.bg-slate-800\/50{background-color:rgba(30,41,59,.5)}.bg-slate-800\/60{background-color:rgba(30,41,59,.6)}.bg-slate-800\/80{background-color:rgba(30,41,59,.8)}.bg-slate-800\/95{background-color:rgba(30,41,59,.95)}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-slate-900\/70{background-color:rgba(15,23,42,.7)}.bg-slate-950{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[3px\]{padding:3px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-\[5px\]{padding-top:5px;padding-bottom:5px}.py-\[7px\]{padding-top:7px;padding-bottom:7px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-3\.5{padding-bottom:.875rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-9{padding-left:2.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-3\.5{padding-top:.875rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\[1\.45\]{line-height:1.45}.leading-\[1\.4\]{line-height:1.4}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-300{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-400\/80{color:rgba(251,191,36,.8)}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-400{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.text-emerald-400\/80{color:rgba(52,211,153,.8)}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-500\/70{color:rgba(16,185,129,.7)}.text-indigo-400{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-400\/80{color:rgba(251,113,133,.8)}.text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-slate-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-500::placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.accent-blue-500{accent-color:#3b82f6}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-black\/40{--tw-shadow-color:rgba(0,0,0,.4);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/50{--tw-shadow-color:rgba(0,0,0,.5);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/60{--tw-shadow-color:rgba(0,0,0,.6);--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-white\/40{--tw-ring-color:hsla(0,0%,100%,.4)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.bg-slate-950{background-color:var(--color-base)!important}.bg-slate-900{background-color:var(--color-surface)!important}.bg-slate-800{background-color:var(--color-raised)!important}.bg-slate-700{background-color:var(--color-muted)!important}.bg-slate-800\/50{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.bg-slate-800\/30{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.bg-slate-800\/60{background-color:color-mix(in srgb,var(--color-raised) 60%,transparent)!important}.bg-slate-800\/80{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.bg-slate-900\/70{background-color:color-mix(in srgb,var(--color-surface) 70%,transparent)!important}.bg-slate-800\/95{background-color:color-mix(in srgb,var(--color-raised) 95%,transparent)!important}.bg-slate-700\/50{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.bg-slate-700\/60{background-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.hover\:bg-slate-700:hover{background-color:var(--color-muted)!important}.hover\:bg-slate-800:hover{background-color:var(--color-raised)!important}.hover\:bg-slate-800\/50:hover{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.hover\:bg-slate-800\/30:hover{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.hover\:bg-slate-700\/50:hover{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.hover\:bg-slate-700\/70:hover{background-color:color-mix(in srgb,var(--color-muted) 70%,transparent)!important}.text-slate-200{color:var(--color-text)!important}.text-slate-300{color:var(--color-soft)!important}.text-slate-400{color:var(--color-dim)!important}.text-slate-500{color:var(--color-subtle)!important}.text-slate-600{color:var(--color-border)!important}.hover\:text-slate-200:hover{color:var(--color-text)!important}.hover\:text-slate-300:hover{color:var(--color-soft)!important}.hover\:text-slate-400:hover{color:var(--color-dim)!important}.border-slate-600{border-color:var(--color-border)!important}.border-slate-700{border-color:var(--color-muted)!important}.border-slate-700\/50{border-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.border-slate-700\/40{border-color:color-mix(in srgb,var(--color-muted) 40%,transparent)!important}.border-slate-700\/60{border-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.border-slate-600\/60{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.hover\:border-slate-500:hover{border-color:var(--color-subtle)!important}.focus\:border-slate-600\/60:focus{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.focus\:bg-slate-800\/80:focus{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.placeholder-slate-500::-moz-placeholder{color:var(--color-subtle)!important}.placeholder-slate-500::placeholder{color:var(--color-subtle)!important}.placeholder-slate-600::-moz-placeholder{color:var(--color-border)!important}.placeholder-slate-600::placeholder{color:var(--color-border)!important}.ring-slate-500{--tw-ring-color:var(--color-subtle)!important}.bg-black\/60{background-color:var(--color-overlay)!important}.shadow-black\/40,.shadow-black\/50,.shadow-black\/60{--tw-shadow-color:var(--color-shadow)!important}.disabled\:bg-slate-600:disabled{background-color:var(--color-border)!important}.disabled\:text-slate-400:disabled{color:var(--color-dim)!important}:root{--color-preview:#a2a2a2;--color-time:#7c7d7d;--color-time-recent:#5cbd6d;--color-dormant:#555;--color-proj-meta:#7c7d7d;--color-search-bg:#2e2f2f;--color-search-text:#acacac;--color-session-hover:hsla(0,0%,100%,.04);--color-header-icon:#cbd5e1;--color-rail-icon:#a5a6a6}.light{--color-preview:#626262;--color-time:#666;--color-time-recent:#51a868;--color-dormant:#aaa;--color-proj-meta:#666;--color-search-bg:#f6f5f5;--color-search-text:#626262;--color-session-hover:rgba(0,0,0,.04);--color-header-icon:#3d4450;--color-rail-icon:#636261}.session-preview{color:var(--color-preview)!important}.session-time{color:var(--color-time)!important}.session-time.recent{color:var(--color-time-recent)!important}.session-status.dormant{color:var(--color-dormant)!important}.group[data-id]:hover,.pill-row:hover,.resumable-row:hover{background-color:var(--color-session-hover)!important}.project-count,.project-menu-btn{color:var(--color-proj-meta)!important}#search-input{background-color:var(--color-search-bg)!important;color:var(--color-search-text)!important}#search-input::-moz-placeholder{color:var(--color-search-text)!important;opacity:.7}#search-input::placeholder{color:var(--color-search-text)!important;opacity:.7}.relative:has(#search-input)>svg{color:var(--color-search-text)!important}.rail-btn:not(.text-slate-200){color:var(--color-rail-icon)!important}.rail-btn.bg-slate-800{background-color:var(--color-rail-active)!important}.icon-btn{color:var(--color-header-icon)!important;border-color:var(--color-border)!important;font-weight:700}.icon-btn svg{stroke-width:2}:root{--color-separator:#2e2f2f}.light{--color-separator:#d8d3cd}#nav-rail{background-color:var(--color-rail)!important}#sidebar{background-color:var(--color-sidebar)!important;border-right-color:var(--color-separator)!important}#sidebar input[type=text],#sidebar select{background-color:var(--color-sidebar-input)!important;border-color:var(--color-sidebar-border)!important}.group:hover{background-color:var(--color-chat-hover)!important}.group.active-session{background-color:var(--color-chat-active)!important}.theme-toggle{position:relative;width:36px;height:36px}.theme-toggle svg{position:absolute;inset:0;margin:auto;transition:opacity .3s ease,transform .3s ease}.theme-toggle .icon-sun{opacity:0;transform:rotate(-90deg) scale(.5)}.light .theme-toggle .icon-sun,.theme-toggle .icon-moon{opacity:1;transform:rotate(0) scale(1)}.light .theme-toggle .icon-moon{opacity:0;transform:rotate(90deg) scale(.5)}html{transition:color .3s ease,background-color .3s ease}.term-wrap{visibility:hidden;position:absolute;overflow:hidden;top:4px;left:4px;right:4px;bottom:0}.term-wrap.active{visibility:visible}.term-wrap .xterm{height:100%}.save-indicator{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;border:1px solid var(--color-search-text);margin-right:4px;opacity:.35;transition:opacity .4s ease}.save-indicator.saved{opacity:.5}.save-indicator .save-tick{color:var(--color-time-recent);display:block}.save-indicator .save-spin{display:none;color:var(--color-dim);animation:save-rotate 1.5s cubic-bezier(.4,0,.2,1) infinite}.save-indicator.saving .save-tick{display:none}.save-indicator.saving .save-spin{display:block}.save-indicator.saving{opacity:.5}@keyframes save-rotate{to{transform:rotate(1turn)}}.tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.5)}.light .tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.25),0 0 0 1px rgba(0,0,0,.05)}.drop-highlight{border-radius:.25rem;background-color:rgba(59,130,246,.1);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:rgba(59,130,246,.3)}.project-drop-line{height:2px;margin:.125rem .5rem;border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.plugin-chevron.collapsed,.project-chevron.collapsed svg{transform:rotate(-90deg)}.tmx-scroll{scrollbar-width:thin;scrollbar-color:transparent transparent}.tmx-scroll::-webkit-scrollbar{width:10px}.tmx-scroll::-webkit-scrollbar-track{background:transparent}.tmx-scroll::-webkit-scrollbar-thumb{background:transparent;border:2px solid transparent;border-radius:9999px;-webkit-transition:background-color .2s ease,border-color .2s ease;transition:background-color .2s ease,border-color .2s ease}.tmx-scroll.is-scrolling,.tmx-scroll:hover{scrollbar-color:color-mix(in srgb,var(--color-muted) 78%,transparent) color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-track,.tmx-scroll:hover::-webkit-scrollbar-track{background:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb,.tmx-scroll:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-muted) 78%,transparent);border-color:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb:hover,.tmx-scroll:hover::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-subtle) 85%,transparent)}.prompt-autocomplete{position:fixed;width:340px;background:var(--color-raised);border:1px solid var(--color-muted);border-radius:10px;box-shadow:0 20px 40px -8px rgba(0,0,0,.5);z-index:100;display:flex;flex-direction:column;overflow:hidden;animation:pa-in .15s ease}@keyframes pa-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.pa-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent)}.pa-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-subtle)}.pa-query{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--color-dim);background:color-mix(in srgb,var(--color-muted) 40%,transparent);padding:2px 6px;border-radius:4px}.pa-list{overflow-y:auto;padding:4px;max-height:184px}.pa-item{padding:8px 10px;border-radius:6px;cursor:pointer;transition:background-color .1s}.pa-item:hover,.pa-selected{background:color-mix(in srgb,var(--color-muted) 40%,transparent)}.pa-name{font-size:13px;font-weight:500;color:var(--color-text)}.pa-name,.pa-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pa-text{font-size:11px;color:var(--color-subtle);margin-top:2px}.pa-item mark{background:rgba(59,130,246,.25);color:inherit;border-radius:2px;padding:0 1px}.pa-footer{display:flex;gap:12px;justify-content:center;padding:6px 12px;border-top:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent);font-size:10px;color:var(--color-subtle)}.pa-footer kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--color-muted) 50%,transparent);color:var(--color-dim)}.pa-empty{padding:20px 12px;text-align:center;font-size:13px;color:var(--color-subtle)}.pa-hint{padding:0 12px 12px;text-align:center;font-size:11px;color:var(--color-border)}.pa-hint kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:color-mix(in srgb,var(--color-muted) 50%,transparent);padding:1px 4px;border-radius:3px}.empty\:hidden:empty{display:none}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity,1))}.hover\:bg-amber-500\/20:hover{background-color:rgba(245,158,11,.2)}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-blue-500\/20:hover{background-color:rgba(59,130,246,.2)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:bg-rose-500\/20:hover{background-color:rgba(244,63,94,.2)}.hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.hover\:bg-slate-700\/50:hover{background-color:rgba(51,65,85,.5)}.hover\:bg-slate-700\/60:hover{background-color:rgba(51,65,85,.6)}.hover\:bg-slate-700\/70:hover{background-color:rgba(51,65,85,.7)}.hover\:bg-slate-800\/30:hover{background-color:rgba(30,41,59,.3)}.hover\:bg-slate-800\/40:hover{background-color:rgba(30,41,59,.4)}.hover\:bg-slate-800\/50:hover{background-color:rgba(30,41,59,.5)}.hover\:text-amber-300:hover{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-emerald-300:hover{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.hover\:text-emerald-400:hover{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hover\:text-indigo-400:hover{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-rose-300:hover{--tw-text-opacity:1;color:rgb(253 164 175/var(--tw-text-opacity,1))}.hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-slate-500:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(100 116 139/var(--tw-ring-opacity,1))}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:border-slate-600\/60:focus{border-color:rgba(71,85,105,.6)}.focus\:bg-slate-800\/80:focus{background-color:rgba(30,41,59,.8)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500\/30:focus{--tw-ring-color:rgba(59,130,246,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-slate-600:disabled{--tw-bg-opacity:1;background-color:rgb(71 85 105/var(--tw-bg-opacity,1))}.disabled\:text-slate-400:disabled{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-0{bottom:0}.bottom-5{bottom:1.25rem}.left-2\.5{left:.625rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.z-10{z-index:10}.z-\[200\]{z-index:200}.z-\[250\]{z-index:250}.z-\[260\]{z-index:260}.z-\[300\]{z-index:300}.z-\[400\]{z-index:400}.z-\[500\]{z-index:500}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mt-2{margin-top:-.5rem}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-0\.5{margin-left:.125rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[160px\]{max-height:160px}.max-h-\[400px\]{max-height:400px}.max-h-\[460px\]{max-height:460px}.min-h-0{min-height:0}.min-h-\[200px\]{min-height:200px}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[180px\]{width:180px}.w-\[18px\]{width:18px}.w-\[340px\]{width:340px}.w-\[354px\]{width:354px}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[16px\]{min-width:16px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[354px\]{min-width:354px}.max-w-2xl{max-width:42rem}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-px>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1px*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-400\/20{border-color:rgba(251,191,36,.2)}.border-blue-400{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.border-red-500\/30{border-color:rgba(239,68,68,.3)}.border-slate-600{--tw-border-opacity:1;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.border-slate-700{--tw-border-opacity:1;border-color:rgb(51 65 85/var(--tw-border-opacity,1))}.border-slate-700\/30{border-color:rgba(51,65,85,.3)}.border-slate-700\/40{border-color:rgba(51,65,85,.4)}.border-slate-700\/50{border-color:rgba(51,65,85,.5)}.border-slate-700\/60{border-color:rgba(51,65,85,.6)}.border-slate-900{--tw-border-opacity:1;border-color:rgb(15 23 42/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-500\/10{background-color:rgba(59,130,246,.1)}.bg-blue-500\/15{background-color:rgba(59,130,246,.15)}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-emerald-600\/20{background-color:rgba(5,150,105,.2)}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-red-600\/20{background-color:rgba(220,38,38,.2)}.bg-rose-500\/10{background-color:rgba(244,63,94,.1)}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.bg-slate-700\/50{background-color:rgba(51,65,85,.5)}.bg-slate-700\/60{background-color:rgba(51,65,85,.6)}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.bg-slate-800\/30{background-color:rgba(30,41,59,.3)}.bg-slate-800\/40{background-color:rgba(30,41,59,.4)}.bg-slate-800\/50{background-color:rgba(30,41,59,.5)}.bg-slate-800\/60{background-color:rgba(30,41,59,.6)}.bg-slate-800\/80{background-color:rgba(30,41,59,.8)}.bg-slate-800\/95{background-color:rgba(30,41,59,.95)}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-slate-900\/70{background-color:rgba(15,23,42,.7)}.bg-slate-950{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.bg-slate-950\/50{background-color:rgba(2,6,23,.5)}.bg-transparent{background-color:transparent}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[3px\]{padding:3px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-\[5px\]{padding-top:5px;padding-bottom:5px}.py-\[7px\]{padding-top:7px;padding-bottom:7px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-3\.5{padding-bottom:.875rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-9{padding-left:2.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-3\.5{padding-top:.875rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\[1\.45\]{line-height:1.45}.leading-\[1\.4\]{line-height:1.4}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-100{--tw-text-opacity:1;color:rgb(254 243 199/var(--tw-text-opacity,1))}.text-amber-200\/90{color:hsla(48,97%,77%,.9)}.text-amber-300{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-400\/80{color:rgba(251,191,36,.8)}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-400{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.text-emerald-400\/80{color:rgba(52,211,153,.8)}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-500\/70{color:rgba(16,185,129,.7)}.text-indigo-400{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-400\/80{color:rgba(251,113,133,.8)}.text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-slate-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-500::placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.accent-blue-500{accent-color:#3b82f6}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-black\/40{--tw-shadow-color:rgba(0,0,0,.4);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/50{--tw-shadow-color:rgba(0,0,0,.5);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/60{--tw-shadow-color:rgba(0,0,0,.6);--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-white\/40{--tw-ring-color:hsla(0,0%,100%,.4)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.bg-slate-950{background-color:var(--color-base)!important}.bg-slate-900{background-color:var(--color-surface)!important}.bg-slate-800{background-color:var(--color-raised)!important}.bg-slate-700{background-color:var(--color-muted)!important}.bg-slate-800\/50{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.bg-slate-800\/30{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.bg-slate-800\/60{background-color:color-mix(in srgb,var(--color-raised) 60%,transparent)!important}.bg-slate-800\/80{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.bg-slate-900\/70{background-color:color-mix(in srgb,var(--color-surface) 70%,transparent)!important}.bg-slate-800\/95{background-color:color-mix(in srgb,var(--color-raised) 95%,transparent)!important}.bg-slate-700\/50{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.bg-slate-700\/60{background-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.hover\:bg-slate-700:hover{background-color:var(--color-muted)!important}.hover\:bg-slate-800:hover{background-color:var(--color-raised)!important}.hover\:bg-slate-800\/50:hover{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.hover\:bg-slate-800\/30:hover{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.hover\:bg-slate-700\/50:hover{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.hover\:bg-slate-700\/70:hover{background-color:color-mix(in srgb,var(--color-muted) 70%,transparent)!important}.text-slate-200{color:var(--color-text)!important}.text-slate-300{color:var(--color-soft)!important}.text-slate-400{color:var(--color-dim)!important}.text-slate-500{color:var(--color-subtle)!important}.text-slate-600{color:var(--color-border)!important}.hover\:text-slate-200:hover{color:var(--color-text)!important}.hover\:text-slate-300:hover{color:var(--color-soft)!important}.hover\:text-slate-400:hover{color:var(--color-dim)!important}.border-slate-600{border-color:var(--color-border)!important}.border-slate-700{border-color:var(--color-muted)!important}.border-slate-700\/50{border-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.border-slate-700\/40{border-color:color-mix(in srgb,var(--color-muted) 40%,transparent)!important}.border-slate-700\/60{border-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.border-slate-600\/60{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.hover\:border-slate-500:hover{border-color:var(--color-subtle)!important}.focus\:border-slate-600\/60:focus{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.focus\:bg-slate-800\/80:focus{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.placeholder-slate-500::-moz-placeholder{color:var(--color-subtle)!important}.placeholder-slate-500::placeholder{color:var(--color-subtle)!important}.placeholder-slate-600::-moz-placeholder{color:var(--color-border)!important}.placeholder-slate-600::placeholder{color:var(--color-border)!important}.ring-slate-500{--tw-ring-color:var(--color-subtle)!important}.bg-black\/60{background-color:var(--color-overlay)!important}.shadow-black\/40,.shadow-black\/50,.shadow-black\/60{--tw-shadow-color:var(--color-shadow)!important}.disabled\:bg-slate-600:disabled{background-color:var(--color-border)!important}.disabled\:text-slate-400:disabled{color:var(--color-dim)!important}:root{--color-preview:#a2a2a2;--color-time:#7c7d7d;--color-time-recent:#5cbd6d;--color-dormant:#555;--color-proj-meta:#7c7d7d;--color-search-bg:#2e2f2f;--color-search-text:#acacac;--color-session-hover:hsla(0,0%,100%,.04);--color-header-icon:#cbd5e1;--color-rail-icon:#a5a6a6}.light{--color-preview:#626262;--color-time:#666;--color-time-recent:#51a868;--color-dormant:#aaa;--color-proj-meta:#666;--color-search-bg:#f6f5f5;--color-search-text:#626262;--color-session-hover:rgba(0,0,0,.04);--color-header-icon:#3d4450;--color-rail-icon:#636261}.session-preview{color:var(--color-preview)!important}.session-time{color:var(--color-time)!important}.session-time.recent{color:var(--color-time-recent)!important}.session-status.dormant{color:var(--color-dormant)!important}.group[data-id]:hover,.pill-row:hover,.resumable-row:hover{background-color:var(--color-session-hover)!important}.project-count,.project-menu-btn{color:var(--color-proj-meta)!important}#search-input{background-color:var(--color-search-bg)!important;color:var(--color-search-text)!important}#search-input::-moz-placeholder{color:var(--color-search-text)!important;opacity:.7}#search-input::placeholder{color:var(--color-search-text)!important;opacity:.7}.relative:has(#search-input)>svg{color:var(--color-search-text)!important}.rail-btn:not(.text-slate-200){color:var(--color-rail-icon)!important}.rail-btn.bg-slate-800{background-color:var(--color-rail-active)!important}.icon-btn{color:var(--color-header-icon)!important;border-color:var(--color-border)!important;font-weight:700}.icon-btn svg{stroke-width:2}:root{--color-separator:#2e2f2f}.light{--color-separator:#d8d3cd}#nav-rail{background-color:var(--color-rail)!important}#sidebar{background-color:var(--color-sidebar)!important;border-right-color:var(--color-separator)!important}#sidebar input[type=text],#sidebar select{background-color:var(--color-sidebar-input)!important;border-color:var(--color-sidebar-border)!important}.group:hover{background-color:var(--color-chat-hover)!important}.group.active-session{background-color:var(--color-chat-active)!important}.theme-toggle{position:relative;width:36px;height:36px}.theme-toggle svg{position:absolute;inset:0;margin:auto;transition:opacity .3s ease,transform .3s ease}.theme-toggle .icon-sun{opacity:0;transform:rotate(-90deg) scale(.5)}.light .theme-toggle .icon-sun,.theme-toggle .icon-moon{opacity:1;transform:rotate(0) scale(1)}.light .theme-toggle .icon-moon{opacity:0;transform:rotate(90deg) scale(.5)}html{transition:color .3s ease,background-color .3s ease}.term-wrap{visibility:hidden;position:absolute;overflow:hidden;top:4px;left:4px;right:4px;bottom:0}.term-wrap.active{visibility:visible}.term-wrap .xterm{height:100%}.save-indicator{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;border:1px solid var(--color-search-text);margin-right:4px;opacity:.35;transition:opacity .4s ease}.save-indicator.saved{opacity:.5}.save-indicator .save-tick{color:var(--color-time-recent);display:block}.save-indicator .save-spin{display:none;color:var(--color-dim);animation:save-rotate 1.5s cubic-bezier(.4,0,.2,1) infinite}.save-indicator.saving .save-tick{display:none}.save-indicator.saving .save-spin{display:block}.save-indicator.saving{opacity:.5}@keyframes save-rotate{to{transform:rotate(1turn)}}.tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.5)}.light .tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.25),0 0 0 1px rgba(0,0,0,.05)}.drop-highlight{border-radius:.25rem;background-color:rgba(59,130,246,.1);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:rgba(59,130,246,.3)}.project-drop-line{height:2px;margin:.125rem .5rem;border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.plugin-chevron.collapsed,.project-chevron.collapsed svg{transform:rotate(-90deg)}.tmx-scroll{scrollbar-width:thin;scrollbar-color:transparent transparent}.tmx-scroll::-webkit-scrollbar{width:10px}.tmx-scroll::-webkit-scrollbar-track{background:transparent}.tmx-scroll::-webkit-scrollbar-thumb{background:transparent;border:2px solid transparent;border-radius:9999px;-webkit-transition:background-color .2s ease,border-color .2s ease;transition:background-color .2s ease,border-color .2s ease}.tmx-scroll.is-scrolling,.tmx-scroll:hover{scrollbar-color:color-mix(in srgb,var(--color-muted) 78%,transparent) color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-track,.tmx-scroll:hover::-webkit-scrollbar-track{background:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb,.tmx-scroll:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-muted) 78%,transparent);border-color:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb:hover,.tmx-scroll:hover::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-subtle) 85%,transparent)}.prompt-autocomplete{position:fixed;width:340px;background:var(--color-raised);border:1px solid var(--color-muted);border-radius:10px;box-shadow:0 20px 40px -8px rgba(0,0,0,.5);z-index:100;display:flex;flex-direction:column;overflow:hidden;animation:pa-in .15s ease}@keyframes pa-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.pa-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent)}.pa-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-subtle)}.pa-query{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--color-dim);background:color-mix(in srgb,var(--color-muted) 40%,transparent);padding:2px 6px;border-radius:4px}.pa-list{overflow-y:auto;padding:4px;max-height:184px}.pa-item{padding:8px 10px;border-radius:6px;cursor:pointer;transition:background-color .1s}.pa-item:hover,.pa-selected{background:color-mix(in srgb,var(--color-muted) 40%,transparent)}.pa-name{font-size:13px;font-weight:500;color:var(--color-text)}.pa-name,.pa-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pa-text{font-size:11px;color:var(--color-subtle);margin-top:2px}.pa-item mark{background:rgba(59,130,246,.25);color:inherit;border-radius:2px;padding:0 1px}.pa-footer{display:flex;gap:12px;justify-content:center;padding:6px 12px;border-top:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent);font-size:10px;color:var(--color-subtle)}.pa-footer kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--color-muted) 50%,transparent);color:var(--color-dim)}.pa-empty{padding:20px 12px;text-align:center;font-size:13px;color:var(--color-subtle)}.pa-hint{padding:0 12px 12px;text-align:center;font-size:11px;color:var(--color-border)}.pa-hint kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:color-mix(in srgb,var(--color-muted) 50%,transparent);padding:1px 4px;border-radius:3px}.empty\:hidden:empty{display:none}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity,1))}.hover\:bg-amber-500\/20:hover{background-color:rgba(245,158,11,.2)}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-blue-500\/20:hover{background-color:rgba(59,130,246,.2)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:bg-rose-500\/20:hover{background-color:rgba(244,63,94,.2)}.hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.hover\:bg-slate-700\/50:hover{background-color:rgba(51,65,85,.5)}.hover\:bg-slate-700\/60:hover{background-color:rgba(51,65,85,.6)}.hover\:bg-slate-700\/70:hover{background-color:rgba(51,65,85,.7)}.hover\:bg-slate-800\/30:hover{background-color:rgba(30,41,59,.3)}.hover\:bg-slate-800\/40:hover{background-color:rgba(30,41,59,.4)}.hover\:bg-slate-800\/50:hover{background-color:rgba(30,41,59,.5)}.hover\:text-amber-300:hover{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-emerald-300:hover{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.hover\:text-emerald-400:hover{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hover\:text-indigo-400:hover{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-rose-300:hover{--tw-text-opacity:1;color:rgb(253 164 175/var(--tw-text-opacity,1))}.hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-slate-500:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(100 116 139/var(--tw-ring-opacity,1))}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:border-slate-600\/60:focus{border-color:rgba(71,85,105,.6)}.focus\:bg-slate-800\/80:focus{background-color:rgba(30,41,59,.8)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500\/30:focus{--tw-ring-color:rgba(59,130,246,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-slate-600:disabled{--tw-bg-opacity:1;background-color:rgb(71 85 105/var(--tw-bg-opacity,1))}.disabled\:text-slate-400:disabled{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}
|
package/server.js
CHANGED
|
@@ -66,9 +66,9 @@ plugins.init(sessions.broadcast, sessions.getSessions, () => require('./handlers
|
|
|
66
66
|
|
|
67
67
|
const MIME = { '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', '.png': 'image/png', '.svg': 'image/svg+xml', '.mp3': 'audio/mpeg' };
|
|
68
68
|
const ALIASES = {
|
|
69
|
-
'/xterm.css':
|
|
70
|
-
'/xterm.js':
|
|
71
|
-
'/addon-fit.js':
|
|
69
|
+
'/xterm.css': require.resolve('@xterm/xterm/css/xterm.css'),
|
|
70
|
+
'/xterm.js': require.resolve('@xterm/xterm/lib/xterm.js'),
|
|
71
|
+
'/addon-fit.js': require.resolve('@xterm/addon-fit/lib/addon-fit.js'),
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
const PUBLIC_ROOT = join(__dirname, 'public');
|
|
@@ -109,33 +109,37 @@ const server = http.createServer((req, res) => {
|
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// Codex
|
|
113
|
-
if (req.method === 'POST' && req.url
|
|
112
|
+
// Codex lifecycle hooks. Silent hooks call start/stop directly; legacy notify still arms a stop.
|
|
113
|
+
if (req.method === 'POST' && req.url.startsWith('/hook/codex/')) {
|
|
114
114
|
let body = '';
|
|
115
115
|
req.on('data', chunk => { body += chunk; if (body.length > 1e5) req.destroy(); });
|
|
116
116
|
req.on('end', () => {
|
|
117
117
|
try {
|
|
118
118
|
const payload = JSON.parse(body);
|
|
119
|
+
const route = req.url.slice('/hook/codex/'.length);
|
|
119
120
|
const clideckId = payload.clideck_id;
|
|
120
121
|
const threadId = payload['thread-id'] || payload.session_id;
|
|
121
122
|
// console.log(`[codex] notify clideck=${clideckId ? clideckId.slice(0,8) : 'none'} thread=${threadId ? threadId.slice(0,8) : 'none'}`);
|
|
122
123
|
const allSessions = sessions.getSessions();
|
|
123
|
-
let
|
|
124
|
+
let matchedId = null;
|
|
124
125
|
if (clideckId && allSessions.has(clideckId)) {
|
|
125
|
-
|
|
126
|
-
// console.log(`[codex] notify matched by clideck_id session=${clideckId.slice(0,8)}`);
|
|
127
|
-
require('./telemetry-receiver').armCodexStop(clideckId);
|
|
126
|
+
matchedId = clideckId;
|
|
128
127
|
} else if (threadId) {
|
|
129
128
|
for (const [id, s] of allSessions) {
|
|
130
129
|
if (s.sessionToken === threadId) {
|
|
131
|
-
|
|
132
|
-
// console.log(`[codex] notify matched by thread session=${id.slice(0,8)} thread=${threadId.slice(0,8)}`);
|
|
133
|
-
require('./telemetry-receiver').armCodexStop(id);
|
|
130
|
+
matchedId = id;
|
|
134
131
|
break;
|
|
135
132
|
}
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
|
-
|
|
135
|
+
if (matchedId) {
|
|
136
|
+
const sess = allSessions.get(matchedId);
|
|
137
|
+
if (sess && threadId && !sess.sessionToken) sess.sessionToken = threadId;
|
|
138
|
+
const telemetry = require('./telemetry-receiver');
|
|
139
|
+
if (route === 'start') telemetry.markCodexStart(matchedId, 'hook');
|
|
140
|
+
else if (route === 'stop') telemetry.armCodexStop(matchedId);
|
|
141
|
+
}
|
|
142
|
+
// if (!matchedId) console.log(`[codex] hook ${route} no match clideck=${clideckId ? clideckId.slice(0,8) : 'none'} thread=${threadId ? threadId.slice(0,8) : 'none'}`);
|
|
139
143
|
} catch {}
|
|
140
144
|
res.writeHead(200).end('{}');
|
|
141
145
|
});
|
|
@@ -263,12 +267,20 @@ const allowedOrigins = new Set([
|
|
|
263
267
|
`http://localhost:${PORT}`, `http://127.0.0.1:${PORT}`,
|
|
264
268
|
`http://[::1]:${PORT}`, `http://${HOST}:${PORT}`,
|
|
265
269
|
]);
|
|
270
|
+
function isAllowedWsOrigin(origin, hostHeader) {
|
|
271
|
+
if (!origin) return true; // non-browser clients
|
|
272
|
+
try {
|
|
273
|
+
const originUrl = new URL(origin);
|
|
274
|
+
if (originUrl.host === hostHeader) return true;
|
|
275
|
+
return allowedOrigins.has(origin);
|
|
276
|
+
} catch {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
266
280
|
const wss = new WebSocketServer({
|
|
267
281
|
server,
|
|
268
282
|
verifyClient: ({ req }) => {
|
|
269
|
-
|
|
270
|
-
if (!origin) return true; // non-browser clients (curl, etc.)
|
|
271
|
-
return allowedOrigins.has(origin);
|
|
283
|
+
return isAllowedWsOrigin(req.headers.origin, req.headers.host);
|
|
272
284
|
},
|
|
273
285
|
});
|
|
274
286
|
wss.on('connection', onConnection);
|
package/sessions.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const pty = require('node-pty');
|
|
2
2
|
const { readFileSync, writeFileSync, existsSync } = require('fs');
|
|
3
3
|
const { join } = require('path');
|
|
4
|
+
const crypto = require('crypto');
|
|
4
5
|
const { parseCommand, resolveValidDir, defaultShell, binName } = require('./utils');
|
|
5
6
|
const activity = require('./activity');
|
|
6
7
|
const transcript = require('./transcript');
|
|
@@ -107,29 +108,7 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
|
|
|
107
108
|
if (preset?.telemetryEnv) telemetry.watchSession(id, bin);
|
|
108
109
|
if (preset?.bridge === 'opencode') opencodeBridge.watchSession(id, cwd);
|
|
109
110
|
|
|
110
|
-
function injectRolePrompt() {
|
|
111
|
-
if (!session.pendingRolePrompt) return;
|
|
112
|
-
transcript.recordInjectedInput(id, session.pendingRolePrompt);
|
|
113
|
-
term.write(session.pendingRolePrompt);
|
|
114
|
-
setTimeout(() => term.write('\r'), 150);
|
|
115
|
-
console.log(`Session ${id.slice(0, 8)}: injected role prompt`);
|
|
116
|
-
delete session.pendingRolePrompt;
|
|
117
|
-
delete session._rolePromptTimer;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
111
|
term.onData((data) => {
|
|
121
|
-
// Role prompts should be injected only when the agent is likely ready for
|
|
122
|
-
// input. For Codex, use the first OTLP startup event instead of a blind
|
|
123
|
-
// fixed startup delay; other agents keep the existing delayed path.
|
|
124
|
-
if (session.pendingRolePrompt && !session._rolePromptTimer) {
|
|
125
|
-
if (session.presetId === 'codex') {
|
|
126
|
-
if (telemetry.hasEvents(id)) injectRolePrompt();
|
|
127
|
-
} else {
|
|
128
|
-
session._rolePromptTimer = setTimeout(() => {
|
|
129
|
-
if (session.pendingRolePrompt) injectRolePrompt();
|
|
130
|
-
}, 3000);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
112
|
session.chunks.push(data);
|
|
134
113
|
session.chunksSize += data.length;
|
|
135
114
|
while (session.chunksSize > MAX_BUFFER && session.chunks.length > 1) {
|
|
@@ -163,7 +142,6 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
|
|
|
163
142
|
resumable.push({
|
|
164
143
|
id, name: s.name, commandId: s.commandId, presetId: s.presetId || 'shell', cwd: s.cwd,
|
|
165
144
|
themeId: s.themeId, sessionToken: s.sessionToken, projectId: s.projectId, muted: !!s.muted,
|
|
166
|
-
roleName: s.roleName || null,
|
|
167
145
|
lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
|
|
168
146
|
savedAt: new Date().toISOString(),
|
|
169
147
|
});
|
|
@@ -201,18 +179,6 @@ function create(msg, ws, cfg) {
|
|
|
201
179
|
return;
|
|
202
180
|
}
|
|
203
181
|
|
|
204
|
-
// If a role was selected, store identity on session and queue prompt injection
|
|
205
|
-
if (msg.roleId) {
|
|
206
|
-
const role = (cfg.roles || []).find(r => r.id === msg.roleId);
|
|
207
|
-
if (role) {
|
|
208
|
-
const s = sessions.get(id);
|
|
209
|
-
if (s) {
|
|
210
|
-
s.roleName = role.name;
|
|
211
|
-
if (role.instructions) s.pendingRolePrompt = role.instructions;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
182
|
const createdPresetId = PRESETS.find(p => binName(p.command) === binName(cmd.command))?.presetId || 'shell';
|
|
217
183
|
const installId = msg.installId || undefined;
|
|
218
184
|
broadcast({ type: 'created', id, name, themeId, commandId: cmd.id, presetId: createdPresetId, projectId, installId });
|
|
@@ -244,7 +210,6 @@ function createProgrammatic(opts, cfg) {
|
|
|
244
210
|
if (err) return { error: err.message };
|
|
245
211
|
|
|
246
212
|
const s = sessions.get(id);
|
|
247
|
-
if (s && opts.roleName) s.roleName = opts.roleName;
|
|
248
213
|
if (s && opts.ephemeral) s.ephemeral = true;
|
|
249
214
|
|
|
250
215
|
const presetId = PRESETS.find(p => binName(p.command) === binName(cmd.command))?.presetId || 'shell';
|
|
@@ -291,7 +256,6 @@ function resume(msg, ws, cfg) {
|
|
|
291
256
|
const s = sessions.get(id);
|
|
292
257
|
if (s) {
|
|
293
258
|
if (saved.muted) s.muted = true;
|
|
294
|
-
if (saved.roleName) s.roleName = saved.roleName;
|
|
295
259
|
}
|
|
296
260
|
|
|
297
261
|
// Remove from resumable list and notify all clients
|
|
@@ -385,7 +349,7 @@ function restart(msg, ws, cfg) {
|
|
|
385
349
|
}
|
|
386
350
|
|
|
387
351
|
const savedToken = s.sessionToken;
|
|
388
|
-
const { name, cwd, commandId, projectId,
|
|
352
|
+
const { name, cwd, commandId, projectId, muted, lastPreview, lastActivityAt } = s;
|
|
389
353
|
|
|
390
354
|
activity.clear(id);
|
|
391
355
|
telemetry.clear(id);
|
|
@@ -404,7 +368,6 @@ function restart(msg, ws, cfg) {
|
|
|
404
368
|
|
|
405
369
|
const next = sessions.get(id);
|
|
406
370
|
if (next) {
|
|
407
|
-
next.roleName = roleName || null;
|
|
408
371
|
next.muted = !!muted;
|
|
409
372
|
next.lastPreview = lastPreview || '';
|
|
410
373
|
next.lastActivityAt = lastActivityAt || null;
|
|
@@ -416,7 +379,6 @@ function restart(msg, ws, cfg) {
|
|
|
416
379
|
function list() {
|
|
417
380
|
return [...sessions].map(([id, s]) => ({
|
|
418
381
|
id, name: s.name, themeId: s.themeId, commandId: s.commandId, presetId: s.presetId || 'shell', projectId: s.projectId, muted: !!s.muted,
|
|
419
|
-
roleName: s.roleName || null,
|
|
420
382
|
// Last preview text for sidebar display on reconnect
|
|
421
383
|
lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
|
|
422
384
|
menu: s._menuKey ? JSON.parse(s._menuKey) : undefined,
|
|
@@ -485,7 +447,6 @@ function saveSessions(cfg) {
|
|
|
485
447
|
.map(([id, s]) => ({
|
|
486
448
|
id, name: s.name, commandId: s.commandId, presetId: s.presetId || 'shell', cwd: s.cwd,
|
|
487
449
|
themeId: s.themeId, sessionToken: s.sessionToken, projectId: s.projectId, muted: !!s.muted,
|
|
488
|
-
roleName: s.roleName || null,
|
|
489
450
|
lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
|
|
490
451
|
savedAt: new Date().toISOString(),
|
|
491
452
|
}));
|
package/telemetry-receiver.js
CHANGED
|
@@ -265,6 +265,23 @@ function armCodexStop(id) {
|
|
|
265
265
|
// console.log(`[codex] pending-stop armed session=${id.slice(0,8)}`);
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
function markCodexStart(id, source = 'hook') {
|
|
269
|
+
codexPendingStop.delete(id);
|
|
270
|
+
codexOutputDone.delete(id);
|
|
271
|
+
codexToolPhasePending.delete(id);
|
|
272
|
+
clearPendingTools(id);
|
|
273
|
+
cancelCodexPendingIdle(id);
|
|
274
|
+
broadcastFn?.({ type: 'session.status', id, working: true, source });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function markCodexIdle(id, source = 'hook') {
|
|
278
|
+
codexPendingStop.delete(id);
|
|
279
|
+
codexOutputDone.delete(id);
|
|
280
|
+
codexToolPhasePending.delete(id);
|
|
281
|
+
clearPendingTools(id);
|
|
282
|
+
scheduleCodexIdle(id, source);
|
|
283
|
+
}
|
|
284
|
+
|
|
268
285
|
function scheduleCodexIdle(id, source) {
|
|
269
286
|
cancelCodexPendingIdle(id);
|
|
270
287
|
const timer = setTimeout(() => {
|
|
@@ -299,4 +316,4 @@ function hasEvents(id) {
|
|
|
299
316
|
return activity.has(id);
|
|
300
317
|
}
|
|
301
318
|
|
|
302
|
-
module.exports = { init, handleLogs, clear, hasEvents, getLastEvent, cancelCodexMenuPoll, watchSession, armCodexStop };
|
|
319
|
+
module.exports = { init, handleLogs, clear, hasEvents, getLastEvent, cancelCodexMenuPoll, watchSession, armCodexStop, markCodexStart, markCodexIdle };
|
package/public/js/roles.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
// Roles panel — manage worker role definitions (core feature, not plugin-specific)
|
|
2
|
-
import { state, send } from './state.js';
|
|
3
|
-
import { esc } from './utils.js';
|
|
4
|
-
|
|
5
|
-
const panel = document.getElementById('panel-roles');
|
|
6
|
-
|
|
7
|
-
function getRoles() { return state.cfg.roles || []; }
|
|
8
|
-
|
|
9
|
-
function save() { send({ type: 'config.update', config: state.cfg }); }
|
|
10
|
-
|
|
11
|
-
export function renderRoles() {
|
|
12
|
-
const roles = getRoles();
|
|
13
|
-
panel.innerHTML = `
|
|
14
|
-
<div class="flex items-center justify-between px-3 pt-3 pb-2">
|
|
15
|
-
<span class="text-sm font-bold text-slate-200 tracking-tight" style="font-family:'JetBrains Mono',monospace">Roles</span>
|
|
16
|
-
<button id="btn-add-role" class="icon-btn w-7 h-7 flex items-center justify-center rounded-md border border-slate-600 text-slate-400 hover:bg-slate-700 hover:text-slate-200 transition-colors text-sm" title="New role">+</button>
|
|
17
|
-
</div>
|
|
18
|
-
<div id="roles-list" class="tmx-scroll flex-1 overflow-y-auto border-t border-slate-700/50"></div>`;
|
|
19
|
-
|
|
20
|
-
panel.querySelector('#btn-add-role').addEventListener('click', () => openEditor());
|
|
21
|
-
|
|
22
|
-
const list = panel.querySelector('#roles-list');
|
|
23
|
-
list.addEventListener('click', (e) => {
|
|
24
|
-
if (e.target.closest('.role-edit')) {
|
|
25
|
-
const idx = +e.target.closest('.role-row').dataset.idx;
|
|
26
|
-
openEditor(idx);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (e.target.closest('.role-del')) {
|
|
30
|
-
const idx = +e.target.closest('.role-row').dataset.idx;
|
|
31
|
-
state.cfg.roles.splice(idx, 1);
|
|
32
|
-
save();
|
|
33
|
-
renderRoles();
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
renderRoleList(roles);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function renderRoleList(roles) {
|
|
42
|
-
const list = panel.querySelector('#roles-list');
|
|
43
|
-
if (!roles.length) {
|
|
44
|
-
list.innerHTML = `<div class="flex flex-col items-center justify-center h-full px-6 text-center">
|
|
45
|
-
<p class="text-sm text-slate-400 mb-1">No roles defined</p>
|
|
46
|
-
<p class="text-xs text-slate-600 leading-relaxed">Define agent identities with a name and instructions.<br>Roles are sent to the agent when a session starts<br>and can be used by plugins like Autopilot.</p>
|
|
47
|
-
</div>`;
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
list.innerHTML = roles.map((r, idx) => `
|
|
51
|
-
<div class="role-row group flex items-start gap-2 px-3 py-2.5 cursor-default hover:bg-slate-800/40 transition-colors ${idx > 0 ? 'border-t border-slate-700/30' : ''}" data-idx="${idx}">
|
|
52
|
-
<div class="flex-1 min-w-0">
|
|
53
|
-
<div class="text-[13px] font-medium text-slate-200 truncate">${esc(r.name)}</div>
|
|
54
|
-
<div class="text-[11px] text-slate-500 mt-0.5 line-clamp-2 leading-relaxed">${esc(r.instructions)}</div>
|
|
55
|
-
</div>
|
|
56
|
-
<div class="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0 mt-0.5">
|
|
57
|
-
<button class="role-edit w-6 h-6 flex items-center justify-center rounded text-slate-500 hover:text-slate-300 hover:bg-slate-700/60 transition-colors" title="Edit">
|
|
58
|
-
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
|
|
59
|
-
</button>
|
|
60
|
-
<button class="role-del w-6 h-6 flex items-center justify-center rounded text-slate-500 hover:text-red-400 hover:bg-slate-700/60 transition-colors" title="Delete">
|
|
61
|
-
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
62
|
-
</button>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
`).join('');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function closeEditor() { document.getElementById('role-editor')?.remove(); }
|
|
69
|
-
|
|
70
|
-
function openEditor(idx) {
|
|
71
|
-
if (document.getElementById('role-editor')) { closeEditor(); if (idx == null) return; }
|
|
72
|
-
const existing = idx != null ? getRoles()[idx] : null;
|
|
73
|
-
|
|
74
|
-
const card = document.createElement('div');
|
|
75
|
-
card.id = 'role-editor';
|
|
76
|
-
card.className = 'p-3 border-b border-slate-700/50 bg-slate-800/30';
|
|
77
|
-
card.innerHTML = `
|
|
78
|
-
<input id="re-name" type="text" maxlength="40" placeholder="Role name" value="${esc(existing?.name || '')}"
|
|
79
|
-
class="w-full px-3 py-2 text-sm bg-slate-900 border border-slate-700 rounded-md text-slate-200 placeholder-slate-500 outline-none focus:border-blue-500 transition-colors mb-2">
|
|
80
|
-
<textarea id="re-instructions" rows="4" placeholder="Who am I? e.g. You are a senior software architect. You break down goals into clear, actionable tasks with acceptance criteria."
|
|
81
|
-
class="w-full max-w-full px-3 py-1.5 text-xs bg-slate-900 border border-slate-700 rounded-md text-slate-200 placeholder-slate-600 outline-none focus:border-blue-500 transition-colors resize-y leading-relaxed font-mono mb-2" style="min-height:5lh">${esc(existing?.instructions || '')}</textarea>
|
|
82
|
-
<div class="flex items-center gap-2">
|
|
83
|
-
<button id="re-save" class="px-4 py-1.5 text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white rounded-md transition-colors">${existing ? 'Save' : 'Add'}</button>
|
|
84
|
-
<button id="re-cancel" class="px-3 py-1.5 text-xs text-slate-500 hover:text-slate-300 transition-colors">Cancel</button>
|
|
85
|
-
</div>`;
|
|
86
|
-
|
|
87
|
-
const list = panel.querySelector('#roles-list');
|
|
88
|
-
list.parentElement.insertBefore(card, list);
|
|
89
|
-
|
|
90
|
-
const nameEl = card.querySelector('#re-name');
|
|
91
|
-
const instrEl = card.querySelector('#re-instructions');
|
|
92
|
-
nameEl.focus();
|
|
93
|
-
|
|
94
|
-
const doSave = () => {
|
|
95
|
-
const name = nameEl.value.trim();
|
|
96
|
-
const instructions = instrEl.value.trim();
|
|
97
|
-
if (!name || !instructions) return;
|
|
98
|
-
if (!state.cfg.roles) state.cfg.roles = [];
|
|
99
|
-
if (idx != null) {
|
|
100
|
-
state.cfg.roles[idx] = { ...state.cfg.roles[idx], name, instructions };
|
|
101
|
-
} else {
|
|
102
|
-
state.cfg.roles.push({ id: crypto.randomUUID(), name, instructions });
|
|
103
|
-
}
|
|
104
|
-
save();
|
|
105
|
-
closeEditor();
|
|
106
|
-
renderRoles();
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
card.querySelector('#re-save').addEventListener('click', doSave);
|
|
110
|
-
card.querySelector('#re-cancel').addEventListener('click', closeEditor);
|
|
111
|
-
nameEl.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeEditor(); });
|
|
112
|
-
}
|