@alisio/server 0.9.0 → 0.10.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.
Files changed (64) hide show
  1. package/dist/index.d.ts +25 -0
  2. package/dist/index.js +40 -2
  3. package/dist/plugins/catalog-schema.d.ts +37 -0
  4. package/dist/plugins/catalog-schema.js +141 -0
  5. package/dist/plugins/catalog-service.d.ts +89 -0
  6. package/dist/plugins/catalog-service.js +306 -0
  7. package/dist/plugins/jobs.d.ts +41 -0
  8. package/dist/plugins/jobs.js +94 -0
  9. package/dist/plugins/sdk-version.d.ts +6 -0
  10. package/dist/plugins/sdk-version.js +28 -0
  11. package/dist/plugins/staging.d.ts +13 -0
  12. package/dist/plugins/staging.js +48 -0
  13. package/dist/routes/plugin-catalog.d.ts +103 -0
  14. package/dist/routes/plugin-catalog.js +436 -0
  15. package/dist/routes/server-restart.d.ts +7 -0
  16. package/dist/routes/server-restart.js +24 -0
  17. package/dist/web/assets/{AgentPicker-Dnt56QuW.js → AgentPicker-B2dNJHaW.js} +1 -1
  18. package/dist/web/assets/{AgentsModal-w9FC_Qnw.js → AgentsModal-BWrVQpCo.js} +1 -1
  19. package/dist/web/assets/{ArtifactPanel-x0WO1Jrn.js → ArtifactPanel-ZkBV87x7.js} +3 -3
  20. package/dist/web/assets/{BtwPanel-DCnyqvqF.js → BtwPanel-BtIHip-u.js} +1 -1
  21. package/dist/web/assets/{ChangelogDialog-B7_Imxij.js → ChangelogDialog-NBwCG0Bo.js} +1 -1
  22. package/dist/web/assets/{ContextTab-MaeId0Vq.js → ContextTab-Bi8MZwMV.js} +1 -1
  23. package/dist/web/assets/{CopyButton-C701fVen.js → CopyButton-Bh3Dsk-o.js} +1 -1
  24. package/dist/web/assets/{GoalBar-2u4r5hjt.js → GoalBar-DVwQsVtS.js} +1 -1
  25. package/dist/web/assets/{MemoryTab-MDHnMK7j.js → MemoryTab-CjZTbnFG.js} +1 -1
  26. package/dist/web/assets/{PermissionsMenu-Cn5x9qqW.js → PermissionsMenu-B03X019W.js} +1 -1
  27. package/dist/web/assets/{PlanReviewPanel-6Ke6aA-x.js → PlanReviewPanel-CM1uxJVs.js} +1 -1
  28. package/dist/web/assets/{PlanViewer-CNB31hZF.js → PlanViewer-DxAqn6IP.js} +1 -1
  29. package/dist/web/assets/{ProofCard-CGzCT48Z.js → ProofCard-B2nxuxaT.js} +1 -1
  30. package/dist/web/assets/{ScratchTab-DcTzefmZ.js → ScratchTab-BLkjPbJC.js} +1 -1
  31. package/dist/web/assets/SettingsModal-BJj7ELxX.js +3 -0
  32. package/dist/web/assets/{SpreadsheetPanel-SfCpgyiY.js → SpreadsheetPanel-BJLNla1t.js} +1 -1
  33. package/dist/web/assets/{TasksTab-B50lA0Fx.js → TasksTab-DhKGW5By.js} +1 -1
  34. package/dist/web/assets/{TextPreview-B63NbGfK.js → TextPreview-CcGkaf6g.js} +1 -1
  35. package/dist/web/assets/{TrajectoryTab-D2E8DaIt.js → TrajectoryTab-U37sw2D0.js} +1 -1
  36. package/dist/web/assets/{agents.module-CrFdY3Vj.js → agents.module-C5jXUIc1.js} +1 -1
  37. package/dist/web/assets/{app-BH2h7887.js → app-CGSX2FCK.js} +3 -3
  38. package/dist/web/assets/{es-sJS5auEJ.js → es-DP4ejr3u.js} +1 -1
  39. package/dist/web/assets/{goal-WebPe6na.js → goal-Cib1wj1f.js} +1 -1
  40. package/dist/web/assets/{i18n-CNPXQ8kr.js → i18n-DqOZSMVh.js} +2 -2
  41. package/dist/web/assets/{icons-D2ozMmbS.js → icons-BZSZMbqh.js} +1 -1
  42. package/dist/web/assets/{index-BF8cv-HX.js → index-CSQo6Bhi.js} +6 -6
  43. package/dist/web/assets/{jsxRuntime-BwIKcin2.js → jsxRuntime-BWhXelbu.js} +1 -1
  44. package/dist/web/assets/{panel-DF57sVn-.js → panel-IZ40pPYp.js} +1 -1
  45. package/dist/web/assets/settings-BAW93knU.css +1 -0
  46. package/dist/web/assets/settings.module-ErkNzIk4.js +1 -0
  47. package/dist/web/assets/{shared-DM5LTOJl.js → shared-C2fqplXu.js} +1 -1
  48. package/dist/web/assets/{strings-CNoJKZqb.js → strings-BUuRkgRy.js} +1 -1
  49. package/dist/web/assets/{tasks-D13Pmq9A.js → tasks-Bw0p2a6l.js} +1 -1
  50. package/dist/web/assets/{view-UpLwcG_p.js → view-B95Eg9o2.js} +1 -1
  51. package/dist/web/assets/view-BeVPW5cE.js +1 -0
  52. package/dist/web/assets/{view-B-6zUlTX.js → view-BkR3NRaK.js} +1 -1
  53. package/dist/web/assets/{view-BeUe5oNy.js → view-C99qRBgc.js} +1 -1
  54. package/dist/web/assets/{view-CfyBjrdk.js → view-CI66xheW.js} +1 -1
  55. package/dist/web/assets/{view-BM0IB6Wb.js → view-CSAgn7-g.js} +1 -1
  56. package/dist/web/assets/{view-1mNrG8x0.js → view-DJAfpDXs.js} +1 -1
  57. package/dist/web/assets/{view-JquKQHkw.js → view-DrHx2F_3.js} +1 -1
  58. package/dist/web/assets/{view-CTmPqVpP.js → view-Du7qfIhE.js} +1 -1
  59. package/dist/web/index.html +7 -7
  60. package/package.json +4 -4
  61. package/dist/web/assets/SettingsModal-BEuZITsr.js +0 -1
  62. package/dist/web/assets/settings-DdWABXsT.css +0 -1
  63. package/dist/web/assets/settings.module-BClxMWCR.js +0 -1
  64. package/dist/web/assets/view-vG-2EBMR.js +0 -1
@@ -0,0 +1,436 @@
1
+ /**
2
+ * Plugin catalog routes: browse the published catalog (cached, revalidated on demand), proxy its
3
+ * covers, install a catalog plugin, uninstall a globally installed npm plugin and list the
4
+ * installed packages. Installing runs npm with the user's privileges and executes third-party
5
+ * code when the plugin loads: it needs an explicit `confirmed: true`, only catalog packages are
6
+ * accepted, and it is refused under `--read-only`. Types are local: the SDK contract is untouched.
7
+ */
8
+ import { createWriteStream } from "node:fs";
9
+ import { stat } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+ import { catalogPluginState, comparePluginVersions, configHome, inspectPluginTarball, installedGlobalPlugins, installLocalPluginTarball, installPlugin, MAX_TARBALL_BYTES, NPM_NOT_FOUND_MESSAGE, parsePluginSpec, pluginCodeImportedFrom, pluginDependencySpec, satisfiesRange, uninstallPlugin, } from "@alisio/core";
12
+ import { readJson } from "../http/body.js";
13
+ import { HttpError } from "../http/errors.js";
14
+ import { CatalogUnavailableError, } from "../plugins/catalog-service.js";
15
+ import { InstallJobs } from "../plugins/jobs.js";
16
+ import { createStaging, isStagingId, removeStaging, stagingFile, sweepStaging, } from "../plugins/staging.js";
17
+ import { is, validate } from "../schemas.js";
18
+ import { workspaceApp } from "./management.js";
19
+ const COVER_CSP = "default-src 'none'; style-src 'unsafe-inline'; sandbox";
20
+ const SLUG = /^[a-z0-9][a-z0-9._-]{0,63}$/;
21
+ /** npm's answer for a version the registry does not have. */
22
+ const NOT_PUBLISHED = /\bETARGET\b|No matching version found/;
23
+ const PEER_CONFLICT = /ERESOLVE|Conflicting peer|require an older/;
24
+ const MAX_ERROR_CHARS = 1500;
25
+ /** How the uploaded version relates to the installed one. */
26
+ function relationOf(uploaded, installed) {
27
+ const order = uploaded ? comparePluginVersions(uploaded, installed) : undefined;
28
+ return order === undefined || order === 0 ? "same" : order > 0 ? "update" : "downgrade";
29
+ }
30
+ const megabytes = (bytes) => Math.round((bytes / (1024 * 1024)) * 10) / 10;
31
+ /** Streams the request body to `file`, failing as soon as it passes `max` bytes. */
32
+ async function receive(req, file, max) {
33
+ const out = createWriteStream(file, { mode: 0o600 });
34
+ let size = 0;
35
+ try {
36
+ for await (const chunk of req) {
37
+ size += chunk.length;
38
+ if (size > max)
39
+ throw new HttpError("validation_failed", `The file is larger than ${megabytes(max)} MB`);
40
+ if (!out.write(chunk))
41
+ await new Promise((resolve) => out.once("drain", resolve));
42
+ }
43
+ }
44
+ finally {
45
+ await new Promise((resolve) => out.end(resolve));
46
+ }
47
+ }
48
+ const errorText = (error) => (error instanceof Error ? error.message : String(error));
49
+ export function registerPluginCatalogRoutes(router, ctx, recycler, options) {
50
+ const { service } = options;
51
+ let mutating = false;
52
+ const jobs = new InstallJobs(options.jobTtlMs);
53
+ /** Packages updated on disk whose old code this process still runs (cleared by a restart). */
54
+ const pendingRestart = new Set();
55
+ const codeLoaded = (name) => pluginCodeImportedFrom(join(configHome(), "plugins", "node_modules", name));
56
+ const maxUpload = options.maxUploadBytes ?? MAX_TARBALL_BYTES;
57
+ void sweepStaging().catch(() => { });
58
+ const loadCatalog = async (refresh) => {
59
+ try {
60
+ return await service.get(refresh);
61
+ }
62
+ catch (error) {
63
+ if (error instanceof CatalogUnavailableError)
64
+ throw new HttpError("provider_unavailable", `The plugin catalog could not be loaded and there is no cached copy: ${error.message}`);
65
+ throw error;
66
+ }
67
+ };
68
+ const refuseReadOnly = (what) => {
69
+ if (ctx.base.readOnly)
70
+ throw new HttpError("capability_ceiling", `${what} is unavailable under --read-only`);
71
+ };
72
+ /** Installs and uninstalls never overlap: they share one npm prefix and one config file. */
73
+ const exclusive = async (task) => {
74
+ if (mutating)
75
+ throw new HttpError("runs_active", "Another plugin install or uninstall is in progress: wait for it to finish, then retry");
76
+ mutating = true;
77
+ try {
78
+ return await task();
79
+ }
80
+ finally {
81
+ mutating = false;
82
+ }
83
+ };
84
+ /** Recycles every open workspace so it re-reads the plugin set; true when any was deferred. */
85
+ const applyToWorkspaces = async () => {
86
+ let deferred = false;
87
+ for (const entry of ctx.workspaces.entries()) {
88
+ const reopened = await recycler.request(entry.id);
89
+ if (!reopened)
90
+ deferred = true;
91
+ recycler.announce(entry.id, ["plugins", "commands"]);
92
+ }
93
+ return deferred;
94
+ };
95
+ const toWire = (plugin, installed) => {
96
+ const { coverSource, ...rest } = plugin;
97
+ const state = catalogPluginState({
98
+ catalogVersion: plugin.version,
99
+ ...(installed.has(plugin.package) ? { installedVersion: installed.get(plugin.package) } : {}),
100
+ ...(plugin.sdk ? { sdkRange: plugin.sdk } : {}),
101
+ ...(options.sdkVersion ? { sdkVersion: options.sdkVersion } : {}),
102
+ });
103
+ return {
104
+ ...rest,
105
+ ...(coverSource
106
+ ? { cover: `/api/plugin-catalog/covers/${encodeURIComponent(plugin.slug)}` }
107
+ : {}),
108
+ ...(state.installedVersion ? { installedVersion: state.installedVersion } : {}),
109
+ updateAvailable: state.updateAvailable,
110
+ compatible: state.compatible,
111
+ };
112
+ };
113
+ // ---- Catalog ------------------------------------------------------------------------------
114
+ router.get("/api/plugin-catalog", async ({ url }) => {
115
+ const raw = url.searchParams.get("refresh");
116
+ if (raw !== null && raw !== "0" && raw !== "1" && raw !== "")
117
+ throw new HttpError("validation_failed", "Invalid query parameter", { fields: ["refresh"] });
118
+ const result = await loadCatalog(raw === "1");
119
+ const installed = await installedGlobalPlugins(configHome());
120
+ const versions = new Map(installed.map((item) => [item.name, item.version]));
121
+ const body = {
122
+ catalog: {
123
+ ...(result.catalog.generatedAt ? { generatedAt: result.catalog.generatedAt } : {}),
124
+ categories: result.catalog.categories,
125
+ plugins: result.catalog.plugins.map((plugin) => toWire(plugin, versions)),
126
+ },
127
+ meta: result.meta,
128
+ installed: installed.map((item) => ({ package: item.name, version: item.version })),
129
+ ...(options.sdkVersion ? { sdkVersion: options.sdkVersion } : {}),
130
+ };
131
+ return { body };
132
+ });
133
+ router.get("/api/plugin-catalog/covers/:slug", async ({ req, res, params }) => {
134
+ const slug = params.slug ?? "";
135
+ if (!SLUG.test(slug))
136
+ throw new HttpError("not_found", "Cover not found");
137
+ const result = await loadCatalog(false);
138
+ const plugin = result.catalog.plugins.find((entry) => entry.slug === slug);
139
+ if (!plugin?.coverSource)
140
+ throw new HttpError("not_found", "Cover not found");
141
+ const cover = await service.cover(plugin, result.meta.fetchedAt);
142
+ if (!cover)
143
+ throw new HttpError("not_found", "Cover not found");
144
+ res.setHeader("Content-Security-Policy", COVER_CSP);
145
+ res.setHeader("X-Content-Type-Options", "nosniff");
146
+ res.setHeader("Cache-Control", "private, max-age=3600");
147
+ res.setHeader("ETag", cover.etag);
148
+ if (req.headers["if-none-match"] === cover.etag) {
149
+ res.writeHead(304);
150
+ res.end();
151
+ return undefined;
152
+ }
153
+ res.writeHead(200, {
154
+ "Content-Type": cover.contentType,
155
+ "Content-Length": String(cover.body.length),
156
+ });
157
+ res.end(cover.body);
158
+ return undefined;
159
+ });
160
+ // ---- Installed packages -------------------------------------------------------------------
161
+ router.get("/api/plugins/packages", async ({ url }) => {
162
+ const opened = await workspaceApp(ctx.workspaces, url.searchParams.get("workspace"));
163
+ const loaded = opened.app.pluginCatalog();
164
+ // Update info comes from the cached catalog: no network unless the cache is stale.
165
+ const catalog = await service.get(false).then((result) => result.catalog, () => undefined);
166
+ const pluginsDir = join(configHome(), "plugins");
167
+ const body = [];
168
+ for (const item of await installedGlobalPlugins(configHome())) {
169
+ const local = (await pluginDependencySpec(pluginsDir, item.name))?.startsWith("file:") ?? false;
170
+ const entry = local ? undefined : catalog?.plugins.find((p) => p.package === item.name);
171
+ const state = entry
172
+ ? catalogPluginState({
173
+ catalogVersion: entry.version,
174
+ installedVersion: item.version,
175
+ ...(entry.sdk ? { sdkRange: entry.sdk } : {}),
176
+ ...(options.sdkVersion ? { sdkVersion: options.sdkVersion } : {}),
177
+ })
178
+ : undefined;
179
+ body.push({
180
+ package: item.name,
181
+ version: item.version,
182
+ pluginIds: loaded.filter((plugin) => plugin.spec === item.name).map((plugin) => plugin.id),
183
+ removable: !ctx.base.readOnly,
184
+ restartRequired: pendingRestart.has(item.name),
185
+ local,
186
+ ...(entry ? { latestVersion: entry.version } : {}),
187
+ updateAvailable: state?.updateAvailable ?? false,
188
+ updateCompatible: state?.compatible ?? true,
189
+ });
190
+ }
191
+ return { body };
192
+ });
193
+ // ---- Install ------------------------------------------------------------------------------
194
+ router.post("/api/plugins/install", async ({ req }) => {
195
+ const input = validate(await readJson(req), {
196
+ package: { check: is.nonEmpty(214), required: true },
197
+ version: { check: is.nonEmpty(64) },
198
+ confirmed: { check: (v) => v === true, required: true },
199
+ });
200
+ refuseReadOnly("Installing plugins");
201
+ const { catalog } = await loadCatalog(false);
202
+ const entry = catalog.plugins.find((plugin) => plugin.package === input.package);
203
+ if (!entry)
204
+ throw new HttpError("validation_failed", "Only plugins listed in the catalog can be installed here", {
205
+ fields: ["package"],
206
+ });
207
+ if (input.version !== undefined && input.version !== entry.version)
208
+ throw new HttpError("validation_failed", `Only the catalog version (${entry.version}) can be installed here`, { fields: ["version"] });
209
+ const installed = new Map((await installedGlobalPlugins(configHome())).map((item) => [item.name, item.version]));
210
+ const state = catalogPluginState({
211
+ catalogVersion: entry.version,
212
+ ...(installed.has(entry.package) ? { installedVersion: installed.get(entry.package) } : {}),
213
+ ...(entry.sdk ? { sdkRange: entry.sdk } : {}),
214
+ ...(options.sdkVersion ? { sdkVersion: options.sdkVersion } : {}),
215
+ });
216
+ if (!state.compatible)
217
+ throw new HttpError("validation_failed", `${entry.name} needs @alisio/sdk ${entry.sdk}, but this Alisio has ${options.sdkVersion}`, { fields: ["package"] });
218
+ return exclusive(async () => {
219
+ let result;
220
+ const loaded = codeLoaded(entry.package);
221
+ const attempt = (spec) => installPlugin({
222
+ spec,
223
+ // An installed package is replaced at the catalog version: update, reinstall or downgrade.
224
+ force: installed.has(entry.package),
225
+ configHome: configHome(),
226
+ ...(options.installRunner ? { runner: options.installRunner } : {}),
227
+ });
228
+ try {
229
+ try {
230
+ result = await attempt(`npm:${entry.package}@${entry.version}`);
231
+ }
232
+ catch (error) {
233
+ // The catalog can list a version npm does not serve yet (it is generated before the
234
+ // publish finishes): fall back to the registry's latest rather than fail the install.
235
+ if (!NOT_PUBLISHED.test(errorText(error)))
236
+ throw error;
237
+ result = await attempt(`npm:${entry.package}`);
238
+ }
239
+ }
240
+ catch (error) {
241
+ const text = errorText(error).slice(0, MAX_ERROR_CHARS);
242
+ if (text.includes(NPM_NOT_FOUND_MESSAGE) || PEER_CONFLICT.test(text))
243
+ throw new HttpError("validation_failed", text);
244
+ throw new HttpError("provider_unavailable", text);
245
+ }
246
+ const restartRequired = loaded;
247
+ if (restartRequired)
248
+ pendingRestart.add(result.packageName);
249
+ const deferred = await applyToWorkspaces();
250
+ const body = {
251
+ package: result.packageName,
252
+ installedVersion: result.installedVersion,
253
+ alreadyInstalled: result.alreadyInstalled,
254
+ updated: result.updated,
255
+ ...(result.previousVersion ? { previousVersion: result.previousVersion } : {}),
256
+ restartPending: deferred,
257
+ restartRequired,
258
+ };
259
+ return { body };
260
+ });
261
+ });
262
+ // ---- Install from a local file --------------------------------------------------------------
263
+ router.post("/api/plugins/local/inspect", async ({ req }) => {
264
+ refuseReadOnly("Installing plugins");
265
+ const type = String(req.headers["content-type"] ?? "")
266
+ .split(";")[0]
267
+ ?.trim();
268
+ if (!["application/gzip", "application/x-gzip", "application/octet-stream"].includes(type ?? ""))
269
+ throw new HttpError("unsupported_media_type", "Send the .tgz as the raw body with Content-Type application/gzip");
270
+ const declared = Number(req.headers["content-length"] ?? Number.NaN);
271
+ if (Number.isFinite(declared) && declared > maxUpload)
272
+ throw new HttpError("validation_failed", `The file is larger than ${megabytes(maxUpload)} MB`);
273
+ await sweepStaging().catch(() => 0);
274
+ const staging = await createStaging();
275
+ try {
276
+ await receive(req, staging.file, maxUpload);
277
+ const info = await inspectPluginTarball(staging.file);
278
+ const installed = info.name
279
+ ? (await installedGlobalPlugins(configHome())).find((item) => item.name === info.name)
280
+ : undefined;
281
+ const compatible = !info.sdkRange || !options.sdkVersion
282
+ ? true
283
+ : satisfiesRange(options.sdkVersion, info.sdkRange);
284
+ const body = {
285
+ ...(info.isAlisioPlugin ? { stagingId: staging.id } : {}),
286
+ ...(info.name ? { package: info.name } : {}),
287
+ ...(info.version ? { version: info.version } : {}),
288
+ ...(info.description ? { description: info.description } : {}),
289
+ ...(info.author ? { author: info.author } : {}),
290
+ ...(info.license ? { license: info.license } : {}),
291
+ ...(info.sdkRange ? { sdkRange: info.sdkRange } : {}),
292
+ compatible,
293
+ isAlisioPlugin: info.isAlisioPlugin,
294
+ reasons: info.reasons,
295
+ lifecycleScripts: info.lifecycleScripts,
296
+ ...(installed
297
+ ? {
298
+ installed: {
299
+ version: installed.version,
300
+ relation: relationOf(info.version, installed.version),
301
+ },
302
+ }
303
+ : {}),
304
+ size: info.size,
305
+ };
306
+ // Nothing to install from an invalid file: it does not stay on disk.
307
+ if (!info.isAlisioPlugin)
308
+ await removeStaging(staging.id);
309
+ return { body };
310
+ }
311
+ catch (error) {
312
+ await removeStaging(staging.id);
313
+ throw error;
314
+ }
315
+ });
316
+ router.delete("/api/plugins/local/staged/:stagingId", async ({ params }) => {
317
+ const id = params.stagingId ?? "";
318
+ if (!isStagingId(id))
319
+ throw new HttpError("validation_failed", "Invalid upload id", { fields: ["stagingId"] });
320
+ await removeStaging(id);
321
+ return { body: { removed: true } };
322
+ });
323
+ router.post("/api/plugins/local/install", async ({ req }) => {
324
+ const input = validate(await readJson(req), {
325
+ stagingId: { check: is.nonEmpty(64), required: true },
326
+ confirmed: { check: (v) => v === true, required: true },
327
+ });
328
+ refuseReadOnly("Installing plugins");
329
+ if (!isStagingId(input.stagingId))
330
+ throw new HttpError("validation_failed", "Invalid upload id", { fields: ["stagingId"] });
331
+ const file = stagingFile(input.stagingId);
332
+ if (!(await stat(file).then(() => true, () => false)))
333
+ throw new HttpError("not_found", "The upload was not found or has expired: select the file again");
334
+ const info = await inspectPluginTarball(file);
335
+ if (!info.isAlisioPlugin) {
336
+ await removeStaging(input.stagingId);
337
+ throw new HttpError("validation_failed", `Not a valid Alisio plugin package: ${info.reasons.join("; ")}`);
338
+ }
339
+ if (mutating)
340
+ throw new HttpError("runs_active", "Another plugin install or uninstall is in progress: wait for it to finish, then retry");
341
+ mutating = true;
342
+ const job = jobs.create();
343
+ job.start("uploading");
344
+ void (async () => {
345
+ const loaded = info.name ? codeLoaded(info.name) : false;
346
+ try {
347
+ const result = await installLocalPluginTarball({
348
+ tarballPath: file,
349
+ configHome: configHome(),
350
+ ...(options.installRunner ? { runner: options.installRunner } : {}),
351
+ onPhase: (phase) => job.start(phase),
352
+ });
353
+ job.start("reloading");
354
+ if (loaded)
355
+ pendingRestart.add(result.packageName);
356
+ const deferred = await applyToWorkspaces();
357
+ job.start("cleanup");
358
+ await removeStaging(input.stagingId);
359
+ const body = {
360
+ package: result.packageName,
361
+ installedVersion: result.installedVersion,
362
+ alreadyInstalled: result.alreadyInstalled,
363
+ updated: result.updated,
364
+ ...(result.previousVersion ? { previousVersion: result.previousVersion } : {}),
365
+ restartPending: deferred,
366
+ restartRequired: loaded,
367
+ };
368
+ job.succeed(body);
369
+ }
370
+ catch (error) {
371
+ await removeStaging(input.stagingId).catch(() => { });
372
+ const text = errorText(error).slice(0, MAX_ERROR_CHARS);
373
+ const tail = error.logTail;
374
+ job.fail(text, tail);
375
+ }
376
+ finally {
377
+ mutating = false;
378
+ }
379
+ })();
380
+ return { status: 202, body: { jobId: job.id } };
381
+ });
382
+ router.get("/api/plugins/jobs/:jobId", ({ params }) => {
383
+ const job = jobs.get(params.jobId ?? "");
384
+ if (!job)
385
+ throw new HttpError("not_found", "Job not found or expired");
386
+ return { body: job.view() };
387
+ });
388
+ // ---- Uninstall ----------------------------------------------------------------------------
389
+ router.delete("/api/plugins/package/:name", async ({ params }) => {
390
+ const name = params.name ?? "";
391
+ let parsed;
392
+ try {
393
+ parsed = parsePluginSpec(name);
394
+ if (parsed.version !== undefined || parsed.name !== name)
395
+ throw new Error("not a bare name");
396
+ }
397
+ catch {
398
+ throw new HttpError("validation_failed", "Invalid package name", { fields: ["name"] });
399
+ }
400
+ refuseReadOnly("Uninstalling plugins");
401
+ const builtin = (ctx.base.builtins ?? []).some((plugin) => name === plugin.id || name === `@alisio/plugin-${plugin.id}`);
402
+ if (builtin)
403
+ throw new HttpError("not_manageable", "Built-in plugins cannot be uninstalled");
404
+ const installed = await installedGlobalPlugins(configHome());
405
+ if (!installed.some((item) => item.name === name)) {
406
+ const project = ctx.workspaces
407
+ .entries()
408
+ .some((entry) => entry.app.pluginCatalog().some((plugin) => plugin.spec === name));
409
+ if (project)
410
+ throw new HttpError("not_manageable", "This plugin is not installed in the global plugins directory: remove it from your project configuration");
411
+ throw new HttpError("not_found", "Plugin package is not installed");
412
+ }
413
+ return exclusive(async () => {
414
+ let removed;
415
+ try {
416
+ removed = await uninstallPlugin({
417
+ packageName: name,
418
+ configHome: configHome(),
419
+ ...(options.installRunner ? { runner: options.installRunner } : {}),
420
+ });
421
+ }
422
+ catch (error) {
423
+ const text = errorText(error).slice(0, MAX_ERROR_CHARS);
424
+ throw new HttpError(text.includes(NPM_NOT_FOUND_MESSAGE) ? "validation_failed" : "provider_unavailable", text);
425
+ }
426
+ pendingRestart.delete(name);
427
+ const deferred = await applyToWorkspaces();
428
+ const body = {
429
+ package: removed.packageName,
430
+ removed: removed.removed,
431
+ restartPending: deferred,
432
+ };
433
+ return { body };
434
+ });
435
+ });
436
+ }
@@ -0,0 +1,7 @@
1
+ import type { Router } from "../http/router.ts";
2
+ export interface RestartRouteOptions {
3
+ /** Present only when a supervisor will respawn this process. */
4
+ restart?: () => void;
5
+ readOnly?: boolean;
6
+ }
7
+ export declare function registerRestartRoutes(router: Router, options: RestartRouteOptions): void;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `GET|POST /api/server/restart`: lets the UI restart the host when a supervisor (the desktop app)
3
+ * respawns it. An update of a plugin that is already loaded only takes effect in a new process,
4
+ * because Node keeps imported modules for the process lifetime. Without a supervisor nothing is
5
+ * exposed beyond `available: false`: a bare `alisio serve` must be restarted by its owner.
6
+ */
7
+ import { readJson } from "../http/body.js";
8
+ import { HttpError } from "../http/errors.js";
9
+ import { validate } from "../schemas.js";
10
+ export function registerRestartRoutes(router, options) {
11
+ router.get("/api/server/restart", () => ({
12
+ body: { available: !!options.restart && !options.readOnly },
13
+ }));
14
+ router.post("/api/server/restart", async ({ req, res }) => {
15
+ validate(await readJson(req), { confirmed: { check: (v) => v === true, required: true } });
16
+ if (options.readOnly)
17
+ throw new HttpError("capability_ceiling", "Restarting is unavailable under --read-only");
18
+ if (!options.restart)
19
+ throw new HttpError("not_manageable", "This server has no supervisor to restart it: restart `alisio serve` yourself");
20
+ const restart = options.restart;
21
+ res.once("finish", () => restart());
22
+ return { body: { restarting: true } };
23
+ });
24
+ }
@@ -1 +1 @@
1
- import{a as e,b as t,g as n,y as r}from"./i18n-CNPXQ8kr.js";import{a as i,d as a,j as o,o as s,t as c}from"./app-BH2h7887.js";import{t as l}from"./jsxRuntime-BwIKcin2.js";import{t as u}from"./icons-D2ozMmbS.js";import{b as d,t as f,u as p}from"./agents.module-CrFdY3Vj.js";import{t as m}from"./settings.module-BClxMWCR.js";import{a as h,c as g,i as _,l as v,r as y}from"./shared-DM5LTOJl.js";function b(){let b=g(),x=v(b?()=>a.agents(b.id):void 0,[b?.id],[`agents`]),S=v(()=>a.agentDefinitions(b?.id),[b?.id],[`agents`]),[C,w]=r(``),[T,E]=r(0),D=n(null);t(()=>{let e=document.activeElement;return D.current?.querySelector(`input`)?.focus(),()=>e?.focus()},[]);let O=()=>{i.value=!1},k=o.value?.agent??x.data?.find(e=>e.default)?.id,A=p(x.data??[],C),j=e=>{O(),c(e.id)},M=t=>{let n=d(t,S.data?.agents??[]);return n?l(y,{tone:n===`project`?`ok`:`off`,children:e(`agentsWin.scope.${n}`)}):l(y,{tone:`off`,children:t.source===`builtin`?e(`agentsWin.builtin`):e(`agentsWin.plugin`)})};return l(`div`,{class:m.backdrop,onMouseDown:e=>{e.target===e.currentTarget&&O()},children:l(`div`,{ref:D,class:`${m.dialog} ${f.picker}`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`agent-picker-title`,onKeyDown:e=>{if(e.key===`Escape`)e.preventDefault(),O();else if(e.key===`ArrowDown`||e.key===`ArrowUp`){e.preventDefault();let t=e.key===`ArrowDown`?1:-1;E(e=>A.length?(e+t+A.length)%A.length:0)}else if(e.key===`Enter`&&e.target.tagName===`INPUT`){e.preventDefault();let t=A[Math.min(T,A.length-1)];t&&j(t)}},children:[l(`div`,{class:f.pickerHead,children:[l(`div`,{class:f.listHead,children:[l(`div`,{children:[l(`h3`,{class:m.pageTitle,id:`agent-picker-title`,children:e(`agentsWin.picker`)}),l(`p`,{class:m.note,children:e(`agentsWin.pickerLead`)})]}),l(`button`,{type:`button`,class:`icon-btn`,"aria-label":e(`common.close`),onClick:O,children:l(u,{name:`x`,size:18})})]}),l(_,{value:C,label:e(`agentsWin.pickerSearch`),onInput:e=>{w(e),E(0)}})]}),l(`div`,{style:{padding:`0 18px`},children:l(h,{loading:x.loading&&!x.data,error:x.error,empty:b?void 0:`settings.noWorkspace`})}),l(`ul`,{class:f.pickerList,"aria-label":e(`agentsWin.picker`),children:[x.data&&!A.length?l(`li`,{class:m.note,children:e(`agentsWin.pickerEmpty`,{query:C})}):null,A.map((t,n)=>l(`li`,{children:l(`button`,{type:`button`,class:f.pickerItem,"data-selected":n===T,"aria-current":t.id===k?`true`:void 0,onMouseEnter:()=>E(n),onClick:()=>j(t),children:[l(`span`,{class:f.pickerTitle,children:[t.name,M(t),t.id===k?l(y,{tone:`ok`,children:e(`agentsWin.current`)}):null,t.id===`build`?l(y,{tone:`off`,children:e(`agentsWin.defaultAgent`)}):null,t.readOnly?l(y,{tone:`warn`,children:e(`agents.readOnly`)}):null]}),l(`span`,{class:m.cardText,children:t.description})]})},t.id)),l(`li`,{children:l(`button`,{type:`button`,class:f.pickerItem,onClick:()=>{O(),s.value=!0},children:l(`span`,{class:f.pickerTitle,children:[l(u,{name:`users`,size:15}),e(`agentsWin.manage`)]})})})]})]})})}export{b as AgentPicker};
1
+ import{a as e,b as t,g as n,y as r}from"./i18n-DqOZSMVh.js";import{a as i,d as a,j as o,o as s,t as c}from"./app-CGSX2FCK.js";import{t as l}from"./jsxRuntime-BWhXelbu.js";import{t as u}from"./icons-BZSZMbqh.js";import{b as d,t as f,u as p}from"./agents.module-C5jXUIc1.js";import{t as m}from"./settings.module-ErkNzIk4.js";import{a as h,c as g,i as _,l as v,r as y}from"./shared-C2fqplXu.js";function b(){let b=g(),x=v(b?()=>a.agents(b.id):void 0,[b?.id],[`agents`]),S=v(()=>a.agentDefinitions(b?.id),[b?.id],[`agents`]),[C,w]=r(``),[T,E]=r(0),D=n(null);t(()=>{let e=document.activeElement;return D.current?.querySelector(`input`)?.focus(),()=>e?.focus()},[]);let O=()=>{i.value=!1},k=o.value?.agent??x.data?.find(e=>e.default)?.id,A=p(x.data??[],C),j=e=>{O(),c(e.id)},M=t=>{let n=d(t,S.data?.agents??[]);return n?l(y,{tone:n===`project`?`ok`:`off`,children:e(`agentsWin.scope.${n}`)}):l(y,{tone:`off`,children:t.source===`builtin`?e(`agentsWin.builtin`):e(`agentsWin.plugin`)})};return l(`div`,{class:m.backdrop,onMouseDown:e=>{e.target===e.currentTarget&&O()},children:l(`div`,{ref:D,class:`${m.dialog} ${f.picker}`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`agent-picker-title`,onKeyDown:e=>{if(e.key===`Escape`)e.preventDefault(),O();else if(e.key===`ArrowDown`||e.key===`ArrowUp`){e.preventDefault();let t=e.key===`ArrowDown`?1:-1;E(e=>A.length?(e+t+A.length)%A.length:0)}else if(e.key===`Enter`&&e.target.tagName===`INPUT`){e.preventDefault();let t=A[Math.min(T,A.length-1)];t&&j(t)}},children:[l(`div`,{class:f.pickerHead,children:[l(`div`,{class:f.listHead,children:[l(`div`,{children:[l(`h3`,{class:m.pageTitle,id:`agent-picker-title`,children:e(`agentsWin.picker`)}),l(`p`,{class:m.note,children:e(`agentsWin.pickerLead`)})]}),l(`button`,{type:`button`,class:`icon-btn`,"aria-label":e(`common.close`),onClick:O,children:l(u,{name:`x`,size:18})})]}),l(_,{value:C,label:e(`agentsWin.pickerSearch`),onInput:e=>{w(e),E(0)}})]}),l(`div`,{style:{padding:`0 18px`},children:l(h,{loading:x.loading&&!x.data,error:x.error,empty:b?void 0:`settings.noWorkspace`})}),l(`ul`,{class:f.pickerList,"aria-label":e(`agentsWin.picker`),children:[x.data&&!A.length?l(`li`,{class:m.note,children:e(`agentsWin.pickerEmpty`,{query:C})}):null,A.map((t,n)=>l(`li`,{children:l(`button`,{type:`button`,class:f.pickerItem,"data-selected":n===T,"aria-current":t.id===k?`true`:void 0,onMouseEnter:()=>E(n),onClick:()=>j(t),children:[l(`span`,{class:f.pickerTitle,children:[t.name,M(t),t.id===k?l(y,{tone:`ok`,children:e(`agentsWin.current`)}):null,t.id===`build`?l(y,{tone:`off`,children:e(`agentsWin.defaultAgent`)}):null,t.readOnly?l(y,{tone:`warn`,children:e(`agents.readOnly`)}):null]}),l(`span`,{class:m.cardText,children:t.description})]})},t.id)),l(`li`,{children:l(`button`,{type:`button`,class:f.pickerItem,onClick:()=>{O(),s.value=!0},children:l(`span`,{class:f.pickerTitle,children:[l(u,{name:`users`,size:15}),e(`agentsWin.manage`)]})})})]})]})})}export{b as AgentPicker};
@@ -1,2 +1,2 @@
1
- import{T as e,_ as t,a as n,b as r,c as i,g as a,l as o,y as s}from"./i18n-CNPXQ8kr.js";import{K as c,Ut as l,d as u,gt as d,it as f,o as p,pt as m,vt as h}from"./app-BH2h7887.js";import{t as g}from"./jsxRuntime-BwIKcin2.js";import{t as _}from"./icons-D2ozMmbS.js";import{t as v}from"./CopyButton-C701fVen.js";import{S as y,_ as b,a as x,c as S,d as C,f as ee,g as w,h as T,i as E,l as D,m as te,n as O,o as k,p as ne,r as A,s as re,t as j,v as ie,x as ae,y as oe}from"./agents.module-CrFdY3Vj.js";import{t as M}from"./settings.module-BClxMWCR.js";import{a as N,c as P,l as F,r as I}from"./shared-DM5LTOJl.js";var L=new Set([`minimal`,`low`,`medium`,`high`,`xhigh`]),R=e=>L.has(e)?n(`agentsWin.level.${e}`):e;function se(e){let[t,n]=s(!0);return g(`section`,{class:j.section,children:[g(`button`,{type:`button`,class:j.sectionHead,"aria-expanded":t,onClick:()=>n(!t),children:[g(_,{name:t?`chevronDown`:`chevronRight`,size:15}),e.title]}),t?g(`div`,{class:j.sectionBody,children:e.children}):null]})}function z(e){return g(`label`,{class:j.field,children:[g(`span`,{class:j.fieldLabel,children:[e.label,e.hint?g(`span`,{class:j.fieldHint,children:e.hint}):null]}),e.children,e.error?g(`span`,{class:j.fieldError,children:e.error}):null]})}function ce(e){let t=e.text.split(`
1
+ import{E as e,_ as t,a as n,b as r,c as i,g as a,l as o,y as s}from"./i18n-DqOZSMVh.js";import{K as c,Ut as l,d as u,gt as d,it as f,o as p,pt as m,vt as h}from"./app-CGSX2FCK.js";import{t as g}from"./jsxRuntime-BWhXelbu.js";import{t as _}from"./icons-BZSZMbqh.js";import{t as v}from"./CopyButton-Bh3Dsk-o.js";import{S as y,_ as b,a as x,c as S,d as C,f as ee,g as w,h as T,i as E,l as D,m as te,n as O,o as k,p as ne,r as A,s as re,t as j,v as ie,x as ae,y as oe}from"./agents.module-C5jXUIc1.js";import{t as M}from"./settings.module-ErkNzIk4.js";import{a as N,c as P,l as F,r as I}from"./shared-C2fqplXu.js";var L=new Set([`minimal`,`low`,`medium`,`high`,`xhigh`]),R=e=>L.has(e)?n(`agentsWin.level.${e}`):e;function se(e){let[t,n]=s(!0);return g(`section`,{class:j.section,children:[g(`button`,{type:`button`,class:j.sectionHead,"aria-expanded":t,onClick:()=>n(!t),children:[g(_,{name:t?`chevronDown`:`chevronRight`,size:15}),e.title]}),t?g(`div`,{class:j.sectionBody,children:e.children}):null]})}function z(e){return g(`label`,{class:j.field,children:[g(`span`,{class:j.fieldLabel,children:[e.label,e.hint?g(`span`,{class:j.fieldHint,children:e.hint}):null]}),e.children,e.error?g(`span`,{class:j.fieldError,children:e.error}):null]})}function ce(e){let t=e.text.split(`
2
2
  `);return g(`section`,{class:j.code,"aria-label":n(`agentsWin.config`),children:[g(`div`,{class:j.codeHead,children:[g(`span`,{children:n(`agentsWin.config`)}),g(v,{text:()=>e.text,label:n(`agentsWin.configCopy`)})]}),g(`p`,{class:M.note,style:{margin:`8px 16px 0`},children:n(`agentsWin.configLead`)}),g(`pre`,{class:j.codeBody,children:g(`code`,{children:t.map((e,t)=>g(`span`,{class:j.codeLine,children:[g(`span`,{class:j.lineNo,"aria-hidden":`true`,children:t+1}),g(`span`,{children:y(e).map((e,t)=>g(`span`,{class:j[`tok-${e.kind}`],children:e.text},t))})]},t))})})]})}function le(e){let[t,i]=s(Date.now()),o=a(null);return r(()=>{o.current?.focus();let e=setInterval(()=>i(Date.now()),1e3);return()=>clearInterval(e)},[]),g(`div`,{class:j.overlay,role:`alertdialog`,"aria-modal":`true`,"aria-labelledby":`building-title`,"aria-describedby":`building-body`,onKeyDown:t=>{t.key===`Escape`&&(t.preventDefault(),e.onCancel())},children:g(`div`,{class:j.overlayCard,children:[g(`div`,{class:j.spinner,"aria-hidden":`true`}),g(`h3`,{class:j.overlayTitle,id:`building-title`,children:n(`agentsWin.building`)}),g(`p`,{class:M.note,id:`building-body`,role:`status`,children:n(`agentsWin.buildingBody`,{model:e.model})}),g(`div`,{class:j.progress,"aria-hidden":`true`,children:g(`div`,{class:j.progressBar})}),g(`p`,{class:M.meta,children:n(`agentsWin.buildingElapsed`,{seconds:Math.max(0,Math.round((t-e.started)/1e3))})}),g(`div`,{class:j.overlayActions,children:g(`button`,{ref:o,type:`button`,class:M.secondary,onClick:e.onCancel,children:n(`common.cancel`)})})]})})}function ue(e){let t=a(null);return r(()=>t.current?.focus(),[]),g(`div`,{class:j.overlay,role:`alertdialog`,"aria-modal":`true`,"aria-labelledby":`created-title`,onKeyDown:t=>{t.key===`Escape`&&(t.preventDefault(),e.onStay())},children:g(`div`,{class:j.overlayCard,children:[g(`h3`,{class:j.overlayTitle,id:`created-title`,children:n(`agentsWin.createdTitle`,{name:e.name})}),g(`p`,{class:M.note,children:e.live?n(`agentsWin.createdBody`):n(`agentsWin.createdRestart`)}),g(`div`,{class:j.overlayActions,children:[e.live?g(`button`,{ref:t,type:`button`,class:M.primary,onClick:e.onTry,children:n(`agentsWin.tryIt`)}):null,g(`button`,{ref:e.live?void 0:t,type:`button`,class:M.secondary,onClick:e.onStay,children:n(`agentsWin.stay`)})]})]})})}function B(i){let{workspace:a,models:o}=i,[v,y]=s(i.initial),[D,te]=s(i.saved),[P,I]=s(`setup`),[L,B]=s(``),[V,H]=s(),[U,W]=s(),[de,G]=s(!1),[K,q]=s(),[fe,pe]=s(!b());r(()=>{if(!v.model&&o?.length){let e=o.find(e=>e.active)??o[0];e&&y(t=>C({...t,model:e.reference},e.capabilities))}},[o]);let me=!D||T(v)!==T(D);r(()=>i.onDirty(me&&!!D),[me,D]);let J=re(o,v.model),Y=F(D?.id?()=>u.agentSessions(D.id):void 0,[D?.id],[`agents`]),X=i.overview?.scopes??(a?[`project`,`global`]:[`global`]),Z=e=>y(t=>({...t,...e})),he=t(()=>x(v,{origin:typeof location<`u`?location.origin:`http://127.0.0.1:4096`,...a?{workspace:a.id}:{}}),[v,a?.id]),ge=o?.find(e=>e.active)?.id??`Alisio`,Q=!!v.instructions.trim(),_e=async e=>{G(!0);try{let t={...w(e),scope:e.scope,...a?{workspace:a.id}:{}},r=e.id?await u.updateAgent(e.id,t):await u.createAgent(t),o=ee(r.agent);y(o),te(o),i.onSaved(o),e.scope===`project`&&f(),d(n(r.live?`agentsWin.saved`:`agentsWin.savedRestart`)),e.id||q({form:o,live:r.live})}catch(e){d(n(`agentsWin.saveFailed`,{message:l(e)}))}finally{G(!1)}},ve=async()=>{if(!a)return;let e=new AbortController;W(void 0),H({controller:e,started:Date.now()});let t;try{let r=await u.draftAgent({workspace:a.id,description:L.trim(),...Q?{base:{name:v.name,description:v.description,instructions:v.instructions}}:{}},e.signal);t=C(ne(v,r),J),y(t),B(``),d(n(`agentsWin.buildDone`,{model:r.generatedBy,guidance:r.guidance}))}catch(t){e.signal.aborted||W(l(t))}finally{H(void 0)}t&&!t.id&&k(t,void 0)&&await _e(t)},$=oe(v.name),ye=ie({saved:!!D?.id,workspace:!!a,sessions:Y.data?.items??[]}),be={define:[`agentsWin.step.define`,`agentsWin.step.defineBody`],runtime:[`agentsWin.step.runtime`,`agentsWin.step.runtimeBody`],session:[`agentsWin.step.session`,`agentsWin.step.sessionBody`],events:[`agentsWin.step.events`,`agentsWin.step.eventsBody`]},xe=async e=>{a&&e.id&&await h(a.id,e.id,e.model||void 0)&&(p.value=!1)};return g(`div`,{style:{position:`relative`},children:[g(`div`,{class:M.tabs,role:`tablist`,"aria-label":n(`agentsWin.title`),children:[`setup`,`sessions`].map(e=>g(`button`,{type:`button`,role:`tab`,class:M.tab,"aria-selected":P===e,onClick:()=>I(e),children:n(`agentsWin.${e}`)},e))}),P===`sessions`?g(`div`,{role:`tabpanel`,children:D?.id?g(e,{children:[g(N,{loading:Y.loading&&!Y.data,error:Y.error}),Y.data&&!Y.data.items.length?g(`p`,{class:M.note,children:n(`agentsWin.sessionsEmpty`)}):null,g(`ul`,{class:M.rows,children:(Y.data?.items??[]).map(e=>g(`li`,{class:M.row,children:g(`button`,{type:`button`,class:j.rowButton,onClick:()=>{p.value=!1,c(e.id)},children:[g(`span`,{class:M.cardName,children:e.title??n(`agentsWin.untitled`)}),g(`p`,{class:M.meta,children:[e.model,` · `,e.workspace]})]})},e.id))}),a?g(`button`,{type:`button`,class:`${M.primary} ${M.addButton}`,onClick:()=>void xe(D),children:n(`agentsWin.startSession`)}):g(`p`,{class:M.note,children:n(`agentsWin.noWorkspace`)})]}):g(`p`,{class:M.note,children:n(`agentsWin.sessionsUnsaved`)})}):g(`div`,{class:j.columns,role:`tabpanel`,children:[g(`div`,{class:j.column,children:[a?g(`section`,{class:j.assist,"aria-labelledby":`assist-title`,children:[g(`h4`,{class:j.assistTitle,id:`assist-title`,children:[g(_,{name:`sparkle`,size:16}),n(Q?`agentsWin.assistRefine`:`agentsWin.assistTitle`)]}),g(`p`,{class:M.note,children:n(Q?`agentsWin.assistRefineLead`:`agentsWin.assistLead`)}),g(`textarea`,{class:j.control,style:{minHeight:`64px`},value:L,placeholder:n(`agentsWin.assistPlaceholder`),"aria-label":n(`agentsWin.assistTitle`),maxLength:2e3,onInput:e=>B(e.target.value)}),U?g(`p`,{class:M.errorText,role:`alert`,children:n(`agentsWin.buildFailed`,{message:U})}):null,g(`div`,{class:j.assistActions,children:g(`button`,{type:`button`,class:M.primary,disabled:!!V||!Q&&!L.trim(),onClick:()=>void ve(),children:n(Q?`agentsWin.assistRefine`:`agentsWin.assistCreate`)})})]}):null,g(se,{title:n(`agentsWin.definition`),children:[g(z,{label:n(`agentsWin.name`),error:$?n(`agentsWin.nameError.${$}`):void 0,children:g(`input`,{class:j.control,value:v.name,maxLength:80,"aria-invalid":$?`true`:void 0,onInput:e=>Z({name:e.target.value})})}),g(z,{label:n(`agentsWin.description`),children:g(`input`,{class:j.control,value:v.description,maxLength:300,placeholder:n(`agentsWin.descriptionPlaceholder`),onInput:e=>Z({description:e.target.value})})}),g(z,{label:n(`agentsWin.instructions`),hint:`${v.instructions.length} / 24000`,children:g(`textarea`,{class:j.control,value:v.instructions,maxLength:24e3,placeholder:n(`agentsWin.instructionsPlaceholder`),onInput:e=>Z({instructions:e.target.value})})}),!v.id&&X.length>1?g(z,{label:n(`agentsWin.scope`),hint:n(`agentsWin.scopeHint`),children:g(`select`,{class:j.control,value:v.scope,title:i.overview?.dirs[v.scope]??``,onChange:e=>Z({scope:e.target.value}),children:X.map(e=>g(`option`,{value:e,title:i.overview?.dirs[e]??``,children:[n(`agentsWin.scope.${e}`),i.overview?.dirs[e]?` — ${ae(e,i.overview.dirs[e])}`:``]},e))})}):g(`p`,{class:M.meta,children:[n(`agentsWin.scope`),`: `,n(`agentsWin.scope.${v.scope}`)]}),v.scope===`project`&&i.overview&&!i.overview.trusted?g(`div`,{class:M.callout,role:`note`,children:[g(_,{name:`shield`,size:16}),g(`div`,{class:M.stack,children:[g(`span`,{children:n(`agentsWin.scopeUntrusted`)}),a?g(`button`,{type:`button`,class:M.secondary,style:{justifySelf:`start`},onClick:async()=>{await m(a,!0)&&i.onTrusted()},children:n(`trust.grant`)}):null]})]}):null]}),g(se,{title:n(`agentsWin.modelSection`),children:[g(z,{label:n(`agentsWin.model`),children:g(`select`,{class:j.control,value:v.model,onChange:e=>{let t=e.target.value;y(e=>C({...e,model:t},re(o,t)))},children:[g(`option`,{value:``,disabled:!0,children:n(`agentsWin.chooseModel`)}),v.model&&!o?.some(e=>e.reference===v.model)?g(`option`,{value:v.model,children:v.model}):null,(o??[]).map(e=>g(`option`,{value:e.reference,children:[e.active?n(`agentsWin.modelActive`,{name:e.name??e.id}):e.name??e.id,` `,`· `,e.providerName,e.labels.length?` — ${e.labels.join(` · `)}`:``]},e.reference))]})}),i.modelsError?g(`p`,{class:M.errorText,children:i.modelsError}):J.known?null:g(`p`,{class:j.fieldHint,children:n(`agentsWin.capsUnknown`)}),g(`div`,{class:j.settingsGrid,children:[g(z,{label:n(`agentsWin.textFormat`),children:g(`select`,{class:j.control,value:v.format,onChange:e=>Z({format:e.target.value}),children:A.filter(e=>J.textFormats.includes(e)).map(e=>g(`option`,{value:e,children:n(`agentsWin.format.${e}`)},e))})}),g(z,{label:n(`agentsWin.effort`),hint:J.reasoning?void 0:n(`agentsWin.unsupported`),children:g(`select`,{class:j.control,value:v.effort,disabled:!J.reasoning,onChange:e=>Z({effort:e.target.value}),children:[g(`option`,{value:``,children:[n(`agentsWin.modelDefault`),J.defaultEffort?` (${R(J.defaultEffort)})`:``]}),J.effortLevels.map(e=>g(`option`,{value:e,children:R(e)},e))]})}),g(z,{label:n(`agentsWin.verbosity`),hint:J.verbosity?void 0:n(`agentsWin.unsupported`),children:g(`select`,{class:j.control,value:v.verbosity,disabled:!J.verbosity,onChange:e=>Z({verbosity:e.target.value}),children:[g(`option`,{value:``,children:n(`agentsWin.modelDefault`)}),E.map(e=>g(`option`,{value:e,children:R(e)},e))]})}),g(z,{label:n(`agentsWin.summary`),hint:J.summary?void 0:n(`agentsWin.unsupported`),children:g(`select`,{class:j.control,value:v.summary,disabled:!J.summary,onChange:e=>Z({summary:e.target.value}),children:[g(`option`,{value:``,children:n(`agentsWin.modelDefault`)}),O.map(e=>g(`option`,{value:e,children:n(`agentsWin.summary.${e}`)},e))]})})]})]}),g(`button`,{type:`button`,class:`${M.primary} ${j.saveButton}`,disabled:de||!!V||!k(v,D),onClick:()=>void _e(v),children:n(`agentsWin.save`)})]}),g(`div`,{class:j.column,children:[g(ce,{text:he}),fe?g(`section`,{class:j.stepper,"aria-labelledby":`getting-started`,children:[g(`h4`,{class:j.stepperHead,id:`getting-started`,children:[n(`agentsWin.gettingStarted`),g(`button`,{type:`button`,class:`icon-btn`,"aria-label":n(`agentsWin.dismiss`),title:n(`agentsWin.dismiss`),onClick:()=>{S(),pe(!1)},children:g(_,{name:`x`,size:15})})]}),g(`ol`,{class:j.steps,children:ye.map((e,t)=>{let[r,i]=be[e.id];return g(`li`,{class:j.step,"data-done":e.done,children:[g(`span`,{class:j.stepNode,"aria-hidden":`true`,children:e.done?`✓`:t+1}),g(`div`,{children:[g(`p`,{class:j.stepTitle,children:[n(r),g(`span`,{class:`sr-only`,children:e.done?` ✓`:``})]}),g(`p`,{class:j.stepText,children:n(i)})]})]},e.id)})})]}):null]})]}),V?g(le,{model:ge,started:V.started,onCancel:()=>V.controller.abort()}):null,K?g(ue,{name:K.form.name,live:K.live&&!!a,onTry:()=>{let e=K.form;q(void 0),xe(e)},onStay:()=>q(void 0)}):null]})}var V=`alisio.agents.maximized`,H=`button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex='0']`;function U(t){let{agent:r}=t;return g(e,{children:[g(I,{tone:r.scope===`project`?`ok`:`off`,children:n(`agentsWin.scope.${r.scope}`)}),r.overridesGlobal?g(I,{tone:`warn`,children:n(`agentsWin.overrides`)}):null,r.overriddenByProject?g(I,{tone:`off`,children:n(`agentsWin.overridden`)}):null]})}function W(){let t=P(),[c,f]=s({kind:`list`}),[m,h]=s(!1),[v,y]=s(()=>i(V)===`1`),b=a(null),x=F(()=>u.agentDefinitions(t?.id),[t?.id],[`agents`]),S=F(()=>u.agentTemplates(),[]),C=F(t?()=>u.agentModels(t.id):void 0,[t?.id]);r(()=>{let e=document.activeElement;return b.current?.querySelector(`button`)?.focus(),()=>e?.focus()},[]);let w=x.data?.defaultScope??(t?`project`:`global`),T=()=>C.data?.find(e=>e.active)?.reference??``,E=()=>c.kind===`editor`&&m&&!window.confirm(n(`agentsWin.unsavedLeave`))?!1:(h(!1),f({kind:`list`}),!0),O=()=>{(c.kind!==`editor`||!m||window.confirm(n(`agentsWin.unsavedLeave`)))&&(p.value=!1)},k=e=>f({kind:`editor`,key:`template:${e.id}:${Date.now()}`,form:te(e,w,T()),assist:!0}),ne=async e=>{if(window.confirm(n(`agentsWin.deleteConfirm`,{path:e.path})))try{let r=await u.deleteAgent(e.id,e.scope,t?.id);d(n(r.live?`agentsWin.deleted`:`agentsWin.deletedRestart`)),x.reload()}catch(e){d(l(e))}},A=x.data?.agents??[];return g(`div`,{class:M.backdrop,onMouseDown:e=>{e.target===e.currentTarget&&O()},children:g(`div`,{ref:b,class:`${M.dialog} ${j.shell}${v?` ${j.maximized}`:``}`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`agents-title`,onKeyDown:e=>{if(e.key===`Escape`&&!e.defaultPrevented&&(e.preventDefault(),c.kind===`editor`?E():O()),e.key===`Tab`){let t=[...b.current?.querySelectorAll(H)??[]],n=t[0],r=t.at(-1);e.shiftKey&&document.activeElement===n?(e.preventDefault(),r?.focus()):!e.shiftKey&&document.activeElement===r&&(e.preventDefault(),n?.focus())}},children:g(`div`,{class:`${M.content} ${j.scroll}`,children:[g(`div`,{class:M.topBar,children:[g(`nav`,{class:j.breadcrumb,"aria-label":n(`agentsWin.title`),children:c.kind===`editor`?g(e,{children:[g(`button`,{type:`button`,class:j.crumbLink,onClick:E,children:n(`agentsWin.back`)}),g(_,{name:`chevronRight`,size:14}),g(`span`,{class:j.crumbCurrent,id:`agents-title`,"aria-current":`page`,children:c.form.id?c.form.name:n(`agentsWin.newAgent`)})]}):g(`span`,{class:j.crumbCurrent,id:`agents-title`,children:n(`agentsWin.title`)})}),g(`button`,{type:`button`,class:`icon-btn`,"aria-label":n(v?`agentsWin.restore`:`agentsWin.maximize`),title:n(v?`agentsWin.restore`:`agentsWin.maximize`),"aria-pressed":v,onClick:()=>{let e=!v;y(e),o(V,e?`1`:void 0)},children:g(_,{name:v?`shrink`:`expand`,size:16})}),g(`button`,{type:`button`,class:`icon-btn`,"aria-label":n(`common.close`),onClick:O,children:g(_,{name:`x`,size:18})})]}),c.kind===`editor`?g(B,{initial:c.form,...c.saved?{saved:c.saved}:{},assist:c.assist,workspace:t,overview:x.data,models:C.data,modelsError:C.error,onDirty:h,onTrusted:()=>{x.reload(),C.reload()},onSaved:e=>{x.reload(),f(t=>t.kind===`editor`?{...t,form:e,saved:e}:t)}},c.key):g(e,{children:[g(`header`,{class:`${M.intro} ${j.listHead}`,children:[g(`div`,{children:[g(`h3`,{class:M.pageTitle,children:n(`agentsWin.title`)}),g(`p`,{class:M.lead,children:n(`agentsWin.lead`)}),t?null:g(`p`,{class:M.note,children:n(`agentsWin.noWorkspace`)})]}),g(`button`,{type:`button`,class:M.primary,onClick:()=>f({kind:`editor`,key:`new:${Date.now()}`,form:D(w,T()),assist:!0}),children:n(`agentsWin.create`)})]}),g(`h4`,{class:M.listTitle,children:[n(`agentsWin.yours`),g(`span`,{class:M.count,children:A.length})]}),g(N,{loading:x.loading&&!x.data,error:x.error}),x.data&&!A.length?g(`p`,{class:M.note,children:n(`agentsWin.none`)}):null,g(`ul`,{class:M.rows,children:A.map(e=>g(`li`,{class:M.row,"data-muted":e.overriddenByProject?`true`:void 0,children:[g(`button`,{type:`button`,class:j.rowButton,onClick:()=>{let t=ee(e);f({kind:`editor`,key:`${e.scope}:${e.id}`,form:t,saved:t,assist:!1})},children:[g(`span`,{class:M.rowTitle,children:[g(`span`,{class:M.cardName,children:e.name}),g(U,{agent:e})]}),g(`p`,{class:M.cardText,children:e.description}),g(`p`,{class:M.meta,children:[e.model||`—`,` · `,g(`code`,{children:e.id})]})]}),g(`div`,{class:M.rowActions,children:g(`button`,{type:`button`,class:`icon-btn`,"aria-label":`${n(`agentsWin.delete`)} ${e.name}`,title:n(`agentsWin.delete`),onClick:()=>void ne(e),children:g(_,{name:`archive`,size:16})})})]},`${e.scope}:${e.id}`))}),g(`h4`,{class:M.listTitle,children:n(`agentsWin.templates`)}),g(N,{loading:S.loading&&!S.data,error:S.error}),g(`ul`,{class:M.grid,children:(S.data??[]).map(e=>g(`li`,{children:g(`button`,{type:`button`,class:j.templateCard,onClick:()=>k(e),children:[g(`span`,{class:j.templateName,children:e.name}),g(`span`,{class:j.templateText,children:e.description})]})},e.id))})]})]})})})}export{W as AgentsModal};