clideck 1.30.2 → 1.30.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/agent-presets.json +22 -4
- package/config.js +2 -1
- package/handlers.js +62 -57
- package/package.json +1 -1
- package/plugin-loader.js +1 -1
- package/plugins/autopilot/clideck-plugin.json +1 -1
- package/plugins/autopilot/index.js +511 -88
- package/plugins/autopilot/prompt.md +23 -21
- package/public/fx/bold-beep-idle.mp3 +0 -0
- package/public/fx/default-beep.mp3 +0 -0
- package/public/fx/echo-beep-idle.mp3 +0 -0
- package/public/fx/musical-beep-idle.mp3 +0 -0
- package/public/fx/small-bleep-idle.mp3 +0 -0
- package/public/fx/soft-beep.mp3 +0 -0
- package/public/fx/space-idle.mp3 +0 -0
- package/public/img/clideck-agent-dark.svg +25 -0
- package/public/img/clideck-agent-light.svg +25 -0
- package/public/index.html +0 -5
- package/public/js/app.js +5 -4
- package/public/js/creator.js +52 -89
- package/public/js/nav.js +2 -2
- package/public/js/settings.js +72 -2
- package/public/js/terminals.js +65 -8
- package/public/js/utils.js +1 -0
- package/public/tailwind.css +1 -1
- package/sessions.js +2 -2
- package/telemetry-receiver.js +8 -1
- package/transcript.js +27 -16
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
You are an autonomous dispatcher for project: {{projectName}}.
|
|
2
2
|
|
|
3
3
|
YOUR ROLE
|
|
4
|
-
You control workflow routing between agents.
|
|
4
|
+
You control workflow routing between agents to accomplish the session goal.
|
|
5
5
|
You do not do the work yourself.
|
|
6
6
|
You do not rewrite agent output.
|
|
7
7
|
You do not send summaries, edits, or instructions of your own to agents.
|
|
@@ -9,23 +9,21 @@ The system forwards existing agent output verbatim. Your job is to choose the be
|
|
|
9
9
|
|
|
10
10
|
IMPORTANT
|
|
11
11
|
You are not a final judge of whether work is good or bad.
|
|
12
|
-
But you must understand the project, the goals, the current state of the work, and what each agent is responsible for, so you can decide
|
|
12
|
+
But you must understand the project, the goals, the current state of the work, and what each agent is responsible for, so you can decide
|
|
13
|
+
the best next routing move.
|
|
14
|
+
You don't stop steering between agents until the seassion goal is complete or truly blocked. Do not stop to ask the user what to do next, just keep routing between agents until there is absolutely no way to proceed without user input, or until the task is complete.
|
|
13
15
|
|
|
14
16
|
This means:
|
|
15
17
|
- You should understand what the project is trying to achieve.
|
|
16
18
|
- You should understand what each agent just produced.
|
|
17
19
|
- You should understand what kind of specialist should act next.
|
|
18
20
|
- You may decide that the next step is not the most obvious direct handoff if another specialist should look first.
|
|
19
|
-
- Example: if a creative output needs analytical grounding, the right next move may be to route it to an analyst before routing it back
|
|
20
|
-
|
|
21
|
+
- Example: if a creative output needs analytical grounding, the right next move may be to route it to an analyst before routing it back
|
|
22
|
+
to the creative.
|
|
21
23
|
|
|
22
24
|
AGENTS
|
|
23
25
|
{{agents}}
|
|
24
26
|
|
|
25
|
-
For each agent, treat their role description as the source of truth for:
|
|
26
|
-
- what they are responsible for
|
|
27
|
-
- what they should not do
|
|
28
|
-
- what kind of outputs they should receive
|
|
29
27
|
|
|
30
28
|
STATE
|
|
31
29
|
You will receive structured workflow state describing:
|
|
@@ -38,26 +36,22 @@ You will receive structured workflow state describing:
|
|
|
38
36
|
|
|
39
37
|
TOOLS
|
|
40
38
|
- route(from, to): Forward one agent's existing output to another idle agent.
|
|
41
|
-
- notify_user(reason): Stop autopilot and notify the user. Use light markdown: **bold** for key terms, `code` for file/function names,
|
|
39
|
+
- notify_user(reason): Stop autopilot and notify the user. Use light markdown: **bold** for key terms, `code` for file/function names,
|
|
40
|
+
bullet lists for summaries. Keep it concise (2-5 sentences). Use ONLY when the work is naturally complete, truly blocked, or requires
|
|
41
|
+
human input.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
When calling `route(from, to)`, use the displayed agent labels exactly as shown in the AGENTS section and workflow state.
|
|
44
|
+
|
|
45
|
+
STEERING RULES
|
|
44
46
|
- Call exactly ONE tool per response.
|
|
45
47
|
- Read the workflow state first, then read the agent outputs.
|
|
46
48
|
- Prefer routing new output over previously routed output.
|
|
47
49
|
- Use the project goal and current state to decide the best next specialist.
|
|
48
|
-
- Use
|
|
49
|
-
- Do not route to an agent whose role makes the handoff inappropriate.
|
|
50
|
+
- Use inferred responsibilities when choosing the next receiver.
|
|
51
|
+
- Do not route to an agent whose inferred role makes the handoff inappropriate.
|
|
50
52
|
- Do not invent new instructions for agents. You only choose who receives whose output.
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
- Do NOT ask the user if you should continue. Do NOT notify them with requests like "Please resume agent X" or "Should I keep going?" or "Is this a good stopping point?"
|
|
54
|
-
- Do NOT alert the user that some agent asking for the user input before proceeding, this is not an execuse to stop and ask the user what to do. You should route the work to the next best agent until the workflow is truly blocked and cannot proceed without user input. (e.g. if the programmer ask for the user input, first make sure the reivewer or QA agent has not already reviewed the code, if not route it to them first)
|
|
55
|
-
- The user may be away from the computer and expects the agents to keep working until the task is naturally complete.
|
|
56
|
-
- You are autonomous. If you are unsure how to proceed, re-read the workflow state and the latest agent outputs, think differently, and route again.
|
|
57
|
-
- Repeat agents with the same output if needed, unless the routing state shows that the same handoff is being repeated without progress.
|
|
58
|
-
- You steer between agents until the task is complete or the user interrupts you, period.
|
|
59
|
-
|
|
60
|
-
HOW TO THINK
|
|
54
|
+
STEERING DECISION-MAKING
|
|
61
55
|
For each decision, reason in this order:
|
|
62
56
|
1. What is the project trying to achieve right now?
|
|
63
57
|
2. What changed most recently?
|
|
@@ -65,5 +59,13 @@ For each decision, reason in this order:
|
|
|
65
59
|
4. Has this output already been consumed by that role?
|
|
66
60
|
5. Is there a better intermediate handoff before sending it to the most obvious role?
|
|
67
61
|
|
|
62
|
+
DO NOT USE notify_user UNLESS ABSOLUTELY NECESSARY
|
|
63
|
+
- Do NOT ask the user if you should continue. Because you MUST continue until the task is complete or truly blocked, asking the user if you should continue is redundant, disruptive and a signed you failed to steer the workflow effectively.
|
|
64
|
+
- Even if the agent ask for the user input, Do NOT alert the user unless you are 100% sure the workflow is truly blocked and cannot proceed without user input. Instead, continue steering between agents.
|
|
65
|
+
- The user may be away from the computer and expects the agents to keep working until the task is naturally complete. stopping to ask for user input when the workflow can continue is disruptive and frustrating for the user.
|
|
66
|
+
- You are autonomous. If you are unsure how to proceed, re-read the workflow state and the latest agent outputs, think differently, and
|
|
67
|
+
route again.
|
|
68
|
+
- One last time, to eliminate mistakes: You steer between agents until the task is complete or the user interrupts you, period.
|
|
69
|
+
|
|
68
70
|
GOAL
|
|
69
71
|
Keep the work moving until the task is complete or truly blocked, by routing each output to the most appropriate next agent.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/public/fx/soft-beep.mp3
CHANGED
|
Binary file
|
package/public/fx/space-idle.mp3
CHANGED
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
|
2
|
+
<title>Clideck Agent</title>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="bubbleGradient" x1="64" y1="72" x2="448" y2="408" gradientUnits="userSpaceOnUse">
|
|
5
|
+
<stop offset="0" stop-color="#2C2F38"/>
|
|
6
|
+
<stop offset="1" stop-color="#171A20"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<linearGradient id="glyphGradient" x1="122" y1="150" x2="395" y2="355" gradientUnits="userSpaceOnUse">
|
|
9
|
+
<stop offset="0" stop-color="#FFFFFF"/>
|
|
10
|
+
<stop offset="1" stop-color="#E7EAF0"/>
|
|
11
|
+
</linearGradient>
|
|
12
|
+
<filter id="glyphShadow" x="82" y="126" width="350" height="254" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
13
|
+
<feOffset dx="0" dy="10"/>
|
|
14
|
+
<feGaussianBlur stdDeviation="10"/>
|
|
15
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0.03 0 0 0 0 0.04 0 0 0 0 0.07 0 0 0 0.35 0"/>
|
|
16
|
+
<feBlend in2="SourceGraphic" result="shape"/>
|
|
17
|
+
</filter>
|
|
18
|
+
</defs>
|
|
19
|
+
<path d="M120 70H392C435.078 70 470 104.922 470 148V266C470 309.078 435.078 344 392 344H215.642L118.159 416.774C111.875 421.467 102.86 417.2 102.562 409.363L99.996 344H88C52.654 344 24 315.346 24 280V166C24 112.98 66.98 70 120 70Z" fill="url(#bubbleGradient)"/>
|
|
20
|
+
<path d="M120 70H392C435.078 70 470 104.922 470 148V266C470 309.078 435.078 344 392 344H215.642L118.159 416.774C111.875 421.467 102.86 417.2 102.562 409.363L99.996 344H88C52.654 344 24 315.346 24 280V166C24 112.98 66.98 70 120 70Z" stroke="#3E4350" stroke-width="2"/>
|
|
21
|
+
<g filter="url(#glyphShadow)" stroke="url(#glyphGradient)" stroke-linecap="round" stroke-linejoin="round">
|
|
22
|
+
<path d="M134 168L245 256L134 344" stroke-width="40"/>
|
|
23
|
+
<path d="M292 344H390" stroke-width="38"/>
|
|
24
|
+
</g>
|
|
25
|
+
</svg>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
|
2
|
+
<title>Clideck Agent</title>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="bubbleGradient" x1="64" y1="72" x2="448" y2="408" gradientUnits="userSpaceOnUse">
|
|
5
|
+
<stop offset="0" stop-color="#F7F8FB"/>
|
|
6
|
+
<stop offset="1" stop-color="#E5E9F1"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<linearGradient id="glyphGradient" x1="122" y1="150" x2="395" y2="355" gradientUnits="userSpaceOnUse">
|
|
9
|
+
<stop offset="0" stop-color="#1D212B"/>
|
|
10
|
+
<stop offset="1" stop-color="#3D4656"/>
|
|
11
|
+
</linearGradient>
|
|
12
|
+
<filter id="glyphShadow" x="82" y="126" width="350" height="254" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
13
|
+
<feOffset dx="0" dy="6"/>
|
|
14
|
+
<feGaussianBlur stdDeviation="7"/>
|
|
15
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0.47 0 0 0 0 0.53 0 0 0 0 0.63 0 0 0 0.18 0"/>
|
|
16
|
+
<feBlend in2="SourceGraphic" result="shape"/>
|
|
17
|
+
</filter>
|
|
18
|
+
</defs>
|
|
19
|
+
<path d="M120 70H392C435.078 70 470 104.922 470 148V266C470 309.078 435.078 344 392 344H215.642L118.159 416.774C111.875 421.467 102.86 417.2 102.562 409.363L99.996 344H88C52.654 344 24 315.346 24 280V166C24 112.98 66.98 70 120 70Z" fill="url(#bubbleGradient)"/>
|
|
20
|
+
<path d="M120 70H392C435.078 70 470 104.922 470 148V266C470 309.078 435.078 344 392 344H215.642L118.159 416.774C111.875 421.467 102.86 417.2 102.562 409.363L99.996 344H88C52.654 344 24 315.346 24 280V166C24 112.98 66.98 70 120 70Z" stroke="#C6CEDA" stroke-width="2"/>
|
|
21
|
+
<g filter="url(#glyphShadow)" stroke="url(#glyphGradient)" stroke-linecap="round" stroke-linejoin="round">
|
|
22
|
+
<path d="M134 168L245 256L134 344" stroke-width="40"/>
|
|
23
|
+
<path d="M292 344H390" stroke-width="38"/>
|
|
24
|
+
</g>
|
|
25
|
+
</svg>
|
package/public/index.html
CHANGED
|
@@ -147,9 +147,6 @@
|
|
|
147
147
|
<button class="rail-btn w-9 h-9 flex items-center justify-center rounded-lg text-slate-500 hover:text-slate-300 hover:bg-slate-800/50 transition-colors" data-panel="prompts" title="Prompt Library">
|
|
148
148
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>
|
|
149
149
|
</button>
|
|
150
|
-
<button class="rail-btn w-9 h-9 flex items-center justify-center rounded-lg text-slate-500 hover:text-slate-300 hover:bg-slate-800/50 transition-colors" data-panel="roles" title="Roles">
|
|
151
|
-
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
|
152
|
-
</button>
|
|
153
150
|
<button class="rail-btn w-9 h-9 flex items-center justify-center rounded-lg text-slate-500 hover:text-slate-300 hover:bg-slate-800/50 transition-colors" data-panel="plugins" title="Plugins">
|
|
154
151
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4m0 12v4M2 12h4m12 0h4"/><circle cx="12" cy="12" r="3"/><path d="M12 8V6m0 12v-2M8 12H6m12 0h-2"/></svg>
|
|
155
152
|
</button>
|
|
@@ -200,8 +197,6 @@
|
|
|
200
197
|
</div>
|
|
201
198
|
<!-- Prompts panel (rendered by prompts.js) -->
|
|
202
199
|
<div id="panel-prompts" class="hidden flex-col flex-1 min-h-0"></div>
|
|
203
|
-
<!-- Roles panel (rendered by roles.js) -->
|
|
204
|
-
<div id="panel-roles" class="hidden flex-col flex-1 min-h-0"></div>
|
|
205
200
|
<!-- Plugins panel -->
|
|
206
201
|
<div id="panel-plugins" class="hidden flex-col flex-1 min-h-0">
|
|
207
202
|
<div class="flex items-center px-4 py-3 border-b border-slate-700/50">
|
package/public/js/app.js
CHANGED
|
@@ -12,7 +12,6 @@ import './nav.js';
|
|
|
12
12
|
import { initDrag, wasDragging } from './drag.js';
|
|
13
13
|
import { registerHotkey, unregisterHotkey, unregisterAllForPlugin } from './hotkeys.js';
|
|
14
14
|
import { renderPrompts } from './prompts.js';
|
|
15
|
-
import { renderRoles } from './roles.js';
|
|
16
15
|
|
|
17
16
|
const shownAgentHealthToasts = new Set();
|
|
18
17
|
let reconnectReplaySkip = null;
|
|
@@ -34,7 +33,6 @@ function connect() {
|
|
|
34
33
|
regroupSessions();
|
|
35
34
|
renderSettings();
|
|
36
35
|
renderPrompts();
|
|
37
|
-
renderRoles();
|
|
38
36
|
refreshCreator();
|
|
39
37
|
for (const [, entry] of state.terms) applyTheme(entry.term, entry.themeId);
|
|
40
38
|
break;
|
|
@@ -682,7 +680,7 @@ function openProjectCreator() {
|
|
|
682
680
|
card.id = 'project-creator';
|
|
683
681
|
card.className = 'p-3 border-b border-slate-700/50 bg-slate-800/30';
|
|
684
682
|
card.innerHTML = `
|
|
685
|
-
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-
|
|
683
|
+
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-1.5">Project folder</div>
|
|
686
684
|
<div class="flex items-center gap-1.5 mb-2">
|
|
687
685
|
<input id="pc-path" type="text" value="${esc(defaultPath)}" placeholder="Project folder path"
|
|
688
686
|
class="flex-1 px-3 py-1.5 text-xs bg-slate-900 border border-slate-700 rounded-md text-slate-400 placeholder-slate-600 outline-none focus:border-blue-500 transition-colors font-mono">
|
|
@@ -690,6 +688,9 @@ function openProjectCreator() {
|
|
|
690
688
|
${FOLDER_SVG}
|
|
691
689
|
</button>
|
|
692
690
|
</div>
|
|
691
|
+
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-1.5">
|
|
692
|
+
Project name <span class="text-slate-600 font-medium normal-case tracking-normal">(auto-filled from folder name)</span>
|
|
693
|
+
</div>
|
|
693
694
|
<input id="pc-name" type="text" maxlength="35" placeholder="Project name"
|
|
694
695
|
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">
|
|
695
696
|
<div class="flex items-center gap-2">
|
|
@@ -1016,7 +1017,7 @@ function renderProjectActions() {
|
|
|
1016
1017
|
if (!projId) continue;
|
|
1017
1018
|
for (const action of actions) {
|
|
1018
1019
|
const btn = document.createElement('button');
|
|
1019
|
-
btn.className = 'project-plugin-action plugin-project-btn
|
|
1020
|
+
btn.className = 'project-plugin-action plugin-project-btn text-slate-600 hover:text-indigo-400 flex-shrink-0 p-0.5';
|
|
1020
1021
|
btn.title = action.title || '';
|
|
1021
1022
|
btn.innerHTML = action.icon || '';
|
|
1022
1023
|
btn.dataset.pluginId = action.pluginId;
|
package/public/js/creator.js
CHANGED
|
@@ -13,6 +13,7 @@ const ANIMALS = [
|
|
|
13
13
|
'Dolphin', 'Lynx', 'Hawk', 'Raven', 'Otter', 'Panther', 'Crane', 'Bison',
|
|
14
14
|
];
|
|
15
15
|
const MRU_KEY = 'termui-last-preset';
|
|
16
|
+
const NO_PROJECT_VALUE = '__none__';
|
|
16
17
|
const FOLDER_SVG = `<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>`;
|
|
17
18
|
|
|
18
19
|
function randomName() {
|
|
@@ -26,6 +27,11 @@ function findCommandForPreset(p) {
|
|
|
26
27
|
|| state.cfg.commands.find(c => binName(c.command) === binName(p.command));
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
function telemetryEnabledForPreset(preset, existing) {
|
|
31
|
+
if (preset?.telemetryEnabled === true) return true;
|
|
32
|
+
return !!existing?.telemetryEnabled;
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
// True if preset binary is missing and the configured command is unchanged from the preset default
|
|
30
36
|
function isPresetMissing(p) {
|
|
31
37
|
if (p.available !== false) return false;
|
|
@@ -96,7 +102,7 @@ function sortedPresets() {
|
|
|
96
102
|
return [...agents, ...shell];
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
function createFromPreset(preset, sessionName, cwd, projectId
|
|
105
|
+
function createFromPreset(preset, sessionName, cwd, projectId) {
|
|
100
106
|
// Find existing command matching this preset
|
|
101
107
|
let cmd = findCommandForPreset(preset);
|
|
102
108
|
// Auto-create the command if it doesn't exist yet
|
|
@@ -114,14 +120,14 @@ function createFromPreset(preset, sessionName, cwd, projectId, roleId) {
|
|
|
114
120
|
resumeCommand: preset.resumeCommand,
|
|
115
121
|
sessionIdPattern: preset.sessionIdPattern,
|
|
116
122
|
outputMarker: preset.outputMarker || null,
|
|
117
|
-
telemetryEnabled:
|
|
123
|
+
telemetryEnabled: telemetryEnabledForPreset(preset),
|
|
118
124
|
telemetryStatus: null,
|
|
119
125
|
bridge: preset.bridge,
|
|
120
126
|
};
|
|
121
127
|
state.cfg.commands.push(cmd);
|
|
122
128
|
send({ type: 'config.update', config: state.cfg });
|
|
123
129
|
}
|
|
124
|
-
send({ type: 'create', commandId: cmd.id, name: sessionName, cwd, projectId: projectId || undefined,
|
|
130
|
+
send({ type: 'create', commandId: cmd.id, name: sessionName, cwd, projectId: projectId || undefined, ...estimateSize() });
|
|
125
131
|
localStorage.setItem(MRU_KEY, preset.presetId);
|
|
126
132
|
}
|
|
127
133
|
|
|
@@ -144,26 +150,23 @@ export function openCreator() {
|
|
|
144
150
|
card.className = 'p-3 border-b border-slate-700/50 bg-slate-800/30';
|
|
145
151
|
card.innerHTML = `
|
|
146
152
|
${(state.cfg.projects?.length) ? `
|
|
153
|
+
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-1.5">Select project</div>
|
|
147
154
|
<input type="hidden" id="creator-project" value="">
|
|
148
155
|
<button type="button" id="creator-project-trigger" class="w-full px-3 py-1.5 text-xs bg-slate-900 border border-slate-700 rounded-md text-slate-400 text-left flex items-center justify-between outline-none hover:border-slate-500 transition-colors cursor-pointer mb-2">
|
|
149
|
-
<span id="creator-project-label">Select project
|
|
156
|
+
<span id="creator-project-label">Select project</span>
|
|
150
157
|
<span class="text-slate-600 ml-2">▾</span>
|
|
151
158
|
</button>` : ''}
|
|
152
|
-
|
|
153
|
-
<input type="
|
|
154
|
-
<button type="button" id="creator-role-trigger" class="w-full px-3 py-1.5 text-xs bg-slate-900 border border-slate-700 rounded-md text-slate-400 text-left flex items-center justify-between outline-none hover:border-slate-500 transition-colors cursor-pointer mb-2">
|
|
155
|
-
<span id="creator-role-label">Select role <span class="opacity-40">- optional</span></span>
|
|
156
|
-
<span class="text-slate-600 ml-2">▾</span>
|
|
157
|
-
</button>` : ''}
|
|
158
|
-
<input id="creator-name" type="text" maxlength="35" placeholder="Session / Agent name"
|
|
159
|
+
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-1.5">Session name</div>
|
|
160
|
+
<input id="creator-name" type="text" maxlength="35" placeholder="Session name"
|
|
159
161
|
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">
|
|
160
|
-
<div class="flex items-center gap-1.5 mb-2">
|
|
162
|
+
<div id="creator-cwd-wrap" class="flex items-center gap-1.5 mb-2 ${(state.cfg.projects?.length) ? 'hidden' : ''}">
|
|
161
163
|
<input id="creator-cwd" type="text" value="${esc(defaultPath)}" placeholder="Working directory"
|
|
162
164
|
class="flex-1 px-3 py-1.5 text-xs bg-slate-900 border border-slate-700 rounded-md text-slate-400 placeholder-slate-600 outline-none focus:border-blue-500 transition-colors font-mono">
|
|
163
165
|
<button id="creator-browse" class="flex-shrink-0 w-7 h-7 flex items-center justify-center rounded-md border border-slate-700 text-slate-500 hover:text-slate-300 hover:bg-slate-700 transition-colors" title="Browse">
|
|
164
166
|
${FOLDER_SVG}
|
|
165
167
|
</button>
|
|
166
168
|
</div>
|
|
169
|
+
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-1.5">Choose agent provider</div>
|
|
167
170
|
<div id="creator-presets" class="space-y-0.5">
|
|
168
171
|
${renderPresetButtons()}
|
|
169
172
|
</div>`;
|
|
@@ -173,7 +176,10 @@ export function openCreator() {
|
|
|
173
176
|
|
|
174
177
|
const nameInput = card.querySelector('#creator-name');
|
|
175
178
|
const cwdInput = card.querySelector('#creator-cwd');
|
|
176
|
-
|
|
179
|
+
const cwdWrap = card.querySelector('#creator-cwd-wrap');
|
|
180
|
+
const projHidden = card.querySelector('#creator-project');
|
|
181
|
+
const projTrigger = card.querySelector('#creator-project-trigger');
|
|
182
|
+
(projTrigger || nameInput).focus();
|
|
177
183
|
|
|
178
184
|
nameInput.addEventListener('keydown', (e) => {
|
|
179
185
|
if (e.key === 'Escape') closeCreator();
|
|
@@ -189,15 +195,33 @@ export function openCreator() {
|
|
|
189
195
|
});
|
|
190
196
|
|
|
191
197
|
// Project picker dropdown
|
|
192
|
-
const projTrigger = card.querySelector('#creator-project-trigger');
|
|
193
198
|
if (projTrigger) {
|
|
199
|
+
const projects = [...(state.cfg.projects || [])].sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
|
|
200
|
+
const projLabel = card.querySelector('#creator-project-label');
|
|
201
|
+
const setProjectSelection = (value) => {
|
|
202
|
+
projHidden.value = value;
|
|
203
|
+
const proj = projects.find(p => p.id === value);
|
|
204
|
+
if (proj) {
|
|
205
|
+
projLabel.textContent = proj.name;
|
|
206
|
+
cwdWrap.classList.add('hidden');
|
|
207
|
+
cwdInput.value = proj.path || defaultPath;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (value === NO_PROJECT_VALUE) {
|
|
211
|
+
projLabel.textContent = 'None (outside project hierarchy)';
|
|
212
|
+
cwdWrap.classList.remove('hidden');
|
|
213
|
+
cwdInput.value = cwdInput.value.trim() || defaultPath;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
projLabel.textContent = 'Select project';
|
|
217
|
+
cwdWrap.classList.add('hidden');
|
|
218
|
+
cwdInput.value = defaultPath;
|
|
219
|
+
};
|
|
220
|
+
|
|
194
221
|
let projMenuCleanup = null;
|
|
195
222
|
projTrigger.addEventListener('click', () => {
|
|
196
223
|
if (projMenuCleanup) { projMenuCleanup(); return; }
|
|
197
224
|
const rect = projTrigger.getBoundingClientRect();
|
|
198
|
-
const hidden = card.querySelector('#creator-project');
|
|
199
|
-
const label = card.querySelector('#creator-project-label');
|
|
200
|
-
const projects = state.cfg.projects || [];
|
|
201
225
|
|
|
202
226
|
const menu = document.createElement('div');
|
|
203
227
|
menu.className = 'fixed z-[500] bg-slate-800 border border-slate-600 rounded-lg shadow-xl shadow-black/40 py-1 overflow-y-auto';
|
|
@@ -207,9 +231,9 @@ export function openCreator() {
|
|
|
207
231
|
menu.style.width = rect.width + 'px';
|
|
208
232
|
|
|
209
233
|
menu.innerHTML = `
|
|
210
|
-
<div class="proj-option px-3 py-1.5 cursor-pointer hover:bg-slate-700 transition-colors text-xs text-slate-400 ${
|
|
234
|
+
<div class="proj-option px-3 py-1.5 cursor-pointer hover:bg-slate-700 transition-colors text-xs text-slate-400 ${projHidden.value === NO_PROJECT_VALUE ? 'bg-slate-700/50' : ''}" data-value="${NO_PROJECT_VALUE}">None (outside project hierarchy)</div>
|
|
211
235
|
${projects.map(p => `
|
|
212
|
-
<div class="proj-option flex items-center gap-2 px-3 py-1.5 cursor-pointer hover:bg-slate-700 transition-colors text-xs text-slate-300 ${
|
|
236
|
+
<div class="proj-option flex items-center gap-2 px-3 py-1.5 cursor-pointer hover:bg-slate-700 transition-colors text-xs text-slate-300 ${projHidden.value === p.id ? 'bg-slate-700/50' : ''}" data-value="${p.id}">
|
|
213
237
|
<span class="w-2 h-2 rounded-full flex-shrink-0" style="background:${p.color || '#3b82f6'}"></span>
|
|
214
238
|
${esc(p.name)}
|
|
215
239
|
</div>`).join('')}`;
|
|
@@ -219,12 +243,7 @@ export function openCreator() {
|
|
|
219
243
|
const onClick = (e) => {
|
|
220
244
|
const item = e.target.closest('.proj-option');
|
|
221
245
|
if (!item) return;
|
|
222
|
-
|
|
223
|
-
const proj = projects.find(p => p.id === item.dataset.value);
|
|
224
|
-
label.innerHTML = proj ? esc(proj.name) : 'Select project <span class="opacity-40">- optional</span>';
|
|
225
|
-
// Auto-set working directory from project path
|
|
226
|
-
if (proj?.path) cwdInput.value = proj.path;
|
|
227
|
-
else cwdInput.value = defaultPath;
|
|
246
|
+
setProjectSelection(item.dataset.value);
|
|
228
247
|
projMenuCleanup();
|
|
229
248
|
};
|
|
230
249
|
const onOutside = (e) => {
|
|
@@ -242,64 +261,6 @@ export function openCreator() {
|
|
|
242
261
|
});
|
|
243
262
|
}
|
|
244
263
|
|
|
245
|
-
// Role picker dropdown
|
|
246
|
-
const roleTrigger = card.querySelector('#creator-role-trigger');
|
|
247
|
-
if (roleTrigger) {
|
|
248
|
-
let roleMenuCleanup = null;
|
|
249
|
-
roleTrigger.addEventListener('click', () => {
|
|
250
|
-
if (roleMenuCleanup) { roleMenuCleanup(); return; }
|
|
251
|
-
const rect = roleTrigger.getBoundingClientRect();
|
|
252
|
-
const hidden = card.querySelector('#creator-role');
|
|
253
|
-
const label = card.querySelector('#creator-role-label');
|
|
254
|
-
const roles = state.cfg.roles || [];
|
|
255
|
-
|
|
256
|
-
const menu = document.createElement('div');
|
|
257
|
-
menu.className = 'fixed z-[500] bg-slate-800 border border-slate-600 rounded-lg shadow-xl shadow-black/40 py-1 overflow-y-auto';
|
|
258
|
-
menu.style.maxHeight = '200px';
|
|
259
|
-
menu.style.left = rect.left + 'px';
|
|
260
|
-
menu.style.top = (rect.bottom + 4) + 'px';
|
|
261
|
-
menu.style.width = rect.width + 'px';
|
|
262
|
-
|
|
263
|
-
menu.innerHTML = `
|
|
264
|
-
<div class="role-option px-3 py-1.5 cursor-pointer hover:bg-slate-700 transition-colors text-xs text-slate-400 ${!hidden.value ? 'bg-slate-700/50' : ''}" data-value="" data-name="">None</div>
|
|
265
|
-
${roles.map(r => `
|
|
266
|
-
<div class="role-option px-3 py-1.5 cursor-pointer hover:bg-slate-700 transition-colors text-xs text-slate-300 ${hidden.value === r.id ? 'bg-slate-700/50' : ''}" data-value="${r.id}" data-name="${esc(r.name)}">
|
|
267
|
-
${esc(r.name)}
|
|
268
|
-
</div>`).join('')}`;
|
|
269
|
-
|
|
270
|
-
document.body.appendChild(menu);
|
|
271
|
-
|
|
272
|
-
const onClick = (e) => {
|
|
273
|
-
const item = e.target.closest('.role-option');
|
|
274
|
-
if (!item) return;
|
|
275
|
-
hidden.value = item.dataset.value;
|
|
276
|
-
const roleName = item.dataset.name;
|
|
277
|
-
label.innerHTML = roleName ? esc(roleName) : 'Select role <span class="opacity-40">- optional</span>';
|
|
278
|
-
// Auto-fill session name from role name (only if user hasn't typed a custom name)
|
|
279
|
-
if (roleName && (!nameInput.value.trim() || nameInput.dataset.autoFilled === '1')) {
|
|
280
|
-
nameInput.value = roleName;
|
|
281
|
-
nameInput.dataset.autoFilled = '1';
|
|
282
|
-
}
|
|
283
|
-
if (!item.dataset.value) nameInput.dataset.autoFilled = '';
|
|
284
|
-
roleMenuCleanup();
|
|
285
|
-
};
|
|
286
|
-
const onOutside = (e) => {
|
|
287
|
-
if (!menu.contains(e.target) && !roleTrigger.contains(e.target)) roleMenuCleanup();
|
|
288
|
-
};
|
|
289
|
-
menu.addEventListener('click', onClick);
|
|
290
|
-
requestAnimationFrame(() => document.addEventListener('click', onOutside));
|
|
291
|
-
|
|
292
|
-
roleMenuCleanup = () => {
|
|
293
|
-
menu.removeEventListener('click', onClick);
|
|
294
|
-
document.removeEventListener('click', onOutside);
|
|
295
|
-
menu.remove();
|
|
296
|
-
roleMenuCleanup = null;
|
|
297
|
-
};
|
|
298
|
-
});
|
|
299
|
-
// Clear auto-fill flag when user manually types
|
|
300
|
-
nameInput.addEventListener('input', () => { nameInput.dataset.autoFilled = ''; });
|
|
301
|
-
}
|
|
302
|
-
|
|
303
264
|
// "Add" button for missing agents — opens install toaster
|
|
304
265
|
card.addEventListener('click', (e) => {
|
|
305
266
|
const installBtn = e.target.closest('.install-btn');
|
|
@@ -314,7 +275,7 @@ export function openCreator() {
|
|
|
314
275
|
if (!preset) return;
|
|
315
276
|
let cmd = findCommandForPreset(preset);
|
|
316
277
|
if (!cmd) {
|
|
317
|
-
cmd = { id: crypto.randomUUID(), presetId: preset.presetId, label: preset.name, icon: preset.icon, command: preset.command, enabled: true, defaultPath: '', isAgent: preset.isAgent, canResume: preset.canResume, resumeCommand: preset.resumeCommand, sessionIdPattern: preset.sessionIdPattern, outputMarker: preset.outputMarker || null, telemetryEnabled:
|
|
278
|
+
cmd = { id: crypto.randomUUID(), presetId: preset.presetId, label: preset.name, icon: preset.icon, command: preset.command, enabled: true, defaultPath: '', isAgent: preset.isAgent, canResume: preset.canResume, resumeCommand: preset.resumeCommand, sessionIdPattern: preset.sessionIdPattern, outputMarker: preset.outputMarker || null, telemetryEnabled: telemetryEnabledForPreset(preset), telemetryStatus: null, bridge: preset.bridge };
|
|
318
279
|
state.cfg.commands.push(cmd);
|
|
319
280
|
send({ type: 'config.update', config: state.cfg });
|
|
320
281
|
}
|
|
@@ -325,13 +286,15 @@ export function openCreator() {
|
|
|
325
286
|
if (!btn) return;
|
|
326
287
|
const preset = state.presets.find(p => p.presetId === btn.dataset.preset);
|
|
327
288
|
if (!preset) return;
|
|
289
|
+
if (projTrigger && !projHidden.value) {
|
|
290
|
+
showToast('Choose a project or select `None (outside project hierarchy)`.', { title: 'Choose Project', type: 'warn' });
|
|
291
|
+
projTrigger.focus();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
328
294
|
const name = nameInput.value.trim() || fallbackName;
|
|
329
295
|
const cwd = cwdInput.value.trim() || undefined;
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
const roleSelect = card.querySelector('#creator-role');
|
|
333
|
-
const roleId = roleSelect?.value || undefined;
|
|
334
|
-
createFromPreset(preset, name, cwd, projectId, roleId);
|
|
296
|
+
const projectId = projHidden?.value && projHidden.value !== NO_PROJECT_VALUE ? projHidden.value : undefined;
|
|
297
|
+
createFromPreset(preset, name, cwd, projectId);
|
|
335
298
|
closeCreator();
|
|
336
299
|
});
|
|
337
300
|
}
|
package/public/js/nav.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { closeThemeMenu } from './settings.js';
|
|
2
2
|
import { closeDropdown } from './prompts.js';
|
|
3
3
|
|
|
4
|
-
const ALL_PANELS = ['chats', 'prompts', '
|
|
5
|
-
const PANEL_TITLES = { chats: 'Sessions', prompts: 'Prompts',
|
|
4
|
+
const ALL_PANELS = ['chats', 'prompts', 'plugins', 'settings'];
|
|
5
|
+
const PANEL_TITLES = { chats: 'Sessions', prompts: 'Prompts', plugins: 'Plugins', settings: 'Settings' };
|
|
6
6
|
const ACTIVE = ['text-slate-200', 'bg-slate-800'];
|
|
7
7
|
const INACTIVE = ['text-slate-500', 'hover:text-slate-300', 'hover:bg-slate-800/50'];
|
|
8
8
|
|
package/public/js/settings.js
CHANGED
|
@@ -24,15 +24,69 @@ document.getElementById('settings-nav').addEventListener('click', (e) => {
|
|
|
24
24
|
if (btn) switchCategory(btn.dataset.cat);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
+
function captureSettingsFocus() {
|
|
28
|
+
const active = document.activeElement;
|
|
29
|
+
if (!active || !(active instanceof HTMLElement)) return null;
|
|
30
|
+
if (!active.closest('#panel-settings')) return null;
|
|
31
|
+
|
|
32
|
+
const snapshot = {
|
|
33
|
+
id: active.id || null,
|
|
34
|
+
selector: null,
|
|
35
|
+
cardIdx: null,
|
|
36
|
+
selectionStart: null,
|
|
37
|
+
selectionEnd: null,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const card = active.closest('.agent-card');
|
|
41
|
+
if (card) {
|
|
42
|
+
snapshot.cardIdx = card.dataset.idx || null;
|
|
43
|
+
for (const cls of ['agent-name', 'agent-command', 'agent-enabled', 'agent-is-agent', 'agent-can-resume', 'agent-resume-cmd']) {
|
|
44
|
+
if (active.classList.contains(cls)) {
|
|
45
|
+
snapshot.selector = `.${cls}`;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof active.selectionStart === 'number' && typeof active.selectionEnd === 'number') {
|
|
52
|
+
snapshot.selectionStart = active.selectionStart;
|
|
53
|
+
snapshot.selectionEnd = active.selectionEnd;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return snapshot;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function restoreSettingsFocus(snapshot) {
|
|
60
|
+
if (!snapshot) return;
|
|
61
|
+
|
|
62
|
+
let target = null;
|
|
63
|
+
if (snapshot.id) target = document.getElementById(snapshot.id);
|
|
64
|
+
if (!target && snapshot.cardIdx != null && snapshot.selector) {
|
|
65
|
+
target = document.querySelector(`.agent-card[data-idx="${snapshot.cardIdx}"] ${snapshot.selector}`);
|
|
66
|
+
}
|
|
67
|
+
if (!(target instanceof HTMLElement)) return;
|
|
68
|
+
|
|
69
|
+
target.focus({ preventScroll: true });
|
|
70
|
+
if (
|
|
71
|
+
typeof snapshot.selectionStart === 'number' &&
|
|
72
|
+
typeof snapshot.selectionEnd === 'number' &&
|
|
73
|
+
typeof target.setSelectionRange === 'function'
|
|
74
|
+
) {
|
|
75
|
+
target.setSelectionRange(snapshot.selectionStart, snapshot.selectionEnd);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
27
79
|
// ── Render all ──
|
|
28
80
|
|
|
29
81
|
export function renderSettings() {
|
|
82
|
+
const focusSnapshot = captureSettingsFocus();
|
|
30
83
|
document.getElementById('cfg-default-path').value = state.cfg.defaultPath || '';
|
|
31
84
|
document.getElementById('cfg-confirm-close').checked = state.cfg.confirmClose !== false;
|
|
32
85
|
renderAgentList();
|
|
33
86
|
renderThemeSection();
|
|
34
87
|
renderNotifications();
|
|
35
88
|
updateVersionFooter();
|
|
89
|
+
restoreSettingsFocus(focusSnapshot);
|
|
36
90
|
}
|
|
37
91
|
|
|
38
92
|
export function updateVersionFooter() {
|
|
@@ -107,6 +161,22 @@ function telemetryPreset(cmd) {
|
|
|
107
161
|
return (state.presets || []).find(p => binName(p.command) === bin);
|
|
108
162
|
}
|
|
109
163
|
|
|
164
|
+
function presetForCommand(existing, command) {
|
|
165
|
+
const presets = state.presets || [];
|
|
166
|
+
if (existing?.presetId) {
|
|
167
|
+
const byId = presets.find(p => p.presetId === existing.presetId);
|
|
168
|
+
if (byId) return byId;
|
|
169
|
+
}
|
|
170
|
+
const bin = binName(command || existing?.command || '');
|
|
171
|
+
return presets.find(p => binName(p.command) === bin) || null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function telemetryEnabledForCommand(existing, command) {
|
|
175
|
+
const preset = presetForCommand(existing, command);
|
|
176
|
+
if (preset?.telemetryEnabled === true) return true;
|
|
177
|
+
return !!existing?.telemetryEnabled;
|
|
178
|
+
}
|
|
179
|
+
|
|
110
180
|
function integrationSection(c) {
|
|
111
181
|
const preset = telemetryPreset(c);
|
|
112
182
|
if (!preset) return '';
|
|
@@ -217,7 +287,7 @@ function openPresetMenu(anchorEl) {
|
|
|
217
287
|
enabled: true, defaultPath: '', isAgent: p.isAgent, canResume: p.canResume,
|
|
218
288
|
resumeCommand: p.resumeCommand, sessionIdPattern: p.sessionIdPattern,
|
|
219
289
|
outputMarker: p.outputMarker || null,
|
|
220
|
-
telemetryEnabled:
|
|
290
|
+
telemetryEnabled: telemetryEnabledForCommand({ presetId: p.presetId, command: p.command }, p.command),
|
|
221
291
|
telemetryStatus: null,
|
|
222
292
|
bridge: p.bridge,
|
|
223
293
|
});
|
|
@@ -453,7 +523,7 @@ function saveConfig() {
|
|
|
453
523
|
resumeCommand: card.querySelector('.agent-resume-cmd')?.value.trim() || null,
|
|
454
524
|
sessionIdPattern: existing.sessionIdPattern || null,
|
|
455
525
|
outputMarker: existing.outputMarker || null,
|
|
456
|
-
telemetryEnabled: existing
|
|
526
|
+
telemetryEnabled: telemetryEnabledForCommand(existing, command),
|
|
457
527
|
telemetryStatus: existing.telemetryStatus || null,
|
|
458
528
|
bridge: existing.bridge,
|
|
459
529
|
};
|