@zythum02/fello-server 0.1.28 → 0.1.30

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 (56) hide show
  1. package/README.md +6 -0
  2. package/out/renderer/assets/{_baseUniq-C01H-TTl.js → _baseUniq-DLyIzzT_.js} +1 -1
  3. package/out/renderer/assets/{arc-BXqrFYj-.js → arc-Zxienobv.js} +1 -1
  4. package/out/renderer/assets/{architectureDiagram-Q4EWVU46-DlFz_4yO.js → architectureDiagram-Q4EWVU46-DLh2t5mE.js} +5 -5
  5. package/out/renderer/assets/{blockDiagram-DXYQGD6D-B_jYpKkP.js → blockDiagram-DXYQGD6D-CtAylmzM.js} +6 -6
  6. package/out/renderer/assets/{c4Diagram-AHTNJAMY-DWRXz6-S.js → c4Diagram-AHTNJAMY-ntNDbqWz.js} +2 -2
  7. package/out/renderer/assets/{channel-iMh-jYBg.js → channel-Z4VgLguf.js} +1 -1
  8. package/out/renderer/assets/{chunk-4BX2VUAB-CDXuLHIo.js → chunk-4BX2VUAB-BQWjQSCS.js} +1 -1
  9. package/out/renderer/assets/{chunk-4TB4RGXK-Bs-z-5kA.js → chunk-4TB4RGXK-DQ4qdRMQ.js} +5 -5
  10. package/out/renderer/assets/{chunk-55IACEB6-DBmLw2WJ.js → chunk-55IACEB6-CLBQrnT7.js} +1 -1
  11. package/out/renderer/assets/{chunk-EDXVE4YY-BhsDT27O.js → chunk-EDXVE4YY-ycJtaU57.js} +1 -1
  12. package/out/renderer/assets/{chunk-FMBD7UC4-C_qJYK5q.js → chunk-FMBD7UC4-D3ZuAVef.js} +1 -1
  13. package/out/renderer/assets/{chunk-OYMX7WX6-DIh7s3uw.js → chunk-OYMX7WX6-CAHx1d_X.js} +3 -3
  14. package/out/renderer/assets/{chunk-QZHKN3VN-CYa45CSJ.js → chunk-QZHKN3VN-eKV97jed.js} +1 -1
  15. package/out/renderer/assets/{chunk-YZCP3GAM-BeF07Xv_.js → chunk-YZCP3GAM-DkaxW5D7.js} +1 -1
  16. package/out/renderer/assets/{classDiagram-6PBFFD2Q-B12_6ldG.js → classDiagram-6PBFFD2Q-CpuoT-Jx.js} +6 -6
  17. package/out/renderer/assets/{classDiagram-v2-HSJHXN6E-B12_6ldG.js → classDiagram-v2-HSJHXN6E-CpuoT-Jx.js} +6 -6
  18. package/out/renderer/assets/{clone-1GZJZPn3.js → clone-C_55Gq29.js} +1 -1
  19. package/out/renderer/assets/{cose-bilkent-S5V4N54A-CuSnz9Ro.js → cose-bilkent-S5V4N54A-DqTN-_zY.js} +1 -1
  20. package/out/renderer/assets/{dagre-KV5264BT-LHPLEy9g.js → dagre-KV5264BT-NIF-wzAb.js} +6 -6
  21. package/out/renderer/assets/{diagram-5BDNPKRD-DWjP3Jzr.js → diagram-5BDNPKRD-Cln0uWD9.js} +6 -6
  22. package/out/renderer/assets/{diagram-G4DWMVQ6-CM3uASto.js → diagram-G4DWMVQ6-BlnD83ee.js} +6 -6
  23. package/out/renderer/assets/{diagram-MMDJMWI5-X5oh5RON.js → diagram-MMDJMWI5-B7PHcRvh.js} +5 -5
  24. package/out/renderer/assets/{diagram-TYMM5635-iO4Xohj9.js → diagram-TYMM5635-B-O4bvMT.js} +5 -5
  25. package/out/renderer/assets/{erDiagram-SMLLAGMA-4xwE7Ysd.js → erDiagram-SMLLAGMA-1ZgzBjAm.js} +4 -4
  26. package/out/renderer/assets/{flowDiagram-DWJPFMVM-DoIqEJWm.js → flowDiagram-DWJPFMVM-CYt7MUO6.js} +6 -6
  27. package/out/renderer/assets/{ganttDiagram-T4ZO3ILL-C48vXSUK.js → ganttDiagram-T4ZO3ILL-nHro8zN9.js} +2 -2
  28. package/out/renderer/assets/{gitGraphDiagram-UUTBAWPF-D3YiiEhK.js → gitGraphDiagram-UUTBAWPF-4nzL1En3.js} +6 -6
  29. package/out/renderer/assets/{graph-CYPfPCZi.js → graph-9FzrK4FU.js} +2 -2
  30. package/out/renderer/assets/{highlighted-body-OFNGDK62-B6M-kMLU.js → highlighted-body-OFNGDK62-VELQyUbs.js} +1 -1
  31. package/out/renderer/assets/{index-Dabo17L1.js → index-DChNtXAm.js} +2207 -566
  32. package/out/renderer/assets/index-I1XmNFoX.css +382 -0
  33. package/out/renderer/assets/{infoDiagram-42DDH7IO-ur7rRidE.js → infoDiagram-42DDH7IO-BgCd2fMi.js} +4 -4
  34. package/out/renderer/assets/{ishikawaDiagram-UXIWVN3A-fJSipMV8.js → ishikawaDiagram-UXIWVN3A-Dc9zBgQ3.js} +1 -1
  35. package/out/renderer/assets/{journeyDiagram-VCZTEJTY-TxjKxXBo.js → journeyDiagram-VCZTEJTY-Cl5-PgKK.js} +4 -4
  36. package/out/renderer/assets/{kanban-definition-6JOO6SKY-DJwjRjlE.js → kanban-definition-6JOO6SKY-CTdiOO_t.js} +2 -2
  37. package/out/renderer/assets/{layout-BT1ASsLg.js → layout-pad9cNSQ.js} +4 -4
  38. package/out/renderer/assets/{linear-ecuEcPGt.js → linear-C9kOAOci.js} +1 -1
  39. package/out/renderer/assets/{min-BYCDJeWK.js → min-B7SQAamY.js} +2 -2
  40. package/out/renderer/assets/{mindmap-definition-QFDTVHPH-D2MxZmzw.js → mindmap-definition-QFDTVHPH-DYc59m-R.js} +3 -3
  41. package/out/renderer/assets/{pieDiagram-DEJITSTG-Cew7QWxE.js → pieDiagram-DEJITSTG-DyXeoEBp.js} +6 -6
  42. package/out/renderer/assets/{quadrantDiagram-34T5L4WZ-CrZCtVJh.js → quadrantDiagram-34T5L4WZ-hFY-GkiJ.js} +2 -2
  43. package/out/renderer/assets/{requirementDiagram-MS252O5E-C69IGxW8.js → requirementDiagram-MS252O5E-ecRwwTfv.js} +3 -3
  44. package/out/renderer/assets/{sankeyDiagram-XADWPNL6-B2tuS_nG.js → sankeyDiagram-XADWPNL6-BTL3sclU.js} +1 -1
  45. package/out/renderer/assets/{sequenceDiagram-FGHM5R23-Dv_lOZN1.js → sequenceDiagram-FGHM5R23-AV2u_2fd.js} +3 -3
  46. package/out/renderer/assets/{stateDiagram-FHFEXIEX-D9dJAqtV.js → stateDiagram-FHFEXIEX-DoTCNWMJ.js} +8 -8
  47. package/out/renderer/assets/{stateDiagram-v2-QKLJ7IA2-CQXoaDY1.js → stateDiagram-v2-QKLJ7IA2--Ezn8EQY.js} +4 -4
  48. package/out/renderer/assets/{timeline-definition-GMOUNBTQ-DnnX0Ml0.js → timeline-definition-GMOUNBTQ-CQvZXAlZ.js} +2 -2
  49. package/out/renderer/assets/{vennDiagram-DHZGUBPP-BCIC8HTk.js → vennDiagram-DHZGUBPP-zJ6WGEop.js} +1 -1
  50. package/out/renderer/assets/{wardley-RL74JXVD-B35jxdMa.js → wardley-RL74JXVD-BPlNC-BP.js} +3 -3
  51. package/out/renderer/assets/{wardleyDiagram-NUSXRM2D-D3rjXear.js → wardleyDiagram-NUSXRM2D-BAQ1Om89.js} +5 -5
  52. package/out/renderer/assets/{xychartDiagram-5P7HB3ND-095YR9W9.js → xychartDiagram-5P7HB3ND--HADKQzk.js} +2 -2
  53. package/out/renderer/index.html +2 -2
  54. package/out/server/main.js +723 -159
  55. package/package.json +3 -1
  56. package/out/renderer/assets/index-D8H7Q_md.css +0 -382
@@ -2,10 +2,10 @@
2
2
 
3
3
  // src/backend/backend.ts
4
4
  import Fuse from "fuse.js";
5
- import { homedir as homedir3 } from "os";
5
+ import { homedir as homedir4 } from "os";
6
6
  import { spawn as spawnPty } from "node-pty";
7
7
  import { omit } from "es-toolkit";
8
- import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
8
+ import { createHash as createHash3, randomUUID as randomUUID5 } from "crypto";
9
9
  import {
10
10
  chmod,
11
11
  mkdir as mkdir2,
@@ -19,9 +19,9 @@ import {
19
19
  copyFile
20
20
  } from "fs/promises";
21
21
  import * as mimeTypes3 from "mime-types";
22
- import { dirname as dirname4, join as join7, relative as relative5, extname as extname2, basename as basename3 } from "path";
23
- import * as fs2 from "fs";
24
- import { fileURLToPath as fileURLToPath2 } from "url";
22
+ import { dirname as dirname6, join as join9, relative as relative5, extname as extname2, basename as basename3 } from "path";
23
+ import * as fs3 from "fs";
24
+ import { fileURLToPath as fileURLToPath3 } from "url";
25
25
  import { createRequire } from "module";
26
26
  import { execFile } from "child_process";
27
27
  import { promisify } from "util";
@@ -129,6 +129,7 @@ function readSettings() {
129
129
  return nextHeaders;
130
130
  })();
131
131
  const contextWindowTokens = typeof cfg?.contextWindowTokens === "number" ? cfg.contextWindowTokens : void 0;
132
+ const modelIdTemplate = typeof cfg?.modelIdTemplate === "string" && cfg.modelIdTemplate.trim() ? cfg.modelIdTemplate.trim() : void 0;
132
133
  next[id] = {
133
134
  type,
134
135
  provider,
@@ -137,7 +138,8 @@ function readSettings() {
137
138
  headers,
138
139
  disabled,
139
140
  order,
140
- contextWindowTokens
141
+ contextWindowTokens,
142
+ modelIdTemplate
141
143
  };
142
144
  }
143
145
  }
@@ -387,7 +389,8 @@ var storageOps = {
387
389
  apiKey: agentMeta.apiKey,
388
390
  headers: Object.assign({}, agentMeta.headers),
389
391
  disabled: agentMeta.disabled,
390
- contextWindowTokens: agentMeta.contextWindowTokens
392
+ contextWindowTokens: agentMeta.contextWindowTokens,
393
+ modelIdTemplate: agentMeta.modelIdTemplate
391
394
  };
392
395
  }
393
396
  throw new Error("Invalid agent type.");
@@ -461,7 +464,8 @@ var storageOps = {
461
464
  headers: Object.assign({}, agent.headers || {}),
462
465
  disabled: agent.disabled,
463
466
  order: idx,
464
- contextWindowTokens: agent.contextWindowTokens !== void 0 ? Number.isInteger(agent.contextWindowTokens) && agent.contextWindowTokens > 0 ? agent.contextWindowTokens : void 0 : void 0
467
+ contextWindowTokens: agent.contextWindowTokens !== void 0 ? Number.isInteger(agent.contextWindowTokens) && agent.contextWindowTokens > 0 ? agent.contextWindowTokens : void 0 : void 0,
468
+ modelIdTemplate: typeof agent.modelIdTemplate === "string" && agent.modelIdTemplate.trim() ? agent.modelIdTemplate.trim() : void 0
465
469
  };
466
470
  } else {
467
471
  throw new Error("Invalid agent type.");
@@ -2191,6 +2195,13 @@ function isOpenAICompatibleModelsResponse(value) {
2191
2195
  if (maybe.data === void 0) return true;
2192
2196
  return Array.isArray(maybe.data);
2193
2197
  }
2198
+ function applyModelIdTemplate(template, modelItem) {
2199
+ return template.replace(/\{(\w+)\}/g, (_match, fieldName) => {
2200
+ const value = modelItem[fieldName];
2201
+ if (value === void 0 || value === null) return _match;
2202
+ return String(value);
2203
+ });
2204
+ }
2194
2205
  var AgentDescription = "Fello/0.1.1 CodeAgent OpenaiCompatibleAgent opencode codex";
2195
2206
  var AVAILABLE_COMMANDS = [
2196
2207
  { name: "compact", description: "Compress conversation context" }
@@ -2204,6 +2215,7 @@ var OpenaiCompatibleAgent = class {
2204
2215
  apiKey;
2205
2216
  headers;
2206
2217
  contextWindowTokens;
2218
+ modelIdTemplate;
2207
2219
  modelsCache = null;
2208
2220
  modelsFetchedAt = 0;
2209
2221
  modelsPending = null;
@@ -2217,6 +2229,7 @@ var OpenaiCompatibleAgent = class {
2217
2229
  this.agentId = options.id;
2218
2230
  this.headers = options.headers || {};
2219
2231
  this.contextWindowTokens = options.contextWindowTokens ?? DEFAULT_CONTEXT_WINDOW_TOKENS;
2232
+ this.modelIdTemplate = options.modelIdTemplate;
2220
2233
  this.provider = createOpenAICompatible({
2221
2234
  name: "openai-compatible",
2222
2235
  baseURL: this.baseUrl,
@@ -2283,7 +2296,10 @@ var OpenaiCompatibleAgent = class {
2283
2296
  const entries = Array.isArray(payload.data) ? payload.data : [];
2284
2297
  const models = entries.map((item) => {
2285
2298
  if (typeof item?.id !== "string" || item.id.trim().length === 0) return null;
2286
- return { modelId: item.id, name: item.id };
2299
+ const rawId = item.id.trim();
2300
+ const modelId = this.modelIdTemplate ? applyModelIdTemplate(this.modelIdTemplate, item) : rawId;
2301
+ if (!modelId.trim()) return null;
2302
+ return { modelId, name: modelId };
2287
2303
  }).filter((item) => item !== null);
2288
2304
  return this.toSessionModelState(models);
2289
2305
  }
@@ -4413,97 +4429,152 @@ var ILinkBridge = class _ILinkBridge {
4413
4429
  }
4414
4430
  };
4415
4431
 
4416
- // src/backend/watcher.ts
4417
- import watcher from "@parcel/watcher";
4418
- import { relative as relative3 } from "path";
4419
- function isFileWatcherEnabled() {
4420
- return storageOps.getSettings().fileWatcher?.enabled ?? true;
4421
- }
4422
- var subscriptions = /* @__PURE__ */ new Map();
4423
- var MAX_BATCH_CHANGES = 1e3;
4424
- var IGNORE = [
4425
- "**/.git/**",
4426
- "**/.svn/**",
4427
- "**/.hg/**",
4428
- "**/node_modules/**",
4429
- "**/bower_components/**",
4430
- "**/.cache/**",
4431
- "**/venv/**",
4432
- "**/.venv/**",
4433
- "**/vendor/**",
4434
- "**/__pycache__/**"
4435
- ];
4436
- var sendEvent = () => false;
4437
- function initWatcher(emitter) {
4438
- sendEvent = emitter;
4439
- void syncWatchers();
4440
- }
4441
- async function syncWatchers() {
4442
- const enabled = isFileWatcherEnabled();
4443
- const projects = storageOps.listProjects();
4444
- const currentProjects = new Map(projects.map((p) => [p.id, p]));
4445
- if (!enabled) {
4446
- for (const [projectId, subscription] of subscriptions.entries()) {
4447
- await subscription.unsubscribe();
4448
- subscriptions.delete(projectId);
4432
+ // src/backend/automation/store.ts
4433
+ import { join as join6, dirname as dirname3 } from "path";
4434
+ import { homedir as homedir2 } from "os";
4435
+ import {
4436
+ mkdirSync as mkdirSync4,
4437
+ writeFileSync as writeFileSync3,
4438
+ readFileSync as readFileSync3,
4439
+ readdirSync as readdirSync2,
4440
+ rmSync as rmSync3,
4441
+ existsSync as existsSync3,
4442
+ statSync as statSync2
4443
+ } from "fs";
4444
+ var AUTOMATIONS_DIR = join6(homedir2(), ".fello", "automations");
4445
+ mkdirSync4(AUTOMATIONS_DIR, { recursive: true });
4446
+ var store = {
4447
+ scheduleDir(scheduleId) {
4448
+ return join6(AUTOMATIONS_DIR, scheduleId);
4449
+ },
4450
+ scheduleConfigPath(scheduleId) {
4451
+ return join6(this.scheduleDir(scheduleId), "schedule.json");
4452
+ },
4453
+ tasksDir(scheduleId) {
4454
+ return join6(this.scheduleDir(scheduleId), "tasks");
4455
+ },
4456
+ taskDir(scheduleId, taskId) {
4457
+ return join6(this.tasksDir(scheduleId), taskId);
4458
+ },
4459
+ taskMetaPath(scheduleId, taskId) {
4460
+ return join6(this.taskDir(scheduleId, taskId), "task.json");
4461
+ },
4462
+ listSchedules() {
4463
+ if (!existsSync3(AUTOMATIONS_DIR)) return [];
4464
+ const dirs = readdirSync2(AUTOMATIONS_DIR);
4465
+ const list = [];
4466
+ for (const dir of dirs) {
4467
+ try {
4468
+ const raw = JSON.parse(readFileSync3(this.scheduleConfigPath(dir), "utf-8"));
4469
+ list.push(raw);
4470
+ } catch {
4471
+ }
4449
4472
  }
4450
- return;
4451
- }
4452
- for (const [projectId, subscription] of subscriptions.entries()) {
4453
- if (!currentProjects.has(projectId)) {
4454
- await subscription.unsubscribe();
4455
- subscriptions.delete(projectId);
4473
+ list.sort((a, b) => b.updatedAt - a.updatedAt);
4474
+ return list;
4475
+ },
4476
+ getSchedule(scheduleId) {
4477
+ try {
4478
+ return JSON.parse(readFileSync3(this.scheduleConfigPath(scheduleId), "utf-8"));
4479
+ } catch {
4480
+ return null;
4456
4481
  }
4457
- }
4458
- for (const [projectId, project] of currentProjects) {
4459
- if (!subscriptions.has(projectId)) {
4460
- await createWatcher(projectId, project.cwd);
4482
+ },
4483
+ saveSchedule(schedule) {
4484
+ const dir = this.scheduleDir(schedule.id);
4485
+ mkdirSync4(dir, { recursive: true });
4486
+ mkdirSync4(this.tasksDir(schedule.id), { recursive: true });
4487
+ writeFileSync3(this.scheduleConfigPath(schedule.id), JSON.stringify(schedule, null, 2));
4488
+ },
4489
+ deleteSchedule(scheduleId) {
4490
+ const dir = this.scheduleDir(scheduleId);
4491
+ if (existsSync3(dir)) {
4492
+ rmSync3(dir, { recursive: true, force: true });
4461
4493
  }
4462
- }
4463
- }
4464
- async function createWatcher(projectId, cwd) {
4465
- const changes = /* @__PURE__ */ new Set();
4466
- let overflowed = false;
4467
- let timeout = null;
4468
- const flush = () => {
4469
- if (changes.size > 0) {
4470
- sendEvent("fs-changed", { projectId, changes: Array.from(changes) });
4471
- changes.clear();
4472
- overflowed = false;
4494
+ },
4495
+ listTasks(scheduleId) {
4496
+ const dir = this.tasksDir(scheduleId);
4497
+ if (!existsSync3(dir)) return [];
4498
+ const dirs = readdirSync2(dir);
4499
+ const list = [];
4500
+ for (const d of dirs) {
4501
+ try {
4502
+ const raw = JSON.parse(readFileSync3(this.taskMetaPath(scheduleId, d), "utf-8"));
4503
+ list.push(raw);
4504
+ } catch {
4505
+ }
4473
4506
  }
4474
- };
4475
- const onChange = (path2) => {
4476
- if (!overflowed) {
4477
- if (changes.size < MAX_BATCH_CHANGES) {
4478
- changes.add(path2);
4507
+ list.sort((a, b) => b.startedAt - a.startedAt);
4508
+ return list;
4509
+ },
4510
+ getTask(scheduleId, taskId) {
4511
+ try {
4512
+ return JSON.parse(readFileSync3(this.taskMetaPath(scheduleId, taskId), "utf-8"));
4513
+ } catch {
4514
+ return null;
4515
+ }
4516
+ },
4517
+ saveTask(scheduleId, task) {
4518
+ const dir = this.taskDir(scheduleId, task.id);
4519
+ mkdirSync4(dir, { recursive: true });
4520
+ writeFileSync3(this.taskMetaPath(scheduleId, task.id), JSON.stringify(task, null, 2));
4521
+ },
4522
+ deleteTask(scheduleId, taskId) {
4523
+ const dir = this.taskDir(scheduleId, taskId);
4524
+ if (existsSync3(dir)) {
4525
+ rmSync3(dir, { recursive: true, force: true });
4526
+ }
4527
+ },
4528
+ listTaskFiles(scheduleId, taskId) {
4529
+ const dir = this.taskDir(scheduleId, taskId);
4530
+ if (!existsSync3(dir)) return [];
4531
+ const entries = readdirSync2(dir);
4532
+ return entries.filter((f) => f !== "task.json").map((f) => {
4533
+ const fullPath = join6(dir, f);
4534
+ const s = statSync2(fullPath);
4535
+ if (s.isDirectory()) {
4536
+ return this._listDirRecursive(fullPath, f);
4537
+ }
4538
+ return f;
4539
+ }).flat().sort();
4540
+ },
4541
+ _listDirRecursive(dir, prefix) {
4542
+ const result = [];
4543
+ const entries = readdirSync2(dir);
4544
+ for (const entry of entries) {
4545
+ const full = join6(dir, entry);
4546
+ const s = statSync2(full);
4547
+ if (s.isDirectory()) {
4548
+ result.push(...this._listDirRecursive(full, join6(prefix, entry)));
4479
4549
  } else {
4480
- overflowed = true;
4481
- changes.clear();
4482
- changes.add(".");
4550
+ result.push(join6(prefix, entry));
4483
4551
  }
4484
4552
  }
4485
- if (timeout) clearTimeout(timeout);
4486
- timeout = setTimeout(flush, 500);
4487
- };
4488
- try {
4489
- const subscription = await watcher.subscribe(
4490
- cwd,
4491
- (err, events) => {
4492
- if (err) {
4493
- console.error(`[Watcher] Error for ${projectId} (${cwd}):`, err);
4494
- return;
4495
- }
4496
- for (const event of events) {
4497
- onChange(toPosixPath(relative3(cwd, event.path)));
4498
- }
4499
- },
4500
- { ignore: IGNORE }
4501
- );
4502
- subscriptions.set(projectId, subscription);
4503
- } catch (err) {
4504
- console.error(`[Watcher] Failed to subscribe for ${projectId} (${cwd}):`, err);
4553
+ return result;
4554
+ },
4555
+ readTaskFile(scheduleId, taskId, filePath) {
4556
+ const base = this.taskDir(scheduleId, taskId);
4557
+ const fullPath = join6(base, filePath);
4558
+ if (!fullPath.startsWith(base + "/") && fullPath !== base) throw new Error("Invalid file path");
4559
+ if (!existsSync3(fullPath)) throw new Error("File not found");
4560
+ return readFileSync3(fullPath, "utf-8");
4561
+ },
4562
+ writeTaskFile(scheduleId, taskId, filePath, content) {
4563
+ const base = this.taskDir(scheduleId, taskId);
4564
+ const fullPath = join6(base, filePath);
4565
+ if (!fullPath.startsWith(base + "/") && fullPath !== base) throw new Error("Invalid file path");
4566
+ mkdirSync4(dirname3(fullPath), { recursive: true });
4567
+ writeFileSync3(fullPath, content);
4505
4568
  }
4506
- }
4569
+ };
4570
+
4571
+ // src/backend/automation/scheduler.ts
4572
+ import { CronJob } from "cron";
4573
+
4574
+ // src/backend/automation/runner.ts
4575
+ import { join as join8, dirname as dirname5 } from "path";
4576
+ import { randomUUID as randomUUID4 } from "crypto";
4577
+ import { fileURLToPath as fileURLToPath2 } from "url";
4507
4578
 
4508
4579
  // src/backend/skills.ts
4509
4580
  import * as fs from "fs";
@@ -4705,6 +4776,427 @@ async function installSkill(source, slug) {
4705
4776
  }
4706
4777
  }
4707
4778
 
4779
+ // src/backend/automation/runner.ts
4780
+ import fs2 from "fs";
4781
+ var __dirname2 = dirname5(fileURLToPath2(import.meta.url));
4782
+ var sendEvent = null;
4783
+ function initRunner(emitter) {
4784
+ sendEvent = emitter;
4785
+ }
4786
+ function resolveAgentInfo(agentId) {
4787
+ const settings = storageOps.getSettings();
4788
+ const agent = settings.agents.find((a) => a.id === agentId);
4789
+ if (!agent) throw new Error(`Unknown agent: ${agentId}`);
4790
+ if (agent.type === "stdio") {
4791
+ const command = agent.command.trim();
4792
+ if (!command) throw new Error(`Agent "${agent.id}" has no command configured.`);
4793
+ return { ...agent, command };
4794
+ }
4795
+ const provider = agent.provider.trim();
4796
+ const baseUrl = agent.baseUrl.trim();
4797
+ const apiKey = agent.apiKey.trim();
4798
+ if (!provider) throw new Error(`Agent "${agent.id}" has no provider configured.`);
4799
+ if (!baseUrl) throw new Error(`Agent "${agent.id}" has no baseUrl configured.`);
4800
+ if (!apiKey) throw new Error(`Agent "${agent.id}" has no apiKey configured.`);
4801
+ return { ...agent, provider, baseUrl, apiKey };
4802
+ }
4803
+ function buildAutomationMcpServers(mcpIds) {
4804
+ const settings = storageOps.getSettings();
4805
+ const servers = [];
4806
+ for (const id of mcpIds) {
4807
+ const config = settings.mcpServers?.find((s) => s.id === id);
4808
+ if (!config) continue;
4809
+ if (config.type === "stdio") {
4810
+ servers.push({
4811
+ name: id,
4812
+ command: config.command,
4813
+ args: config.args,
4814
+ env: Object.entries(config.env).map(([k, v]) => ({ name: k, value: v }))
4815
+ });
4816
+ } else if (config.type === "http") {
4817
+ servers.push({
4818
+ type: "http",
4819
+ name: id,
4820
+ url: config.url,
4821
+ headers: Object.entries(config.headers).map(([k, v]) => ({ name: k, value: v }))
4822
+ });
4823
+ }
4824
+ }
4825
+ return servers;
4826
+ }
4827
+ async function setupSkillsServer(taskDir) {
4828
+ const socketPath = join8(TEMP_DIR, `auto-${randomUUID4()}.socket`);
4829
+ const server = await startSocketServer(socketPath);
4830
+ server.registry("skills/catalog", async () => {
4831
+ return getSkillsCatalog({ projectRoot: taskDir }).map(({ id, name, description }) => ({
4832
+ id,
4833
+ name,
4834
+ description
4835
+ }));
4836
+ });
4837
+ server.registry("skills/detail", async (payload) => {
4838
+ const { id } = payload;
4839
+ const catalog = getSkillsCatalog({ projectRoot: taskDir });
4840
+ const skill = catalog.find((s) => s.id === id);
4841
+ if (!skill) return { error: `Skill '${id}' not found.` };
4842
+ const skillDir = getSkillSystemPathFromId(skill.id, { projectRoot: taskDir });
4843
+ if (!skillDir) return { error: `Failed to read skill '${id}'` };
4844
+ let body = "";
4845
+ try {
4846
+ const text = fs2.readFileSync(join8(skillDir, SKILL_FILENAME), "utf8");
4847
+ body = parseSkillFrontmatter(text).body;
4848
+ } catch {
4849
+ return { error: `Failed to read skill '${id}'` };
4850
+ }
4851
+ let supportingFiles = [];
4852
+ try {
4853
+ supportingFiles = listSkillFiles(skill.id, { projectRoot: taskDir });
4854
+ } catch {
4855
+ }
4856
+ return {
4857
+ id: skill.id,
4858
+ name: skill.name,
4859
+ description: skill.description,
4860
+ instructions: body,
4861
+ root_path: skillDir,
4862
+ supporting_files: supportingFiles
4863
+ };
4864
+ });
4865
+ const catalogData = getSkillsCatalog({ projectRoot: taskDir }).map(
4866
+ ({ id, name, description }) => ({ id, name, description })
4867
+ );
4868
+ const catalogFile = join8(TEMP_DIR, `auto-catalog-${randomUUID4()}.json`);
4869
+ fs2.writeFileSync(catalogFile, JSON.stringify(catalogData), "utf8");
4870
+ const mcpServer = {
4871
+ name: "skills",
4872
+ command: process.execPath,
4873
+ args: [
4874
+ join8(__dirname2, "../scripts/mcp-skills/server.mjs"),
4875
+ "--project-dir",
4876
+ taskDir,
4877
+ "--socket-path",
4878
+ socketPath,
4879
+ "--catalog",
4880
+ catalogFile
4881
+ ],
4882
+ env: [{ name: "ELECTRON_RUN_AS_NODE", value: "1" }]
4883
+ };
4884
+ return { server, mcpServer };
4885
+ }
4886
+ async function executeTask(scheduleId) {
4887
+ if (runningTasks.has(scheduleId)) {
4888
+ throw new Error("Schedule task is already running");
4889
+ }
4890
+ const schedule = store.getSchedule(scheduleId);
4891
+ if (!schedule) throw new Error("Schedule not found");
4892
+ runningTasks.add(scheduleId);
4893
+ const taskId = String(Date.now());
4894
+ const startedAt = Date.now();
4895
+ const task = {
4896
+ id: taskId,
4897
+ scheduleId,
4898
+ startedAt,
4899
+ completedAt: null,
4900
+ status: "running"
4901
+ };
4902
+ store.saveTask(scheduleId, task);
4903
+ sendEvent?.("task-update", { scheduleId, task });
4904
+ const notifications = [];
4905
+ try {
4906
+ const agentInfo = resolveAgentInfo(schedule.agentId);
4907
+ const taskDir = store.taskDir(scheduleId, taskId);
4908
+ const bridge = new ACPBridge(schedule.agentId, {
4909
+ agentInfo,
4910
+ onSessionUpdate: (notification) => {
4911
+ notifications.push({
4912
+ ...notification,
4913
+ update: {
4914
+ ...notification.update,
4915
+ _meta: {
4916
+ ...notification.update?._meta,
4917
+ fello: { receivedAt: Date.now(), displayId: `auto-${notifications.length}` }
4918
+ }
4919
+ }
4920
+ });
4921
+ },
4922
+ onPermissionRequest: async (req) => {
4923
+ const opt = req.options.find((o) => o.kind === "allow_always") ?? req.options.find((o) => o.kind === "allow_once") ?? req.options[0];
4924
+ return { outcome: { outcome: "selected", optionId: opt?.optionId ?? "allow" } };
4925
+ },
4926
+ onAgentTerminalOutput: () => {
4927
+ }
4928
+ });
4929
+ await bridge.connect();
4930
+ const mcpServers = buildAutomationMcpServers(schedule.mcpServers ?? []);
4931
+ let skillsServer = null;
4932
+ const effectiveFeatures = schedule.features ?? [];
4933
+ if (effectiveFeatures.includes("skills")) {
4934
+ const skills = await setupSkillsServer(taskDir);
4935
+ skillsServer = skills.server;
4936
+ mcpServers.unshift(skills.mcpServer);
4937
+ }
4938
+ const { sessionId } = await bridge.newSession({ cwd: taskDir, mcpServers });
4939
+ const promptContent = [{ type: "text", text: schedule.prompt }];
4940
+ await bridge.sendPrompt({ sessionId, prompt: promptContent });
4941
+ const messages = reduceNotificationsToMessages(notifications);
4942
+ store.writeTaskFile(
4943
+ scheduleId,
4944
+ taskId,
4945
+ "README.md",
4946
+ generateReadme(schedule, messages, startedAt)
4947
+ );
4948
+ store.writeTaskFile(scheduleId, taskId, "conversation.json", JSON.stringify(messages, null, 2));
4949
+ await bridge.kill();
4950
+ skillsServer?.stop();
4951
+ schedule.lastRunAt = Date.now();
4952
+ schedule.updatedAt = Date.now();
4953
+ store.saveSchedule(schedule);
4954
+ sendEvent?.("schedules-changed", void 0);
4955
+ task.completedAt = Date.now();
4956
+ task.status = "success";
4957
+ store.saveTask(scheduleId, task);
4958
+ sendEvent?.("task-update", { scheduleId, task });
4959
+ return task;
4960
+ } catch (err) {
4961
+ const errorMessage = err instanceof Error ? err.message : String(err);
4962
+ task.completedAt = Date.now();
4963
+ task.status = "error";
4964
+ task.error = errorMessage;
4965
+ store.saveTask(scheduleId, task);
4966
+ sendEvent?.("task-update", { scheduleId, task });
4967
+ console.error(`[Automation] Task failed:`, errorMessage);
4968
+ return task;
4969
+ } finally {
4970
+ runningTasks.delete(scheduleId);
4971
+ }
4972
+ }
4973
+ function reduceNotificationsToMessages(notifications) {
4974
+ const messages = [];
4975
+ for (const n of notifications) {
4976
+ const update = n.update;
4977
+ if (!update) continue;
4978
+ switch (update.sessionUpdate) {
4979
+ case "user_message_chunk": {
4980
+ const content = update.content;
4981
+ if (!content || content.type !== "text") break;
4982
+ const last = messages[messages.length - 1];
4983
+ if (last && last.role === "user") {
4984
+ last.text += content.text ?? "";
4985
+ } else {
4986
+ messages.push({ role: "user", text: content.text ?? "" });
4987
+ }
4988
+ break;
4989
+ }
4990
+ case "agent_message_chunk": {
4991
+ const content = update.content;
4992
+ if (!content || content.type !== "text") break;
4993
+ const last = messages[messages.length - 1];
4994
+ if (last && last.role === "assistant") {
4995
+ last.text += content.text ?? "";
4996
+ } else {
4997
+ messages.push({ role: "assistant", text: content.text ?? "" });
4998
+ }
4999
+ break;
5000
+ }
5001
+ case "agent_thought_chunk": {
5002
+ const content = update.content;
5003
+ if (!content || content.type !== "text") break;
5004
+ const last = messages[messages.length - 1];
5005
+ if (last && last.role === "thought") {
5006
+ last.text += content.text ?? "";
5007
+ } else {
5008
+ messages.push({ role: "thought", text: content.text ?? "" });
5009
+ }
5010
+ break;
5011
+ }
5012
+ case "tool_call":
5013
+ case "tool_call_update": {
5014
+ const title = update.title ?? "";
5015
+ const content = update.content;
5016
+ let text = "";
5017
+ if (Array.isArray(content)) {
5018
+ for (const block of content) {
5019
+ if (block.type === "text") text += block.text ?? "";
5020
+ }
5021
+ }
5022
+ messages.push({ role: "tool_call", text, toolTitle: title });
5023
+ break;
5024
+ }
5025
+ }
5026
+ }
5027
+ return messages;
5028
+ }
5029
+ function generateReadme(schedule, messages, startedAt) {
5030
+ const lines = [];
5031
+ lines.push(`# ${schedule.name}`);
5032
+ const locale = storageOps.getSettings().i18n?.language;
5033
+ lines.push(`> Run at ${new Date(startedAt).toLocaleString(locale)}`);
5034
+ lines.push("");
5035
+ for (const msg of messages) {
5036
+ if (msg.role === "user" || msg.role === "assistant") {
5037
+ const text = msg.text.trim();
5038
+ if (text) lines.push(text + "\n");
5039
+ }
5040
+ }
5041
+ return lines.join("\n");
5042
+ }
5043
+
5044
+ // src/backend/automation/scheduler.ts
5045
+ var scheduledCrons = /* @__PURE__ */ new Map();
5046
+ function getNextRun(schedule) {
5047
+ const job = scheduledCrons.get(schedule.id);
5048
+ if (!job) return null;
5049
+ try {
5050
+ return job.nextDate().toMillis();
5051
+ } catch {
5052
+ return null;
5053
+ }
5054
+ }
5055
+ function scheduleCron(schedule) {
5056
+ unscheduleCron(schedule.id);
5057
+ if (schedule.cron.type !== "cron" || !schedule.cron.expr) {
5058
+ return;
5059
+ }
5060
+ try {
5061
+ const cronJob = new CronJob(
5062
+ schedule.cron.expr,
5063
+ async () => {
5064
+ if (runningTasks.has(schedule.id)) {
5065
+ console.log(`[Automation] "${schedule.name}" already running, skipping`);
5066
+ return;
5067
+ }
5068
+ await executeTask(schedule.id);
5069
+ },
5070
+ null,
5071
+ false
5072
+ );
5073
+ scheduledCrons.set(schedule.id, cronJob);
5074
+ cronJob.start();
5075
+ const next = getNextRun(schedule);
5076
+ console.log(
5077
+ `[Automation] Scheduled "${schedule.name}" (${schedule.cron.expr}) next: ${next ? new Date(next).toLocaleString(storageOps.getSettings().i18n?.language) : "?"}`
5078
+ );
5079
+ } catch (err) {
5080
+ console.error(`[Automation] Failed to schedule "${schedule.name}":`, err);
5081
+ }
5082
+ }
5083
+ function unscheduleCron(scheduleId) {
5084
+ const existing = scheduledCrons.get(scheduleId);
5085
+ if (existing) {
5086
+ existing.stop();
5087
+ scheduledCrons.delete(scheduleId);
5088
+ }
5089
+ }
5090
+ function restoreActiveSchedules() {
5091
+ const schedules = store.listSchedules();
5092
+ for (const s of schedules) {
5093
+ if (s.cron.type === "cron" && s.cron.expr) {
5094
+ scheduleCron(s);
5095
+ }
5096
+ }
5097
+ console.log(`[Automation] Restored ${scheduledCrons.size} active schedule(s)`);
5098
+ }
5099
+ function stopAllCrons() {
5100
+ for (const job of scheduledCrons.values()) {
5101
+ job.stop();
5102
+ }
5103
+ scheduledCrons.clear();
5104
+ console.log("[Automation] All crons stopped");
5105
+ }
5106
+ var runningTasks = /* @__PURE__ */ new Set();
5107
+
5108
+ // src/backend/watcher.ts
5109
+ import watcher from "@parcel/watcher";
5110
+ import { relative as relative4 } from "path";
5111
+ function isFileWatcherEnabled() {
5112
+ return storageOps.getSettings().fileWatcher?.enabled ?? true;
5113
+ }
5114
+ var subscriptions = /* @__PURE__ */ new Map();
5115
+ var MAX_BATCH_CHANGES = 1e3;
5116
+ var IGNORE = [
5117
+ "**/.git/**",
5118
+ "**/.svn/**",
5119
+ "**/.hg/**",
5120
+ "**/node_modules/**",
5121
+ "**/bower_components/**",
5122
+ "**/.cache/**",
5123
+ "**/venv/**",
5124
+ "**/.venv/**",
5125
+ "**/vendor/**",
5126
+ "**/__pycache__/**"
5127
+ ];
5128
+ var sendEvent2 = () => false;
5129
+ function initWatcher(emitter) {
5130
+ sendEvent2 = emitter;
5131
+ void syncWatchers();
5132
+ }
5133
+ async function syncWatchers() {
5134
+ const enabled = isFileWatcherEnabled();
5135
+ const projects = storageOps.listProjects();
5136
+ const currentProjects = new Map(projects.map((p) => [p.id, p]));
5137
+ if (!enabled) {
5138
+ for (const [projectId, subscription] of subscriptions.entries()) {
5139
+ await subscription.unsubscribe();
5140
+ subscriptions.delete(projectId);
5141
+ }
5142
+ return;
5143
+ }
5144
+ for (const [projectId, subscription] of subscriptions.entries()) {
5145
+ if (!currentProjects.has(projectId)) {
5146
+ await subscription.unsubscribe();
5147
+ subscriptions.delete(projectId);
5148
+ }
5149
+ }
5150
+ for (const [projectId, project] of currentProjects) {
5151
+ if (!subscriptions.has(projectId)) {
5152
+ await createWatcher(projectId, project.cwd);
5153
+ }
5154
+ }
5155
+ }
5156
+ async function createWatcher(projectId, cwd) {
5157
+ const changes = /* @__PURE__ */ new Set();
5158
+ let overflowed = false;
5159
+ let timeout = null;
5160
+ const flush = () => {
5161
+ if (changes.size > 0) {
5162
+ sendEvent2("fs-changed", { projectId, changes: Array.from(changes) });
5163
+ changes.clear();
5164
+ overflowed = false;
5165
+ }
5166
+ };
5167
+ const onChange = (path2) => {
5168
+ if (!overflowed) {
5169
+ if (changes.size < MAX_BATCH_CHANGES) {
5170
+ changes.add(path2);
5171
+ } else {
5172
+ overflowed = true;
5173
+ changes.clear();
5174
+ changes.add(".");
5175
+ }
5176
+ }
5177
+ if (timeout) clearTimeout(timeout);
5178
+ timeout = setTimeout(flush, 500);
5179
+ };
5180
+ try {
5181
+ const subscription = await watcher.subscribe(
5182
+ cwd,
5183
+ (err, events) => {
5184
+ if (err) {
5185
+ console.error(`[Watcher] Error for ${projectId} (${cwd}):`, err);
5186
+ return;
5187
+ }
5188
+ for (const event of events) {
5189
+ onChange(toPosixPath(relative4(cwd, event.path)));
5190
+ }
5191
+ },
5192
+ { ignore: IGNORE }
5193
+ );
5194
+ subscriptions.set(projectId, subscription);
5195
+ } catch (err) {
5196
+ console.error(`[Watcher] Failed to subscribe for ${projectId} (${cwd}):`, err);
5197
+ }
5198
+ }
5199
+
4708
5200
  // src/backend/i18n.ts
4709
5201
  import i18next from "i18next";
4710
5202
 
@@ -4868,7 +5360,7 @@ function setLanguage(lang) {
4868
5360
  // src/backend/backend.ts
4869
5361
  var require2 = createRequire(import.meta.url);
4870
5362
  var execFileAsync = promisify(execFile);
4871
- var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
5363
+ var __dirname3 = dirname6(fileURLToPath3(import.meta.url));
4872
5364
  function buildMcpServersConfig(sessionMcpIds, options) {
4873
5365
  const { project, socketPath, features = ALL_FEATURES } = options;
4874
5366
  const servers = [];
@@ -4876,13 +5368,13 @@ function buildMcpServersConfig(sessionMcpIds, options) {
4876
5368
  const skillCatalog = getSkillsCatalog({
4877
5369
  projectRoot: project.cwd
4878
5370
  }).map((skill) => omit(skill, ["scope", "level"]));
4879
- const skillCatalogFilename = join7(TEMP_DIR, `project-${project.id}-${randomUUID4()}.json`);
4880
- fs2.writeFileSync(skillCatalogFilename, JSON.stringify(skillCatalog), "utf8");
5371
+ const skillCatalogFilename = join9(TEMP_DIR, `project-${project.id}-${randomUUID5()}.json`);
5372
+ fs3.writeFileSync(skillCatalogFilename, JSON.stringify(skillCatalog), "utf8");
4881
5373
  servers.push({
4882
5374
  name: "skills",
4883
5375
  command: process.execPath,
4884
5376
  args: [
4885
- join7(__dirname2, "../scripts/mcp-skills/server.mjs"),
5377
+ join9(__dirname3, "../scripts/mcp-skills/server.mjs"),
4886
5378
  "--project-dir",
4887
5379
  project.cwd,
4888
5380
  "--socket-path",
@@ -4903,7 +5395,7 @@ function buildMcpServersConfig(sessionMcpIds, options) {
4903
5395
  name: "ask-user",
4904
5396
  command: process.execPath,
4905
5397
  args: [
4906
- join7(__dirname2, "../scripts/mcp-ask-user/server.mjs"),
5398
+ join9(__dirname3, "../scripts/mcp-ask-user/server.mjs"),
4907
5399
  "--project-dir",
4908
5400
  project.cwd,
4909
5401
  "--socket-path",
@@ -4953,7 +5445,7 @@ function generateSessionSocketPath(key) {
4953
5445
  if (process.platform === "win32") {
4954
5446
  return `\\\\.\\pipe\\fello-${key}-${timestamp}`;
4955
5447
  }
4956
- return join7(SOCKETS_DIR, `${key}-${timestamp}.socket`);
5448
+ return join9(SOCKETS_DIR, `${key}-${timestamp}.socket`);
4957
5449
  }
4958
5450
  async function createSessionSocketServer(sessionId, options) {
4959
5451
  const existing = sessionSocketServers.get(sessionId);
@@ -4997,10 +5489,10 @@ async function createSessionSocketServer(sessionId, options) {
4997
5489
  if (!skillDir) {
4998
5490
  return { error: `Failed to Read skill '${id}'` };
4999
5491
  }
5000
- const skillFile = join7(skillDir, SKILL_FILENAME);
5492
+ const skillFile = join9(skillDir, SKILL_FILENAME);
5001
5493
  let body = "";
5002
5494
  try {
5003
- const text = fs2.readFileSync(skillFile, "utf8");
5495
+ const text = fs3.readFileSync(skillFile, "utf8");
5004
5496
  const parsed = parseSkillFrontmatter(text);
5005
5497
  body = parsed.body;
5006
5498
  } catch {
@@ -5055,9 +5547,9 @@ function formatAskUserForWeChat(request) {
5055
5547
  }
5056
5548
  async function askUser(options) {
5057
5549
  const { sessionId } = options;
5058
- const askUserId = randomUUID4();
5550
+ const askUserId = randomUUID5();
5059
5551
  const request = { ...options, askUserId };
5060
- sendEvent2("ask-user-request", request);
5552
+ sendEvent3("ask-user-request", request);
5061
5553
  if (ilinkBridge?.isConnected && sessionId === ilinkActiveSessionId) {
5062
5554
  const userId = ilinkBridge.userId;
5063
5555
  if (userId) {
@@ -5076,7 +5568,7 @@ async function askUser(options) {
5076
5568
  pendingAskUserRequests.delete(askUserId);
5077
5569
  const response = { value: null, reason: "timeout" };
5078
5570
  resolve4(response);
5079
- sendEvent2("ask-user-response", {
5571
+ sendEvent3("ask-user-response", {
5080
5572
  sessionId,
5081
5573
  askUserId,
5082
5574
  value: null,
@@ -5097,7 +5589,7 @@ function getILinkBridge() {
5097
5589
  if (!ilinkBridge) {
5098
5590
  ilinkBridge = new ILinkBridge({
5099
5591
  onStatusChange: (status) => {
5100
- sendEvent2("ilink-status-changed", { status });
5592
+ sendEvent3("ilink-status-changed", { status });
5101
5593
  broadcastWebUIEvent("ilink-status-changed", { status });
5102
5594
  },
5103
5595
  onMessage: async (msg) => {
@@ -5182,7 +5674,7 @@ function getILinkBridge() {
5182
5674
  ilinkReplyBuffer = "";
5183
5675
  writeActiveSessionId(entry.sessionId).catch(() => {
5184
5676
  });
5185
- sendEvent2("ilink-active-session-changed", { sessionId: entry.sessionId });
5677
+ sendEvent3("ilink-active-session-changed", { sessionId: entry.sessionId });
5186
5678
  if (msg.from_user_id) {
5187
5679
  ilinkBridge?.sendTextReply(
5188
5680
  msg.from_user_id,
@@ -5246,7 +5738,7 @@ function getILinkBridge() {
5246
5738
  ilinkReplyBuffer = "";
5247
5739
  writeActiveSessionId(newSessionId).catch(() => {
5248
5740
  });
5249
- sendEvent2("ilink-active-session-changed", { sessionId: newSessionId });
5741
+ sendEvent3("ilink-active-session-changed", { sessionId: newSessionId });
5250
5742
  if (msg.from_user_id) {
5251
5743
  ilinkBridge?.sendTextReply(
5252
5744
  msg.from_user_id,
@@ -5555,7 +6047,7 @@ var terminals = /* @__PURE__ */ new Map();
5555
6047
  var clientTerminals = /* @__PURE__ */ new Map();
5556
6048
  var terminalCounter = 0;
5557
6049
  var isNodePtyHelperPrepared = false;
5558
- var sendEvent2 = () => false;
6050
+ var sendEvent3 = () => false;
5559
6051
  function broadcastAndSaveSessionUpdate(sessionId, notification) {
5560
6052
  const enrichedNotification = {
5561
6053
  ...notification,
@@ -5565,7 +6057,7 @@ function broadcastAndSaveSessionUpdate(sessionId, notification) {
5565
6057
  ...notification.update?._meta,
5566
6058
  fello: {
5567
6059
  receivedAt: Date.now(),
5568
- displayId: randomUUID4()
6060
+ displayId: randomUUID5()
5569
6061
  }
5570
6062
  }
5571
6063
  }
@@ -5617,7 +6109,7 @@ function broadcastAndSaveSessionUpdate(sessionId, notification) {
5617
6109
  } else {
5618
6110
  storageOps.appendSessionMessage(sessionId, enrichedNotification);
5619
6111
  }
5620
- sendEvent2("session-update", { sessionId, notification: enrichedNotification });
6112
+ sendEvent3("session-update", { sessionId, notification: enrichedNotification });
5621
6113
  }
5622
6114
  function markProjectFsDirty(projectId) {
5623
6115
  const nextVersion = (projectFsVersions.get(projectId) ?? 0) + 1;
@@ -5639,7 +6131,7 @@ async function buildSearchIndex(cwd) {
5639
6131
  fileScene.add(dir);
5640
6132
  const entries = await readdir(dir).catch(() => []);
5641
6133
  for (const name of entries) {
5642
- const full = join7(dir, name);
6134
+ const full = join9(dir, name);
5643
6135
  const s = await stat3(full).catch(() => null);
5644
6136
  if (!s) continue;
5645
6137
  if (isIgnorePath(full, cwd)) continue;
@@ -5654,7 +6146,7 @@ async function buildSearchIndex(cwd) {
5654
6146
  return allFiles;
5655
6147
  }
5656
6148
  function initBackend(emitter) {
5657
- sendEvent2 = (channel, payload) => {
6149
+ sendEvent3 = (channel, payload) => {
5658
6150
  if (channel === "fs-changed") {
5659
6151
  markProjectFsDirty(payload.projectId);
5660
6152
  }
@@ -5662,20 +6154,22 @@ function initBackend(emitter) {
5662
6154
  const sentNative = emitter(channel, payload);
5663
6155
  return sentWebUI || sentNative;
5664
6156
  };
5665
- initWatcher(sendEvent2);
6157
+ initWatcher(sendEvent3);
6158
+ initRunner(sendEvent3);
6159
+ restoreActiveSchedules();
5666
6160
  getILinkBridge().tryRestore().then(async (restored) => {
5667
6161
  if (restored) {
5668
6162
  const savedId = await readActiveSessionId();
5669
6163
  if (savedId && storageOps.getSession(savedId)) {
5670
6164
  ilinkActiveSessionId = savedId;
5671
- sendEvent2("ilink-active-session-changed", { sessionId: savedId });
6165
+ sendEvent3("ilink-active-session-changed", { sessionId: savedId });
5672
6166
  }
5673
6167
  }
5674
6168
  }).catch((err) => {
5675
6169
  console.warn("[iLink] Failed to restore session:", err);
5676
6170
  });
5677
6171
  }
5678
- function resolveAgentInfo(agentId) {
6172
+ function resolveAgentInfo2(agentId) {
5679
6173
  const settings = storageOps.getSettings();
5680
6174
  const agent = settings.agents.find((a) => a.id === agentId);
5681
6175
  if (!agent) {
@@ -5708,7 +6202,7 @@ async function ensureBridge(agentId) {
5708
6202
  }
5709
6203
  await pooledBridge.kill();
5710
6204
  }
5711
- const agentInfo = resolveAgentInfo(agentId);
6205
+ const agentInfo = resolveAgentInfo2(agentId);
5712
6206
  const nextBridge = new ACPBridge(agentId, {
5713
6207
  agentInfo,
5714
6208
  onSessionUpdate: (notification) => {
@@ -5718,7 +6212,7 @@ async function ensureBridge(agentId) {
5718
6212
  if (notification.update.title) {
5719
6213
  storageOps.updateSession(sessionId, { title: notification.update.title });
5720
6214
  const updated = storageOps.getSession(sessionId);
5721
- if (updated) sendEvent2("session-changed", { session: updated });
6215
+ if (updated) sendEvent3("session-changed", { session: updated });
5722
6216
  }
5723
6217
  }
5724
6218
  if (sessionUpdate === "current_mode_update") {
@@ -5727,7 +6221,7 @@ async function ensureBridge(agentId) {
5727
6221
  session.modes.currentModeId = notification.update.currentModeId ?? null;
5728
6222
  storageOps.updateSession(sessionId, { modes: session.modes });
5729
6223
  const updated = storageOps.getSession(sessionId);
5730
- if (updated) sendEvent2("session-changed", { session: updated });
6224
+ if (updated) sendEvent3("session-changed", { session: updated });
5731
6225
  }
5732
6226
  }
5733
6227
  if (restoringSessions.has(sessionId) && sessionUpdate !== "available_commands_update" && sessionUpdate !== "usage_update") {
@@ -5790,7 +6284,7 @@ async function ensureBridge(agentId) {
5790
6284
  } catch (err) {
5791
6285
  console.error("write terminal output error: ", { sessionId, terminalId, err });
5792
6286
  }
5793
- sendEvent2("agent-terminal-output", { sessionId, terminalId, data });
6287
+ sendEvent3("agent-terminal-output", { sessionId, terminalId, data });
5794
6288
  }
5795
6289
  });
5796
6290
  let newConnectPromise;
@@ -5805,6 +6299,7 @@ async function ensureBridge(agentId) {
5805
6299
  return newConnectPromise;
5806
6300
  }
5807
6301
  async function clearBackend() {
6302
+ stopAllCrons();
5808
6303
  for (const ss of sessionSocketServers.values()) {
5809
6304
  ss.stop();
5810
6305
  }
@@ -5822,11 +6317,11 @@ async function clearBackend() {
5822
6317
  clientTerminals.clear();
5823
6318
  await Promise.all(killPromises);
5824
6319
  for (const file of await readdir(TEMP_DIR)) {
5825
- await rm2(join7(TEMP_DIR, file), { recursive: true, force: true });
6320
+ await rm2(join9(TEMP_DIR, file), { recursive: true, force: true });
5826
6321
  }
5827
6322
  }
5828
6323
  async function resolveTerminalCwd(preferredCwd) {
5829
- const candidates = [preferredCwd, process.cwd(), homedir3()].map((value) => value.trim()).filter((value, index, array) => value.length > 0 && array.indexOf(value) === index);
6324
+ const candidates = [preferredCwd, process.cwd(), homedir4()].map((value) => value.trim()).filter((value, index, array) => value.length > 0 && array.indexOf(value) === index);
5830
6325
  for (const candidate of candidates) {
5831
6326
  const info = await stat3(candidate).catch(() => null);
5832
6327
  if (info?.isDirectory()) return candidate;
@@ -5847,8 +6342,8 @@ async function ensureNodePtySpawnHelperExecutable() {
5847
6342
  if (process.platform === "win32") return;
5848
6343
  if (isNodePtyHelperPrepared) return;
5849
6344
  const packageJsonPath = require2.resolve("node-pty/package.json");
5850
- const packageDir = dirname4(packageJsonPath);
5851
- const helperPath = join7(
6345
+ const packageDir = dirname6(packageJsonPath);
6346
+ const helperPath = join9(
5852
6347
  packageDir,
5853
6348
  "prebuilds",
5854
6349
  `${process.platform}-${process.arch}`,
@@ -5911,14 +6406,14 @@ async function createTerminalProcess(cwd, initialSize) {
5911
6406
  const terminalId = createHash3("sha1").update(terminalSeed).digest("hex").slice(0, 12);
5912
6407
  terminals.set(terminalId, child);
5913
6408
  child.onData((data) => {
5914
- sendEvent2("terminal-output", { terminalId, data });
6409
+ sendEvent3("terminal-output", { terminalId, data });
5915
6410
  });
5916
6411
  child.onExit((exitCode) => {
5917
6412
  terminals.delete(terminalId);
5918
6413
  for (const set of clientTerminals.values()) {
5919
6414
  set.delete(terminalId);
5920
6415
  }
5921
- sendEvent2("terminal-exit", { terminalId, exitCode });
6416
+ sendEvent3("terminal-exit", { terminalId, exitCode });
5922
6417
  });
5923
6418
  return terminalId;
5924
6419
  }
@@ -5996,7 +6491,7 @@ var backendHandlers = {
5996
6491
  if (!skillDir) {
5997
6492
  throw new Error(`Failed to read skill: ${skillId}`);
5998
6493
  }
5999
- return join7(skillDir, SKILL_FILENAME);
6494
+ return join9(skillDir, SKILL_FILENAME);
6000
6495
  },
6001
6496
  async uninstallSkill({ skillId, projectId }) {
6002
6497
  const projectRoot = projectId ? storageOps.getProject(projectId)?.cwd : void 0;
@@ -6027,13 +6522,13 @@ var backendHandlers = {
6027
6522
  async startWebUIServer({ port: port2, token: token2 }) {
6028
6523
  const { url } = await startWebUI({ port: port2, token: token2 });
6029
6524
  const status = { enabled: true, url };
6030
- sendEvent2("webui-status-changed", { status });
6525
+ sendEvent3("webui-status-changed", { status });
6031
6526
  return status;
6032
6527
  },
6033
6528
  async stopWebUIServer() {
6034
6529
  stopWebUI();
6035
6530
  const status = { enabled: false, url: null };
6036
- sendEvent2("webui-status-changed", { status });
6531
+ sendEvent3("webui-status-changed", { status });
6037
6532
  return status;
6038
6533
  },
6039
6534
  async getSettings() {
@@ -6070,12 +6565,12 @@ var backendHandlers = {
6070
6565
  projectFsVersions.set(ProjectInfo.id, 0);
6071
6566
  }
6072
6567
  await syncWatchers();
6073
- sendEvent2("projects-changed", void 0);
6568
+ sendEvent3("projects-changed", void 0);
6074
6569
  return ProjectInfo;
6075
6570
  },
6076
6571
  async renameProject({ projectId, title }) {
6077
6572
  storageOps.updateProjectTitle(projectId, title);
6078
- sendEvent2("projects-changed", void 0);
6573
+ sendEvent3("projects-changed", void 0);
6079
6574
  },
6080
6575
  async deleteProject(projectId) {
6081
6576
  const projectSessions = storageOps.listSessions().filter((session) => session.projectId === projectId);
@@ -6109,14 +6604,14 @@ var backendHandlers = {
6109
6604
  }
6110
6605
  clearProjectSearchState(projectId);
6111
6606
  await syncWatchers();
6112
- sendEvent2("projects-changed", void 0);
6113
- sendEvent2("sessions-changed", void 0);
6607
+ sendEvent3("projects-changed", void 0);
6608
+ sendEvent3("sessions-changed", void 0);
6114
6609
  },
6115
6610
  async newSession({ projectId, agentId, mcpServers, permissionMode, features }) {
6116
6611
  const project = storageOps.getProject(projectId);
6117
6612
  if (!project) throw new Error("Project does not exist");
6118
6613
  const b = await ensureBridge(agentId);
6119
- const socketPath = generateSessionSocketPath(randomUUID4());
6614
+ const socketPath = generateSessionSocketPath(randomUUID5());
6120
6615
  const sessionMcpIds = mcpServers ?? (storageOps.getSettings().mcpServers || []).filter((s) => !s.disabled).map((s) => s.id);
6121
6616
  const effectiveFeatures = features ?? ALL_FEATURES;
6122
6617
  const activeMcpServers = buildMcpServersConfig(sessionMcpIds, {
@@ -6141,7 +6636,7 @@ var backendHandlers = {
6141
6636
  initializeInfo: b.initializeInfo
6142
6637
  });
6143
6638
  await createSessionSocketServer(sessionInfo.id, { socketPath, project });
6144
- sendEvent2("sessions-changed", void 0);
6639
+ sendEvent3("sessions-changed", void 0);
6145
6640
  return {
6146
6641
  sessionId: sessionInfo.id,
6147
6642
  initializeInfo: b.initializeInfo,
@@ -6166,10 +6661,10 @@ var backendHandlers = {
6166
6661
  if (session.isStreaming) {
6167
6662
  storageOps.updateSession(sessionId, { isStreaming: false });
6168
6663
  session.isStreaming = false;
6169
- sendEvent2("session-changed", { session });
6664
+ sendEvent3("session-changed", { session });
6170
6665
  }
6171
6666
  const existingSocketServer = sessionSocketServers.get(session.id);
6172
- const socketPath = existingSocketServer ? existingSocketServer.socketPath : generateSessionSocketPath(randomUUID4());
6667
+ const socketPath = existingSocketServer ? existingSocketServer.socketPath : generateSessionSocketPath(randomUUID5());
6173
6668
  const activeMcpServers = buildMcpServersConfig(session.mcpServers, {
6174
6669
  project,
6175
6670
  socketPath,
@@ -6230,7 +6725,7 @@ var backendHandlers = {
6230
6725
  }
6231
6726
  const freshSession = storageOps.getSession(session.id);
6232
6727
  if (freshSession) {
6233
- sendEvent2("session-changed", { session: freshSession });
6728
+ sendEvent3("session-changed", { session: freshSession });
6234
6729
  }
6235
6730
  return {
6236
6731
  sessionId: session.id,
@@ -6293,7 +6788,7 @@ var backendHandlers = {
6293
6788
  const b = await ensureBridge(session.agentId);
6294
6789
  if (!b.isSessionLoaded(session.resumeId)) {
6295
6790
  console.log(`[Fello] Session ${session.resumeId} not loaded in Agent, lazy loading...`);
6296
- const socketPath = generateSessionSocketPath(randomUUID4());
6791
+ const socketPath = generateSessionSocketPath(randomUUID5());
6297
6792
  const activeMcpServers = buildMcpServersConfig(session.mcpServers, {
6298
6793
  project,
6299
6794
  socketPath,
@@ -6308,8 +6803,8 @@ var backendHandlers = {
6308
6803
  }
6309
6804
  storageOps.updateSession(sessionId, { isStreaming: true });
6310
6805
  const updated = storageOps.getSession(sessionId);
6311
- if (updated) sendEvent2("session-changed", { session: updated });
6312
- sendEvent2("prompt-start", { sessionId });
6806
+ if (updated) sendEvent3("session-changed", { session: updated });
6807
+ sendEvent3("prompt-start", { sessionId });
6313
6808
  if (ilinkBridge?.isConnected && sessionId === ilinkActiveSessionId) {
6314
6809
  const userId = ilinkBridge.userId;
6315
6810
  if (userId) {
@@ -6339,14 +6834,14 @@ var backendHandlers = {
6339
6834
  promptError = err instanceof Error ? err.message : String(err);
6340
6835
  throw err;
6341
6836
  } finally {
6342
- sendEvent2("prompt-end", {
6837
+ sendEvent3("prompt-end", {
6343
6838
  sessionId,
6344
6839
  stopReason: promptResponse?.stopReason,
6345
6840
  error: promptError
6346
6841
  });
6347
6842
  storageOps.updateSession(sessionId, { isStreaming: false });
6348
6843
  const updated2 = storageOps.getSession(sessionId);
6349
- if (updated2) sendEvent2("session-changed", { session: updated2 });
6844
+ if (updated2) sendEvent3("session-changed", { session: updated2 });
6350
6845
  if (ilinkBridge?.isConnected && sessionId === ilinkActiveSessionId) {
6351
6846
  const userId = ilinkBridge.userId;
6352
6847
  if (userId) {
@@ -6415,7 +6910,7 @@ var backendHandlers = {
6415
6910
  clearTimeout(pending.timeoutId);
6416
6911
  pending.resolve({ value, reason: reason ?? null });
6417
6912
  pendingAskUserRequests.delete(askUserId);
6418
- sendEvent2("ask-user-response", {
6913
+ sendEvent3("ask-user-response", {
6419
6914
  sessionId,
6420
6915
  askUserId,
6421
6916
  value,
@@ -6428,7 +6923,7 @@ var backendHandlers = {
6428
6923
  async updateSession({ sessionId, ...updates }) {
6429
6924
  storageOps.updateSession(sessionId, updates);
6430
6925
  const session = storageOps.getSession(sessionId);
6431
- if (session) sendEvent2("session-changed", { session });
6926
+ if (session) sendEvent3("session-changed", { session });
6432
6927
  },
6433
6928
  async changeWorkDir() {
6434
6929
  return { ok: false, cwd: null };
@@ -6461,7 +6956,7 @@ var backendHandlers = {
6461
6956
  } catch (error) {
6462
6957
  console.warn("[iLink] Failed to clear persisted active session:", error);
6463
6958
  }
6464
- sendEvent2("ilink-active-session-changed", { sessionId: null });
6959
+ sendEvent3("ilink-active-session-changed", { sessionId: null });
6465
6960
  }
6466
6961
  if (session) {
6467
6962
  try {
@@ -6476,7 +6971,7 @@ var backendHandlers = {
6476
6971
  );
6477
6972
  }
6478
6973
  }
6479
- sendEvent2("sessions-changed", void 0);
6974
+ sendEvent3("sessions-changed", void 0);
6480
6975
  },
6481
6976
  async getSystemFilePath({ projectId, path: inputPath, isAbsolute: isAbsolute4 }) {
6482
6977
  const project = storageOps.getProject(projectId);
@@ -6491,14 +6986,14 @@ var backendHandlers = {
6491
6986
  if (!project) throw new Error("Project not found");
6492
6987
  const cwd = destDir || project.cwd;
6493
6988
  const fileName = basename3(sourcePath);
6494
- let destPath = join7(cwd, fileName);
6989
+ let destPath = join9(cwd, fileName);
6495
6990
  let counter = 1;
6496
6991
  while (true) {
6497
6992
  const info = await stat3(destPath).catch(() => null);
6498
6993
  if (!info) break;
6499
6994
  const ext = extname2(fileName);
6500
6995
  const name = basename3(fileName, ext);
6501
- destPath = join7(cwd, `${name}(${counter})${ext}`);
6996
+ destPath = join9(cwd, `${name}(${counter})${ext}`);
6502
6997
  counter++;
6503
6998
  }
6504
6999
  await copyFile(sourcePath, destPath);
@@ -6569,7 +7064,7 @@ var backendHandlers = {
6569
7064
  session.models.currentModelId = modelId;
6570
7065
  storageOps.updateSession(session.id, { models: session.models });
6571
7066
  const updated = storageOps.getSession(session.id);
6572
- if (updated) sendEvent2("session-changed", { session: updated });
7067
+ if (updated) sendEvent3("session-changed", { session: updated });
6573
7068
  }
6574
7069
  },
6575
7070
  async getModes({ sessionId }) {
@@ -6591,7 +7086,7 @@ var backendHandlers = {
6591
7086
  session.modes.currentModeId = modeId;
6592
7087
  storageOps.updateSession(session.id, { modes: session.modes });
6593
7088
  const updated = storageOps.getSession(session.id);
6594
- if (updated) sendEvent2("session-changed", { session: updated });
7089
+ if (updated) sendEvent3("session-changed", { session: updated });
6595
7090
  }
6596
7091
  },
6597
7092
  async searchFiles({ projectId, query }) {
@@ -6603,7 +7098,7 @@ var backendHandlers = {
6603
7098
  const entries = await readdir(cwd).catch(() => []);
6604
7099
  const results = [];
6605
7100
  for (const name of entries) {
6606
- const full = join7(cwd, name);
7101
+ const full = join9(cwd, name);
6607
7102
  if (isIgnorePath(full, cwd)) continue;
6608
7103
  if (fileScene.has(full)) continue;
6609
7104
  fileScene.add(full);
@@ -6645,7 +7140,7 @@ var backendHandlers = {
6645
7140
  const entries = await readdir(startPath).catch(() => []);
6646
7141
  const results = [];
6647
7142
  for (const name of entries) {
6648
- const full = join7(startPath, name);
7143
+ const full = join9(startPath, name);
6649
7144
  const s = await stat3(full).catch(() => null);
6650
7145
  if (!s) continue;
6651
7146
  if (isIgnorePath(full, cwd)) continue;
@@ -6669,7 +7164,7 @@ var backendHandlers = {
6669
7164
  if (isFolder) {
6670
7165
  await mkdir2(targetPath, { recursive: true });
6671
7166
  } else {
6672
- await mkdir2(dirname4(targetPath), { recursive: true });
7167
+ await mkdir2(dirname6(targetPath), { recursive: true });
6673
7168
  await writeFile3(targetPath, "");
6674
7169
  }
6675
7170
  markProjectFsDirty(projectId);
@@ -6740,7 +7235,7 @@ var backendHandlers = {
6740
7235
  const ext = extname2(fileName);
6741
7236
  const base = basename3(fileName, ext);
6742
7237
  let counter = 0;
6743
- let currentDest = join7(destDir, fileName);
7238
+ let currentDest = join9(destDir, fileName);
6744
7239
  while (true) {
6745
7240
  const existing = await stat3(currentDest).catch(() => null);
6746
7241
  if (!existing) break;
@@ -6748,7 +7243,7 @@ var backendHandlers = {
6748
7243
  throw new Error("Cannot overwrite a folder with a file");
6749
7244
  }
6750
7245
  counter++;
6751
- currentDest = join7(destDir, `${base}(${counter})${ext}`);
7246
+ currentDest = join9(destDir, `${base}(${counter})${ext}`);
6752
7247
  }
6753
7248
  const buffer = Buffer.from(base64, "base64");
6754
7249
  await mkdir2(destDir, { recursive: true });
@@ -6867,7 +7362,7 @@ var backendHandlers = {
6867
7362
  const project = storageOps.getProject(projectId);
6868
7363
  if (!project) throw new Error("Project not found");
6869
7364
  const targetPath = resolveSafePath(project.cwd, relativePath);
6870
- const cwd = dirname4(targetPath);
7365
+ const cwd = dirname6(targetPath);
6871
7366
  const relPath = relative5(cwd, targetPath);
6872
7367
  const { stdout } = await execFileAsync("git", ["show", `HEAD:./${relPath}`], {
6873
7368
  cwd,
@@ -6910,14 +7405,14 @@ var backendHandlers = {
6910
7405
  ilinkActiveSessionId = null;
6911
7406
  ilinkReplyBuffer = "";
6912
7407
  await writeActiveSessionId(null);
6913
- sendEvent2("ilink-active-session-changed", { sessionId: null });
7408
+ sendEvent3("ilink-active-session-changed", { sessionId: null });
6914
7409
  },
6915
7410
  async setActiveIlinkSession({ sessionId }) {
6916
7411
  if (!sessionId) {
6917
7412
  ilinkActiveSessionId = null;
6918
7413
  ilinkReplyBuffer = "";
6919
7414
  await writeActiveSessionId(null);
6920
- sendEvent2("ilink-active-session-changed", { sessionId: null });
7415
+ sendEvent3("ilink-active-session-changed", { sessionId: null });
6921
7416
  return;
6922
7417
  }
6923
7418
  const session = storageOps.getSession(sessionId);
@@ -6925,7 +7420,7 @@ var backendHandlers = {
6925
7420
  ilinkActiveSessionId = sessionId;
6926
7421
  ilinkReplyBuffer = "";
6927
7422
  await writeActiveSessionId(sessionId);
6928
- sendEvent2("ilink-active-session-changed", { sessionId });
7423
+ sendEvent3("ilink-active-session-changed", { sessionId });
6929
7424
  },
6930
7425
  async getActiveIlinkSession() {
6931
7426
  if (ilinkActiveSessionId) {
@@ -6940,11 +7435,80 @@ var backendHandlers = {
6940
7435
  } catch {
6941
7436
  }
6942
7437
  return { sessionId: null };
7438
+ },
7439
+ // ── Automation Handlers ───────────────────────────────────────────
7440
+ async listSchedules() {
7441
+ return store.listSchedules().map((s) => ({ ...s, nextRunAt: getNextRun(s) }));
7442
+ },
7443
+ async getServerTimezone() {
7444
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
7445
+ },
7446
+ async createSchedule(params) {
7447
+ const scheduleId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
7448
+ const now = Date.now();
7449
+ const schedule = {
7450
+ id: scheduleId,
7451
+ name: params.name,
7452
+ agentId: params.agentId,
7453
+ prompt: params.prompt,
7454
+ cron: { type: params.cron.type, expr: params.cron.expr },
7455
+ createdAt: now,
7456
+ updatedAt: now,
7457
+ lastRunAt: null,
7458
+ features: (params.features ?? []).filter((f) => f !== "ask_user"),
7459
+ mcpServers: params.mcpServers ?? []
7460
+ };
7461
+ store.saveSchedule(schedule);
7462
+ if (schedule.cron.type === "cron" && schedule.cron.expr) {
7463
+ scheduleCron(schedule);
7464
+ }
7465
+ sendEvent3("schedules-changed", void 0);
7466
+ return schedule;
7467
+ },
7468
+ async updateSchedule({ scheduleId, updates }) {
7469
+ const schedule = store.getSchedule(scheduleId);
7470
+ if (!schedule) throw new Error("Schedule not found");
7471
+ Object.assign(schedule, updates);
7472
+ schedule.updatedAt = Date.now();
7473
+ store.saveSchedule(schedule);
7474
+ if (schedule.cron.type === "cron" && schedule.cron.expr) {
7475
+ scheduleCron(schedule);
7476
+ } else {
7477
+ unscheduleCron(scheduleId);
7478
+ }
7479
+ sendEvent3("schedules-changed", void 0);
7480
+ return schedule;
7481
+ },
7482
+ async deleteSchedule({ scheduleId }) {
7483
+ unscheduleCron(scheduleId);
7484
+ store.deleteSchedule(scheduleId);
7485
+ sendEvent3("schedules-changed", void 0);
7486
+ },
7487
+ async triggerSchedule({ scheduleId }) {
7488
+ return executeTask(scheduleId);
7489
+ },
7490
+ async getTasks({ scheduleId }) {
7491
+ return store.listTasks(scheduleId);
7492
+ },
7493
+ async getTaskFiles({ scheduleId, taskId }) {
7494
+ return store.listTaskFiles(scheduleId, taskId);
7495
+ },
7496
+ async readTaskFile({ scheduleId, taskId, filePath }) {
7497
+ return store.readTaskFile(scheduleId, taskId, filePath);
7498
+ },
7499
+ async getTaskFileSystemPath({ scheduleId, taskId, filePath }) {
7500
+ const base = store.taskDir(scheduleId, taskId);
7501
+ const fullPath = join9(base, filePath);
7502
+ if (!fullPath.startsWith(base + "/") && fullPath !== base) throw new Error("Invalid file path");
7503
+ return fullPath;
7504
+ },
7505
+ async deleteTask({ scheduleId, taskId }) {
7506
+ store.deleteTask(scheduleId, taskId);
6943
7507
  }
6944
7508
  };
6945
7509
 
6946
7510
  // package.json
6947
- var version = "0.1.28";
7511
+ var version = "0.1.30";
6948
7512
 
6949
7513
  // src/server/main.ts
6950
7514
  var port;