teleton 0.5.2 → 0.7.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 (40) hide show
  1. package/README.md +243 -105
  2. package/dist/chunk-FNV5FF35.js +331 -0
  3. package/dist/chunk-LRCPA7SC.js +149 -0
  4. package/dist/{chunk-WUTMT6DW.js → chunk-N3F7E7DR.js} +114 -566
  5. package/dist/chunk-ND2X5FWB.js +368 -0
  6. package/dist/chunk-NERLQY2H.js +421 -0
  7. package/dist/{chunk-YBA6IBGT.js → chunk-OCLG5GKI.js} +24 -24
  8. package/dist/{chunk-WOXBZOQX.js → chunk-OGIG552S.js} +2152 -4488
  9. package/dist/chunk-RCMD3U65.js +141 -0
  10. package/dist/{chunk-O4R7V5Y2.js → chunk-RO62LO6Z.js} +11 -1
  11. package/dist/chunk-TCD4NZDA.js +3226 -0
  12. package/dist/chunk-UCN6TI25.js +143 -0
  13. package/dist/{chunk-WL2Q3VRD.js → chunk-UDD7FYOU.js} +12 -4
  14. package/dist/chunk-VAUJSSD3.js +20 -0
  15. package/dist/chunk-XBE4JB7C.js +8 -0
  16. package/dist/chunk-XBKSS6DM.js +58 -0
  17. package/dist/cli/index.js +1179 -412
  18. package/dist/client-3VWE7NC4.js +29 -0
  19. package/dist/{get-my-gifts-KVULMBJ3.js → get-my-gifts-RI7FAXAL.js} +3 -1
  20. package/dist/index.js +17 -8
  21. package/dist/{memory-Y5J7CXAR.js → memory-RD7ZSTRV.js} +16 -10
  22. package/dist/{migrate-UEQCDWL2.js → migrate-GO4NOBT7.js} +14 -6
  23. package/dist/{server-BQY7CM2N.js → server-OWVEZTR3.js} +869 -93
  24. package/dist/setup-server-C7ZTPHD5.js +934 -0
  25. package/dist/{task-dependency-resolver-TRPILAHM.js → task-dependency-resolver-WKZWJLLM.js} +19 -15
  26. package/dist/{task-executor-N7XNVK5N.js → task-executor-PD3H4MLO.js} +5 -2
  27. package/dist/tool-adapter-Y3TCEQOC.js +145 -0
  28. package/dist/tool-index-MIVK3D7H.js +250 -0
  29. package/dist/{transcript-7V4UNID4.js → transcript-UDJZP6NK.js} +2 -1
  30. package/dist/web/assets/complete-fZLnb5Ot.js +1 -0
  31. package/dist/web/assets/index-B_FcaX5D.css +1 -0
  32. package/dist/web/assets/index-CbeAP4_n.js +67 -0
  33. package/dist/web/assets/index.es-oXiZF7Hc.js +11 -0
  34. package/dist/web/assets/login-telegram-BP7CJDmx.js +1 -0
  35. package/dist/web/assets/run-DOrDowjK.js +1 -0
  36. package/dist/web/index.html +2 -2
  37. package/package.json +21 -15
  38. package/dist/chunk-5WWR4CU3.js +0 -124
  39. package/dist/web/assets/index-CDMbujHf.css +0 -1
  40. package/dist/web/assets/index-DDX8oQ2z.js +0 -67
@@ -1,27 +1,56 @@
1
1
  import {
2
+ CONFIGURABLE_KEYS,
2
3
  WorkspaceSecurityError,
4
+ adaptPlugin,
5
+ clearPromptCache,
6
+ deleteNestedValue,
7
+ deletePluginSecret,
8
+ ensurePluginDeps,
9
+ getNestedValue,
10
+ listPluginSecretKeys,
11
+ readRawConfig,
12
+ setNestedValue,
3
13
  validateDirectory,
4
14
  validatePath,
5
15
  validateReadPath,
6
- validateWritePath
7
- } from "./chunk-5WWR4CU3.js";
8
- import "./chunk-O4R7V5Y2.js";
16
+ validateWritePath,
17
+ writePluginSecret,
18
+ writeRawConfig
19
+ } from "./chunk-TCD4NZDA.js";
20
+ import "./chunk-NERLQY2H.js";
21
+ import "./chunk-QUAPFI2N.js";
22
+ import "./chunk-TSKJCWQQ.js";
9
23
  import {
24
+ getErrorMessage
25
+ } from "./chunk-XBE4JB7C.js";
26
+ import "./chunk-LRCPA7SC.js";
27
+ import "./chunk-UCN6TI25.js";
28
+ import "./chunk-XBKSS6DM.js";
29
+ import "./chunk-RO62LO6Z.js";
30
+ import "./chunk-VAUJSSD3.js";
31
+ import "./chunk-4DU3C27M.js";
32
+ import {
33
+ WORKSPACE_PATHS,
10
34
  WORKSPACE_ROOT
11
35
  } from "./chunk-EYWNOHMJ.js";
36
+ import {
37
+ addLogListener,
38
+ clearLogListeners,
39
+ createLogger
40
+ } from "./chunk-RCMD3U65.js";
12
41
  import {
13
42
  getTaskStore
14
43
  } from "./chunk-NUGDTPE4.js";
15
44
  import "./chunk-QGM4M3NI.js";
16
45
 
17
46
  // src/webui/server.ts
18
- import { Hono as Hono9 } from "hono";
47
+ import { Hono as Hono12 } from "hono";
19
48
  import { serve } from "@hono/node-server";
20
49
  import { cors } from "hono/cors";
21
50
  import { bodyLimit } from "hono/body-limit";
22
51
  import { setCookie, getCookie, deleteCookie } from "hono/cookie";
23
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
24
- import { join as join3, dirname, resolve, relative as relative2 } from "path";
52
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
53
+ import { join as join4, dirname, resolve as resolve2, relative as relative2 } from "path";
25
54
  import { fileURLToPath } from "url";
26
55
 
27
56
  // src/webui/middleware/auth.ts
@@ -45,54 +74,43 @@ function safeCompare(a, b) {
45
74
 
46
75
  // src/webui/log-interceptor.ts
47
76
  var LogInterceptor = class {
48
- listeners = /* @__PURE__ */ new Set();
49
- isPatched = false;
50
- originalMethods = {
51
- log: console.log,
52
- warn: console.warn,
53
- error: console.error
54
- };
77
+ cleanups = /* @__PURE__ */ new Map();
78
+ installed = false;
79
+ /**
80
+ * Install the interceptor. Now a lightweight no-op since pino streams
81
+ * are always active — kept for API compat with server.ts start/stop.
82
+ */
55
83
  install() {
56
- if (this.isPatched) return;
57
- const levels = ["log", "warn", "error"];
58
- for (const level of levels) {
59
- const original = this.originalMethods[level];
60
- console[level] = (...args) => {
61
- original.apply(console, args);
62
- if (this.listeners.size > 0) {
63
- const entry = {
64
- level,
65
- message: args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(arg)).join(" "),
66
- timestamp: Date.now()
67
- };
68
- for (const listener of this.listeners) {
69
- try {
70
- listener(entry);
71
- } catch (err) {
72
- original.call(console, "\u274C Log listener error:", err);
73
- }
74
- }
75
- }
76
- };
77
- }
78
- this.isPatched = true;
84
+ this.installed = true;
79
85
  }
80
86
  uninstall() {
81
- if (!this.isPatched) return;
82
- console.log = this.originalMethods.log;
83
- console.warn = this.originalMethods.warn;
84
- console.error = this.originalMethods.error;
85
- this.isPatched = false;
87
+ for (const cleanup of this.cleanups.values()) {
88
+ cleanup();
89
+ }
90
+ this.cleanups.clear();
91
+ this.installed = false;
86
92
  }
87
93
  addListener(listener) {
88
- this.listeners.add(listener);
89
- return () => this.listeners.delete(listener);
94
+ const cleanup = addLogListener(listener);
95
+ this.cleanups.set(listener, cleanup);
96
+ return () => {
97
+ cleanup();
98
+ this.cleanups.delete(listener);
99
+ };
90
100
  }
91
101
  removeListener(listener) {
92
- this.listeners.delete(listener);
102
+ const cleanup = this.cleanups.get(listener);
103
+ if (cleanup) {
104
+ cleanup();
105
+ this.cleanups.delete(listener);
106
+ }
93
107
  }
94
108
  clear() {
95
- this.listeners.clear();
109
+ for (const cleanup of this.cleanups.values()) {
110
+ cleanup();
111
+ }
112
+ this.cleanups.clear();
113
+ clearLogListeners();
96
114
  }
97
115
  };
98
116
  var logInterceptor = new LogInterceptor();
@@ -110,8 +128,6 @@ function createStatusRoutes(deps) {
110
128
  model: config.agent.model,
111
129
  provider: config.agent.provider,
112
130
  sessionCount: sessionCountRow?.count ?? 0,
113
- paused: false,
114
- // TODO: get from message handler
115
131
  toolCount: deps.toolRegistry.getAll().length
116
132
  };
117
133
  const response = {
@@ -122,7 +138,7 @@ function createStatusRoutes(deps) {
122
138
  } catch (error) {
123
139
  const response = {
124
140
  success: false,
125
- error: error instanceof Error ? error.message : String(error)
141
+ error: getErrorMessage(error)
126
142
  };
127
143
  return c.json(response, 500);
128
144
  }
@@ -169,11 +185,60 @@ function createToolsRoutes(deps) {
169
185
  } catch (error) {
170
186
  const response = {
171
187
  success: false,
172
- error: error instanceof Error ? error.message : String(error)
188
+ error: getErrorMessage(error)
173
189
  };
174
190
  return c.json(response, 500);
175
191
  }
176
192
  });
193
+ app.get("/rag", (c) => {
194
+ try {
195
+ const config = deps.agent.getConfig();
196
+ const toolIndex = deps.toolRegistry.getToolIndex();
197
+ const response = {
198
+ success: true,
199
+ data: {
200
+ enabled: config.tool_rag.enabled,
201
+ indexed: toolIndex?.isIndexed ?? false,
202
+ topK: config.tool_rag.top_k,
203
+ totalTools: deps.toolRegistry.count,
204
+ alwaysInclude: config.tool_rag.always_include,
205
+ skipUnlimitedProviders: config.tool_rag.skip_unlimited_providers
206
+ }
207
+ };
208
+ return c.json(response);
209
+ } catch (error) {
210
+ return c.json({ success: false, error: String(error) }, 500);
211
+ }
212
+ });
213
+ app.put("/rag", async (c) => {
214
+ try {
215
+ const config = deps.agent.getConfig();
216
+ const body = await c.req.json();
217
+ const { enabled, topK } = body;
218
+ if (enabled !== void 0) {
219
+ config.tool_rag.enabled = enabled;
220
+ }
221
+ if (topK !== void 0) {
222
+ if (topK < 5 || topK > 200) {
223
+ return c.json({ success: false, error: "topK must be between 5 and 200" }, 400);
224
+ }
225
+ config.tool_rag.top_k = topK;
226
+ }
227
+ const toolIndex = deps.toolRegistry.getToolIndex();
228
+ const response = {
229
+ success: true,
230
+ data: {
231
+ enabled: config.tool_rag.enabled,
232
+ indexed: toolIndex?.isIndexed ?? false,
233
+ topK: config.tool_rag.top_k,
234
+ totalTools: deps.toolRegistry.count
235
+ }
236
+ };
237
+ return c.json(response);
238
+ } catch (error) {
239
+ return c.json({ success: false, error: String(error) }, 500);
240
+ }
241
+ });
177
242
  app.put("/:name", async (c) => {
178
243
  try {
179
244
  const toolName = c.req.param("name");
@@ -230,7 +295,7 @@ function createToolsRoutes(deps) {
230
295
  } catch (error) {
231
296
  const response = {
232
297
  success: false,
233
- error: error instanceof Error ? error.message : String(error)
298
+ error: getErrorMessage(error)
234
299
  };
235
300
  return c.json(response, 500);
236
301
  }
@@ -258,7 +323,7 @@ function createToolsRoutes(deps) {
258
323
  } catch (error) {
259
324
  const response = {
260
325
  success: false,
261
- error: error instanceof Error ? error.message : String(error)
326
+ error: getErrorMessage(error)
262
327
  };
263
328
  return c.json(response, 500);
264
329
  }
@@ -290,7 +355,7 @@ function createToolsRoutes(deps) {
290
355
  } catch (error) {
291
356
  const response = {
292
357
  success: false,
293
- error: error instanceof Error ? error.message : String(error)
358
+ error: getErrorMessage(error)
294
359
  };
295
360
  return c.json(response, 500);
296
361
  }
@@ -327,9 +392,9 @@ function createLogsRoutes(_deps) {
327
392
  }),
328
393
  event: "log"
329
394
  });
330
- await new Promise((resolve2) => {
331
- if (aborted) return resolve2();
332
- stream.onAbort(() => resolve2());
395
+ await new Promise((resolve3) => {
396
+ if (aborted) return resolve3();
397
+ stream.onAbort(() => resolve3());
333
398
  });
334
399
  if (cleanup) cleanup();
335
400
  });
@@ -384,7 +449,7 @@ function createMemoryRoutes(deps) {
384
449
  } catch (error) {
385
450
  const response = {
386
451
  success: false,
387
- error: error instanceof Error ? error.message : String(error)
452
+ error: getErrorMessage(error)
388
453
  };
389
454
  return c.json(response, 500);
390
455
  }
@@ -418,7 +483,7 @@ function createMemoryRoutes(deps) {
418
483
  } catch (error) {
419
484
  const response = {
420
485
  success: false,
421
- error: error instanceof Error ? error.message : String(error)
486
+ error: getErrorMessage(error)
422
487
  };
423
488
  return c.json(response, 500);
424
489
  }
@@ -439,7 +504,7 @@ function createMemoryRoutes(deps) {
439
504
  } catch (error) {
440
505
  const response = {
441
506
  success: false,
442
- error: error instanceof Error ? error.message : String(error)
507
+ error: getErrorMessage(error)
443
508
  };
444
509
  return c.json(response, 500);
445
510
  }
@@ -488,7 +553,7 @@ function createSoulRoutes(_deps) {
488
553
  } catch (error) {
489
554
  const response = {
490
555
  success: false,
491
- error: error instanceof Error ? error.message : String(error)
556
+ error: getErrorMessage(error)
492
557
  };
493
558
  return c.json(response, 500);
494
559
  }
@@ -521,6 +586,7 @@ function createSoulRoutes(_deps) {
521
586
  }
522
587
  const filePath = join(WORKSPACE_ROOT, filename);
523
588
  writeFileSync(filePath, body.content, "utf-8");
589
+ clearPromptCache();
524
590
  const response = {
525
591
  success: true,
526
592
  data: { message: `${filename} updated successfully` }
@@ -529,7 +595,7 @@ function createSoulRoutes(_deps) {
529
595
  } catch (error) {
530
596
  const response = {
531
597
  success: false,
532
- error: error instanceof Error ? error.message : String(error)
598
+ error: getErrorMessage(error)
533
599
  };
534
600
  return c.json(response, 500);
535
601
  }
@@ -551,8 +617,124 @@ function createPluginsRoutes(deps) {
551
617
  return app;
552
618
  }
553
619
 
554
- // src/webui/routes/workspace.ts
620
+ // src/webui/routes/mcp.ts
555
621
  import { Hono as Hono7 } from "hono";
622
+ var SAFE_PACKAGE_RE = /^[@a-zA-Z0-9._\/-]+$/;
623
+ var SAFE_ARG_RE = /^[a-zA-Z0-9._\/:=@-]+$/;
624
+ function createMcpRoutes(deps) {
625
+ const app = new Hono7();
626
+ app.get("/", (c) => {
627
+ const response = {
628
+ success: true,
629
+ data: deps.mcpServers
630
+ };
631
+ return c.json(response);
632
+ });
633
+ app.post("/", async (c) => {
634
+ try {
635
+ const body = await c.req.json();
636
+ if (!body.package && !body.url) {
637
+ return c.json(
638
+ { success: false, error: "Either 'package' or 'url' is required" },
639
+ 400
640
+ );
641
+ }
642
+ if (body.package && !SAFE_PACKAGE_RE.test(body.package)) {
643
+ return c.json(
644
+ {
645
+ success: false,
646
+ error: "Invalid package name \u2014 only alphanumeric, @, /, ., - allowed"
647
+ },
648
+ 400
649
+ );
650
+ }
651
+ if (body.args) {
652
+ for (const arg of body.args) {
653
+ if (!SAFE_ARG_RE.test(arg)) {
654
+ return c.json(
655
+ {
656
+ success: false,
657
+ error: `Invalid argument "${arg}" \u2014 only alphanumeric, ., /, :, =, @, - allowed`
658
+ },
659
+ 400
660
+ );
661
+ }
662
+ }
663
+ }
664
+ const raw = readRawConfig(deps.configPath);
665
+ if (!raw.mcp || typeof raw.mcp !== "object") raw.mcp = { servers: {} };
666
+ const mcp = raw.mcp;
667
+ if (!mcp.servers || typeof mcp.servers !== "object") mcp.servers = {};
668
+ const servers = mcp.servers;
669
+ const serverName = body.name || deriveServerName(body.package || body.url || "unknown");
670
+ if (servers[serverName]) {
671
+ return c.json(
672
+ { success: false, error: `Server "${serverName}" already exists` },
673
+ 409
674
+ );
675
+ }
676
+ const entry = {};
677
+ if (body.url) {
678
+ entry.url = body.url;
679
+ } else {
680
+ entry.command = "npx";
681
+ entry.args = ["-y", body.package, ...body.args || []];
682
+ }
683
+ if (body.scope && body.scope !== "always") entry.scope = body.scope;
684
+ if (body.env && Object.keys(body.env).length > 0) entry.env = body.env;
685
+ servers[serverName] = entry;
686
+ writeRawConfig(raw, deps.configPath);
687
+ return c.json({
688
+ success: true,
689
+ data: { name: serverName, message: "Server added. Restart teleton to connect." }
690
+ });
691
+ } catch (error) {
692
+ return c.json(
693
+ {
694
+ success: false,
695
+ error: getErrorMessage(error)
696
+ },
697
+ 500
698
+ );
699
+ }
700
+ });
701
+ app.delete("/:name", (c) => {
702
+ try {
703
+ const name = c.req.param("name");
704
+ const raw = readRawConfig(deps.configPath);
705
+ const mcp = raw.mcp || {};
706
+ const servers = mcp.servers || {};
707
+ if (!servers[name]) {
708
+ return c.json(
709
+ { success: false, error: `Server "${name}" not found` },
710
+ 404
711
+ );
712
+ }
713
+ delete servers[name];
714
+ writeRawConfig(raw, deps.configPath);
715
+ return c.json({
716
+ success: true,
717
+ data: { name, message: "Server removed. Restart teleton to apply." }
718
+ });
719
+ } catch (error) {
720
+ return c.json(
721
+ {
722
+ success: false,
723
+ error: getErrorMessage(error)
724
+ },
725
+ 500
726
+ );
727
+ }
728
+ });
729
+ return app;
730
+ }
731
+ function deriveServerName(pkg) {
732
+ const unscoped = pkg.includes("/") ? pkg.split("/").pop() : pkg;
733
+ return unscoped.replace(/^server-/, "").replace(/^mcp-server-/, "").replace(/^mcp-/, "");
734
+ }
735
+
736
+ // src/webui/routes/workspace.ts
737
+ import { Hono as Hono8 } from "hono";
556
738
  import {
557
739
  readFileSync as readFileSync2,
558
740
  writeFileSync as writeFileSync2,
@@ -565,7 +747,7 @@ import {
565
747
  } from "fs";
566
748
  import { join as join2, relative } from "path";
567
749
  function errorResponse(c, error, status = 500) {
568
- const message = error instanceof Error ? error.message : String(error);
750
+ const message = getErrorMessage(error);
569
751
  const code = error instanceof WorkspaceSecurityError ? 403 : status;
570
752
  const response = { success: false, error: message };
571
753
  return c.json(response, code);
@@ -614,7 +796,7 @@ function listDir(absPath, recursive) {
614
796
  return entries;
615
797
  }
616
798
  function createWorkspaceRoutes(_deps) {
617
- const app = new Hono7();
799
+ const app = new Hono8();
618
800
  app.get("/", (c) => {
619
801
  try {
620
802
  const subpath = c.req.query("path") || "";
@@ -778,10 +960,10 @@ function createWorkspaceRoutes(_deps) {
778
960
  }
779
961
 
780
962
  // src/webui/routes/tasks.ts
781
- import { Hono as Hono8 } from "hono";
963
+ import { Hono as Hono9 } from "hono";
782
964
  var VALID_STATUSES = ["pending", "in_progress", "done", "failed", "cancelled"];
783
965
  function createTasksRoutes(deps) {
784
- const app = new Hono8();
966
+ const app = new Hono9();
785
967
  function store() {
786
968
  return getTaskStore(deps.memory.db);
787
969
  }
@@ -804,7 +986,7 @@ function createTasksRoutes(deps) {
804
986
  } catch (error) {
805
987
  const response = {
806
988
  success: false,
807
- error: error instanceof Error ? error.message : String(error)
989
+ error: getErrorMessage(error)
808
990
  };
809
991
  return c.json(response, 500);
810
992
  }
@@ -830,7 +1012,7 @@ function createTasksRoutes(deps) {
830
1012
  } catch (error) {
831
1013
  const response = {
832
1014
  success: false,
833
- error: error instanceof Error ? error.message : String(error)
1015
+ error: getErrorMessage(error)
834
1016
  };
835
1017
  return c.json(response, 500);
836
1018
  }
@@ -847,7 +1029,7 @@ function createTasksRoutes(deps) {
847
1029
  } catch (error) {
848
1030
  const response = {
849
1031
  success: false,
850
- error: error instanceof Error ? error.message : String(error)
1032
+ error: getErrorMessage(error)
851
1033
  };
852
1034
  return c.json(response, 500);
853
1035
  }
@@ -864,7 +1046,7 @@ function createTasksRoutes(deps) {
864
1046
  } catch (error) {
865
1047
  const response = {
866
1048
  success: false,
867
- error: error instanceof Error ? error.message : String(error)
1049
+ error: getErrorMessage(error)
868
1050
  };
869
1051
  return c.json(response, 500);
870
1052
  }
@@ -881,7 +1063,7 @@ function createTasksRoutes(deps) {
881
1063
  } catch (error) {
882
1064
  const response = {
883
1065
  success: false,
884
- error: error instanceof Error ? error.message : String(error)
1066
+ error: getErrorMessage(error)
885
1067
  };
886
1068
  return c.json(response, 500);
887
1069
  }
@@ -889,23 +1071,618 @@ function createTasksRoutes(deps) {
889
1071
  return app;
890
1072
  }
891
1073
 
1074
+ // src/webui/routes/config.ts
1075
+ import { Hono as Hono10 } from "hono";
1076
+ function createConfigRoutes(deps) {
1077
+ const app = new Hono10();
1078
+ app.get("/", (c) => {
1079
+ try {
1080
+ const raw = readRawConfig(deps.configPath);
1081
+ const data = Object.entries(CONFIGURABLE_KEYS).map(([key, meta]) => {
1082
+ const value = getNestedValue(raw, key);
1083
+ const isSet = value != null && value !== "";
1084
+ return {
1085
+ key,
1086
+ set: isSet,
1087
+ value: isSet ? meta.mask(String(value)) : null,
1088
+ sensitive: meta.sensitive,
1089
+ type: meta.type,
1090
+ category: meta.category,
1091
+ description: meta.description,
1092
+ ...meta.options ? { options: meta.options } : {}
1093
+ };
1094
+ });
1095
+ const response = { success: true, data };
1096
+ return c.json(response);
1097
+ } catch (err) {
1098
+ return c.json(
1099
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1100
+ 500
1101
+ );
1102
+ }
1103
+ });
1104
+ app.put("/:key", async (c) => {
1105
+ const key = c.req.param("key");
1106
+ const meta = CONFIGURABLE_KEYS[key];
1107
+ if (!meta) {
1108
+ const allowed = Object.keys(CONFIGURABLE_KEYS).join(", ");
1109
+ return c.json(
1110
+ {
1111
+ success: false,
1112
+ error: `Key "${key}" is not configurable. Allowed: ${allowed}`
1113
+ },
1114
+ 400
1115
+ );
1116
+ }
1117
+ let body;
1118
+ try {
1119
+ body = await c.req.json();
1120
+ } catch {
1121
+ return c.json({ success: false, error: "Invalid JSON body" }, 400);
1122
+ }
1123
+ const value = body.value;
1124
+ if (value == null || typeof value !== "string") {
1125
+ return c.json(
1126
+ { success: false, error: "Missing or invalid 'value' field" },
1127
+ 400
1128
+ );
1129
+ }
1130
+ const validationErr = meta.validate(value);
1131
+ if (validationErr) {
1132
+ return c.json(
1133
+ { success: false, error: `Invalid value for ${key}: ${validationErr}` },
1134
+ 400
1135
+ );
1136
+ }
1137
+ try {
1138
+ const parsed = meta.parse(value);
1139
+ const raw = readRawConfig(deps.configPath);
1140
+ setNestedValue(raw, key, parsed);
1141
+ writeRawConfig(raw, deps.configPath);
1142
+ const runtimeConfig = deps.agent.getConfig();
1143
+ setNestedValue(runtimeConfig, key, parsed);
1144
+ const result = {
1145
+ key,
1146
+ set: true,
1147
+ value: meta.mask(value),
1148
+ sensitive: meta.sensitive,
1149
+ type: meta.type,
1150
+ category: meta.category,
1151
+ description: meta.description,
1152
+ ...meta.options ? { options: meta.options } : {}
1153
+ };
1154
+ return c.json({ success: true, data: result });
1155
+ } catch (err) {
1156
+ return c.json(
1157
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1158
+ 500
1159
+ );
1160
+ }
1161
+ });
1162
+ app.delete("/:key", (c) => {
1163
+ const key = c.req.param("key");
1164
+ const meta = CONFIGURABLE_KEYS[key];
1165
+ if (!meta) {
1166
+ const allowed = Object.keys(CONFIGURABLE_KEYS).join(", ");
1167
+ return c.json(
1168
+ {
1169
+ success: false,
1170
+ error: `Key "${key}" is not configurable. Allowed: ${allowed}`
1171
+ },
1172
+ 400
1173
+ );
1174
+ }
1175
+ try {
1176
+ const raw = readRawConfig(deps.configPath);
1177
+ deleteNestedValue(raw, key);
1178
+ writeRawConfig(raw, deps.configPath);
1179
+ const runtimeConfig = deps.agent.getConfig();
1180
+ deleteNestedValue(runtimeConfig, key);
1181
+ const result = {
1182
+ key,
1183
+ set: false,
1184
+ value: null,
1185
+ sensitive: meta.sensitive,
1186
+ type: meta.type,
1187
+ category: meta.category,
1188
+ description: meta.description,
1189
+ ...meta.options ? { options: meta.options } : {}
1190
+ };
1191
+ return c.json({ success: true, data: result });
1192
+ } catch (err) {
1193
+ return c.json(
1194
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1195
+ 500
1196
+ );
1197
+ }
1198
+ });
1199
+ return app;
1200
+ }
1201
+
1202
+ // src/webui/routes/marketplace.ts
1203
+ import { Hono as Hono11 } from "hono";
1204
+
1205
+ // src/webui/services/marketplace.ts
1206
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "fs";
1207
+ import { join as join3, resolve } from "path";
1208
+ import { pathToFileURL } from "url";
1209
+ var log = createLogger("WebUI");
1210
+ var REGISTRY_URL = "https://raw.githubusercontent.com/TONresistor/teleton-plugins/main/registry.json";
1211
+ var PLUGIN_BASE_URL = "https://raw.githubusercontent.com/TONresistor/teleton-plugins/main";
1212
+ var GITHUB_API_BASE = "https://api.github.com/repos/TONresistor/teleton-plugins/contents";
1213
+ var CACHE_TTL = 5 * 60 * 1e3;
1214
+ var PLUGINS_DIR = WORKSPACE_PATHS.PLUGINS_DIR;
1215
+ var VALID_ID = /^[a-z0-9][a-z0-9-]*$/;
1216
+ var MarketplaceService = class {
1217
+ deps;
1218
+ cache = null;
1219
+ fetchPromise = null;
1220
+ manifestCache = /* @__PURE__ */ new Map();
1221
+ installing = /* @__PURE__ */ new Set();
1222
+ constructor(deps) {
1223
+ this.deps = deps;
1224
+ }
1225
+ // ── Registry ────────────────────────────────────────────────────────
1226
+ async getRegistry(forceRefresh = false) {
1227
+ if (!forceRefresh && this.cache && Date.now() - this.cache.fetchedAt < CACHE_TTL) {
1228
+ return this.cache.entries;
1229
+ }
1230
+ if (this.fetchPromise) return this.fetchPromise;
1231
+ this.fetchPromise = this.fetchRegistry();
1232
+ try {
1233
+ const entries = await this.fetchPromise;
1234
+ this.cache = { entries, fetchedAt: Date.now() };
1235
+ return entries;
1236
+ } catch (err) {
1237
+ if (this.cache) {
1238
+ log.warn({ err }, "Registry fetch failed, using stale cache");
1239
+ return this.cache.entries;
1240
+ }
1241
+ throw err;
1242
+ } finally {
1243
+ this.fetchPromise = null;
1244
+ }
1245
+ }
1246
+ async fetchRegistry() {
1247
+ const res = await fetch(REGISTRY_URL);
1248
+ if (!res.ok) throw new Error(`Registry fetch failed: ${res.status} ${res.statusText}`);
1249
+ const data = await res.json();
1250
+ const plugins = Array.isArray(data) ? data : data?.plugins;
1251
+ if (!Array.isArray(plugins)) throw new Error("Registry has no plugins array");
1252
+ const VALID_PATH = /^[a-zA-Z0-9][a-zA-Z0-9._\/-]*$/;
1253
+ for (const entry of plugins) {
1254
+ if (!entry.id || !entry.name || !entry.path) {
1255
+ throw new Error(`Invalid registry entry: missing required fields (id=${entry.id ?? "?"})`);
1256
+ }
1257
+ if (!VALID_PATH.test(entry.path) || entry.path.includes("..")) {
1258
+ throw new Error(`Invalid registry path for "${entry.id}": "${entry.path}"`);
1259
+ }
1260
+ }
1261
+ return plugins;
1262
+ }
1263
+ // ── Remote manifest ─────────────────────────────────────────────────
1264
+ async fetchRemoteManifest(entry) {
1265
+ const cached = this.manifestCache.get(entry.id);
1266
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
1267
+ return cached.data;
1268
+ }
1269
+ const url = `${PLUGIN_BASE_URL}/${entry.path}/manifest.json`;
1270
+ const res = await fetch(url);
1271
+ if (!res.ok) {
1272
+ return {
1273
+ name: entry.name,
1274
+ version: "0.0.0",
1275
+ description: entry.description,
1276
+ author: entry.author
1277
+ };
1278
+ }
1279
+ const raw = await res.json();
1280
+ const data = {
1281
+ ...raw,
1282
+ author: normalizeAuthor(raw.author)
1283
+ };
1284
+ this.manifestCache.set(entry.id, { data, fetchedAt: Date.now() });
1285
+ return data;
1286
+ }
1287
+ // ── List plugins (combined view) ────────────────────────────────────
1288
+ async listPlugins(forceRefresh = false) {
1289
+ const registry = await this.getRegistry(forceRefresh);
1290
+ const results = [];
1291
+ const manifests = await Promise.allSettled(
1292
+ registry.map((entry) => this.fetchRemoteManifest(entry))
1293
+ );
1294
+ for (let i = 0; i < registry.length; i++) {
1295
+ const entry = registry[i];
1296
+ const manifestResult = manifests[i];
1297
+ const manifest = manifestResult.status === "fulfilled" ? manifestResult.value : {
1298
+ name: entry.name,
1299
+ version: "0.0.0",
1300
+ description: entry.description,
1301
+ author: entry.author
1302
+ };
1303
+ const installed = this.deps.modules.find((m) => m.name === entry.id || m.name === entry.name);
1304
+ const installedVersion = installed?.version ?? null;
1305
+ const remoteVersion = manifest.version || "0.0.0";
1306
+ let status = "available";
1307
+ if (installedVersion) {
1308
+ status = installedVersion !== remoteVersion ? "updatable" : "installed";
1309
+ }
1310
+ let toolCount = manifest.tools?.length ?? 0;
1311
+ let tools = manifest.tools ?? [];
1312
+ if (installed) {
1313
+ const moduleTools = this.deps.toolRegistry.getModuleTools(installed.name);
1314
+ const allToolDefs = this.deps.toolRegistry.getAll();
1315
+ const toolMap = new Map(allToolDefs.map((t) => [t.name, t]));
1316
+ tools = moduleTools.map((mt) => ({
1317
+ name: mt.name,
1318
+ description: toolMap.get(mt.name)?.description ?? ""
1319
+ }));
1320
+ toolCount = tools.length;
1321
+ }
1322
+ results.push({
1323
+ id: entry.id,
1324
+ name: entry.name,
1325
+ description: manifest.description || entry.description,
1326
+ author: manifest.author || entry.author,
1327
+ tags: entry.tags,
1328
+ remoteVersion,
1329
+ installedVersion,
1330
+ status,
1331
+ toolCount,
1332
+ tools,
1333
+ secrets: manifest.secrets
1334
+ });
1335
+ }
1336
+ return results;
1337
+ }
1338
+ // ── Install ─────────────────────────────────────────────────────────
1339
+ async installPlugin(pluginId) {
1340
+ this.validateId(pluginId);
1341
+ if (this.installing.has(pluginId)) {
1342
+ throw new ConflictError(`Plugin "${pluginId}" is already being installed`);
1343
+ }
1344
+ const existing = this.findModuleByPluginId(pluginId);
1345
+ if (existing) {
1346
+ throw new ConflictError(`Plugin "${pluginId}" is already installed`);
1347
+ }
1348
+ this.installing.add(pluginId);
1349
+ const pluginDir = join3(PLUGINS_DIR, pluginId);
1350
+ try {
1351
+ const registry = await this.getRegistry();
1352
+ const entry = registry.find((e) => e.id === pluginId);
1353
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found in registry`);
1354
+ const manifest = await this.fetchRemoteManifest(entry);
1355
+ mkdirSync2(pluginDir, { recursive: true });
1356
+ await this.downloadDir(entry.path, pluginDir);
1357
+ await ensurePluginDeps(pluginDir, pluginId);
1358
+ const indexPath = join3(pluginDir, "index.js");
1359
+ const moduleUrl = pathToFileURL(indexPath).href + `?t=${Date.now()}`;
1360
+ const mod = await import(moduleUrl);
1361
+ const adapted = adaptPlugin(
1362
+ mod,
1363
+ pluginId,
1364
+ this.deps.config,
1365
+ this.deps.loadedModuleNames,
1366
+ this.deps.sdkDeps
1367
+ );
1368
+ adapted.migrate?.(this.deps.pluginContext.db);
1369
+ const tools = adapted.tools(this.deps.config);
1370
+ const toolCount = this.deps.toolRegistry.registerPluginTools(adapted.name, tools);
1371
+ await adapted.start?.(this.deps.pluginContext);
1372
+ this.deps.modules.push(adapted);
1373
+ this.deps.rewireHooks();
1374
+ return {
1375
+ name: adapted.name,
1376
+ version: adapted.version,
1377
+ toolCount
1378
+ };
1379
+ } catch (err) {
1380
+ if (existsSync2(pluginDir)) {
1381
+ try {
1382
+ rmSync2(pluginDir, { recursive: true, force: true });
1383
+ } catch (cleanupErr) {
1384
+ log.error({ err: cleanupErr }, `Failed to cleanup ${pluginDir}`);
1385
+ }
1386
+ }
1387
+ throw err;
1388
+ } finally {
1389
+ this.installing.delete(pluginId);
1390
+ }
1391
+ }
1392
+ // ── Uninstall ───────────────────────────────────────────────────────
1393
+ async uninstallPlugin(pluginId) {
1394
+ this.validateId(pluginId);
1395
+ if (this.installing.has(pluginId)) {
1396
+ throw new ConflictError(`Plugin "${pluginId}" has an operation in progress`);
1397
+ }
1398
+ const mod = this.findModuleByPluginId(pluginId);
1399
+ if (!mod) {
1400
+ throw new Error(`Plugin "${pluginId}" is not installed`);
1401
+ }
1402
+ const moduleName = mod.name;
1403
+ const idx = this.deps.modules.indexOf(mod);
1404
+ this.installing.add(pluginId);
1405
+ try {
1406
+ await mod.stop?.();
1407
+ this.deps.toolRegistry.removePluginTools(moduleName);
1408
+ if (idx >= 0) this.deps.modules.splice(idx, 1);
1409
+ this.deps.rewireHooks();
1410
+ const pluginDir = join3(PLUGINS_DIR, pluginId);
1411
+ if (existsSync2(pluginDir)) {
1412
+ rmSync2(pluginDir, { recursive: true, force: true });
1413
+ }
1414
+ return { message: `Plugin "${pluginId}" uninstalled successfully` };
1415
+ } finally {
1416
+ this.installing.delete(pluginId);
1417
+ }
1418
+ }
1419
+ // ── Update ──────────────────────────────────────────────────────────
1420
+ async updatePlugin(pluginId) {
1421
+ await this.uninstallPlugin(pluginId);
1422
+ return this.installPlugin(pluginId);
1423
+ }
1424
+ // ── Helpers ─────────────────────────────────────────────────────────
1425
+ /**
1426
+ * Resolve a registry plugin ID to the actual loaded module.
1427
+ * Handles name mismatch: registry id "fragment" → module name "Fragment Marketplace".
1428
+ */
1429
+ findModuleByPluginId(pluginId) {
1430
+ let mod = this.deps.modules.find((m) => m.name === pluginId);
1431
+ if (mod) return mod;
1432
+ const entry = this.cache?.entries.find((e) => e.id === pluginId);
1433
+ if (entry) {
1434
+ mod = this.deps.modules.find((m) => m.name === entry.name);
1435
+ }
1436
+ return mod ?? null;
1437
+ }
1438
+ /**
1439
+ * Recursively download a GitHub directory to a local path.
1440
+ * Uses the GitHub Contents API to list files, then fetches each via raw.githubusercontent.
1441
+ */
1442
+ async downloadDir(remotePath, localDir, depth = 0) {
1443
+ if (depth > 5) throw new Error("Plugin directory too deeply nested");
1444
+ const res = await fetch(`${GITHUB_API_BASE}/${remotePath}`);
1445
+ if (!res.ok) throw new Error(`Failed to list directory "${remotePath}": ${res.status}`);
1446
+ const entries = await res.json();
1447
+ for (const item of entries) {
1448
+ if (!item.name || /[/\\]/.test(item.name) || item.name === ".." || item.name === ".") {
1449
+ throw new Error(`Invalid entry name in plugin directory: "${item.name}"`);
1450
+ }
1451
+ const target = resolve(localDir, item.name);
1452
+ if (!target.startsWith(resolve(PLUGINS_DIR))) {
1453
+ throw new Error(`Path escape detected: ${target}`);
1454
+ }
1455
+ if (item.type === "dir") {
1456
+ mkdirSync2(target, { recursive: true });
1457
+ await this.downloadDir(item.path, target, depth + 1);
1458
+ } else if (item.type === "file" && item.download_url) {
1459
+ const url = new URL(item.download_url);
1460
+ if (!url.hostname.endsWith("githubusercontent.com") && !url.hostname.endsWith("github.com")) {
1461
+ throw new Error(`Untrusted download host: ${url.hostname}`);
1462
+ }
1463
+ const fileRes = await fetch(item.download_url);
1464
+ if (!fileRes.ok) throw new Error(`Failed to download ${item.name}: ${fileRes.status}`);
1465
+ const content = await fileRes.text();
1466
+ writeFileSync3(target, content, "utf-8");
1467
+ }
1468
+ }
1469
+ }
1470
+ validateId(id) {
1471
+ if (!VALID_ID.test(id)) {
1472
+ throw new Error(`Invalid plugin ID: "${id}"`);
1473
+ }
1474
+ }
1475
+ };
1476
+ function normalizeAuthor(author) {
1477
+ if (typeof author === "string") return author;
1478
+ if (author && typeof author === "object" && "name" in author) {
1479
+ return String(author.name);
1480
+ }
1481
+ return "unknown";
1482
+ }
1483
+ var ConflictError = class extends Error {
1484
+ constructor(message) {
1485
+ super(message);
1486
+ this.name = "ConflictError";
1487
+ }
1488
+ };
1489
+
1490
+ // src/webui/routes/marketplace.ts
1491
+ var VALID_ID2 = /^[a-z0-9][a-z0-9-]*$/;
1492
+ var VALID_KEY = /^[a-zA-Z][a-zA-Z0-9_]*$/;
1493
+ function createMarketplaceRoutes(deps) {
1494
+ const app = new Hono11();
1495
+ let service = null;
1496
+ const getService = () => {
1497
+ if (!deps.marketplace) return null;
1498
+ service ??= new MarketplaceService({ ...deps.marketplace, toolRegistry: deps.toolRegistry });
1499
+ return service;
1500
+ };
1501
+ app.get("/", async (c) => {
1502
+ const svc = getService();
1503
+ if (!svc) {
1504
+ return c.json({ success: false, error: "Marketplace not configured" }, 501);
1505
+ }
1506
+ try {
1507
+ const refresh = c.req.query("refresh") === "true";
1508
+ const plugins = await svc.listPlugins(refresh);
1509
+ return c.json({ success: true, data: plugins });
1510
+ } catch (err) {
1511
+ return c.json(
1512
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1513
+ 500
1514
+ );
1515
+ }
1516
+ });
1517
+ app.post("/install", async (c) => {
1518
+ const svc = getService();
1519
+ if (!svc) {
1520
+ return c.json({ success: false, error: "Marketplace not configured" }, 501);
1521
+ }
1522
+ try {
1523
+ const body = await c.req.json();
1524
+ if (!body.id) {
1525
+ return c.json({ success: false, error: "Missing plugin id" }, 400);
1526
+ }
1527
+ const result = await svc.installPlugin(body.id);
1528
+ deps.plugins.length = 0;
1529
+ deps.plugins.push(
1530
+ ...deps.marketplace.modules.filter((m) => deps.toolRegistry.isPluginModule(m.name)).map((m) => ({ name: m.name, version: m.version ?? "0.0.0" }))
1531
+ );
1532
+ return c.json({ success: true, data: result });
1533
+ } catch (err) {
1534
+ const status = err instanceof ConflictError ? 409 : 500;
1535
+ return c.json(
1536
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1537
+ status
1538
+ );
1539
+ }
1540
+ });
1541
+ app.post("/uninstall", async (c) => {
1542
+ const svc = getService();
1543
+ if (!svc) {
1544
+ return c.json({ success: false, error: "Marketplace not configured" }, 501);
1545
+ }
1546
+ try {
1547
+ const body = await c.req.json();
1548
+ if (!body.id) {
1549
+ return c.json({ success: false, error: "Missing plugin id" }, 400);
1550
+ }
1551
+ const result = await svc.uninstallPlugin(body.id);
1552
+ deps.plugins.length = 0;
1553
+ deps.plugins.push(
1554
+ ...deps.marketplace.modules.filter((m) => deps.toolRegistry.isPluginModule(m.name)).map((m) => ({ name: m.name, version: m.version ?? "0.0.0" }))
1555
+ );
1556
+ return c.json({ success: true, data: result });
1557
+ } catch (err) {
1558
+ const status = err instanceof ConflictError ? 409 : 500;
1559
+ return c.json(
1560
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1561
+ status
1562
+ );
1563
+ }
1564
+ });
1565
+ app.post("/update", async (c) => {
1566
+ const svc = getService();
1567
+ if (!svc) {
1568
+ return c.json({ success: false, error: "Marketplace not configured" }, 501);
1569
+ }
1570
+ try {
1571
+ const body = await c.req.json();
1572
+ if (!body.id) {
1573
+ return c.json({ success: false, error: "Missing plugin id" }, 400);
1574
+ }
1575
+ const result = await svc.updatePlugin(body.id);
1576
+ deps.plugins.length = 0;
1577
+ deps.plugins.push(
1578
+ ...deps.marketplace.modules.filter((m) => deps.toolRegistry.isPluginModule(m.name)).map((m) => ({ name: m.name, version: m.version ?? "0.0.0" }))
1579
+ );
1580
+ return c.json({ success: true, data: result });
1581
+ } catch (err) {
1582
+ const status = err instanceof ConflictError ? 409 : 500;
1583
+ return c.json(
1584
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1585
+ status
1586
+ );
1587
+ }
1588
+ });
1589
+ app.get("/secrets/:pluginId", async (c) => {
1590
+ const svc = getService();
1591
+ if (!svc) {
1592
+ return c.json({ success: false, error: "Marketplace not configured" }, 501);
1593
+ }
1594
+ const pluginId = c.req.param("pluginId");
1595
+ if (!VALID_ID2.test(pluginId)) {
1596
+ return c.json({ success: false, error: "Invalid plugin ID" }, 400);
1597
+ }
1598
+ try {
1599
+ const plugins = await svc.listPlugins();
1600
+ const plugin = plugins.find((p) => p.id === pluginId);
1601
+ const declared = plugin?.secrets ?? {};
1602
+ const configured = listPluginSecretKeys(pluginId);
1603
+ return c.json({ success: true, data: { declared, configured } });
1604
+ } catch (err) {
1605
+ return c.json(
1606
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1607
+ 500
1608
+ );
1609
+ }
1610
+ });
1611
+ app.put("/secrets/:pluginId/:key", async (c) => {
1612
+ const pluginId = c.req.param("pluginId");
1613
+ const key = c.req.param("key");
1614
+ if (!VALID_ID2.test(pluginId)) {
1615
+ return c.json({ success: false, error: "Invalid plugin ID" }, 400);
1616
+ }
1617
+ if (!key || !VALID_KEY.test(key)) {
1618
+ return c.json(
1619
+ { success: false, error: "Invalid key name \u2014 use letters, digits, underscores" },
1620
+ 400
1621
+ );
1622
+ }
1623
+ try {
1624
+ const body = await c.req.json();
1625
+ if (typeof body.value !== "string" || !body.value) {
1626
+ return c.json({ success: false, error: "Missing or invalid value" }, 400);
1627
+ }
1628
+ writePluginSecret(pluginId, key, body.value);
1629
+ return c.json({
1630
+ success: true,
1631
+ data: { key, set: true }
1632
+ });
1633
+ } catch (err) {
1634
+ return c.json(
1635
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1636
+ 500
1637
+ );
1638
+ }
1639
+ });
1640
+ app.delete("/secrets/:pluginId/:key", async (c) => {
1641
+ const pluginId = c.req.param("pluginId");
1642
+ const key = c.req.param("key");
1643
+ if (!VALID_ID2.test(pluginId)) {
1644
+ return c.json({ success: false, error: "Invalid plugin ID" }, 400);
1645
+ }
1646
+ if (!key || !VALID_KEY.test(key)) {
1647
+ return c.json(
1648
+ { success: false, error: "Invalid key name \u2014 use letters, digits, underscores" },
1649
+ 400
1650
+ );
1651
+ }
1652
+ try {
1653
+ deletePluginSecret(pluginId, key);
1654
+ return c.json({
1655
+ success: true,
1656
+ data: { key, set: false }
1657
+ });
1658
+ } catch (err) {
1659
+ return c.json(
1660
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1661
+ 500
1662
+ );
1663
+ }
1664
+ });
1665
+ return app;
1666
+ }
1667
+
892
1668
  // src/webui/server.ts
1669
+ var log2 = createLogger("WebUI");
893
1670
  function findWebDist() {
894
1671
  const candidates = [
895
- resolve("dist/web"),
1672
+ resolve2("dist/web"),
896
1673
  // npm start / teleton start (from project root)
897
- resolve("web")
1674
+ resolve2("web")
898
1675
  // fallback
899
1676
  ];
900
1677
  const __dirname = dirname(fileURLToPath(import.meta.url));
901
1678
  candidates.push(
902
- resolve(__dirname, "web"),
1679
+ resolve2(__dirname, "web"),
903
1680
  // dist/web when __dirname = dist/
904
- resolve(__dirname, "../dist/web")
1681
+ resolve2(__dirname, "../dist/web")
905
1682
  // when running with tsx from src/
906
1683
  );
907
1684
  for (const candidate of candidates) {
908
- if (existsSync2(join3(candidate, "index.html"))) {
1685
+ if (existsSync3(join4(candidate, "index.html"))) {
909
1686
  return candidate;
910
1687
  }
911
1688
  }
@@ -918,7 +1695,7 @@ var WebUIServer = class {
918
1695
  authToken;
919
1696
  constructor(deps) {
920
1697
  this.deps = deps;
921
- this.app = new Hono9();
1698
+ this.app = new Hono12();
922
1699
  this.authToken = deps.config.auth_token || generateToken();
923
1700
  this.setupMiddleware();
924
1701
  this.setupRoutes();
@@ -950,7 +1727,7 @@ var WebUIServer = class {
950
1727
  const start = Date.now();
951
1728
  await next();
952
1729
  const duration = Date.now() - start;
953
- console.log(`\u{1F4E1} ${c.req.method} ${c.req.path} \u2192 ${c.res.status} (${duration}ms)`);
1730
+ log2.info(`${c.req.method} ${c.req.path} \u2192 ${c.res.status} (${duration}ms)`);
954
1731
  });
955
1732
  }
956
1733
  this.app.use(
@@ -1023,11 +1800,14 @@ var WebUIServer = class {
1023
1800
  this.app.route("/api/memory", createMemoryRoutes(this.deps));
1024
1801
  this.app.route("/api/soul", createSoulRoutes(this.deps));
1025
1802
  this.app.route("/api/plugins", createPluginsRoutes(this.deps));
1803
+ this.app.route("/api/mcp", createMcpRoutes(this.deps));
1026
1804
  this.app.route("/api/workspace", createWorkspaceRoutes(this.deps));
1027
1805
  this.app.route("/api/tasks", createTasksRoutes(this.deps));
1806
+ this.app.route("/api/config", createConfigRoutes(this.deps));
1807
+ this.app.route("/api/marketplace", createMarketplaceRoutes(this.deps));
1028
1808
  const webDist = findWebDist();
1029
1809
  if (webDist) {
1030
- const indexHtml = readFileSync3(join3(webDist, "index.html"), "utf-8");
1810
+ const indexHtml = readFileSync3(join4(webDist, "index.html"), "utf-8");
1031
1811
  const mimeTypes = {
1032
1812
  js: "application/javascript",
1033
1813
  css: "text/css",
@@ -1041,9 +1821,9 @@ var WebUIServer = class {
1041
1821
  woff: "font/woff"
1042
1822
  };
1043
1823
  this.app.get("*", (c) => {
1044
- const filePath = resolve(join3(webDist, c.req.path));
1824
+ const filePath = resolve2(join4(webDist, c.req.path));
1045
1825
  const rel = relative2(webDist, filePath);
1046
- if (rel.startsWith("..") || resolve(filePath) !== filePath) {
1826
+ if (rel.startsWith("..") || resolve2(filePath) !== filePath) {
1047
1827
  return c.html(indexHtml);
1048
1828
  }
1049
1829
  try {
@@ -1062,7 +1842,7 @@ var WebUIServer = class {
1062
1842
  });
1063
1843
  }
1064
1844
  this.app.onError((err, c) => {
1065
- console.error("WebUI error:", err);
1845
+ log2.error({ err }, "WebUI error");
1066
1846
  return c.json(
1067
1847
  {
1068
1848
  success: false,
@@ -1073,7 +1853,7 @@ var WebUIServer = class {
1073
1853
  });
1074
1854
  }
1075
1855
  async start() {
1076
- return new Promise((resolve2, reject) => {
1856
+ return new Promise((resolve3, reject) => {
1077
1857
  try {
1078
1858
  logInterceptor.install();
1079
1859
  this.server = serve(
@@ -1084,14 +1864,10 @@ var WebUIServer = class {
1084
1864
  },
1085
1865
  (info) => {
1086
1866
  const url = `http://${info.address}:${info.port}`;
1087
- console.log(`
1088
- \u{1F310} WebUI server running`);
1089
- console.log(` URL: ${url}/auth/exchange?token=${this.authToken}`);
1090
- console.log(
1091
- ` Token: ${maskToken(this.authToken)} (use Bearer header for API access)
1092
- `
1093
- );
1094
- resolve2();
1867
+ log2.info(`WebUI server running`);
1868
+ log2.info(`URL: ${url}/auth/exchange?token=${this.authToken}`);
1869
+ log2.info(`Token: ${maskToken(this.authToken)} (use Bearer header for API access)`);
1870
+ resolve3();
1095
1871
  }
1096
1872
  );
1097
1873
  } catch (error) {
@@ -1102,11 +1878,11 @@ var WebUIServer = class {
1102
1878
  }
1103
1879
  async stop() {
1104
1880
  if (this.server) {
1105
- return new Promise((resolve2) => {
1881
+ return new Promise((resolve3) => {
1106
1882
  this.server.close(() => {
1107
1883
  logInterceptor.uninstall();
1108
- console.log("\u{1F310} WebUI server stopped");
1109
- resolve2();
1884
+ log2.info("WebUI server stopped");
1885
+ resolve3();
1110
1886
  });
1111
1887
  });
1112
1888
  }