ompchamber 3.4.0 → 3.6.0
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/dist/client/index.html +1 -1
- package/dist/client/index.js +387 -383
- package/dist/client/static/js/index-7n838fz5.css +1 -0
- package/dist/client/static/js/{index-h05z90mw.js → index-qp2sfmn2.js} +614 -615
- package/package.json +1 -1
- package/src/cli/lib/commands/status.js +7 -0
- package/src/client/components/common/session-actions-menu/index.tsx +201 -0
- package/src/client/components/common/session-delete-modal/index.tsx +128 -0
- package/src/client/components/layout/session-sidebar/CategoryItem.tsx +18 -0
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +109 -99
- package/src/client/components/layout/session-sidebar/about-modal/UpdateLog.tsx +50 -0
- package/src/client/components/layout/session-sidebar/about-modal/UpdateSection.tsx +12 -17
- package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +18 -0
- package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +82 -64
- package/src/client/components/settings/ProjectSelectorDropdown.tsx +8 -2
- package/src/client/components/settings/SettingsSidebarList.tsx +5 -1
- package/src/client/components/settings/categories/CommandSettings.tsx +47 -9
- package/src/client/components/settings/categories/SkillCatalogSettings.tsx +23 -43
- package/src/client/components/settings/categories/SkillSettings.tsx +51 -7
- package/src/client/components/settings/categories/command-settings/DetailPane.tsx +109 -103
- package/src/client/components/settings/categories/command-settings/SidebarList.tsx +6 -1
- package/src/client/components/settings/categories/skill-settings/DetailPane.tsx +130 -31
- package/src/client/components/settings/categories/skill-settings/SidebarList.tsx +9 -3
- package/src/client/data/settings/command.ts +0 -40
- package/src/client/hooks/settings/workspace-roots.ts +99 -0
- package/src/client/hooks/ui/updates/index.ts +178 -0
- package/src/client/hooks/workspace/session-delete.ts +94 -0
- package/src/server/db.server.ts +41 -11
- package/src/server/index.ts +17 -1
- package/src/server/lib/btw/purge.server.ts +56 -0
- package/src/server/lib/db/client.ts +17 -0
- package/src/server/lib/db/compact-chat-overlay.test.ts +121 -0
- package/src/server/lib/db/compact-chat-overlay.ts +90 -0
- package/src/server/lib/fs/path-segment.test.ts +100 -0
- package/src/server/lib/fs/path-segment.ts +45 -0
- package/src/server/lib/lifecycle/fd-pressure.test.ts +66 -0
- package/src/server/lib/lifecycle/fd-pressure.ts +159 -0
- package/src/server/lib/omp/config/commands.test.ts +167 -0
- package/src/server/lib/omp/config/commands.ts +195 -0
- package/src/server/lib/omp/config/scope.test.ts +49 -0
- package/src/server/lib/omp/config/scope.ts +65 -0
- package/src/server/lib/omp/config/skills-catalog.ts +35 -3
- package/src/server/lib/omp/config/skills.test.ts +105 -0
- package/src/server/lib/omp/config/skills.ts +260 -47
- package/src/server/lib/omp/rpc/process.ts +22 -14
- package/src/server/lib/omp/rpc/session-registry.ts +6 -0
- package/src/server/lib/omp/rpc/utility.ts +45 -23
- package/src/server/lib/omp/session/delete.server.ts +136 -0
- package/src/server/lib/omp/session/merge-stored-anchor.test.ts +85 -0
- package/src/server/lib/omp/session/merge-stored-overlay-rows.test.ts +106 -0
- package/src/server/lib/omp/session/merge-stored.ts +78 -11
- package/src/server/lib/omp/session/reader.ts +13 -0
- package/src/server/lib/omp/session/reload.server.test.ts +84 -0
- package/src/server/lib/omp/session/reload.server.ts +47 -0
- package/src/server/lib/omp/session/subagent-presence.ts +40 -9
- package/src/server/lib/terminal/liveness.server.ts +65 -0
- package/src/server/lib/terminal/runtime.server.ts +70 -117
- package/src/server/lib/terminal/session.server.ts +5 -2
- package/src/server/lib/terminal/store.ts +72 -0
- package/src/server/lib/updates/apply.ts +62 -33
- package/src/server/lib/updates/install.ts +9 -2
- package/src/server/lib/updates/omp.ts +37 -9
- package/src/server/lib/updates/single-flight.test.ts +61 -0
- package/src/server/lib/updates/single-flight.ts +68 -0
- package/src/server/routes/chat/session.ts +13 -3
- package/src/server/routes/health/probe.ts +8 -0
- package/src/server/routes/omp/index.ts +0 -2
- package/src/server/routes/sessions/delete.test.ts +84 -0
- package/src/server/routes/sessions/delete.ts +83 -0
- package/src/server/routes/sessions/index.ts +5 -0
- package/src/server/routes/settings/commands.ts +102 -15
- package/src/server/routes/settings/skills.ts +165 -113
- package/src/server/routes/terminal/sessions.ts +1 -1
- package/src/server/routes/updates/apply.ts +101 -14
- package/src/shared/lib/chat/composer/client.ts +27 -18
- package/src/shared/lib/chat/composer/scope.test.ts +87 -0
- package/src/shared/lib/markdown/katex.ts +11 -1
- package/src/shared/lib/markdown/marked.ts +68 -13
- package/src/shared/lib/markdown/math.test.ts +53 -1
- package/src/shared/lib/updates/progress.test.ts +54 -0
- package/src/shared/lib/updates/progress.ts +50 -0
- package/src/shared/lib/workspace/session-state/store.ts +20 -0
- package/src/shared/types/settings/command.ts +15 -2
- package/src/shared/types/settings/skill.ts +13 -0
- package/dist/client/static/js/index-bsxxt89j.css +0 -1
- package/src/client/hooks/ui/updates.ts +0 -108
- package/src/server/routes/omp/commands.ts +0 -29
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'preact/hooks';
|
|
2
|
-
import { Check, Eye, EyeOff, Info, Save, Sparkles, Trash2, User } from 'lucide-preact';
|
|
2
|
+
import { Check, Eye, EyeOff, Info, RefreshCw, Save, Sparkles, Trash2, User } from 'lucide-preact';
|
|
3
3
|
import type { SkillItem } from '@/shared/types';
|
|
4
4
|
|
|
5
5
|
interface SkillDetailPaneProps {
|
|
@@ -8,6 +8,18 @@ interface SkillDetailPaneProps {
|
|
|
8
8
|
onSave: (skillData: Partial<SkillItem>) => void;
|
|
9
9
|
onDelete?: (id: string) => void;
|
|
10
10
|
onOpenCatalog?: () => void;
|
|
11
|
+
onReload?: () => void;
|
|
12
|
+
isReloading?: boolean;
|
|
13
|
+
/** Disabled when no workspace is selected: there is no `.omp/skills` to
|
|
14
|
+
* write a project skill into. */
|
|
15
|
+
projectScopeDisabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Root a NEW skill lands in, from the scope the panel is showing. Without
|
|
18
|
+
* this a create made while a workspace is selected wrote to the user root —
|
|
19
|
+
* the skill then appeared in a list the panel was not showing, and the
|
|
20
|
+
* workspace's own `.omp/skills` never received it.
|
|
21
|
+
*/
|
|
22
|
+
defaultLocation?: 'user' | 'project';
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
export function SkillDetailPane({
|
|
@@ -16,33 +28,45 @@ export function SkillDetailPane({
|
|
|
16
28
|
onSave,
|
|
17
29
|
onDelete,
|
|
18
30
|
onOpenCatalog,
|
|
31
|
+
onReload,
|
|
32
|
+
isReloading = false,
|
|
33
|
+
projectScopeDisabled = false,
|
|
34
|
+
defaultLocation = 'user',
|
|
19
35
|
}: SkillDetailPaneProps) {
|
|
20
36
|
const [name, setName] = useState('');
|
|
21
37
|
const [description, setDescription] = useState('');
|
|
22
38
|
const [location, setLocation] = useState<'user' | 'project'>('user');
|
|
23
|
-
const [locationLabel, setLocationLabel] = useState('User /
|
|
39
|
+
const [locationLabel, setLocationLabel] = useState('User / omp agent');
|
|
24
40
|
const [instructions, setInstructions] = useState('');
|
|
41
|
+
const [hidden, setHidden] = useState(false);
|
|
25
42
|
const [savedToast, setSavedToast] = useState(false);
|
|
26
43
|
const [showLineNumbers, setShowLineNumbers] = useState(true);
|
|
27
44
|
|
|
45
|
+
// A skill the chamber does not own (a Claude plugin, a registry package) is
|
|
46
|
+
// shown read-only: its file belongs to another tool, and a save here would
|
|
47
|
+
// rewrite it. `managed` is absent on a brand-new draft, which is editable.
|
|
48
|
+
const readOnly = !isCreatingNew && skill != null && skill.managed !== true;
|
|
49
|
+
|
|
28
50
|
useEffect(() => {
|
|
29
51
|
if (isCreatingNew || !skill) {
|
|
30
52
|
setName('new-skill');
|
|
31
53
|
setDescription('');
|
|
32
|
-
setLocation(
|
|
33
|
-
setLocationLabel('User /
|
|
54
|
+
setLocation(defaultLocation);
|
|
55
|
+
setLocationLabel(defaultLocation === 'project' ? 'Project / .omp/skills' : 'User / omp agent');
|
|
34
56
|
setInstructions(`---
|
|
35
57
|
description: ""
|
|
36
58
|
---
|
|
37
59
|
`);
|
|
60
|
+
setHidden(false);
|
|
38
61
|
} else {
|
|
39
62
|
setName(skill.name);
|
|
40
63
|
setDescription(skill.description);
|
|
41
64
|
setLocation(skill.location);
|
|
42
|
-
setLocationLabel(skill.locationLabel || 'User /
|
|
65
|
+
setLocationLabel(skill.locationLabel || 'User / omp agent');
|
|
43
66
|
setInstructions(skill.instructions);
|
|
67
|
+
setHidden(skill.hidden === true);
|
|
44
68
|
}
|
|
45
|
-
}, [skill, isCreatingNew]);
|
|
69
|
+
}, [skill, isCreatingNew, defaultLocation]);
|
|
46
70
|
|
|
47
71
|
const handleSave = () => {
|
|
48
72
|
onSave({
|
|
@@ -51,6 +75,7 @@ description: ""
|
|
|
51
75
|
location,
|
|
52
76
|
locationLabel,
|
|
53
77
|
instructions,
|
|
78
|
+
hidden,
|
|
54
79
|
});
|
|
55
80
|
setSavedToast(true);
|
|
56
81
|
setTimeout(() => setSavedToast(false), 2000);
|
|
@@ -68,22 +93,44 @@ description: ""
|
|
|
68
93
|
{isCreatingNew ? 'New Skill' : name}
|
|
69
94
|
</h2>
|
|
70
95
|
<p className="text-xs text-ink/60 mt-0.5">
|
|
71
|
-
{isCreatingNew ? 'Configure a new skill' :
|
|
96
|
+
{isCreatingNew ? 'Configure a new skill' : `Configure skill details and prompt rules · ${locationLabel}`}
|
|
72
97
|
</p>
|
|
73
98
|
</div>
|
|
74
99
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
<div className="flex items-center gap-2">
|
|
101
|
+
{onReload && (
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
onClick={onReload}
|
|
105
|
+
disabled={isReloading}
|
|
106
|
+
title="Re-discover skills in the running agents"
|
|
107
|
+
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-ink/20 hover:border-ink/40 text-xs font-medium text-ink hover:bg-ink/5 transition-colors cursor-pointer disabled:opacity-50"
|
|
108
|
+
>
|
|
109
|
+
<RefreshCw size={14} className={isReloading ? 'animate-spin text-ink/70' : 'text-ink/70'} />
|
|
110
|
+
<span>Reload</span>
|
|
111
|
+
</button>
|
|
112
|
+
)}
|
|
113
|
+
{onOpenCatalog && (
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={onOpenCatalog}
|
|
117
|
+
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-ink/20 hover:border-ink/40 text-xs font-medium text-ink hover:bg-ink/5 transition-colors cursor-pointer"
|
|
118
|
+
>
|
|
119
|
+
<Sparkles size={14} className="text-ink/70" />
|
|
120
|
+
<span>Browse Catalog</span>
|
|
121
|
+
</button>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
85
124
|
</div>
|
|
86
125
|
|
|
126
|
+
{readOnly && (
|
|
127
|
+
<div className="rounded-lg border border-ink/15 bg-ink/5 px-3 py-2 text-[11px] text-ink/70 leading-relaxed">
|
|
128
|
+
This skill is provided by <span className="font-mono text-ink/80">{skill?.source}</span> and is read-only here.
|
|
129
|
+
Edit <span className="font-mono text-ink/80">{skill?.filePath}</span> at its source, or install a copy into
|
|
130
|
+
{' '}<span className="font-mono text-ink/80">~/.omp/agent/skills</span> to own it.
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
|
|
87
134
|
{/* Basic Information Section */}
|
|
88
135
|
<div className="space-y-4">
|
|
89
136
|
<h3 className="text-xs font-bold uppercase tracking-wider text-ink">
|
|
@@ -99,7 +146,8 @@ description: ""
|
|
|
99
146
|
<div className="group relative">
|
|
100
147
|
<Info size={13} className="text-ink/40 cursor-help" />
|
|
101
148
|
<div className="absolute left-0 top-5 hidden group-hover:block w-64 p-2 rounded-lg bg-ink text-paper text-[11px] shadow-lg z-20 pointer-events-none leading-relaxed">
|
|
102
|
-
Unique identifier and runtime execution scope for this skill.
|
|
149
|
+
Unique identifier and runtime execution scope for this skill. A project skill is written under
|
|
150
|
+
{' '}<span className="font-mono"><workspace>/.omp/skills</span>.
|
|
103
151
|
</div>
|
|
104
152
|
</div>
|
|
105
153
|
</div>
|
|
@@ -109,14 +157,42 @@ description: ""
|
|
|
109
157
|
type="text"
|
|
110
158
|
value={name}
|
|
111
159
|
onChange={(e) => setName(e.currentTarget.value)}
|
|
160
|
+
disabled={readOnly}
|
|
112
161
|
placeholder="skill-name"
|
|
113
|
-
className="flex-1 px-3 py-2 rounded-lg border border-ink/20 hover:border-ink/40 bg-paper focus:outline-none focus:border-ink text-xs font-mono text-ink transition-colors"
|
|
162
|
+
className="flex-1 px-3 py-2 rounded-lg border border-ink/20 hover:border-ink/40 bg-paper focus:outline-none focus:border-ink text-xs font-mono text-ink transition-colors disabled:opacity-60"
|
|
114
163
|
/>
|
|
115
164
|
<div className="flex items-center gap-1.5 px-3 py-2 rounded-lg border border-ink/20 bg-ink/5 text-xs text-ink/80 flex-shrink-0">
|
|
116
165
|
<User size={14} className="text-ink/60" />
|
|
117
166
|
<span>{locationLabel}</span>
|
|
118
167
|
</div>
|
|
119
168
|
</div>
|
|
169
|
+
|
|
170
|
+
{/* Scope selector: only a chamber-managed skill can move roots, and
|
|
171
|
+
the project scope needs a workspace to land in. */}
|
|
172
|
+
{!readOnly && (
|
|
173
|
+
<div className="flex items-center gap-2 mt-2">
|
|
174
|
+
{(['user', 'project'] as const).map((scope) => {
|
|
175
|
+
const disabled = scope === 'project' && projectScopeDisabled;
|
|
176
|
+
return (
|
|
177
|
+
<button
|
|
178
|
+
key={scope}
|
|
179
|
+
type="button"
|
|
180
|
+
disabled={disabled}
|
|
181
|
+
title={disabled ? 'Select a workspace folder to write a project skill' : undefined}
|
|
182
|
+
onClick={() => {
|
|
183
|
+
setLocation(scope);
|
|
184
|
+
setLocationLabel(scope === 'user' ? 'User / omp agent' : 'Project / .omp/skills');
|
|
185
|
+
}}
|
|
186
|
+
className={`px-2.5 py-1 rounded-md text-[11px] font-medium transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed ${
|
|
187
|
+
location === scope ? 'bg-ink text-paper' : 'border border-ink/20 text-ink/70 hover:bg-ink/5'
|
|
188
|
+
}`}
|
|
189
|
+
>
|
|
190
|
+
{scope === 'user' ? 'User scope' : 'Project scope'}
|
|
191
|
+
</button>
|
|
192
|
+
);
|
|
193
|
+
})}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
120
196
|
</div>
|
|
121
197
|
|
|
122
198
|
{/* Description */}
|
|
@@ -128,7 +204,8 @@ description: ""
|
|
|
128
204
|
<div className="group relative">
|
|
129
205
|
<Info size={13} className="text-ink/40 cursor-help" />
|
|
130
206
|
<div className="absolute left-0 top-5 hidden group-hover:block w-64 p-2 rounded-lg bg-ink text-paper text-[11px] shadow-lg z-20 pointer-events-none leading-relaxed">
|
|
131
|
-
Short description helping autonomous agents decide when to invoke this skill.
|
|
207
|
+
Short description helping autonomous agents decide when to invoke this skill. Required — omp does not
|
|
208
|
+
load a SKILL.md without one.
|
|
132
209
|
</div>
|
|
133
210
|
</div>
|
|
134
211
|
</div>
|
|
@@ -137,10 +214,29 @@ description: ""
|
|
|
137
214
|
rows={3}
|
|
138
215
|
value={description}
|
|
139
216
|
onChange={(e) => setDescription(e.currentTarget.value)}
|
|
217
|
+
disabled={readOnly}
|
|
140
218
|
placeholder="Brief description of what this skill does..."
|
|
141
|
-
className="w-full px-3 py-2 rounded-lg border border-ink/20 hover:border-ink/40 bg-paper focus:outline-none focus:border-ink text-xs text-ink transition-colors resize-none leading-relaxed"
|
|
219
|
+
className="w-full px-3 py-2 rounded-lg border border-ink/20 hover:border-ink/40 bg-paper focus:outline-none focus:border-ink text-xs text-ink transition-colors resize-none leading-relaxed disabled:opacity-60"
|
|
142
220
|
/>
|
|
143
221
|
</div>
|
|
222
|
+
|
|
223
|
+
{/* Model invocation toggle */}
|
|
224
|
+
<label className="flex items-start gap-2 cursor-pointer select-none">
|
|
225
|
+
<input
|
|
226
|
+
type="checkbox"
|
|
227
|
+
checked={hidden}
|
|
228
|
+
disabled={readOnly}
|
|
229
|
+
onChange={(e) => setHidden(e.currentTarget.checked)}
|
|
230
|
+
className="mt-0.5 accent-ink cursor-pointer"
|
|
231
|
+
/>
|
|
232
|
+
<span className="text-xs text-ink/80 leading-relaxed">
|
|
233
|
+
Hide from the model's skill listing
|
|
234
|
+
<span className="block text-[11px] text-ink/50">
|
|
235
|
+
The skill stays runnable via <span className="font-mono">/skill:{name || 'name'}</span>; it is only kept out
|
|
236
|
+
of the automatic skill catalog the model sees.
|
|
237
|
+
</span>
|
|
238
|
+
</span>
|
|
239
|
+
</label>
|
|
144
240
|
</div>
|
|
145
241
|
|
|
146
242
|
{/* Instructions Section */}
|
|
@@ -173,8 +269,9 @@ description: ""
|
|
|
173
269
|
rows={Math.max(lineCount, 8)}
|
|
174
270
|
value={instructions}
|
|
175
271
|
onChange={(e) => setInstructions(e.currentTarget.value)}
|
|
272
|
+
disabled={readOnly}
|
|
176
273
|
spellcheck={false}
|
|
177
|
-
className="flex-1 p-3 bg-transparent text-ink font-mono text-xs leading-relaxed resize-none focus:outline-none overflow-y-auto"
|
|
274
|
+
className="flex-1 p-3 bg-transparent text-ink font-mono text-xs leading-relaxed resize-none focus:outline-none overflow-y-auto disabled:opacity-60"
|
|
178
275
|
placeholder="Type markdown instructions..."
|
|
179
276
|
/>
|
|
180
277
|
</div>
|
|
@@ -183,7 +280,7 @@ description: ""
|
|
|
183
280
|
|
|
184
281
|
{/* Footer Actions */}
|
|
185
282
|
<div className="pt-4 border-t border-ink/10 flex items-center justify-between">
|
|
186
|
-
{!isCreatingNew && skill && onDelete ? (
|
|
283
|
+
{!isCreatingNew && skill && onDelete && !readOnly ? (
|
|
187
284
|
<button
|
|
188
285
|
type="button"
|
|
189
286
|
onClick={() => onDelete(skill.id)}
|
|
@@ -196,14 +293,16 @@ description: ""
|
|
|
196
293
|
<div />
|
|
197
294
|
)}
|
|
198
295
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
296
|
+
{!readOnly && (
|
|
297
|
+
<button
|
|
298
|
+
type="button"
|
|
299
|
+
onClick={handleSave}
|
|
300
|
+
className="flex items-center gap-1.5 px-4 py-2 rounded-lg bg-ink text-paper text-xs font-medium hover:bg-ink/90 transition-colors shadow-xs cursor-pointer"
|
|
301
|
+
>
|
|
302
|
+
{savedToast ? <Check size={14} /> : <Save size={14} />}
|
|
303
|
+
<span>{savedToast ? 'Saved' : isCreatingNew ? 'Create Skill' : 'Save Changes'}</span>
|
|
304
|
+
</button>
|
|
305
|
+
)}
|
|
207
306
|
</div>
|
|
208
307
|
</div>
|
|
209
308
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState } from 'preact/hooks';
|
|
2
2
|
import { ChevronDown, ChevronRight, Plus } from 'lucide-preact';
|
|
3
3
|
import type { SkillItem } from '@/shared/types';
|
|
4
|
-
import { ProjectSelectorDropdown } from '@/client/components/settings/ProjectSelectorDropdown';
|
|
4
|
+
import { ProjectSelectorDropdown, type ProjectOption } from '@/client/components/settings/ProjectSelectorDropdown';
|
|
5
5
|
|
|
6
6
|
interface SkillSidebarListProps {
|
|
7
7
|
skills: SkillItem[];
|
|
@@ -11,6 +11,8 @@ interface SkillSidebarListProps {
|
|
|
11
11
|
onAddNewSkill: () => void;
|
|
12
12
|
selectedProject: string;
|
|
13
13
|
onSelectProject: (project: string) => void;
|
|
14
|
+
/** Real workspace folders; defaults to the shared placeholder list. */
|
|
15
|
+
projectOptions?: ProjectOption[];
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export function SkillSidebarList({
|
|
@@ -21,6 +23,7 @@ export function SkillSidebarList({
|
|
|
21
23
|
onAddNewSkill,
|
|
22
24
|
selectedProject,
|
|
23
25
|
onSelectProject,
|
|
26
|
+
projectOptions,
|
|
24
27
|
}: SkillSidebarListProps) {
|
|
25
28
|
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
|
26
29
|
|
|
@@ -55,8 +58,9 @@ export function SkillSidebarList({
|
|
|
55
58
|
variant="compact"
|
|
56
59
|
selectedProject={selectedProject}
|
|
57
60
|
onChangeProject={onSelectProject}
|
|
61
|
+
{...(projectOptions ? { options: projectOptions } : {})}
|
|
58
62
|
getTriggerLabel={(options, value) =>
|
|
59
|
-
options.find((option) => option.id === value)?.label || value
|
|
63
|
+
options.find((option) => option.id === value || option.value === value)?.label || value
|
|
60
64
|
}
|
|
61
65
|
/>
|
|
62
66
|
|
|
@@ -77,8 +81,10 @@ export function SkillSidebarList({
|
|
|
77
81
|
|
|
78
82
|
{/* Skills List */}
|
|
79
83
|
<div className="flex-1 scrollbar-overlay-container scrollbar-overlay-static p-1.5 space-y-2">
|
|
84
|
+
{/* The picker above names the scope; the list is exactly that scope's
|
|
85
|
+
skills, so a second scope claim here would only be able to lie. */}
|
|
80
86
|
<div className="px-2 pt-1 pb-0.5 text-[10px] font-bold text-ink/50 uppercase tracking-wider">
|
|
81
|
-
|
|
87
|
+
SKILLS
|
|
82
88
|
</div>
|
|
83
89
|
|
|
84
90
|
{/* Temporary "New Skill" indicator item */}
|
|
@@ -7,8 +7,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
7
7
|
name: 'init',
|
|
8
8
|
description: 'guided AGENTS.md setup',
|
|
9
9
|
scope: 'system',
|
|
10
|
-
overrideAgent: 'Not selected',
|
|
11
|
-
overrideModel: 'Not selected',
|
|
12
10
|
template: `Initialize AGENTS.md instructions for this repository following guided prompts. Context: $ARGUMENTS`,
|
|
13
11
|
isBuiltIn: true,
|
|
14
12
|
},
|
|
@@ -17,8 +15,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
17
15
|
name: 'review',
|
|
18
16
|
description: 'review changes [commit|branch|working]',
|
|
19
17
|
scope: 'system',
|
|
20
|
-
overrideAgent: 'code-reviewer',
|
|
21
|
-
overrideModel: 'Not selected',
|
|
22
18
|
template: `Review the current changes in $ARGUMENTS and summarize bugs, formatting issues, and test considerations.\n!\`git diff --stat\``,
|
|
23
19
|
isBuiltIn: true,
|
|
24
20
|
},
|
|
@@ -28,8 +24,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
28
24
|
name: 'debugging',
|
|
29
25
|
description: 'MUST USE for any real runtime debugging session',
|
|
30
26
|
scope: 'user',
|
|
31
|
-
overrideAgent: 'coder',
|
|
32
|
-
overrideModel: 'Not selected',
|
|
33
27
|
template: `Analyze the runtime error log and isolate the root failure:\n!\`bun run test\`\nFix the issue and verify: $ARGUMENTS`,
|
|
34
28
|
},
|
|
35
29
|
{
|
|
@@ -37,8 +31,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
37
31
|
name: 'frontend',
|
|
38
32
|
description: 'MUST USE for frontend/web UI/UX development',
|
|
39
33
|
scope: 'user',
|
|
40
|
-
overrideAgent: 'frontend-specialist',
|
|
41
|
-
overrideModel: 'Not selected',
|
|
42
34
|
template: `Implement clean responsive UI component following Tailwind and theme variable specifications.\nTarget file: @$ARGUMENTS`,
|
|
43
35
|
},
|
|
44
36
|
{
|
|
@@ -46,8 +38,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
46
38
|
name: 'git-master',
|
|
47
39
|
description: 'MUST USE for ANY git operations and commit conventions',
|
|
48
40
|
scope: 'user',
|
|
49
|
-
overrideAgent: 'git-sync',
|
|
50
|
-
overrideModel: 'Not selected',
|
|
51
41
|
template: `Inspect git status and generate clean conventional commit message:\n!\`git status -s\`\nDetails: $ARGUMENTS`,
|
|
52
42
|
},
|
|
53
43
|
{
|
|
@@ -55,8 +45,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
55
45
|
name: 'goal',
|
|
56
46
|
description: '(builtin) Set, show, pause, resume session goal',
|
|
57
47
|
scope: 'user',
|
|
58
|
-
overrideAgent: 'Sisyphus - ultraworker',
|
|
59
|
-
overrideModel: 'Not selected',
|
|
60
48
|
template: `Manage current goal state: $ARGUMENTS`,
|
|
61
49
|
},
|
|
62
50
|
{
|
|
@@ -64,8 +52,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
64
52
|
name: 'handoff',
|
|
65
53
|
description: '(builtin) Create a detailed context handoff report',
|
|
66
54
|
scope: 'user',
|
|
67
|
-
overrideAgent: 'general',
|
|
68
|
-
overrideModel: 'Not selected',
|
|
69
55
|
template: `Summarize work in progress, architectural decisions, and open items for handoff to next agent session.\n$ARGUMENTS`,
|
|
70
56
|
},
|
|
71
57
|
{
|
|
@@ -73,8 +59,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
73
59
|
name: 'hyperplan',
|
|
74
60
|
description: '(builtin) Adversarial multi-agent plan builder',
|
|
75
61
|
scope: 'user',
|
|
76
|
-
overrideAgent: 'Prometheus - Plan Builder',
|
|
77
|
-
overrideModel: 'Not selected',
|
|
78
62
|
template: `Construct a detailed plan with review gates and adversarial critiques for $ARGUMENTS.`,
|
|
79
63
|
},
|
|
80
64
|
{
|
|
@@ -82,8 +66,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
82
66
|
name: 'init-deep',
|
|
83
67
|
description: '(builtin) Initialize hierarchical AGENTS.md setup',
|
|
84
68
|
scope: 'user',
|
|
85
|
-
overrideAgent: 'architect',
|
|
86
|
-
overrideModel: 'Not selected',
|
|
87
69
|
template: `Analyze directory structure and construct domain-specific AGENTS.md files for subpackages: $ARGUMENTS`,
|
|
88
70
|
},
|
|
89
71
|
{
|
|
@@ -91,8 +73,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
91
73
|
name: 'playwright',
|
|
92
74
|
description: 'MUST USE for any browser-related testing',
|
|
93
75
|
scope: 'user',
|
|
94
|
-
overrideAgent: 'e2e-runner',
|
|
95
|
-
overrideModel: 'Not selected',
|
|
96
76
|
template: `Run playwright tests for scenario: $ARGUMENTS\n!\`bunx playwright test\``,
|
|
97
77
|
},
|
|
98
78
|
{
|
|
@@ -100,8 +80,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
100
80
|
name: 'pty-open-background-spy',
|
|
101
81
|
description: 'Open PTY Sessions Web Interface',
|
|
102
82
|
scope: 'user',
|
|
103
|
-
overrideAgent: 'devops-runner',
|
|
104
|
-
overrideModel: 'Not selected',
|
|
105
83
|
template: `Launch PTY background session monitor with target: $ARGUMENTS`,
|
|
106
84
|
},
|
|
107
85
|
{
|
|
@@ -109,8 +87,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
109
87
|
name: 'pty-show-server-url',
|
|
110
88
|
description: 'Show PTY Sessions Web Interface URL',
|
|
111
89
|
scope: 'user',
|
|
112
|
-
overrideAgent: 'devops-runner',
|
|
113
|
-
overrideModel: 'Not selected',
|
|
114
90
|
template: `Output the active PTY session web socket endpoint and UI URL.`,
|
|
115
91
|
},
|
|
116
92
|
{
|
|
@@ -118,8 +94,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
118
94
|
name: 'refactor',
|
|
119
95
|
description: '(builtin) Intelligent refactoring coordinator',
|
|
120
96
|
scope: 'user',
|
|
121
|
-
overrideAgent: 'refactor-pro',
|
|
122
|
-
overrideModel: 'Not selected',
|
|
123
97
|
template: `Refactor module @$ARGUMENTS into decoupled single-responsibility files without changing public behavior. Keep files under 350 lines.`,
|
|
124
98
|
},
|
|
125
99
|
{
|
|
@@ -127,8 +101,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
127
101
|
name: 'benchmark',
|
|
128
102
|
description: 'Run performance benchmark suites and record timings',
|
|
129
103
|
scope: 'user',
|
|
130
|
-
overrideAgent: 'performance-tuner',
|
|
131
|
-
overrideModel: 'Not selected',
|
|
132
104
|
template: `Execute benchmark suite on target: $ARGUMENTS`,
|
|
133
105
|
},
|
|
134
106
|
{
|
|
@@ -136,8 +108,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
136
108
|
name: 'bundle-analyze',
|
|
137
109
|
description: 'Inspect vendor bundle size and dynamic chunk splitting',
|
|
138
110
|
scope: 'user',
|
|
139
|
-
overrideAgent: 'bundle-optimizer',
|
|
140
|
-
overrideModel: 'Not selected',
|
|
141
111
|
template: `Run build analyzer and report chunk weight statistics: !\`bun run build\``,
|
|
142
112
|
},
|
|
143
113
|
{
|
|
@@ -145,8 +115,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
145
115
|
name: 'db-migrate',
|
|
146
116
|
description: 'Review and execute database migrations safely',
|
|
147
117
|
scope: 'user',
|
|
148
|
-
overrideAgent: 'database-analyzer',
|
|
149
|
-
overrideModel: 'Not selected',
|
|
150
118
|
template: `Review pending schema migrations and verify backward compatibility: $ARGUMENTS`,
|
|
151
119
|
},
|
|
152
120
|
{
|
|
@@ -154,8 +122,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
154
122
|
name: 'doc-generate',
|
|
155
123
|
description: 'Generate API documentation and usage guides',
|
|
156
124
|
scope: 'user',
|
|
157
|
-
overrideAgent: 'doc-writer',
|
|
158
|
-
overrideModel: 'Not selected',
|
|
159
125
|
template: `Draft comprehensive technical documentation for module @$ARGUMENTS`,
|
|
160
126
|
},
|
|
161
127
|
{
|
|
@@ -163,8 +129,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
163
129
|
name: 'security-scan',
|
|
164
130
|
description: 'Run static security analysis and check dependency vulnerabilities',
|
|
165
131
|
scope: 'user',
|
|
166
|
-
overrideAgent: 'security-auditor',
|
|
167
|
-
overrideModel: 'Not selected',
|
|
168
132
|
template: `Scan repository for leaked secrets and outdated dependencies: !\`npm audit\``,
|
|
169
133
|
},
|
|
170
134
|
{
|
|
@@ -172,8 +136,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
172
136
|
name: 'test-unit',
|
|
173
137
|
description: 'Run unit test suite with coverage report',
|
|
174
138
|
scope: 'user',
|
|
175
|
-
overrideAgent: 'test-engineer',
|
|
176
|
-
overrideModel: 'Not selected',
|
|
177
139
|
template: `Run test suite with coverage: !\`bun test\` - Focus: $ARGUMENTS`,
|
|
178
140
|
},
|
|
179
141
|
{
|
|
@@ -181,8 +143,6 @@ export const DEFAULT_COMMANDS_LIST: CommandItem[] = [
|
|
|
181
143
|
name: 'typecheck',
|
|
182
144
|
description: 'Run strict TypeScript compiler verification',
|
|
183
145
|
scope: 'user',
|
|
184
|
-
overrideAgent: 'lint-fixer',
|
|
185
|
-
overrideModel: 'Not selected',
|
|
186
146
|
template: `Run strict typecheck without emitting files: !\`bunx tsc --noEmit\``,
|
|
187
147
|
},
|
|
188
148
|
];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
7
|
+
import type { ProjectOption } from '@/client/components/settings/ProjectSelectorDropdown';
|
|
8
|
+
|
|
9
|
+
interface ProjectConfigItem {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
path?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WorkspaceRoots {
|
|
16
|
+
options: ProjectOption[];
|
|
17
|
+
/** Workspace root for a selected option id, or null for the user scope. */
|
|
18
|
+
rootFor: (optionId: string) => string | null;
|
|
19
|
+
/** Query string (with leading `?`) for a read scoped to this option. */
|
|
20
|
+
queryFor: (optionId: string) => string;
|
|
21
|
+
/** Extra body fields scoping a write to this option. */
|
|
22
|
+
scopeBodyFor: (optionId: string) => { root?: string; scope?: string };
|
|
23
|
+
/** True when this option is a real workspace (the project scope exists). */
|
|
24
|
+
isWorkspace: (optionId: string) => boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Option id for the user-level (no workspace) scope. */
|
|
28
|
+
export const GLOBAL_SCOPE_ID = 'global';
|
|
29
|
+
|
|
30
|
+
const USER_OPTION: ProjectOption = {
|
|
31
|
+
id: GLOBAL_SCOPE_ID,
|
|
32
|
+
name: 'global',
|
|
33
|
+
label: 'User (all projects)',
|
|
34
|
+
value: '',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The workspace folders the skills/commands panels can be scoped to.
|
|
39
|
+
*
|
|
40
|
+
* These are the real registered folders (`/api/settings/projects`), not the
|
|
41
|
+
* hardcoded placeholder list the panels used to offer: a panel scoped to a
|
|
42
|
+
* made-up project name sent a root the server could not resolve, so the picker
|
|
43
|
+
* silently did nothing. The `value` of each option is the folder's project
|
|
44
|
+
* path, which is what the API takes as `?root=`.
|
|
45
|
+
*
|
|
46
|
+
* The USER scope is `?scope=user` rather than "no root": omp resolves
|
|
47
|
+
* project-scope skills and commands from its process cwd, so an unscoped read
|
|
48
|
+
* answered for whatever directory the server happened to run in. `scope=user`
|
|
49
|
+
* pins that read to the agent dir, which is the only cwd whose inventory is
|
|
50
|
+
* the user's own.
|
|
51
|
+
*/
|
|
52
|
+
export function useWorkspaceRoots(): WorkspaceRoots {
|
|
53
|
+
const [options, setOptions] = useState<ProjectOption[]>([USER_OPTION]);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
let active = true;
|
|
57
|
+
fetch('/api/settings/projects')
|
|
58
|
+
.then((res) => res.json())
|
|
59
|
+
.then((data: { projects?: ProjectConfigItem[] }) => {
|
|
60
|
+
if (!active) return;
|
|
61
|
+
const folders = (data.projects ?? [])
|
|
62
|
+
.filter((project) => typeof project.path === 'string' && project.path)
|
|
63
|
+
.map((project) => ({
|
|
64
|
+
id: project.id,
|
|
65
|
+
name: project.name,
|
|
66
|
+
label: project.name,
|
|
67
|
+
value: project.path as string,
|
|
68
|
+
}));
|
|
69
|
+
setOptions([...folders, USER_OPTION]);
|
|
70
|
+
})
|
|
71
|
+
.catch((error) => console.error('Failed to load workspace folders:', error));
|
|
72
|
+
return () => {
|
|
73
|
+
active = false;
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
// The dropdown reports the option's VALUE (a project path) while a panel may
|
|
78
|
+
// hold either that or the option id, so both resolve to the same root. An
|
|
79
|
+
// id-only lookup silently answered the user scope for every workspace.
|
|
80
|
+
const rootFor = (optionId: string): string | null => {
|
|
81
|
+
const option = options.find((candidate) => candidate.id === optionId || candidate.value === optionId);
|
|
82
|
+
const value = option?.value ?? '';
|
|
83
|
+
return value || null;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const queryFor = (optionId: string): string => {
|
|
87
|
+
const root = rootFor(optionId);
|
|
88
|
+
return root ? `?root=${encodeURIComponent(root)}` : '?scope=user';
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const scopeBodyFor = (optionId: string): { root?: string; scope?: string } => {
|
|
92
|
+
const root = rootFor(optionId);
|
|
93
|
+
return root ? { root } : { scope: 'user' };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const isWorkspace = (optionId: string): boolean => rootFor(optionId) !== null;
|
|
97
|
+
|
|
98
|
+
return { options, rootFor, queryFor, scopeBodyFor, isWorkspace };
|
|
99
|
+
}
|