clideck 1.30.1 → 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/agent-presets.json +2 -2
- package/config.js +6 -3
- 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 +537 -91
- 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 +29 -16
- package/public/js/creator.js +45 -87
- package/public/js/nav.js +2 -2
- package/public/js/terminals.js +77 -17
- package/public/tailwind.css +1 -1
- package/sessions.js +8 -2
- package/skills/research-experiment/SKILL.md +224 -0
- package/skills/research-experiment/agents/openai.yaml +4 -0
- package/telemetry-receiver.js +51 -15
- 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,22 +12,15 @@ 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();
|
|
17
|
+
let reconnectReplaySkip = null;
|
|
18
18
|
|
|
19
19
|
function connect() {
|
|
20
20
|
state.ws = new WebSocket(`ws://${location.host}`);
|
|
21
21
|
|
|
22
22
|
state.ws.onopen = () => {
|
|
23
|
-
|
|
24
|
-
state.terms.clear();
|
|
25
|
-
state.pills.clear();
|
|
26
|
-
state.activePill = null;
|
|
27
|
-
document.getElementById('session-list').innerHTML = '';
|
|
28
|
-
document.getElementById('pill-log-panel')?.remove();
|
|
29
|
-
state.active = null;
|
|
30
|
-
document.getElementById('empty').style.display = 'flex';
|
|
23
|
+
reconnectReplaySkip = new Set(state.terms.keys());
|
|
31
24
|
send({ type: 'remote.status' });
|
|
32
25
|
};
|
|
33
26
|
|
|
@@ -40,7 +33,6 @@ function connect() {
|
|
|
40
33
|
regroupSessions();
|
|
41
34
|
renderSettings();
|
|
42
35
|
renderPrompts();
|
|
43
|
-
renderRoles();
|
|
44
36
|
refreshCreator();
|
|
45
37
|
for (const [, entry] of state.terms) applyTheme(entry.term, entry.themeId);
|
|
46
38
|
break;
|
|
@@ -64,8 +56,16 @@ function connect() {
|
|
|
64
56
|
renderResumable();
|
|
65
57
|
break;
|
|
66
58
|
case 'sessions':
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
{
|
|
60
|
+
const liveIds = new Set(msg.list.map(s => s.id));
|
|
61
|
+
for (const id of [...state.terms.keys()]) {
|
|
62
|
+
if (!liveIds.has(id)) removeTerminal(id);
|
|
63
|
+
}
|
|
64
|
+
msg.list.forEach(s => addTerminal(s.id, s.name, s.themeId, s.commandId, s.projectId, s.muted, s.lastPreview, s.presetId));
|
|
65
|
+
if (!state.active || !state.terms.has(state.active)) {
|
|
66
|
+
if (msg.list.length) select(msg.list[0].id);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
69
|
break;
|
|
70
70
|
case 'created':
|
|
71
71
|
if (!state.terms.has(msg.id)) addTerminal(msg.id, msg.name, msg.themeId, msg.commandId, msg.projectId, msg.muted, msg.lastPreview, msg.presetId);
|
|
@@ -75,6 +75,7 @@ function connect() {
|
|
|
75
75
|
break;
|
|
76
76
|
case 'output': {
|
|
77
77
|
const entry = state.terms.get(msg.id);
|
|
78
|
+
if (msg.replay && reconnectReplaySkip?.has(msg.id) && entry) break;
|
|
78
79
|
if (entry && !entry.queue(msg.data)) entry.term.write(msg.data);
|
|
79
80
|
updatePreview(msg.id);
|
|
80
81
|
markUnread(msg.id);
|
|
@@ -104,6 +105,7 @@ function connect() {
|
|
|
104
105
|
}
|
|
105
106
|
case 'session.history': {
|
|
106
107
|
const entry = state.terms.get(msg.id);
|
|
108
|
+
if (msg.replay && reconnectReplaySkip?.has(msg.id) && entry) break;
|
|
107
109
|
if (entry && !entry.queue(msg.text + '\n')) entry.term.write(msg.text + '\n');
|
|
108
110
|
updatePreview(msg.id);
|
|
109
111
|
break;
|
|
@@ -254,8 +256,16 @@ function connect() {
|
|
|
254
256
|
break;
|
|
255
257
|
}
|
|
256
258
|
case 'pills':
|
|
257
|
-
|
|
258
|
-
|
|
259
|
+
{
|
|
260
|
+
const liveIds = new Set(msg.list.map(p => p.id));
|
|
261
|
+
for (const id of [...state.pills.keys()]) {
|
|
262
|
+
if (!liveIds.has(id)) removePill(id);
|
|
263
|
+
}
|
|
264
|
+
for (const p of msg.list) {
|
|
265
|
+
if (state.pills.has(p.id)) updatePill(p);
|
|
266
|
+
else addPill(p);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
259
269
|
break;
|
|
260
270
|
case 'pill.added':
|
|
261
271
|
addPill(msg.pill);
|
|
@@ -670,7 +680,7 @@ function openProjectCreator() {
|
|
|
670
680
|
card.id = 'project-creator';
|
|
671
681
|
card.className = 'p-3 border-b border-slate-700/50 bg-slate-800/30';
|
|
672
682
|
card.innerHTML = `
|
|
673
|
-
<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>
|
|
674
684
|
<div class="flex items-center gap-1.5 mb-2">
|
|
675
685
|
<input id="pc-path" type="text" value="${esc(defaultPath)}" placeholder="Project folder path"
|
|
676
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">
|
|
@@ -678,6 +688,9 @@ function openProjectCreator() {
|
|
|
678
688
|
${FOLDER_SVG}
|
|
679
689
|
</button>
|
|
680
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>
|
|
681
694
|
<input id="pc-name" type="text" maxlength="35" placeholder="Project name"
|
|
682
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">
|
|
683
696
|
<div class="flex items-center gap-2">
|
|
@@ -1004,7 +1017,7 @@ function renderProjectActions() {
|
|
|
1004
1017
|
if (!projId) continue;
|
|
1005
1018
|
for (const action of actions) {
|
|
1006
1019
|
const btn = document.createElement('button');
|
|
1007
|
-
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';
|
|
1008
1021
|
btn.title = action.title || '';
|
|
1009
1022
|
btn.innerHTML = action.icon || '';
|
|
1010
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
|
|