ompchamber 3.5.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 +357 -353
- package/dist/client/static/js/index-7n838fz5.css +1 -0
- package/dist/client/static/js/{index-w0cem7n5.js → index-qp2sfmn2.js} +591 -592
- package/package.json +1 -1
- package/src/cli/lib/commands/status.js +7 -0
- 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/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/server/db.server.ts +41 -11
- package/src/server/index.ts +17 -1
- 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/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/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/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/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/types/settings/command.ts +15 -2
- package/src/shared/types/settings/skill.ts +13 -0
- package/dist/client/static/js/index-vx0ernrv.css +0 -1
- package/src/client/hooks/ui/updates.ts +0 -108
- package/src/server/routes/omp/commands.ts +0 -29
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One update at a time, for the whole server.
|
|
3
|
+
*
|
|
4
|
+
* An update is long and it replaces an install in place, so overlapping runs
|
|
5
|
+
* are not merely wasteful: two `updateOmpChamber` calls mean two
|
|
6
|
+
* `git merge --ff-only` plus two `bun install` in the same checkout, and two
|
|
7
|
+
* `omp update` calls race over the same binary.
|
|
8
|
+
*
|
|
9
|
+
* The console cannot be the gate. Its "a run is in flight" state is JavaScript,
|
|
10
|
+
* so a page reload forgets it and offers the button again while the first run
|
|
11
|
+
* is still going — measured: a reload mid-run followed by one more click left
|
|
12
|
+
* two `omp update` children alive at once.
|
|
13
|
+
*
|
|
14
|
+
* The claim is taken SYNCHRONOUSLY, before the run's first `await`, so two
|
|
15
|
+
* requests arriving in the same tick cannot both pass. Release is bound to the
|
|
16
|
+
* token `beginUpdate` issues rather than to the target name, so a stale release
|
|
17
|
+
* cannot free a claim belonging to a different run.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { UpdateTarget } from '@/shared/types/updates';
|
|
21
|
+
|
|
22
|
+
/** What the console is told when a second run is refused. */
|
|
23
|
+
export function updateBusyMessage(target: UpdateTarget): string {
|
|
24
|
+
return `An ${target === 'omp' ? 'Oh-My-Pi' : 'OMPChamber'} update is already running.`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class UpdateInProgressError extends Error {
|
|
28
|
+
constructor(target: UpdateTarget) {
|
|
29
|
+
super(updateBusyMessage(target));
|
|
30
|
+
this.name = 'UpdateInProgressError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let held: { token: symbol; target: UpdateTarget } | null = null;
|
|
35
|
+
|
|
36
|
+
/** The target of the update in flight, or null when none is. */
|
|
37
|
+
export function runningUpdate(): UpdateTarget | null {
|
|
38
|
+
return held?.target ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Take the slot. Throws when another run already holds it. */
|
|
42
|
+
function beginUpdate(target: UpdateTarget): symbol {
|
|
43
|
+
if (held) throw new UpdateInProgressError(held.target);
|
|
44
|
+
const token = Symbol('update');
|
|
45
|
+
held = { token, target };
|
|
46
|
+
return token;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Release the slot; a token that no longer owns it is ignored. */
|
|
50
|
+
function releaseUpdate(token: symbol): void {
|
|
51
|
+
if (held?.token === token) held = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run `run` while holding the slot.
|
|
56
|
+
*
|
|
57
|
+
* The release is in a `finally`, so a failed update cannot leave the server
|
|
58
|
+
* answering "already running" forever — there is no other way to clear the
|
|
59
|
+
* flag, and a stuck flag would need a restart.
|
|
60
|
+
*/
|
|
61
|
+
export async function withUpdateSlot<T>(target: UpdateTarget, run: () => Promise<T>): Promise<T> {
|
|
62
|
+
const token = beginUpdate(target);
|
|
63
|
+
try {
|
|
64
|
+
return await run();
|
|
65
|
+
} finally {
|
|
66
|
+
releaseUpdate(token);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -6,7 +6,7 @@ import { getSessionData } from '@/client/data/mock/chat';
|
|
|
6
6
|
import { isMockMode } from '@/server/mock.server';
|
|
7
7
|
import { findSessionFileById } from '@/server/lib/omp/session/locator';
|
|
8
8
|
import { loadSessionMessages, loadSessionModel, loadSessionThinkingLevel, loadSessionTitle } from '@/server/lib/omp/session/messages';
|
|
9
|
-
import { mergeOmpAttachments } from '@/server/lib/omp/session/merge-stored';
|
|
9
|
+
import { mergeOmpAttachments, overlayRowsForOmpSession } from '@/server/lib/omp/session/merge-stored';
|
|
10
10
|
import { readRawHeaderLine } from '@/server/lib/omp/session/files';
|
|
11
11
|
import { formatNewSessionTitle } from '@/shared/lib/omp/session/default-title';
|
|
12
12
|
|
|
@@ -202,9 +202,19 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
|
|
202
202
|
updatedMessages = [...currentMessages, data.message];
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// A session omp owns on disk has its transcript in the JSONL, so the
|
|
206
|
+
// overlay keeps only the rows omp cannot record there — the user turns
|
|
207
|
+
// (raw composer text, attachment metadata) and the notice rows. A session
|
|
208
|
+
// with no file is chamber-created: the DB is its only copy, so it stays
|
|
209
|
+
// whole. The gate is the file's existence, never the id's shape — an omp
|
|
210
|
+
// uuid whose file is gone still needs its full row.
|
|
211
|
+
const persistedMessages = (await findSessionFileById(sessionId))
|
|
212
|
+
? overlayRowsForOmpSession(updatedMessages)
|
|
213
|
+
: updatedMessages;
|
|
214
|
+
|
|
205
215
|
await db.run(
|
|
206
216
|
'INSERT OR REPLACE INTO chat_sessions (session_id, title, messages, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)',
|
|
207
|
-
[sessionId, title, JSON.stringify(
|
|
217
|
+
[sessionId, title, JSON.stringify(persistedMessages)]
|
|
208
218
|
);
|
|
209
219
|
|
|
210
220
|
return json({
|
|
@@ -212,7 +222,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
|
|
212
222
|
session: {
|
|
213
223
|
id: sessionId,
|
|
214
224
|
title,
|
|
215
|
-
messages:
|
|
225
|
+
messages: persistedMessages,
|
|
216
226
|
},
|
|
217
227
|
});
|
|
218
228
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { json, NO_STORE_HEADERS } from '@/server/lib/remix-compat';
|
|
2
2
|
import pkg from '@/../package.json';
|
|
3
|
+
import { countOpenFileDescriptors } from '@/server/lib/lifecycle/fd-pressure';
|
|
3
4
|
import { resolveLaunchMode } from '@/server/lib/lifecycle/launch-mode';
|
|
4
5
|
import { isMockMode } from '@/server/mock.server';
|
|
5
6
|
|
|
@@ -9,6 +10,12 @@ const STARTED_ISO = new Date(STARTED_AT).toISOString();
|
|
|
9
10
|
/**
|
|
10
11
|
* GET /api/health — lightweight readiness probe for the `ompchamber status` CLI.
|
|
11
12
|
* Dependency-light by design: never touches the database and never throws.
|
|
13
|
+
*
|
|
14
|
+
* It does sweep the descriptor table (`countOpenFileDescriptors`, ~1 ms): a dev
|
|
15
|
+
* server walks toward the 10,240-descriptor point where every `Bun.spawn` in
|
|
16
|
+
* the process fails, and the sweep is the only way to say so *before* the
|
|
17
|
+
* terminal panel, the omp child and git all break at once. Null where the host
|
|
18
|
+
* cannot answer.
|
|
12
19
|
*/
|
|
13
20
|
export async function loader() {
|
|
14
21
|
return json(
|
|
@@ -30,6 +37,7 @@ export async function loader() {
|
|
|
30
37
|
launchMode: resolveLaunchMode(),
|
|
31
38
|
port: Number(Bun.env.PORT) || 3000,
|
|
32
39
|
host: Bun.env.HOST || 'localhost',
|
|
40
|
+
fds: countOpenFileDescriptors(),
|
|
33
41
|
},
|
|
34
42
|
{ headers: NO_STORE_HEADERS },
|
|
35
43
|
);
|
|
@@ -3,7 +3,6 @@ import * as ompState from '@/server/routes/omp/state';
|
|
|
3
3
|
import * as ompSidebar from '@/server/routes/omp/sidebar';
|
|
4
4
|
import * as ompSessionStats from '@/server/routes/omp/session-stats';
|
|
5
5
|
import * as ompSessionTodos from '@/server/routes/omp/session-todos';
|
|
6
|
-
import * as ompCommands from '@/server/routes/omp/commands';
|
|
7
6
|
import * as ompExtensions from '@/server/routes/omp/extensions';
|
|
8
7
|
import * as ompLogin from '@/server/routes/omp/login';
|
|
9
8
|
import * as ompPlugins from '@/server/routes/omp/plugins';
|
|
@@ -14,7 +13,6 @@ export const ompBindings: HandlerBinding[] = [
|
|
|
14
13
|
...bindingsFor(ompSidebar, '/api/omp/sidebar'),
|
|
15
14
|
...bindingsFor(ompSessionStats, '/api/omp/session-stats'),
|
|
16
15
|
...bindingsFor(ompSessionTodos, '/api/omp/session-todos'),
|
|
17
|
-
...bindingsFor(ompCommands, '/api/omp/commands'),
|
|
18
16
|
...bindingsFor(ompExtensions, '/api/omp/extensions'),
|
|
19
17
|
...bindingsFor(ompLogin, '/api/omp/login'),
|
|
20
18
|
...bindingsFor(ompPlugins, '/api/omp/plugins'),
|
|
@@ -8,6 +8,18 @@ import type { CommandItem } from '@/shared/types';
|
|
|
8
8
|
import { isRecord } from '@/shared/lib/util/guards';
|
|
9
9
|
import { createSettingsListStore } from '@/server/lib/db/settings-store';
|
|
10
10
|
import { runUtilityCommand } from '@/server/lib/omp/rpc/utility';
|
|
11
|
+
import { resolveDiscoveryScope, type DiscoveryScope } from '@/server/lib/omp/config/scope';
|
|
12
|
+
import {
|
|
13
|
+
deleteCommandFile,
|
|
14
|
+
locateCommandFile,
|
|
15
|
+
readCommandFile,
|
|
16
|
+
writeCommandFile,
|
|
17
|
+
} from '@/server/lib/omp/config/commands';
|
|
18
|
+
import { reloadLiveSessions } from '@/server/lib/omp/session/reload.server';
|
|
19
|
+
|
|
20
|
+
function errorMessage(error: unknown): string {
|
|
21
|
+
return error instanceof Error ? error.message : String(error);
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
const commandsStore = createSettingsListStore<CommandItem>({
|
|
13
25
|
key: 'omp_commands_settings',
|
|
@@ -60,19 +72,21 @@ function toLiveCommandItem(value: unknown): CommandItem | null {
|
|
|
60
72
|
id: `omp-live-${name}`,
|
|
61
73
|
name,
|
|
62
74
|
description: typeof value.description === 'string' ? value.description : '',
|
|
63
|
-
scope: '
|
|
75
|
+
scope: 'system',
|
|
76
|
+
source: value.source,
|
|
64
77
|
template: `/${name}`,
|
|
65
|
-
isBuiltIn:
|
|
78
|
+
isBuiltIn: value.source !== 'file',
|
|
66
79
|
...(aliases.length ? { aliases } : {}),
|
|
67
80
|
...(subcommands.length ? { subcommands } : {}),
|
|
68
81
|
...(hint ? { inputHint: hint } : {}),
|
|
69
82
|
};
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
/** Read live agent commands (builtin/skill/custom/extension/file sources) via
|
|
73
|
-
|
|
85
|
+
/** Read live agent commands (builtin/skill/custom/extension/file sources) via
|
|
86
|
+
* RPC, scoped to `cwd` so a workspace's own `.omp/commands` are discovered. */
|
|
87
|
+
async function loadAgentCommands(cwd: string): Promise<CommandItem[]> {
|
|
74
88
|
try {
|
|
75
|
-
const data = await runUtilityCommand<{ commands?: unknown }>({ type: 'get_available_commands' }, 30_000);
|
|
89
|
+
const data = await runUtilityCommand<{ commands?: unknown }>({ type: 'get_available_commands' }, 30_000, cwd);
|
|
76
90
|
if (!Array.isArray(data.commands)) return [];
|
|
77
91
|
return data.commands.flatMap((command) => {
|
|
78
92
|
const item = toLiveCommandItem(command);
|
|
@@ -83,48 +97,121 @@ async function loadAgentCommands(): Promise<CommandItem[]> {
|
|
|
83
97
|
}
|
|
84
98
|
}
|
|
85
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Attach what the settings pane needs to decide whether a live command is a
|
|
102
|
+
* file the chamber owns: the file backing it (if any), its scope, and its body.
|
|
103
|
+
* `get_available_commands` reports the name, description and hint but not the
|
|
104
|
+
* path, so the answer comes from looking the name up in the roots omp reads.
|
|
105
|
+
*/
|
|
106
|
+
async function withCommandFile(command: CommandItem, scope: DiscoveryScope): Promise<CommandItem> {
|
|
107
|
+
const found = await locateCommandFile(command.name, scope.workspace);
|
|
108
|
+
if (!found) {
|
|
109
|
+
// No markdown file backs this name: a builtin, a `skill:<name>` entry, an
|
|
110
|
+
// extension or a TypeScript custom command. It runs, but nothing here owns
|
|
111
|
+
// a file for it.
|
|
112
|
+
return { ...command, isBuiltIn: true, managed: false };
|
|
113
|
+
}
|
|
114
|
+
const file = await readCommandFile(found.filePath);
|
|
115
|
+
return {
|
|
116
|
+
...command,
|
|
117
|
+
// The file's scope is the truth, not omp's silence about where it found it.
|
|
118
|
+
scope: found.scope,
|
|
119
|
+
isBuiltIn: false,
|
|
120
|
+
managed: found.managed,
|
|
121
|
+
filePath: found.filePath,
|
|
122
|
+
...(file ? { template: file.body, description: file.description || command.description } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
86
126
|
/** Merge live agent commands ahead of app-local custom commands. */
|
|
87
|
-
async function mergeCommands(custom: CommandItem[]): Promise<CommandItem[]> {
|
|
88
|
-
const live = await loadAgentCommands();
|
|
127
|
+
async function mergeCommands(custom: CommandItem[], scope: DiscoveryScope): Promise<CommandItem[]> {
|
|
128
|
+
const live = await Promise.all((await loadAgentCommands(scope.cwd)).map((c) => withCommandFile(c, scope)));
|
|
89
129
|
const liveNames = new Set(live.map((c) => c.name.toLowerCase()));
|
|
90
130
|
return [...live, ...custom.filter((c) => !liveNames.has(c.name.toLowerCase()))];
|
|
91
131
|
}
|
|
92
132
|
|
|
93
|
-
export async function loader({ request
|
|
133
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
94
134
|
try {
|
|
95
135
|
const db = await getDb();
|
|
96
136
|
const mock = isMockMode();
|
|
97
137
|
const commands = await commandsStore.read(db);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
138
|
+
if (mock) return json({ commands, isMock: true });
|
|
139
|
+
|
|
140
|
+
// The workspace root decides which project-scope commands/skills exist, so
|
|
141
|
+
// an unscoped read must not silently answer with $HOME's inventory.
|
|
142
|
+
const url = new URL(request.url);
|
|
143
|
+
const scope = await resolveDiscoveryScope(url.searchParams.get('root'), url.searchParams.get('scope'));
|
|
144
|
+
const merged = await mergeCommands(commands, scope);
|
|
145
|
+
return json({ commands: merged, isMock: false });
|
|
146
|
+
} catch (error) {
|
|
101
147
|
const mock = isMockMode();
|
|
102
|
-
return json({ error: error
|
|
148
|
+
return json({ error: errorMessage(error), commands: mock ? DEFAULT_COMMANDS_LIST : [], isMock: mock }, { status: 500 });
|
|
103
149
|
}
|
|
104
150
|
}
|
|
105
151
|
|
|
106
152
|
export async function action({ request, params }: ActionFunctionArgs) {
|
|
107
153
|
try {
|
|
108
154
|
const db = await getDb();
|
|
155
|
+
const url = new URL(request.url);
|
|
109
156
|
|
|
110
157
|
if (request.method === 'DELETE') {
|
|
111
|
-
const url = new URL(request.url);
|
|
112
158
|
const id = url.searchParams.get('id');
|
|
113
159
|
if (!id) return json({ error: 'id is required' }, { status: 400 });
|
|
160
|
+
if (isMockMode()) {
|
|
161
|
+
const list = await commandsStore.remove(db, id);
|
|
162
|
+
return json({ success: true, commands: list });
|
|
163
|
+
}
|
|
114
164
|
|
|
165
|
+
// A live command's id carries its name (`omp-live-<name>`); the pane's
|
|
166
|
+
// delete is only offered for one the chamber owns, and the file is the
|
|
167
|
+
// real thing to remove. A chamber-local row still deletes as a row.
|
|
168
|
+
const name = id.startsWith('omp-live-') ? id.slice('omp-live-'.length) : null;
|
|
169
|
+
const scope = await resolveDiscoveryScope(url.searchParams.get('root'), url.searchParams.get('scope'));
|
|
170
|
+
if (name) {
|
|
171
|
+
await deleteCommandFile(name, scope.workspace);
|
|
172
|
+
await reloadLiveSessions();
|
|
173
|
+
return json({ success: true, commands: await mergeCommands(await commandsStore.read(db), scope) });
|
|
174
|
+
}
|
|
115
175
|
const list = await commandsStore.remove(db, id);
|
|
116
176
|
return json({ success: true, commands: list });
|
|
117
177
|
}
|
|
118
178
|
|
|
119
179
|
if (request.method === 'POST' || request.method === 'PUT') {
|
|
120
180
|
const body = await request.json();
|
|
181
|
+
const scope = await resolveDiscoveryScope(
|
|
182
|
+
url.searchParams.get('root') ?? (typeof body.root === 'string' ? body.root : null),
|
|
183
|
+
url.searchParams.get('scope') ?? (typeof body.scope === 'string' ? body.scope : null),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Only a markdown file the chamber owns is written as a file. A live
|
|
187
|
+
// command from another provider (an extension command, a `.claude`
|
|
188
|
+
// command, a TypeScript custom command) has no file here, and writing one
|
|
189
|
+
// with the same name would SHADOW it — omp resolves a name present in
|
|
190
|
+
// both places to the file. A brand-new draft (no source) is a create.
|
|
191
|
+
const draft = isRecord(body.command) ? (body.command as unknown as CommandItem) : null;
|
|
192
|
+
if (!isMockMode() && draft && (draft.managed === true || (!draft.source && draft.isBuiltIn !== true))) {
|
|
193
|
+
const previousName = typeof body.previousName === 'string' ? body.previousName : null;
|
|
194
|
+
const target = draft.scope === 'project' ? 'project' : 'user';
|
|
195
|
+
await writeCommandFile({
|
|
196
|
+
scope: target,
|
|
197
|
+
projectDir: scope.workspace,
|
|
198
|
+
previousName,
|
|
199
|
+
name: draft.name,
|
|
200
|
+
description: draft.description ?? '',
|
|
201
|
+
argumentHint: draft.inputHint ?? '',
|
|
202
|
+
body: draft.template ?? '',
|
|
203
|
+
});
|
|
204
|
+
await reloadLiveSessions();
|
|
205
|
+
return json({ success: true, commands: await mergeCommands(await commandsStore.read(db), scope) });
|
|
206
|
+
}
|
|
207
|
+
|
|
121
208
|
const updatedCommands = await commandsStore.upsert(db, body);
|
|
122
209
|
await commandsStore.write(db, updatedCommands);
|
|
123
210
|
return json({ success: true, commands: updatedCommands });
|
|
124
211
|
}
|
|
125
212
|
|
|
126
213
|
return methodNotAllowed({ request, params });
|
|
127
|
-
} catch (error
|
|
128
|
-
return json({ error: error
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return json({ error: errorMessage(error) }, { status: 500 });
|
|
129
216
|
}
|
|
130
217
|
}
|