clideck 1.30.2 → 1.30.3
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/handlers.js +1 -1
- 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/index.html +0 -5
- package/public/js/app.js +5 -4
- package/public/js/creator.js +45 -87
- package/public/js/nav.js +2 -2
- package/public/js/terminals.js +63 -8
- package/public/tailwind.css +1 -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
|
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() {
|
|
@@ -96,7 +97,7 @@ function sortedPresets() {
|
|
|
96
97
|
return [...agents, ...shell];
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
function createFromPreset(preset, sessionName, cwd, projectId
|
|
100
|
+
function createFromPreset(preset, sessionName, cwd, projectId) {
|
|
100
101
|
// Find existing command matching this preset
|
|
101
102
|
let cmd = findCommandForPreset(preset);
|
|
102
103
|
// Auto-create the command if it doesn't exist yet
|
|
@@ -121,7 +122,7 @@ function createFromPreset(preset, sessionName, cwd, projectId, roleId) {
|
|
|
121
122
|
state.cfg.commands.push(cmd);
|
|
122
123
|
send({ type: 'config.update', config: state.cfg });
|
|
123
124
|
}
|
|
124
|
-
send({ type: 'create', commandId: cmd.id, name: sessionName, cwd, projectId: projectId || undefined,
|
|
125
|
+
send({ type: 'create', commandId: cmd.id, name: sessionName, cwd, projectId: projectId || undefined, ...estimateSize() });
|
|
125
126
|
localStorage.setItem(MRU_KEY, preset.presetId);
|
|
126
127
|
}
|
|
127
128
|
|
|
@@ -144,26 +145,23 @@ export function openCreator() {
|
|
|
144
145
|
card.className = 'p-3 border-b border-slate-700/50 bg-slate-800/30';
|
|
145
146
|
card.innerHTML = `
|
|
146
147
|
${(state.cfg.projects?.length) ? `
|
|
148
|
+
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-1.5">Select project</div>
|
|
147
149
|
<input type="hidden" id="creator-project" value="">
|
|
148
150
|
<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
|
|
151
|
+
<span id="creator-project-label">Select project</span>
|
|
150
152
|
<span class="text-slate-600 ml-2">▾</span>
|
|
151
153
|
</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"
|
|
154
|
+
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-1.5">Session name</div>
|
|
155
|
+
<input id="creator-name" type="text" maxlength="35" placeholder="Session name"
|
|
159
156
|
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">
|
|
157
|
+
<div id="creator-cwd-wrap" class="flex items-center gap-1.5 mb-2 ${(state.cfg.projects?.length) ? 'hidden' : ''}">
|
|
161
158
|
<input id="creator-cwd" type="text" value="${esc(defaultPath)}" placeholder="Working directory"
|
|
162
159
|
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
160
|
<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
161
|
${FOLDER_SVG}
|
|
165
162
|
</button>
|
|
166
163
|
</div>
|
|
164
|
+
<div class="text-[10px] font-semibold uppercase tracking-wider text-slate-500 mb-1.5">Choose agent provider</div>
|
|
167
165
|
<div id="creator-presets" class="space-y-0.5">
|
|
168
166
|
${renderPresetButtons()}
|
|
169
167
|
</div>`;
|
|
@@ -173,7 +171,10 @@ export function openCreator() {
|
|
|
173
171
|
|
|
174
172
|
const nameInput = card.querySelector('#creator-name');
|
|
175
173
|
const cwdInput = card.querySelector('#creator-cwd');
|
|
176
|
-
|
|
174
|
+
const cwdWrap = card.querySelector('#creator-cwd-wrap');
|
|
175
|
+
const projHidden = card.querySelector('#creator-project');
|
|
176
|
+
const projTrigger = card.querySelector('#creator-project-trigger');
|
|
177
|
+
(projTrigger || nameInput).focus();
|
|
177
178
|
|
|
178
179
|
nameInput.addEventListener('keydown', (e) => {
|
|
179
180
|
if (e.key === 'Escape') closeCreator();
|
|
@@ -189,15 +190,33 @@ export function openCreator() {
|
|
|
189
190
|
});
|
|
190
191
|
|
|
191
192
|
// Project picker dropdown
|
|
192
|
-
const projTrigger = card.querySelector('#creator-project-trigger');
|
|
193
193
|
if (projTrigger) {
|
|
194
|
+
const projects = [...(state.cfg.projects || [])].sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
|
|
195
|
+
const projLabel = card.querySelector('#creator-project-label');
|
|
196
|
+
const setProjectSelection = (value) => {
|
|
197
|
+
projHidden.value = value;
|
|
198
|
+
const proj = projects.find(p => p.id === value);
|
|
199
|
+
if (proj) {
|
|
200
|
+
projLabel.textContent = proj.name;
|
|
201
|
+
cwdWrap.classList.add('hidden');
|
|
202
|
+
cwdInput.value = proj.path || defaultPath;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (value === NO_PROJECT_VALUE) {
|
|
206
|
+
projLabel.textContent = 'None (outside project hierarchy)';
|
|
207
|
+
cwdWrap.classList.remove('hidden');
|
|
208
|
+
cwdInput.value = cwdInput.value.trim() || defaultPath;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
projLabel.textContent = 'Select project';
|
|
212
|
+
cwdWrap.classList.add('hidden');
|
|
213
|
+
cwdInput.value = defaultPath;
|
|
214
|
+
};
|
|
215
|
+
|
|
194
216
|
let projMenuCleanup = null;
|
|
195
217
|
projTrigger.addEventListener('click', () => {
|
|
196
218
|
if (projMenuCleanup) { projMenuCleanup(); return; }
|
|
197
219
|
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
220
|
|
|
202
221
|
const menu = document.createElement('div');
|
|
203
222
|
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 +226,9 @@ export function openCreator() {
|
|
|
207
226
|
menu.style.width = rect.width + 'px';
|
|
208
227
|
|
|
209
228
|
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 ${
|
|
229
|
+
<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
230
|
${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 ${
|
|
231
|
+
<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
232
|
<span class="w-2 h-2 rounded-full flex-shrink-0" style="background:${p.color || '#3b82f6'}"></span>
|
|
214
233
|
${esc(p.name)}
|
|
215
234
|
</div>`).join('')}`;
|
|
@@ -219,12 +238,7 @@ export function openCreator() {
|
|
|
219
238
|
const onClick = (e) => {
|
|
220
239
|
const item = e.target.closest('.proj-option');
|
|
221
240
|
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;
|
|
241
|
+
setProjectSelection(item.dataset.value);
|
|
228
242
|
projMenuCleanup();
|
|
229
243
|
};
|
|
230
244
|
const onOutside = (e) => {
|
|
@@ -242,64 +256,6 @@ export function openCreator() {
|
|
|
242
256
|
});
|
|
243
257
|
}
|
|
244
258
|
|
|
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
259
|
// "Add" button for missing agents — opens install toaster
|
|
304
260
|
card.addEventListener('click', (e) => {
|
|
305
261
|
const installBtn = e.target.closest('.install-btn');
|
|
@@ -325,13 +281,15 @@ export function openCreator() {
|
|
|
325
281
|
if (!btn) return;
|
|
326
282
|
const preset = state.presets.find(p => p.presetId === btn.dataset.preset);
|
|
327
283
|
if (!preset) return;
|
|
284
|
+
if (projTrigger && !projHidden.value) {
|
|
285
|
+
showToast('Choose a project or select `None (outside project hierarchy)`.', { title: 'Choose Project', type: 'warn' });
|
|
286
|
+
projTrigger.focus();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
328
289
|
const name = nameInput.value.trim() || fallbackName;
|
|
329
290
|
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);
|
|
291
|
+
const projectId = projHidden?.value && projHidden.value !== NO_PROJECT_VALUE ? projHidden.value : undefined;
|
|
292
|
+
createFromPreset(preset, name, cwd, projectId);
|
|
335
293
|
closeCreator();
|
|
336
294
|
});
|
|
337
295
|
}
|
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/terminals.js
CHANGED
|
@@ -3,6 +3,7 @@ import { esc, miniMarkdown, resolveIconPath } from './utils.js';
|
|
|
3
3
|
import { resolveTheme, resolveAccent, applyTheme } from './profiles.js';
|
|
4
4
|
import { attachToTerminal, registerHotkey } from './hotkeys.js';
|
|
5
5
|
import { closeDropdown } from './prompts.js';
|
|
6
|
+
import { showToast } from './toast.js';
|
|
6
7
|
function isLightBg(themeId) {
|
|
7
8
|
const bg = resolveTheme(themeId)?.background;
|
|
8
9
|
if (!bg || bg[0] !== '#') return false;
|
|
@@ -119,27 +120,68 @@ function positionMenu(menu, anchorRect) {
|
|
|
119
120
|
menu.style.visibility = 'hidden';
|
|
120
121
|
document.body.appendChild(menu);
|
|
121
122
|
const mh = menu.offsetHeight;
|
|
123
|
+
const mw = menu.offsetWidth;
|
|
122
124
|
const gap = 4;
|
|
123
125
|
const spaceBelow = window.innerHeight - anchorRect.bottom - gap;
|
|
126
|
+
const left = Math.min(
|
|
127
|
+
Math.max(gap, anchorRect.left),
|
|
128
|
+
Math.max(gap, window.innerWidth - mw - gap)
|
|
129
|
+
);
|
|
124
130
|
menu.style.top = (spaceBelow >= mh
|
|
125
131
|
? anchorRect.bottom + gap
|
|
126
132
|
: Math.max(gap, anchorRect.top - gap - mh)) + 'px';
|
|
127
|
-
menu.style.
|
|
133
|
+
menu.style.left = left + 'px';
|
|
128
134
|
menu.style.visibility = '';
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
function
|
|
137
|
+
function pointRect(x, y) {
|
|
138
|
+
return { top: y, bottom: y, left: x, right: x };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function copyTerminalSelection(sessionId) {
|
|
142
|
+
const entry = state.terms.get(sessionId);
|
|
143
|
+
const text = entry?.term?.getSelection() || '';
|
|
144
|
+
if (!text) return;
|
|
145
|
+
try {
|
|
146
|
+
await navigator.clipboard.writeText(text);
|
|
147
|
+
} catch {
|
|
148
|
+
showToast('Clipboard write failed.', { type: 'error' });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function pasteIntoTerminal(sessionId) {
|
|
153
|
+
try {
|
|
154
|
+
const text = await navigator.clipboard.readText();
|
|
155
|
+
if (text) send({ type: 'input', id: sessionId, data: text });
|
|
156
|
+
} catch {
|
|
157
|
+
showToast('Clipboard read failed.', { type: 'error' });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function openMenu(sessionId, anchor) {
|
|
132
162
|
closeMenu();
|
|
133
163
|
|
|
134
|
-
const rect =
|
|
164
|
+
const rect = anchor?.getBoundingClientRect ? anchor.getBoundingClientRect() : pointRect(anchor.x, anchor.y);
|
|
135
165
|
const menu = document.createElement('div');
|
|
136
166
|
menu.className = 'fixed z-[400] min-w-[160px] bg-slate-800 border border-slate-700 rounded-lg shadow-xl shadow-black/40 py-1';
|
|
137
167
|
|
|
138
168
|
const entry = state.terms.get(sessionId);
|
|
139
169
|
const projects = state.cfg.projects || [];
|
|
170
|
+
const hasSelection = !!entry?.term?.hasSelection();
|
|
140
171
|
|
|
141
172
|
let html = '';
|
|
142
173
|
|
|
174
|
+
html += `
|
|
175
|
+
<button class="menu-action flex items-center gap-2.5 w-full px-3 py-2 text-sm ${hasSelection ? 'text-slate-300 hover:bg-slate-700' : 'text-slate-600 cursor-default'} transition-colors text-left" data-action="copy" ${hasSelection ? '' : 'disabled'}>
|
|
176
|
+
<span class="flex-shrink-0 text-slate-400"><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"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></span>
|
|
177
|
+
Copy
|
|
178
|
+
</button>
|
|
179
|
+
<button class="menu-action flex items-center gap-2.5 w-full px-3 py-2 text-sm text-slate-300 hover:bg-slate-700 transition-colors text-left" data-action="paste">
|
|
180
|
+
<span class="flex-shrink-0 text-slate-400"><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="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2"/><path d="M8 2h6a2 2 0 0 1 2 2v6H8a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Z"/><path d="M8 10v4"/><path d="M12 14H4a2 2 0 0 0-2 2v2"/></svg></span>
|
|
181
|
+
Paste
|
|
182
|
+
</button>
|
|
183
|
+
<div class="border-t border-slate-700/50 my-1"></div>`;
|
|
184
|
+
|
|
143
185
|
// Project submenu items
|
|
144
186
|
if (projects.length) {
|
|
145
187
|
html += `<div class="px-3 py-1 text-[10px] font-semibold uppercase tracking-wider text-slate-600">Move to project</div>`;
|
|
@@ -187,12 +229,16 @@ function openMenu(sessionId, anchorEl) {
|
|
|
187
229
|
menu.innerHTML = html;
|
|
188
230
|
positionMenu(menu, rect);
|
|
189
231
|
|
|
190
|
-
const onClick = (e) => {
|
|
232
|
+
const onClick = async (e) => {
|
|
191
233
|
const btn = e.target.closest('.menu-action');
|
|
192
234
|
if (!btn) return;
|
|
193
235
|
closeMenu();
|
|
194
236
|
const action = btn.dataset.action;
|
|
195
|
-
if (action === '
|
|
237
|
+
if (action === 'copy') {
|
|
238
|
+
await copyTerminalSelection(sessionId);
|
|
239
|
+
} else if (action === 'paste') {
|
|
240
|
+
await pasteIntoTerminal(sessionId);
|
|
241
|
+
} else if (action === 'rename') {
|
|
196
242
|
startRename(sessionId);
|
|
197
243
|
} else if (action === 'mute') {
|
|
198
244
|
toggleMute(sessionId);
|
|
@@ -417,6 +463,14 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
417
463
|
|
|
418
464
|
term.open(el);
|
|
419
465
|
attachToTerminal(term, presetId);
|
|
466
|
+
const onContextMenu = (e) => {
|
|
467
|
+
if (e.shiftKey) return;
|
|
468
|
+
e.preventDefault();
|
|
469
|
+
e.stopPropagation();
|
|
470
|
+
select(id);
|
|
471
|
+
openMenu(id, { x: e.clientX, y: e.clientY });
|
|
472
|
+
};
|
|
473
|
+
el.addEventListener('contextmenu', onContextMenu);
|
|
420
474
|
let fitted = false, pending = [];
|
|
421
475
|
// [FIT-GUARD] only call fit() when proposed dimensions actually change — prevents
|
|
422
476
|
// unnecessary buffer reflows that cause scrollbar jumpiness on sub-pixel layout shifts
|
|
@@ -461,7 +515,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
461
515
|
}
|
|
462
516
|
}, 500);
|
|
463
517
|
const cancelFitRaf = () => { if (fitRaf) { cancelAnimationFrame(fitRaf); fitRaf = 0; } };
|
|
464
|
-
state.terms.set(id, { term, fit, el, ro, cancelFitRaf, themeId, commandId, presetId: presetId || null, projectId: projectId || null, muted: !!muted, working: false, workStartedAt: null, stopBounce, queue: (data) => { if (!fitted) { pending.push(data); return true; } return false; }, lastActivityAt: Date.now(), unread: false, lastPreviewText: lastPreview || '', searchText: '' });
|
|
518
|
+
state.terms.set(id, { term, fit, el, ro, cancelFitRaf, onContextMenu, themeId, commandId, presetId: presetId || null, projectId: projectId || null, muted: !!muted, working: false, workStartedAt: null, stopBounce, queue: (data) => { if (!fitted) { pending.push(data); return true; } return false; }, lastActivityAt: Date.now(), unread: false, lastPreviewText: lastPreview || '', searchText: '' });
|
|
465
519
|
document.getElementById('empty').style.display = 'none';
|
|
466
520
|
document.getElementById('terminals').style.pointerEvents = '';
|
|
467
521
|
if (muted) requestAnimationFrame(() => updateMuteIndicator(id));
|
|
@@ -475,6 +529,7 @@ export function removeTerminal(id) {
|
|
|
475
529
|
if (entry.stopBounce) entry.stopBounce();
|
|
476
530
|
entry.cancelFitRaf?.();
|
|
477
531
|
entry.ro?.disconnect();
|
|
532
|
+
entry.el.removeEventListener?.('contextmenu', entry.onContextMenu);
|
|
478
533
|
entry.term.dispose();
|
|
479
534
|
entry.el.remove();
|
|
480
535
|
state.terms.delete(id);
|
|
@@ -823,11 +878,11 @@ export function regroupSessions() {
|
|
|
823
878
|
<span class="w-2 h-2 rounded-full flex-shrink-0" style="background:${projectColor(proj)}"></span>
|
|
824
879
|
<span class="project-name flex-1 text-[11px] font-semibold uppercase tracking-wider text-slate-500 truncate">${esc(proj.name)}</span>
|
|
825
880
|
<span class="project-count text-[10px] text-slate-600">0</span>
|
|
826
|
-
<button class="project-path-btn
|
|
881
|
+
<button class="project-path-btn ${proj.path ? 'text-slate-600 hover:text-slate-300' : 'text-slate-700 cursor-default'} flex-shrink-0 p-0.5" title="${proj.path ? 'Open project folder' : 'Project path not set'}" ${proj.path ? '' : 'disabled'}>
|
|
827
882
|
${PATH_SVG}
|
|
828
883
|
</button>
|
|
829
884
|
<span class="project-plugin-actions"></span>
|
|
830
|
-
<button class="project-menu-btn
|
|
885
|
+
<button class="project-menu-btn text-slate-600 hover:text-slate-400 flex-shrink-0 p-0.5" title="Project menu">
|
|
831
886
|
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 20 20"><circle cx="10" cy="4" r="1.5" fill="currentColor"/><circle cx="10" cy="10" r="1.5" fill="currentColor"/><circle cx="10" cy="16" r="1.5" fill="currentColor"/></svg>
|
|
832
887
|
</button>
|
|
833
888
|
</div>
|