teleton 0.7.3 → 0.7.5

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 (36) hide show
  1. package/README.md +64 -35
  2. package/dist/{chunk-RBU6JXD3.js → chunk-2GLHOJ5C.js} +268 -59
  3. package/dist/chunk-5UVXJMOX.js +292 -0
  4. package/dist/{chunk-DAMCNMYL.js → chunk-AVDWXYQ7.js} +73 -28
  5. package/dist/{chunk-RMLQS3X6.js → chunk-CB2Y45HA.js} +106 -1
  6. package/dist/{chunk-5PLZ3KSO.js → chunk-DMXTIRUW.js} +5 -6
  7. package/dist/{chunk-A4GCOHCE.js → chunk-G2LLMJXJ.js} +1751 -116
  8. package/dist/{chunk-FNV5FF35.js → chunk-LCCVZ4D2.js} +32 -16
  9. package/dist/{chunk-BU453WX4.js → chunk-OGMVWDVU.js} +4172 -3792
  10. package/dist/chunk-QOQWUUA4.js +158 -0
  11. package/dist/{chunk-4DU3C27M.js → chunk-R4YSJ4EY.js} +5 -1
  12. package/dist/{chunk-XBKSS6DM.js → chunk-VFA7QMCZ.js} +5 -3
  13. package/dist/{chunk-VAUJSSD3.js → chunk-XQUHC3JZ.js} +1 -1
  14. package/dist/{chunk-RO62LO6Z.js → chunk-YP25WTQK.js} +2 -0
  15. package/dist/cli/index.js +234 -289
  16. package/dist/{client-RTNALK7W.js → client-O37XDCJB.js} +4 -5
  17. package/dist/index.js +12 -13
  18. package/dist/{memory-5SS3Q5EA.js → memory-KQALFUV3.js} +6 -7
  19. package/dist/{migrate-M7SJMDOL.js → migrate-UV3WEL5D.js} +6 -7
  20. package/dist/{server-FOC5P7U6.js → server-BHHJGUDF.js} +324 -16
  21. package/dist/{setup-server-BVVD2PR6.js → setup-server-G7UG2DI3.js} +26 -118
  22. package/dist/store-H4XPNGC2.js +34 -0
  23. package/dist/{task-dependency-resolver-WKZWJLLM.js → task-dependency-resolver-VMEVJRPO.js} +2 -2
  24. package/dist/{task-executor-PD3H4MLO.js → task-executor-WWSPBJ4V.js} +1 -1
  25. package/dist/{tool-index-MIVK3D7H.js → tool-index-2KH3OB6X.js} +5 -5
  26. package/dist/web/assets/index-BrVqauzj.css +1 -0
  27. package/dist/web/assets/index-Bx8JW3gV.js +72 -0
  28. package/dist/web/assets/{index.es-7MTSV5SL.js → index.es-Pet5-M13.js} +1 -1
  29. package/dist/web/index.html +2 -2
  30. package/package.json +3 -3
  31. package/dist/chunk-JQDLW7IE.js +0 -107
  32. package/dist/chunk-UCN6TI25.js +0 -143
  33. package/dist/web/assets/index-By_fs4Jl.js +0 -72
  34. package/dist/web/assets/index-CRDIf07k.css +0 -1
  35. package/scripts/patch-gramjs.sh +0 -46
  36. package/scripts/postinstall.mjs +0 -16
@@ -8,12 +8,11 @@ import {
8
8
  loadContextFromTranscript,
9
9
  registerCocoonModels,
10
10
  registerLocalModels
11
- } from "./chunk-5PLZ3KSO.js";
12
- import "./chunk-JQDLW7IE.js";
13
- import "./chunk-RMLQS3X6.js";
11
+ } from "./chunk-DMXTIRUW.js";
12
+ import "./chunk-CB2Y45HA.js";
14
13
  import "./chunk-OCLG5GKI.js";
15
- import "./chunk-VAUJSSD3.js";
16
- import "./chunk-4DU3C27M.js";
14
+ import "./chunk-XQUHC3JZ.js";
15
+ import "./chunk-R4YSJ4EY.js";
17
16
  import "./chunk-EYWNOHMJ.js";
18
17
  import "./chunk-RCMD3U65.js";
19
18
  import "./chunk-QGM4M3NI.js";
package/dist/index.js CHANGED
@@ -1,24 +1,23 @@
1
1
  import {
2
2
  TeletonApp,
3
3
  main
4
- } from "./chunk-BU453WX4.js";
4
+ } from "./chunk-OGMVWDVU.js";
5
5
  import "./chunk-WIKM24GZ.js";
6
6
  import "./chunk-U7FQYCBQ.js";
7
- import "./chunk-A4GCOHCE.js";
8
- import "./chunk-DAMCNMYL.js";
7
+ import "./chunk-G2LLMJXJ.js";
8
+ import "./chunk-AVDWXYQ7.js";
9
+ import "./chunk-5UVXJMOX.js";
9
10
  import "./chunk-TSKJCWQQ.js";
10
11
  import "./chunk-XBE4JB7C.js";
11
- import "./chunk-5PLZ3KSO.js";
12
- import "./chunk-JQDLW7IE.js";
13
- import "./chunk-RMLQS3X6.js";
12
+ import "./chunk-DMXTIRUW.js";
13
+ import "./chunk-CB2Y45HA.js";
14
14
  import "./chunk-OCLG5GKI.js";
15
- import "./chunk-RBU6JXD3.js";
16
- import "./chunk-UCN6TI25.js";
17
- import "./chunk-FNV5FF35.js";
18
- import "./chunk-XBKSS6DM.js";
19
- import "./chunk-RO62LO6Z.js";
20
- import "./chunk-VAUJSSD3.js";
21
- import "./chunk-4DU3C27M.js";
15
+ import "./chunk-2GLHOJ5C.js";
16
+ import "./chunk-LCCVZ4D2.js";
17
+ import "./chunk-VFA7QMCZ.js";
18
+ import "./chunk-YP25WTQK.js";
19
+ import "./chunk-XQUHC3JZ.js";
20
+ import "./chunk-R4YSJ4EY.js";
22
21
  import "./chunk-EYWNOHMJ.js";
23
22
  import "./chunk-RCMD3U65.js";
24
23
  import "./chunk-NUGDTPE4.js";
@@ -16,8 +16,7 @@ import {
16
16
  initializeMemory,
17
17
  runMigrations,
18
18
  setSchemaVersion
19
- } from "./chunk-RBU6JXD3.js";
20
- import "./chunk-UCN6TI25.js";
19
+ } from "./chunk-2GLHOJ5C.js";
21
20
  import {
22
21
  AnthropicEmbeddingProvider,
23
22
  CachedEmbeddingProvider,
@@ -27,11 +26,11 @@ import {
27
26
  deserializeEmbedding,
28
27
  hashText,
29
28
  serializeEmbedding
30
- } from "./chunk-FNV5FF35.js";
31
- import "./chunk-XBKSS6DM.js";
32
- import "./chunk-RO62LO6Z.js";
33
- import "./chunk-VAUJSSD3.js";
34
- import "./chunk-4DU3C27M.js";
29
+ } from "./chunk-LCCVZ4D2.js";
30
+ import "./chunk-VFA7QMCZ.js";
31
+ import "./chunk-YP25WTQK.js";
32
+ import "./chunk-XQUHC3JZ.js";
33
+ import "./chunk-R4YSJ4EY.js";
35
34
  import "./chunk-EYWNOHMJ.js";
36
35
  import "./chunk-RCMD3U65.js";
37
36
  import {
@@ -1,12 +1,11 @@
1
1
  import {
2
2
  getDatabase
3
- } from "./chunk-RBU6JXD3.js";
4
- import "./chunk-UCN6TI25.js";
5
- import "./chunk-FNV5FF35.js";
6
- import "./chunk-XBKSS6DM.js";
7
- import "./chunk-RO62LO6Z.js";
8
- import "./chunk-VAUJSSD3.js";
9
- import "./chunk-4DU3C27M.js";
3
+ } from "./chunk-2GLHOJ5C.js";
4
+ import "./chunk-LCCVZ4D2.js";
5
+ import "./chunk-VFA7QMCZ.js";
6
+ import "./chunk-YP25WTQK.js";
7
+ import "./chunk-XQUHC3JZ.js";
8
+ import "./chunk-R4YSJ4EY.js";
10
9
  import {
11
10
  TELETON_ROOT
12
11
  } from "./chunk-EYWNOHMJ.js";
@@ -1,3 +1,6 @@
1
+ import {
2
+ getModelsForProvider
3
+ } from "./chunk-QOQWUUA4.js";
1
4
  import {
2
5
  CONFIGURABLE_KEYS,
3
6
  WorkspaceSecurityError,
@@ -7,6 +10,7 @@ import {
7
10
  deletePluginSecret,
8
11
  ensurePluginDeps,
9
12
  getNestedValue,
13
+ getTokenUsage,
10
14
  listPluginSecretKeys,
11
15
  readRawConfig,
12
16
  setNestedValue,
@@ -16,18 +20,31 @@ import {
16
20
  validateWritePath,
17
21
  writePluginSecret,
18
22
  writeRawConfig
19
- } from "./chunk-A4GCOHCE.js";
20
- import "./chunk-DAMCNMYL.js";
23
+ } from "./chunk-G2LLMJXJ.js";
24
+ import {
25
+ invalidateEndpointCache,
26
+ invalidateTonClientCache,
27
+ setToncenterApiKey
28
+ } from "./chunk-AVDWXYQ7.js";
29
+ import "./chunk-5UVXJMOX.js";
21
30
  import "./chunk-TSKJCWQQ.js";
22
31
  import {
23
32
  getErrorMessage
24
33
  } from "./chunk-XBE4JB7C.js";
25
- import "./chunk-RMLQS3X6.js";
26
- import "./chunk-UCN6TI25.js";
27
- import "./chunk-XBKSS6DM.js";
28
- import "./chunk-RO62LO6Z.js";
29
- import "./chunk-VAUJSSD3.js";
30
- import "./chunk-4DU3C27M.js";
34
+ import "./chunk-DMXTIRUW.js";
35
+ import {
36
+ getProviderMetadata,
37
+ validateApiKeyFormat
38
+ } from "./chunk-CB2Y45HA.js";
39
+ import "./chunk-OCLG5GKI.js";
40
+ import "./chunk-2GLHOJ5C.js";
41
+ import "./chunk-LCCVZ4D2.js";
42
+ import {
43
+ setTonapiKey
44
+ } from "./chunk-VFA7QMCZ.js";
45
+ import "./chunk-YP25WTQK.js";
46
+ import "./chunk-XQUHC3JZ.js";
47
+ import "./chunk-R4YSJ4EY.js";
31
48
  import {
32
49
  WORKSPACE_PATHS,
33
50
  WORKSPACE_ROOT
@@ -128,7 +145,8 @@ function createStatusRoutes(deps) {
128
145
  model: config.agent.model,
129
146
  provider: config.agent.provider,
130
147
  sessionCount: sessionCountRow?.count ?? 0,
131
- toolCount: deps.toolRegistry.getAll().length
148
+ toolCount: deps.toolRegistry.getAll().length,
149
+ tokenUsage: getTokenUsage()
132
150
  };
133
151
  const response = {
134
152
  success: true,
@@ -214,7 +232,7 @@ function createToolsRoutes(deps) {
214
232
  try {
215
233
  const config = deps.agent.getConfig();
216
234
  const body = await c.req.json();
217
- const { enabled, topK } = body;
235
+ const { enabled, topK, alwaysInclude, skipUnlimitedProviders } = body;
218
236
  if (enabled !== void 0) {
219
237
  config.tool_rag.enabled = enabled;
220
238
  }
@@ -224,6 +242,28 @@ function createToolsRoutes(deps) {
224
242
  }
225
243
  config.tool_rag.top_k = topK;
226
244
  }
245
+ if (alwaysInclude !== void 0) {
246
+ if (!Array.isArray(alwaysInclude) || alwaysInclude.some((s) => typeof s !== "string" || s.length === 0)) {
247
+ return c.json(
248
+ { success: false, error: "alwaysInclude must be an array of non-empty strings" },
249
+ 400
250
+ );
251
+ }
252
+ config.tool_rag.always_include = alwaysInclude;
253
+ }
254
+ if (skipUnlimitedProviders !== void 0) {
255
+ config.tool_rag.skip_unlimited_providers = skipUnlimitedProviders;
256
+ }
257
+ const raw = readRawConfig(deps.configPath);
258
+ setNestedValue(raw, "tool_rag.enabled", config.tool_rag.enabled);
259
+ setNestedValue(raw, "tool_rag.top_k", config.tool_rag.top_k);
260
+ setNestedValue(raw, "tool_rag.always_include", config.tool_rag.always_include);
261
+ setNestedValue(
262
+ raw,
263
+ "tool_rag.skip_unlimited_providers",
264
+ config.tool_rag.skip_unlimited_providers
265
+ );
266
+ writeRawConfig(raw, deps.configPath);
227
267
  const toolIndex = deps.toolRegistry.getToolIndex();
228
268
  const response = {
229
269
  success: true,
@@ -231,7 +271,9 @@ function createToolsRoutes(deps) {
231
271
  enabled: config.tool_rag.enabled,
232
272
  indexed: toolIndex?.isIndexed ?? false,
233
273
  topK: config.tool_rag.top_k,
234
- totalTools: deps.toolRegistry.count
274
+ totalTools: deps.toolRegistry.count,
275
+ alwaysInclude: config.tool_rag.always_include,
276
+ skipUnlimitedProviders: config.tool_rag.skip_unlimited_providers
235
277
  }
236
278
  };
237
279
  return c.json(response);
@@ -509,6 +551,69 @@ function createMemoryRoutes(deps) {
509
551
  return c.json(response, 500);
510
552
  }
511
553
  });
554
+ app.get("/sources/:sourceKey", (c) => {
555
+ try {
556
+ const sourceKey = decodeURIComponent(c.req.param("sourceKey"));
557
+ const rows = deps.memory.db.prepare(
558
+ `
559
+ SELECT id, text, source, path, start_line, end_line, updated_at
560
+ FROM knowledge
561
+ WHERE COALESCE(path, source) = ?
562
+ ORDER BY start_line ASC, updated_at DESC
563
+ `
564
+ ).all(sourceKey);
565
+ const chunks = rows.map((row) => ({
566
+ id: row.id,
567
+ text: row.text,
568
+ source: row.path || row.source,
569
+ startLine: row.start_line,
570
+ endLine: row.end_line,
571
+ updatedAt: row.updated_at
572
+ }));
573
+ const response = {
574
+ success: true,
575
+ data: chunks
576
+ };
577
+ return c.json(response);
578
+ } catch (error) {
579
+ const response = {
580
+ success: false,
581
+ error: getErrorMessage(error)
582
+ };
583
+ return c.json(response, 500);
584
+ }
585
+ });
586
+ app.get("/sources", (c) => {
587
+ try {
588
+ const rows = deps.memory.db.prepare(
589
+ `
590
+ SELECT
591
+ COALESCE(path, source) AS source_key,
592
+ COUNT(*) AS entry_count,
593
+ MAX(updated_at) AS last_updated
594
+ FROM knowledge
595
+ GROUP BY source_key
596
+ ORDER BY last_updated DESC
597
+ `
598
+ ).all();
599
+ const sources = rows.map((row) => ({
600
+ source: row.source_key,
601
+ entryCount: row.entry_count,
602
+ lastUpdated: row.last_updated
603
+ }));
604
+ const response = {
605
+ success: true,
606
+ data: sources
607
+ };
608
+ return c.json(response);
609
+ } catch (error) {
610
+ const response = {
611
+ success: false,
612
+ error: getErrorMessage(error)
613
+ };
614
+ return c.json(response, 500);
615
+ }
616
+ });
512
617
  return app;
513
618
  }
514
619
 
@@ -750,6 +855,16 @@ function errorResponse(c, error, status = 500) {
750
855
  const response = { success: false, error: message };
751
856
  return c.json(response, code);
752
857
  }
858
+ var IMAGE_MIME_TYPES = {
859
+ ".png": "image/png",
860
+ ".jpg": "image/jpeg",
861
+ ".jpeg": "image/jpeg",
862
+ ".gif": "image/gif",
863
+ ".webp": "image/webp",
864
+ ".svg": "image/svg+xml",
865
+ ".bmp": "image/bmp",
866
+ ".ico": "image/x-icon"
867
+ };
753
868
  function getWorkspaceStats(dir) {
754
869
  let files = 0;
755
870
  let size = 0;
@@ -822,6 +937,45 @@ function createWorkspaceRoutes(_deps) {
822
937
  return errorResponse(c, error);
823
938
  }
824
939
  });
940
+ app.get("/raw", (c) => {
941
+ try {
942
+ const path = c.req.query("path");
943
+ if (!path) {
944
+ const response = { success: false, error: "Missing 'path' query parameter" };
945
+ return c.json(response, 400);
946
+ }
947
+ const validated = validateReadPath(path);
948
+ const mime = IMAGE_MIME_TYPES[validated.extension];
949
+ if (!mime) {
950
+ const response = {
951
+ success: false,
952
+ error: "Unsupported file type for raw preview"
953
+ };
954
+ return c.json(response, 415);
955
+ }
956
+ const stats = statSync(validated.absolutePath);
957
+ if (stats.size > 5 * 1024 * 1024) {
958
+ const response = {
959
+ success: false,
960
+ error: "Image too large for preview (max 5MB)"
961
+ };
962
+ return c.json(response, 413);
963
+ }
964
+ const buffer = readFileSync2(validated.absolutePath);
965
+ const headers = {
966
+ "Content-Type": mime,
967
+ "Content-Length": String(buffer.byteLength),
968
+ "Content-Disposition": "inline",
969
+ "Cache-Control": "private, max-age=60"
970
+ };
971
+ if (validated.extension === ".svg") {
972
+ headers["Content-Security-Policy"] = "sandbox";
973
+ }
974
+ return c.body(buffer, 200, headers);
975
+ } catch (error) {
976
+ return errorResponse(c, error);
977
+ }
978
+ });
825
979
  app.get("/read", (c) => {
826
980
  try {
827
981
  const path = c.req.query("path");
@@ -960,6 +1114,7 @@ function createWorkspaceRoutes(_deps) {
960
1114
  // src/webui/routes/tasks.ts
961
1115
  import { Hono as Hono9 } from "hono";
962
1116
  var VALID_STATUSES = ["pending", "in_progress", "done", "failed", "cancelled"];
1117
+ var TERMINAL_STATUSES = ["done", "failed", "cancelled"];
963
1118
  function createTasksRoutes(deps) {
964
1119
  const app = new Hono9();
965
1120
  function store() {
@@ -1032,6 +1187,32 @@ function createTasksRoutes(deps) {
1032
1187
  return c.json(response, 500);
1033
1188
  }
1034
1189
  });
1190
+ app.post("/clean", async (c) => {
1191
+ try {
1192
+ const body = await c.req.json().catch(() => ({ status: void 0 }));
1193
+ const status = body.status;
1194
+ if (!status || !TERMINAL_STATUSES.includes(status)) {
1195
+ const response2 = {
1196
+ success: false,
1197
+ error: `Invalid status. Must be one of: ${TERMINAL_STATUSES.join(", ")}`
1198
+ };
1199
+ return c.json(response2, 400);
1200
+ }
1201
+ const tasks = store().listTasks({ status });
1202
+ let deleted = 0;
1203
+ for (const t of tasks) {
1204
+ if (store().deleteTask(t.id)) deleted++;
1205
+ }
1206
+ const response = { success: true, data: { deleted } };
1207
+ return c.json(response);
1208
+ } catch (error) {
1209
+ const response = {
1210
+ success: false,
1211
+ error: getErrorMessage(error)
1212
+ };
1213
+ return c.json(response, 500);
1214
+ }
1215
+ });
1035
1216
  app.post("/clean-done", (c) => {
1036
1217
  try {
1037
1218
  const doneTasks = store().listTasks({ status: "done" });
@@ -1071,23 +1252,36 @@ function createTasksRoutes(deps) {
1071
1252
 
1072
1253
  // src/webui/routes/config.ts
1073
1254
  import { Hono as Hono10 } from "hono";
1255
+ var CONFIG_SIDE_EFFECTS = {
1256
+ tonapi_key: (v) => setTonapiKey(v),
1257
+ toncenter_api_key: (v) => {
1258
+ setToncenterApiKey(v);
1259
+ invalidateEndpointCache();
1260
+ invalidateTonClientCache();
1261
+ }
1262
+ };
1074
1263
  function createConfigRoutes(deps) {
1075
1264
  const app = new Hono10();
1076
1265
  app.get("/", (c) => {
1077
1266
  try {
1078
1267
  const raw = readRawConfig(deps.configPath);
1079
1268
  const data = Object.entries(CONFIGURABLE_KEYS).map(([key, meta]) => {
1080
- const value = getNestedValue(raw, key);
1081
- const isSet = value != null && value !== "";
1269
+ const rawValue = getNestedValue(raw, key);
1270
+ const isSet = rawValue != null && rawValue !== "" && !(Array.isArray(rawValue) && rawValue.length === 0);
1271
+ const displayValue = isSet ? meta.type === "array" ? JSON.stringify(rawValue) : meta.mask(String(rawValue)) : null;
1082
1272
  return {
1083
1273
  key,
1274
+ label: meta.label,
1084
1275
  set: isSet,
1085
- value: isSet ? meta.mask(String(value)) : null,
1276
+ value: displayValue,
1086
1277
  sensitive: meta.sensitive,
1087
1278
  type: meta.type,
1088
1279
  category: meta.category,
1089
1280
  description: meta.description,
1090
- ...meta.options ? { options: meta.options } : {}
1281
+ hotReload: meta.hotReload,
1282
+ ...meta.options ? { options: meta.options } : {},
1283
+ ...meta.optionLabels ? { optionLabels: meta.optionLabels } : {},
1284
+ ...meta.itemType ? { itemType: meta.itemType } : {}
1091
1285
  };
1092
1286
  });
1093
1287
  const response = { success: true, data };
@@ -1119,6 +1313,56 @@ function createConfigRoutes(deps) {
1119
1313
  return c.json({ success: false, error: "Invalid JSON body" }, 400);
1120
1314
  }
1121
1315
  const value = body.value;
1316
+ if (meta.type === "array") {
1317
+ if (!Array.isArray(value)) {
1318
+ return c.json(
1319
+ { success: false, error: "Value must be an array for array keys" },
1320
+ 400
1321
+ );
1322
+ }
1323
+ for (let i = 0; i < value.length; i++) {
1324
+ const itemStr = String(value[i]);
1325
+ const itemErr = meta.validate(itemStr);
1326
+ if (itemErr) {
1327
+ return c.json(
1328
+ {
1329
+ success: false,
1330
+ error: `Invalid item at index ${i} for ${key}: ${itemErr}`
1331
+ },
1332
+ 400
1333
+ );
1334
+ }
1335
+ }
1336
+ try {
1337
+ const parsed = value.map((item) => meta.parse(String(item)));
1338
+ const raw = readRawConfig(deps.configPath);
1339
+ setNestedValue(raw, key, parsed);
1340
+ writeRawConfig(raw, deps.configPath);
1341
+ const runtimeConfig = deps.agent.getConfig();
1342
+ setNestedValue(runtimeConfig, key, parsed);
1343
+ const result = {
1344
+ key,
1345
+ label: meta.label,
1346
+ set: parsed.length > 0,
1347
+ value: JSON.stringify(parsed),
1348
+ sensitive: meta.sensitive,
1349
+ type: meta.type,
1350
+ category: meta.category,
1351
+ description: meta.description,
1352
+ hotReload: meta.hotReload,
1353
+ ...meta.itemType ? { itemType: meta.itemType } : {}
1354
+ };
1355
+ return c.json({ success: true, data: result });
1356
+ } catch (err) {
1357
+ return c.json(
1358
+ {
1359
+ success: false,
1360
+ error: err instanceof Error ? err.message : String(err)
1361
+ },
1362
+ 500
1363
+ );
1364
+ }
1365
+ }
1122
1366
  if (value == null || typeof value !== "string") {
1123
1367
  return c.json(
1124
1368
  { success: false, error: "Missing or invalid 'value' field" },
@@ -1136,17 +1380,32 @@ function createConfigRoutes(deps) {
1136
1380
  const parsed = meta.parse(value);
1137
1381
  const raw = readRawConfig(deps.configPath);
1138
1382
  setNestedValue(raw, key, parsed);
1383
+ if (key === "telegram.owner_id" && typeof parsed === "number") {
1384
+ const adminIds = getNestedValue(raw, "telegram.admin_ids") ?? [];
1385
+ if (!adminIds.includes(parsed)) {
1386
+ setNestedValue(raw, "telegram.admin_ids", [...adminIds, parsed]);
1387
+ }
1388
+ }
1139
1389
  writeRawConfig(raw, deps.configPath);
1140
1390
  const runtimeConfig = deps.agent.getConfig();
1141
1391
  setNestedValue(runtimeConfig, key, parsed);
1392
+ CONFIG_SIDE_EFFECTS[key]?.(parsed);
1393
+ if (key === "telegram.owner_id" && typeof parsed === "number") {
1394
+ const rtAdminIds = getNestedValue(runtimeConfig, "telegram.admin_ids") ?? [];
1395
+ if (!rtAdminIds.includes(parsed)) {
1396
+ setNestedValue(runtimeConfig, "telegram.admin_ids", [...rtAdminIds, parsed]);
1397
+ }
1398
+ }
1142
1399
  const result = {
1143
1400
  key,
1401
+ label: meta.label,
1144
1402
  set: true,
1145
1403
  value: meta.mask(value),
1146
1404
  sensitive: meta.sensitive,
1147
1405
  type: meta.type,
1148
1406
  category: meta.category,
1149
1407
  description: meta.description,
1408
+ hotReload: meta.hotReload,
1150
1409
  ...meta.options ? { options: meta.options } : {}
1151
1410
  };
1152
1411
  return c.json({ success: true, data: result });
@@ -1176,15 +1435,19 @@ function createConfigRoutes(deps) {
1176
1435
  writeRawConfig(raw, deps.configPath);
1177
1436
  const runtimeConfig = deps.agent.getConfig();
1178
1437
  deleteNestedValue(runtimeConfig, key);
1438
+ CONFIG_SIDE_EFFECTS[key]?.(void 0);
1179
1439
  const result = {
1180
1440
  key,
1441
+ label: meta.label,
1181
1442
  set: false,
1182
1443
  value: null,
1183
1444
  sensitive: meta.sensitive,
1184
1445
  type: meta.type,
1185
1446
  category: meta.category,
1186
1447
  description: meta.description,
1187
- ...meta.options ? { options: meta.options } : {}
1448
+ hotReload: meta.hotReload,
1449
+ ...meta.options ? { options: meta.options } : {},
1450
+ ...meta.itemType ? { itemType: meta.itemType } : {}
1188
1451
  };
1189
1452
  return c.json({ success: true, data: result });
1190
1453
  } catch (err) {
@@ -1194,6 +1457,51 @@ function createConfigRoutes(deps) {
1194
1457
  );
1195
1458
  }
1196
1459
  });
1460
+ app.get("/models/:provider", (c) => {
1461
+ const provider = c.req.param("provider");
1462
+ const models = getModelsForProvider(provider);
1463
+ return c.json({ success: true, data: models });
1464
+ });
1465
+ app.get("/provider-meta/:provider", (c) => {
1466
+ const provider = c.req.param("provider");
1467
+ try {
1468
+ const meta = getProviderMetadata(provider);
1469
+ const needsKey = provider !== "claude-code" && provider !== "cocoon" && provider !== "local";
1470
+ return c.json({
1471
+ success: true,
1472
+ data: {
1473
+ needsKey,
1474
+ keyHint: meta.keyHint,
1475
+ keyPrefix: meta.keyPrefix,
1476
+ consoleUrl: meta.consoleUrl,
1477
+ displayName: meta.displayName
1478
+ }
1479
+ });
1480
+ } catch (err) {
1481
+ return c.json(
1482
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1483
+ 400
1484
+ );
1485
+ }
1486
+ });
1487
+ app.post("/validate-api-key", async (c) => {
1488
+ try {
1489
+ const body = await c.req.json();
1490
+ if (!body.provider || !body.apiKey) {
1491
+ return c.json({ success: false, error: "Missing provider or apiKey" }, 400);
1492
+ }
1493
+ const error = validateApiKeyFormat(body.provider, body.apiKey);
1494
+ return c.json({
1495
+ success: true,
1496
+ data: { valid: !error, error: error ?? null }
1497
+ });
1498
+ } catch (err) {
1499
+ return c.json(
1500
+ { success: false, error: err instanceof Error ? err.message : String(err) },
1501
+ 400
1502
+ );
1503
+ }
1504
+ });
1197
1505
  return app;
1198
1506
  }
1199
1507