@zythum02/fello-server 0.1.28 → 0.1.29

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 (55) hide show
  1. package/out/renderer/assets/{_baseUniq-C01H-TTl.js → _baseUniq-BApwm7pv.js} +1 -1
  2. package/out/renderer/assets/{arc-BXqrFYj-.js → arc-yKenDsmO.js} +1 -1
  3. package/out/renderer/assets/{architectureDiagram-Q4EWVU46-DlFz_4yO.js → architectureDiagram-Q4EWVU46-C31yFVWH.js} +5 -5
  4. package/out/renderer/assets/{blockDiagram-DXYQGD6D-B_jYpKkP.js → blockDiagram-DXYQGD6D-6liCvTXv.js} +6 -6
  5. package/out/renderer/assets/{c4Diagram-AHTNJAMY-DWRXz6-S.js → c4Diagram-AHTNJAMY-Co_b_6bH.js} +2 -2
  6. package/out/renderer/assets/{channel-iMh-jYBg.js → channel-Dc_jjyUr.js} +1 -1
  7. package/out/renderer/assets/{chunk-4BX2VUAB-CDXuLHIo.js → chunk-4BX2VUAB-Cg18SQei.js} +1 -1
  8. package/out/renderer/assets/{chunk-4TB4RGXK-Bs-z-5kA.js → chunk-4TB4RGXK-CvSqwINq.js} +5 -5
  9. package/out/renderer/assets/{chunk-55IACEB6-DBmLw2WJ.js → chunk-55IACEB6-CfBiPWtx.js} +1 -1
  10. package/out/renderer/assets/{chunk-EDXVE4YY-BhsDT27O.js → chunk-EDXVE4YY-DMjVfNdS.js} +1 -1
  11. package/out/renderer/assets/{chunk-FMBD7UC4-C_qJYK5q.js → chunk-FMBD7UC4-BbpFqyFG.js} +1 -1
  12. package/out/renderer/assets/{chunk-OYMX7WX6-DIh7s3uw.js → chunk-OYMX7WX6-Cf724x--.js} +3 -3
  13. package/out/renderer/assets/{chunk-QZHKN3VN-CYa45CSJ.js → chunk-QZHKN3VN-4ZNIwknr.js} +1 -1
  14. package/out/renderer/assets/{chunk-YZCP3GAM-BeF07Xv_.js → chunk-YZCP3GAM-D1j7xGGR.js} +1 -1
  15. package/out/renderer/assets/{classDiagram-6PBFFD2Q-B12_6ldG.js → classDiagram-6PBFFD2Q-OFMeoPav.js} +6 -6
  16. package/out/renderer/assets/{classDiagram-v2-HSJHXN6E-B12_6ldG.js → classDiagram-v2-HSJHXN6E-OFMeoPav.js} +6 -6
  17. package/out/renderer/assets/{clone-1GZJZPn3.js → clone-Cm3RO2Zl.js} +1 -1
  18. package/out/renderer/assets/{cose-bilkent-S5V4N54A-CuSnz9Ro.js → cose-bilkent-S5V4N54A-lCfWcP6y.js} +1 -1
  19. package/out/renderer/assets/{dagre-KV5264BT-LHPLEy9g.js → dagre-KV5264BT-4I0h4wyh.js} +6 -6
  20. package/out/renderer/assets/{diagram-5BDNPKRD-DWjP3Jzr.js → diagram-5BDNPKRD-C1mEpAlt.js} +6 -6
  21. package/out/renderer/assets/{diagram-G4DWMVQ6-CM3uASto.js → diagram-G4DWMVQ6-C_g_DZm9.js} +6 -6
  22. package/out/renderer/assets/{diagram-MMDJMWI5-X5oh5RON.js → diagram-MMDJMWI5-Di6wZV6-.js} +5 -5
  23. package/out/renderer/assets/{diagram-TYMM5635-iO4Xohj9.js → diagram-TYMM5635-aF8hIDx1.js} +5 -5
  24. package/out/renderer/assets/{erDiagram-SMLLAGMA-4xwE7Ysd.js → erDiagram-SMLLAGMA-Bk9QFZ27.js} +4 -4
  25. package/out/renderer/assets/{flowDiagram-DWJPFMVM-DoIqEJWm.js → flowDiagram-DWJPFMVM-Dfg0LLup.js} +6 -6
  26. package/out/renderer/assets/{ganttDiagram-T4ZO3ILL-C48vXSUK.js → ganttDiagram-T4ZO3ILL-lTjaJTs0.js} +2 -2
  27. package/out/renderer/assets/{gitGraphDiagram-UUTBAWPF-D3YiiEhK.js → gitGraphDiagram-UUTBAWPF-C74WBszH.js} +6 -6
  28. package/out/renderer/assets/{graph-CYPfPCZi.js → graph-DGEX1eV1.js} +2 -2
  29. package/out/renderer/assets/{highlighted-body-OFNGDK62-B6M-kMLU.js → highlighted-body-OFNGDK62-DQWoSdTY.js} +1 -1
  30. package/out/renderer/assets/{index-Dabo17L1.js → index-BZzw3jIU.js} +1506 -244
  31. package/out/renderer/assets/index-Cv3lJTlc.css +382 -0
  32. package/out/renderer/assets/{infoDiagram-42DDH7IO-ur7rRidE.js → infoDiagram-42DDH7IO-CR4ZLAM1.js} +4 -4
  33. package/out/renderer/assets/{ishikawaDiagram-UXIWVN3A-fJSipMV8.js → ishikawaDiagram-UXIWVN3A-uXL-4FGw.js} +1 -1
  34. package/out/renderer/assets/{journeyDiagram-VCZTEJTY-TxjKxXBo.js → journeyDiagram-VCZTEJTY-Ds2OL3Lo.js} +4 -4
  35. package/out/renderer/assets/{kanban-definition-6JOO6SKY-DJwjRjlE.js → kanban-definition-6JOO6SKY-CZ1CZVyP.js} +2 -2
  36. package/out/renderer/assets/{layout-BT1ASsLg.js → layout-CNGveWdi.js} +4 -4
  37. package/out/renderer/assets/{linear-ecuEcPGt.js → linear-CnHAiAJ3.js} +1 -1
  38. package/out/renderer/assets/{min-BYCDJeWK.js → min-D63AfN0e.js} +2 -2
  39. package/out/renderer/assets/{mindmap-definition-QFDTVHPH-D2MxZmzw.js → mindmap-definition-QFDTVHPH-C3U3xkIJ.js} +3 -3
  40. package/out/renderer/assets/{pieDiagram-DEJITSTG-Cew7QWxE.js → pieDiagram-DEJITSTG-C8AxK_-F.js} +6 -6
  41. package/out/renderer/assets/{quadrantDiagram-34T5L4WZ-CrZCtVJh.js → quadrantDiagram-34T5L4WZ-BdiZmi7w.js} +2 -2
  42. package/out/renderer/assets/{requirementDiagram-MS252O5E-C69IGxW8.js → requirementDiagram-MS252O5E-BHIfl5Tz.js} +3 -3
  43. package/out/renderer/assets/{sankeyDiagram-XADWPNL6-B2tuS_nG.js → sankeyDiagram-XADWPNL6-SthTR8cL.js} +1 -1
  44. package/out/renderer/assets/{sequenceDiagram-FGHM5R23-Dv_lOZN1.js → sequenceDiagram-FGHM5R23-COKEbCYM.js} +3 -3
  45. package/out/renderer/assets/{stateDiagram-FHFEXIEX-D9dJAqtV.js → stateDiagram-FHFEXIEX-7zRGBf7O.js} +8 -8
  46. package/out/renderer/assets/{stateDiagram-v2-QKLJ7IA2-CQXoaDY1.js → stateDiagram-v2-QKLJ7IA2-BqHJ_ZBX.js} +4 -4
  47. package/out/renderer/assets/{timeline-definition-GMOUNBTQ-DnnX0Ml0.js → timeline-definition-GMOUNBTQ-AvZAT5J-.js} +2 -2
  48. package/out/renderer/assets/{vennDiagram-DHZGUBPP-BCIC8HTk.js → vennDiagram-DHZGUBPP-oSTndBDY.js} +1 -1
  49. package/out/renderer/assets/{wardley-RL74JXVD-B35jxdMa.js → wardley-RL74JXVD-C8hIuf19.js} +3 -3
  50. package/out/renderer/assets/{wardleyDiagram-NUSXRM2D-D3rjXear.js → wardleyDiagram-NUSXRM2D-Dv2kSOMM.js} +5 -5
  51. package/out/renderer/assets/{xychartDiagram-5P7HB3ND-095YR9W9.js → xychartDiagram-5P7HB3ND-J9z4uuMK.js} +2 -2
  52. package/out/renderer/index.html +2 -2
  53. package/out/server/main.js +695 -159
  54. package/package.json +3 -1
  55. 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,399 @@ 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 }) => ({ id, name, description }));
4832
+ });
4833
+ server.registry("skills/detail", async (payload) => {
4834
+ const { id } = payload;
4835
+ const catalog = getSkillsCatalog({ projectRoot: taskDir });
4836
+ const skill = catalog.find((s) => s.id === id);
4837
+ if (!skill) return { error: `Skill '${id}' not found.` };
4838
+ const skillDir = getSkillSystemPathFromId(skill.id, { projectRoot: taskDir });
4839
+ if (!skillDir) return { error: `Failed to read skill '${id}'` };
4840
+ let body = "";
4841
+ try {
4842
+ const text = fs2.readFileSync(join8(skillDir, SKILL_FILENAME), "utf8");
4843
+ body = parseSkillFrontmatter(text).body;
4844
+ } catch {
4845
+ return { error: `Failed to read skill '${id}'` };
4846
+ }
4847
+ let supportingFiles = [];
4848
+ try {
4849
+ supportingFiles = listSkillFiles(skill.id, { projectRoot: taskDir });
4850
+ } catch {
4851
+ }
4852
+ return { id: skill.id, name: skill.name, description: skill.description, instructions: body, root_path: skillDir, supporting_files: supportingFiles };
4853
+ });
4854
+ const catalogData = getSkillsCatalog({ projectRoot: taskDir }).map(({ id, name, description }) => ({ id, name, description }));
4855
+ const catalogFile = join8(TEMP_DIR, `auto-catalog-${randomUUID4()}.json`);
4856
+ fs2.writeFileSync(catalogFile, JSON.stringify(catalogData), "utf8");
4857
+ const mcpServer = {
4858
+ name: "skills",
4859
+ command: process.execPath,
4860
+ args: [
4861
+ join8(__dirname2, "../scripts/mcp-skills/server.mjs"),
4862
+ "--project-dir",
4863
+ taskDir,
4864
+ "--socket-path",
4865
+ socketPath,
4866
+ "--catalog",
4867
+ catalogFile
4868
+ ],
4869
+ env: [{ name: "ELECTRON_RUN_AS_NODE", value: "1" }]
4870
+ };
4871
+ return { server, mcpServer };
4872
+ }
4873
+ async function executeTask(scheduleId) {
4874
+ if (runningTasks.has(scheduleId)) {
4875
+ throw new Error("Schedule task is already running");
4876
+ }
4877
+ const schedule = store.getSchedule(scheduleId);
4878
+ if (!schedule) throw new Error("Schedule not found");
4879
+ runningTasks.add(scheduleId);
4880
+ const taskId = String(Date.now());
4881
+ const startedAt = Date.now();
4882
+ const task = { id: taskId, scheduleId, startedAt, completedAt: null, status: "running" };
4883
+ store.saveTask(scheduleId, task);
4884
+ sendEvent?.("task-update", { scheduleId, task });
4885
+ const notifications = [];
4886
+ try {
4887
+ const agentInfo = resolveAgentInfo(schedule.agentId);
4888
+ const taskDir = store.taskDir(scheduleId, taskId);
4889
+ const bridge = new ACPBridge(schedule.agentId, {
4890
+ agentInfo,
4891
+ onSessionUpdate: (notification) => {
4892
+ notifications.push({
4893
+ ...notification,
4894
+ update: {
4895
+ ...notification.update,
4896
+ _meta: { ...notification.update?._meta, fello: { receivedAt: Date.now(), displayId: `auto-${notifications.length}` } }
4897
+ }
4898
+ });
4899
+ },
4900
+ onPermissionRequest: async (req) => {
4901
+ const opt = req.options.find((o) => o.kind === "allow_always") ?? req.options.find((o) => o.kind === "allow_once") ?? req.options[0];
4902
+ return { outcome: { outcome: "selected", optionId: opt?.optionId ?? "allow" } };
4903
+ },
4904
+ onAgentTerminalOutput: () => {
4905
+ }
4906
+ });
4907
+ await bridge.connect();
4908
+ const mcpServers = buildAutomationMcpServers(schedule.mcpServers ?? []);
4909
+ let skillsServer = null;
4910
+ const effectiveFeatures = schedule.features ?? [];
4911
+ if (effectiveFeatures.includes("skills")) {
4912
+ const skills = await setupSkillsServer(taskDir);
4913
+ skillsServer = skills.server;
4914
+ mcpServers.unshift(skills.mcpServer);
4915
+ }
4916
+ const { sessionId } = await bridge.newSession({ cwd: taskDir, mcpServers });
4917
+ const promptContent = [{ type: "text", text: schedule.prompt }];
4918
+ await bridge.sendPrompt({ sessionId, prompt: promptContent });
4919
+ const messages = reduceNotificationsToMessages(notifications);
4920
+ store.writeTaskFile(scheduleId, taskId, "README.md", generateReadme(schedule, messages, startedAt));
4921
+ store.writeTaskFile(scheduleId, taskId, "conversation.json", JSON.stringify(messages, null, 2));
4922
+ await bridge.kill();
4923
+ skillsServer?.stop();
4924
+ schedule.lastRunAt = Date.now();
4925
+ schedule.updatedAt = Date.now();
4926
+ store.saveSchedule(schedule);
4927
+ sendEvent?.("schedules-changed", void 0);
4928
+ task.completedAt = Date.now();
4929
+ task.status = "success";
4930
+ store.saveTask(scheduleId, task);
4931
+ sendEvent?.("task-update", { scheduleId, task });
4932
+ return task;
4933
+ } catch (err) {
4934
+ const errorMessage = err instanceof Error ? err.message : String(err);
4935
+ task.completedAt = Date.now();
4936
+ task.status = "error";
4937
+ task.error = errorMessage;
4938
+ store.saveTask(scheduleId, task);
4939
+ sendEvent?.("task-update", { scheduleId, task });
4940
+ console.error(`[Automation] Task failed:`, errorMessage);
4941
+ return task;
4942
+ } finally {
4943
+ runningTasks.delete(scheduleId);
4944
+ }
4945
+ }
4946
+ function reduceNotificationsToMessages(notifications) {
4947
+ const messages = [];
4948
+ for (const n of notifications) {
4949
+ const update = n.update;
4950
+ if (!update) continue;
4951
+ switch (update.sessionUpdate) {
4952
+ case "user_message_chunk": {
4953
+ const content = update.content;
4954
+ if (!content || content.type !== "text") break;
4955
+ const last = messages[messages.length - 1];
4956
+ if (last && last.role === "user") {
4957
+ last.text += content.text ?? "";
4958
+ } else {
4959
+ messages.push({ role: "user", text: content.text ?? "" });
4960
+ }
4961
+ break;
4962
+ }
4963
+ case "agent_message_chunk": {
4964
+ const content = update.content;
4965
+ if (!content || content.type !== "text") break;
4966
+ const last = messages[messages.length - 1];
4967
+ if (last && last.role === "assistant") {
4968
+ last.text += content.text ?? "";
4969
+ } else {
4970
+ messages.push({ role: "assistant", text: content.text ?? "" });
4971
+ }
4972
+ break;
4973
+ }
4974
+ case "agent_thought_chunk": {
4975
+ const content = update.content;
4976
+ if (!content || content.type !== "text") break;
4977
+ const last = messages[messages.length - 1];
4978
+ if (last && last.role === "thought") {
4979
+ last.text += content.text ?? "";
4980
+ } else {
4981
+ messages.push({ role: "thought", text: content.text ?? "" });
4982
+ }
4983
+ break;
4984
+ }
4985
+ case "tool_call":
4986
+ case "tool_call_update": {
4987
+ const title = update.title ?? "";
4988
+ const content = update.content;
4989
+ let text = "";
4990
+ if (Array.isArray(content)) {
4991
+ for (const block of content) {
4992
+ if (block.type === "text") text += block.text ?? "";
4993
+ }
4994
+ }
4995
+ messages.push({ role: "tool_call", text, toolTitle: title });
4996
+ break;
4997
+ }
4998
+ }
4999
+ }
5000
+ return messages;
5001
+ }
5002
+ function generateReadme(schedule, messages, startedAt) {
5003
+ const lines = [];
5004
+ lines.push(`# ${schedule.name}`);
5005
+ lines.push(`> Run at ${new Date(startedAt).toLocaleString()}`);
5006
+ lines.push("");
5007
+ for (const msg of messages) {
5008
+ if (msg.role === "user" || msg.role === "assistant") {
5009
+ const text = msg.text.trim();
5010
+ if (text) lines.push(text + "\n");
5011
+ }
5012
+ }
5013
+ return lines.join("\n");
5014
+ }
5015
+
5016
+ // src/backend/automation/scheduler.ts
5017
+ var scheduledCrons = /* @__PURE__ */ new Map();
5018
+ function getNextRun(schedule) {
5019
+ const job = scheduledCrons.get(schedule.id);
5020
+ if (!job) return null;
5021
+ try {
5022
+ return job.nextDate().toMillis();
5023
+ } catch {
5024
+ return null;
5025
+ }
5026
+ }
5027
+ function scheduleCron(schedule) {
5028
+ unscheduleCron(schedule.id);
5029
+ if (schedule.cron.type !== "cron" || !schedule.cron.expr) {
5030
+ return;
5031
+ }
5032
+ try {
5033
+ const cronJob = new CronJob(
5034
+ schedule.cron.expr,
5035
+ async () => {
5036
+ if (runningTasks.has(schedule.id)) {
5037
+ console.log(`[Automation] "${schedule.name}" already running, skipping`);
5038
+ return;
5039
+ }
5040
+ await executeTask(schedule.id);
5041
+ },
5042
+ null,
5043
+ false
5044
+ );
5045
+ scheduledCrons.set(schedule.id, cronJob);
5046
+ cronJob.start();
5047
+ const next = getNextRun(schedule);
5048
+ console.log(
5049
+ `[Automation] Scheduled "${schedule.name}" (${schedule.cron.expr}) next: ${next ? new Date(next).toLocaleString() : "?"}`
5050
+ );
5051
+ } catch (err) {
5052
+ console.error(`[Automation] Failed to schedule "${schedule.name}":`, err);
5053
+ }
5054
+ }
5055
+ function unscheduleCron(scheduleId) {
5056
+ const existing = scheduledCrons.get(scheduleId);
5057
+ if (existing) {
5058
+ existing.stop();
5059
+ scheduledCrons.delete(scheduleId);
5060
+ }
5061
+ }
5062
+ function restoreActiveSchedules() {
5063
+ const schedules = store.listSchedules();
5064
+ for (const s of schedules) {
5065
+ if (s.cron.type === "cron" && s.cron.expr) {
5066
+ scheduleCron(s);
5067
+ }
5068
+ }
5069
+ console.log(`[Automation] Restored ${scheduledCrons.size} active schedule(s)`);
5070
+ }
5071
+ function stopAllCrons() {
5072
+ for (const job of scheduledCrons.values()) {
5073
+ job.stop();
5074
+ }
5075
+ scheduledCrons.clear();
5076
+ console.log("[Automation] All crons stopped");
5077
+ }
5078
+ var runningTasks = /* @__PURE__ */ new Set();
5079
+
5080
+ // src/backend/watcher.ts
5081
+ import watcher from "@parcel/watcher";
5082
+ import { relative as relative4 } from "path";
5083
+ function isFileWatcherEnabled() {
5084
+ return storageOps.getSettings().fileWatcher?.enabled ?? true;
5085
+ }
5086
+ var subscriptions = /* @__PURE__ */ new Map();
5087
+ var MAX_BATCH_CHANGES = 1e3;
5088
+ var IGNORE = [
5089
+ "**/.git/**",
5090
+ "**/.svn/**",
5091
+ "**/.hg/**",
5092
+ "**/node_modules/**",
5093
+ "**/bower_components/**",
5094
+ "**/.cache/**",
5095
+ "**/venv/**",
5096
+ "**/.venv/**",
5097
+ "**/vendor/**",
5098
+ "**/__pycache__/**"
5099
+ ];
5100
+ var sendEvent2 = () => false;
5101
+ function initWatcher(emitter) {
5102
+ sendEvent2 = emitter;
5103
+ void syncWatchers();
5104
+ }
5105
+ async function syncWatchers() {
5106
+ const enabled = isFileWatcherEnabled();
5107
+ const projects = storageOps.listProjects();
5108
+ const currentProjects = new Map(projects.map((p) => [p.id, p]));
5109
+ if (!enabled) {
5110
+ for (const [projectId, subscription] of subscriptions.entries()) {
5111
+ await subscription.unsubscribe();
5112
+ subscriptions.delete(projectId);
5113
+ }
5114
+ return;
5115
+ }
5116
+ for (const [projectId, subscription] of subscriptions.entries()) {
5117
+ if (!currentProjects.has(projectId)) {
5118
+ await subscription.unsubscribe();
5119
+ subscriptions.delete(projectId);
5120
+ }
5121
+ }
5122
+ for (const [projectId, project] of currentProjects) {
5123
+ if (!subscriptions.has(projectId)) {
5124
+ await createWatcher(projectId, project.cwd);
5125
+ }
5126
+ }
5127
+ }
5128
+ async function createWatcher(projectId, cwd) {
5129
+ const changes = /* @__PURE__ */ new Set();
5130
+ let overflowed = false;
5131
+ let timeout = null;
5132
+ const flush = () => {
5133
+ if (changes.size > 0) {
5134
+ sendEvent2("fs-changed", { projectId, changes: Array.from(changes) });
5135
+ changes.clear();
5136
+ overflowed = false;
5137
+ }
5138
+ };
5139
+ const onChange = (path2) => {
5140
+ if (!overflowed) {
5141
+ if (changes.size < MAX_BATCH_CHANGES) {
5142
+ changes.add(path2);
5143
+ } else {
5144
+ overflowed = true;
5145
+ changes.clear();
5146
+ changes.add(".");
5147
+ }
5148
+ }
5149
+ if (timeout) clearTimeout(timeout);
5150
+ timeout = setTimeout(flush, 500);
5151
+ };
5152
+ try {
5153
+ const subscription = await watcher.subscribe(
5154
+ cwd,
5155
+ (err, events) => {
5156
+ if (err) {
5157
+ console.error(`[Watcher] Error for ${projectId} (${cwd}):`, err);
5158
+ return;
5159
+ }
5160
+ for (const event of events) {
5161
+ onChange(toPosixPath(relative4(cwd, event.path)));
5162
+ }
5163
+ },
5164
+ { ignore: IGNORE }
5165
+ );
5166
+ subscriptions.set(projectId, subscription);
5167
+ } catch (err) {
5168
+ console.error(`[Watcher] Failed to subscribe for ${projectId} (${cwd}):`, err);
5169
+ }
5170
+ }
5171
+
4708
5172
  // src/backend/i18n.ts
4709
5173
  import i18next from "i18next";
4710
5174
 
@@ -4868,7 +5332,7 @@ function setLanguage(lang) {
4868
5332
  // src/backend/backend.ts
4869
5333
  var require2 = createRequire(import.meta.url);
4870
5334
  var execFileAsync = promisify(execFile);
4871
- var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
5335
+ var __dirname3 = dirname6(fileURLToPath3(import.meta.url));
4872
5336
  function buildMcpServersConfig(sessionMcpIds, options) {
4873
5337
  const { project, socketPath, features = ALL_FEATURES } = options;
4874
5338
  const servers = [];
@@ -4876,13 +5340,13 @@ function buildMcpServersConfig(sessionMcpIds, options) {
4876
5340
  const skillCatalog = getSkillsCatalog({
4877
5341
  projectRoot: project.cwd
4878
5342
  }).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");
5343
+ const skillCatalogFilename = join9(TEMP_DIR, `project-${project.id}-${randomUUID5()}.json`);
5344
+ fs3.writeFileSync(skillCatalogFilename, JSON.stringify(skillCatalog), "utf8");
4881
5345
  servers.push({
4882
5346
  name: "skills",
4883
5347
  command: process.execPath,
4884
5348
  args: [
4885
- join7(__dirname2, "../scripts/mcp-skills/server.mjs"),
5349
+ join9(__dirname3, "../scripts/mcp-skills/server.mjs"),
4886
5350
  "--project-dir",
4887
5351
  project.cwd,
4888
5352
  "--socket-path",
@@ -4903,7 +5367,7 @@ function buildMcpServersConfig(sessionMcpIds, options) {
4903
5367
  name: "ask-user",
4904
5368
  command: process.execPath,
4905
5369
  args: [
4906
- join7(__dirname2, "../scripts/mcp-ask-user/server.mjs"),
5370
+ join9(__dirname3, "../scripts/mcp-ask-user/server.mjs"),
4907
5371
  "--project-dir",
4908
5372
  project.cwd,
4909
5373
  "--socket-path",
@@ -4953,7 +5417,7 @@ function generateSessionSocketPath(key) {
4953
5417
  if (process.platform === "win32") {
4954
5418
  return `\\\\.\\pipe\\fello-${key}-${timestamp}`;
4955
5419
  }
4956
- return join7(SOCKETS_DIR, `${key}-${timestamp}.socket`);
5420
+ return join9(SOCKETS_DIR, `${key}-${timestamp}.socket`);
4957
5421
  }
4958
5422
  async function createSessionSocketServer(sessionId, options) {
4959
5423
  const existing = sessionSocketServers.get(sessionId);
@@ -4997,10 +5461,10 @@ async function createSessionSocketServer(sessionId, options) {
4997
5461
  if (!skillDir) {
4998
5462
  return { error: `Failed to Read skill '${id}'` };
4999
5463
  }
5000
- const skillFile = join7(skillDir, SKILL_FILENAME);
5464
+ const skillFile = join9(skillDir, SKILL_FILENAME);
5001
5465
  let body = "";
5002
5466
  try {
5003
- const text = fs2.readFileSync(skillFile, "utf8");
5467
+ const text = fs3.readFileSync(skillFile, "utf8");
5004
5468
  const parsed = parseSkillFrontmatter(text);
5005
5469
  body = parsed.body;
5006
5470
  } catch {
@@ -5055,9 +5519,9 @@ function formatAskUserForWeChat(request) {
5055
5519
  }
5056
5520
  async function askUser(options) {
5057
5521
  const { sessionId } = options;
5058
- const askUserId = randomUUID4();
5522
+ const askUserId = randomUUID5();
5059
5523
  const request = { ...options, askUserId };
5060
- sendEvent2("ask-user-request", request);
5524
+ sendEvent3("ask-user-request", request);
5061
5525
  if (ilinkBridge?.isConnected && sessionId === ilinkActiveSessionId) {
5062
5526
  const userId = ilinkBridge.userId;
5063
5527
  if (userId) {
@@ -5076,7 +5540,7 @@ async function askUser(options) {
5076
5540
  pendingAskUserRequests.delete(askUserId);
5077
5541
  const response = { value: null, reason: "timeout" };
5078
5542
  resolve4(response);
5079
- sendEvent2("ask-user-response", {
5543
+ sendEvent3("ask-user-response", {
5080
5544
  sessionId,
5081
5545
  askUserId,
5082
5546
  value: null,
@@ -5097,7 +5561,7 @@ function getILinkBridge() {
5097
5561
  if (!ilinkBridge) {
5098
5562
  ilinkBridge = new ILinkBridge({
5099
5563
  onStatusChange: (status) => {
5100
- sendEvent2("ilink-status-changed", { status });
5564
+ sendEvent3("ilink-status-changed", { status });
5101
5565
  broadcastWebUIEvent("ilink-status-changed", { status });
5102
5566
  },
5103
5567
  onMessage: async (msg) => {
@@ -5182,7 +5646,7 @@ function getILinkBridge() {
5182
5646
  ilinkReplyBuffer = "";
5183
5647
  writeActiveSessionId(entry.sessionId).catch(() => {
5184
5648
  });
5185
- sendEvent2("ilink-active-session-changed", { sessionId: entry.sessionId });
5649
+ sendEvent3("ilink-active-session-changed", { sessionId: entry.sessionId });
5186
5650
  if (msg.from_user_id) {
5187
5651
  ilinkBridge?.sendTextReply(
5188
5652
  msg.from_user_id,
@@ -5246,7 +5710,7 @@ function getILinkBridge() {
5246
5710
  ilinkReplyBuffer = "";
5247
5711
  writeActiveSessionId(newSessionId).catch(() => {
5248
5712
  });
5249
- sendEvent2("ilink-active-session-changed", { sessionId: newSessionId });
5713
+ sendEvent3("ilink-active-session-changed", { sessionId: newSessionId });
5250
5714
  if (msg.from_user_id) {
5251
5715
  ilinkBridge?.sendTextReply(
5252
5716
  msg.from_user_id,
@@ -5555,7 +6019,7 @@ var terminals = /* @__PURE__ */ new Map();
5555
6019
  var clientTerminals = /* @__PURE__ */ new Map();
5556
6020
  var terminalCounter = 0;
5557
6021
  var isNodePtyHelperPrepared = false;
5558
- var sendEvent2 = () => false;
6022
+ var sendEvent3 = () => false;
5559
6023
  function broadcastAndSaveSessionUpdate(sessionId, notification) {
5560
6024
  const enrichedNotification = {
5561
6025
  ...notification,
@@ -5565,7 +6029,7 @@ function broadcastAndSaveSessionUpdate(sessionId, notification) {
5565
6029
  ...notification.update?._meta,
5566
6030
  fello: {
5567
6031
  receivedAt: Date.now(),
5568
- displayId: randomUUID4()
6032
+ displayId: randomUUID5()
5569
6033
  }
5570
6034
  }
5571
6035
  }
@@ -5617,7 +6081,7 @@ function broadcastAndSaveSessionUpdate(sessionId, notification) {
5617
6081
  } else {
5618
6082
  storageOps.appendSessionMessage(sessionId, enrichedNotification);
5619
6083
  }
5620
- sendEvent2("session-update", { sessionId, notification: enrichedNotification });
6084
+ sendEvent3("session-update", { sessionId, notification: enrichedNotification });
5621
6085
  }
5622
6086
  function markProjectFsDirty(projectId) {
5623
6087
  const nextVersion = (projectFsVersions.get(projectId) ?? 0) + 1;
@@ -5639,7 +6103,7 @@ async function buildSearchIndex(cwd) {
5639
6103
  fileScene.add(dir);
5640
6104
  const entries = await readdir(dir).catch(() => []);
5641
6105
  for (const name of entries) {
5642
- const full = join7(dir, name);
6106
+ const full = join9(dir, name);
5643
6107
  const s = await stat3(full).catch(() => null);
5644
6108
  if (!s) continue;
5645
6109
  if (isIgnorePath(full, cwd)) continue;
@@ -5654,7 +6118,7 @@ async function buildSearchIndex(cwd) {
5654
6118
  return allFiles;
5655
6119
  }
5656
6120
  function initBackend(emitter) {
5657
- sendEvent2 = (channel, payload) => {
6121
+ sendEvent3 = (channel, payload) => {
5658
6122
  if (channel === "fs-changed") {
5659
6123
  markProjectFsDirty(payload.projectId);
5660
6124
  }
@@ -5662,20 +6126,22 @@ function initBackend(emitter) {
5662
6126
  const sentNative = emitter(channel, payload);
5663
6127
  return sentWebUI || sentNative;
5664
6128
  };
5665
- initWatcher(sendEvent2);
6129
+ initWatcher(sendEvent3);
6130
+ initRunner(sendEvent3);
6131
+ restoreActiveSchedules();
5666
6132
  getILinkBridge().tryRestore().then(async (restored) => {
5667
6133
  if (restored) {
5668
6134
  const savedId = await readActiveSessionId();
5669
6135
  if (savedId && storageOps.getSession(savedId)) {
5670
6136
  ilinkActiveSessionId = savedId;
5671
- sendEvent2("ilink-active-session-changed", { sessionId: savedId });
6137
+ sendEvent3("ilink-active-session-changed", { sessionId: savedId });
5672
6138
  }
5673
6139
  }
5674
6140
  }).catch((err) => {
5675
6141
  console.warn("[iLink] Failed to restore session:", err);
5676
6142
  });
5677
6143
  }
5678
- function resolveAgentInfo(agentId) {
6144
+ function resolveAgentInfo2(agentId) {
5679
6145
  const settings = storageOps.getSettings();
5680
6146
  const agent = settings.agents.find((a) => a.id === agentId);
5681
6147
  if (!agent) {
@@ -5708,7 +6174,7 @@ async function ensureBridge(agentId) {
5708
6174
  }
5709
6175
  await pooledBridge.kill();
5710
6176
  }
5711
- const agentInfo = resolveAgentInfo(agentId);
6177
+ const agentInfo = resolveAgentInfo2(agentId);
5712
6178
  const nextBridge = new ACPBridge(agentId, {
5713
6179
  agentInfo,
5714
6180
  onSessionUpdate: (notification) => {
@@ -5718,7 +6184,7 @@ async function ensureBridge(agentId) {
5718
6184
  if (notification.update.title) {
5719
6185
  storageOps.updateSession(sessionId, { title: notification.update.title });
5720
6186
  const updated = storageOps.getSession(sessionId);
5721
- if (updated) sendEvent2("session-changed", { session: updated });
6187
+ if (updated) sendEvent3("session-changed", { session: updated });
5722
6188
  }
5723
6189
  }
5724
6190
  if (sessionUpdate === "current_mode_update") {
@@ -5727,7 +6193,7 @@ async function ensureBridge(agentId) {
5727
6193
  session.modes.currentModeId = notification.update.currentModeId ?? null;
5728
6194
  storageOps.updateSession(sessionId, { modes: session.modes });
5729
6195
  const updated = storageOps.getSession(sessionId);
5730
- if (updated) sendEvent2("session-changed", { session: updated });
6196
+ if (updated) sendEvent3("session-changed", { session: updated });
5731
6197
  }
5732
6198
  }
5733
6199
  if (restoringSessions.has(sessionId) && sessionUpdate !== "available_commands_update" && sessionUpdate !== "usage_update") {
@@ -5790,7 +6256,7 @@ async function ensureBridge(agentId) {
5790
6256
  } catch (err) {
5791
6257
  console.error("write terminal output error: ", { sessionId, terminalId, err });
5792
6258
  }
5793
- sendEvent2("agent-terminal-output", { sessionId, terminalId, data });
6259
+ sendEvent3("agent-terminal-output", { sessionId, terminalId, data });
5794
6260
  }
5795
6261
  });
5796
6262
  let newConnectPromise;
@@ -5805,6 +6271,7 @@ async function ensureBridge(agentId) {
5805
6271
  return newConnectPromise;
5806
6272
  }
5807
6273
  async function clearBackend() {
6274
+ stopAllCrons();
5808
6275
  for (const ss of sessionSocketServers.values()) {
5809
6276
  ss.stop();
5810
6277
  }
@@ -5822,11 +6289,11 @@ async function clearBackend() {
5822
6289
  clientTerminals.clear();
5823
6290
  await Promise.all(killPromises);
5824
6291
  for (const file of await readdir(TEMP_DIR)) {
5825
- await rm2(join7(TEMP_DIR, file), { recursive: true, force: true });
6292
+ await rm2(join9(TEMP_DIR, file), { recursive: true, force: true });
5826
6293
  }
5827
6294
  }
5828
6295
  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);
6296
+ const candidates = [preferredCwd, process.cwd(), homedir4()].map((value) => value.trim()).filter((value, index, array) => value.length > 0 && array.indexOf(value) === index);
5830
6297
  for (const candidate of candidates) {
5831
6298
  const info = await stat3(candidate).catch(() => null);
5832
6299
  if (info?.isDirectory()) return candidate;
@@ -5847,8 +6314,8 @@ async function ensureNodePtySpawnHelperExecutable() {
5847
6314
  if (process.platform === "win32") return;
5848
6315
  if (isNodePtyHelperPrepared) return;
5849
6316
  const packageJsonPath = require2.resolve("node-pty/package.json");
5850
- const packageDir = dirname4(packageJsonPath);
5851
- const helperPath = join7(
6317
+ const packageDir = dirname6(packageJsonPath);
6318
+ const helperPath = join9(
5852
6319
  packageDir,
5853
6320
  "prebuilds",
5854
6321
  `${process.platform}-${process.arch}`,
@@ -5911,14 +6378,14 @@ async function createTerminalProcess(cwd, initialSize) {
5911
6378
  const terminalId = createHash3("sha1").update(terminalSeed).digest("hex").slice(0, 12);
5912
6379
  terminals.set(terminalId, child);
5913
6380
  child.onData((data) => {
5914
- sendEvent2("terminal-output", { terminalId, data });
6381
+ sendEvent3("terminal-output", { terminalId, data });
5915
6382
  });
5916
6383
  child.onExit((exitCode) => {
5917
6384
  terminals.delete(terminalId);
5918
6385
  for (const set of clientTerminals.values()) {
5919
6386
  set.delete(terminalId);
5920
6387
  }
5921
- sendEvent2("terminal-exit", { terminalId, exitCode });
6388
+ sendEvent3("terminal-exit", { terminalId, exitCode });
5922
6389
  });
5923
6390
  return terminalId;
5924
6391
  }
@@ -5996,7 +6463,7 @@ var backendHandlers = {
5996
6463
  if (!skillDir) {
5997
6464
  throw new Error(`Failed to read skill: ${skillId}`);
5998
6465
  }
5999
- return join7(skillDir, SKILL_FILENAME);
6466
+ return join9(skillDir, SKILL_FILENAME);
6000
6467
  },
6001
6468
  async uninstallSkill({ skillId, projectId }) {
6002
6469
  const projectRoot = projectId ? storageOps.getProject(projectId)?.cwd : void 0;
@@ -6027,13 +6494,13 @@ var backendHandlers = {
6027
6494
  async startWebUIServer({ port: port2, token: token2 }) {
6028
6495
  const { url } = await startWebUI({ port: port2, token: token2 });
6029
6496
  const status = { enabled: true, url };
6030
- sendEvent2("webui-status-changed", { status });
6497
+ sendEvent3("webui-status-changed", { status });
6031
6498
  return status;
6032
6499
  },
6033
6500
  async stopWebUIServer() {
6034
6501
  stopWebUI();
6035
6502
  const status = { enabled: false, url: null };
6036
- sendEvent2("webui-status-changed", { status });
6503
+ sendEvent3("webui-status-changed", { status });
6037
6504
  return status;
6038
6505
  },
6039
6506
  async getSettings() {
@@ -6070,12 +6537,12 @@ var backendHandlers = {
6070
6537
  projectFsVersions.set(ProjectInfo.id, 0);
6071
6538
  }
6072
6539
  await syncWatchers();
6073
- sendEvent2("projects-changed", void 0);
6540
+ sendEvent3("projects-changed", void 0);
6074
6541
  return ProjectInfo;
6075
6542
  },
6076
6543
  async renameProject({ projectId, title }) {
6077
6544
  storageOps.updateProjectTitle(projectId, title);
6078
- sendEvent2("projects-changed", void 0);
6545
+ sendEvent3("projects-changed", void 0);
6079
6546
  },
6080
6547
  async deleteProject(projectId) {
6081
6548
  const projectSessions = storageOps.listSessions().filter((session) => session.projectId === projectId);
@@ -6109,14 +6576,14 @@ var backendHandlers = {
6109
6576
  }
6110
6577
  clearProjectSearchState(projectId);
6111
6578
  await syncWatchers();
6112
- sendEvent2("projects-changed", void 0);
6113
- sendEvent2("sessions-changed", void 0);
6579
+ sendEvent3("projects-changed", void 0);
6580
+ sendEvent3("sessions-changed", void 0);
6114
6581
  },
6115
6582
  async newSession({ projectId, agentId, mcpServers, permissionMode, features }) {
6116
6583
  const project = storageOps.getProject(projectId);
6117
6584
  if (!project) throw new Error("Project does not exist");
6118
6585
  const b = await ensureBridge(agentId);
6119
- const socketPath = generateSessionSocketPath(randomUUID4());
6586
+ const socketPath = generateSessionSocketPath(randomUUID5());
6120
6587
  const sessionMcpIds = mcpServers ?? (storageOps.getSettings().mcpServers || []).filter((s) => !s.disabled).map((s) => s.id);
6121
6588
  const effectiveFeatures = features ?? ALL_FEATURES;
6122
6589
  const activeMcpServers = buildMcpServersConfig(sessionMcpIds, {
@@ -6141,7 +6608,7 @@ var backendHandlers = {
6141
6608
  initializeInfo: b.initializeInfo
6142
6609
  });
6143
6610
  await createSessionSocketServer(sessionInfo.id, { socketPath, project });
6144
- sendEvent2("sessions-changed", void 0);
6611
+ sendEvent3("sessions-changed", void 0);
6145
6612
  return {
6146
6613
  sessionId: sessionInfo.id,
6147
6614
  initializeInfo: b.initializeInfo,
@@ -6166,10 +6633,10 @@ var backendHandlers = {
6166
6633
  if (session.isStreaming) {
6167
6634
  storageOps.updateSession(sessionId, { isStreaming: false });
6168
6635
  session.isStreaming = false;
6169
- sendEvent2("session-changed", { session });
6636
+ sendEvent3("session-changed", { session });
6170
6637
  }
6171
6638
  const existingSocketServer = sessionSocketServers.get(session.id);
6172
- const socketPath = existingSocketServer ? existingSocketServer.socketPath : generateSessionSocketPath(randomUUID4());
6639
+ const socketPath = existingSocketServer ? existingSocketServer.socketPath : generateSessionSocketPath(randomUUID5());
6173
6640
  const activeMcpServers = buildMcpServersConfig(session.mcpServers, {
6174
6641
  project,
6175
6642
  socketPath,
@@ -6230,7 +6697,7 @@ var backendHandlers = {
6230
6697
  }
6231
6698
  const freshSession = storageOps.getSession(session.id);
6232
6699
  if (freshSession) {
6233
- sendEvent2("session-changed", { session: freshSession });
6700
+ sendEvent3("session-changed", { session: freshSession });
6234
6701
  }
6235
6702
  return {
6236
6703
  sessionId: session.id,
@@ -6293,7 +6760,7 @@ var backendHandlers = {
6293
6760
  const b = await ensureBridge(session.agentId);
6294
6761
  if (!b.isSessionLoaded(session.resumeId)) {
6295
6762
  console.log(`[Fello] Session ${session.resumeId} not loaded in Agent, lazy loading...`);
6296
- const socketPath = generateSessionSocketPath(randomUUID4());
6763
+ const socketPath = generateSessionSocketPath(randomUUID5());
6297
6764
  const activeMcpServers = buildMcpServersConfig(session.mcpServers, {
6298
6765
  project,
6299
6766
  socketPath,
@@ -6308,8 +6775,8 @@ var backendHandlers = {
6308
6775
  }
6309
6776
  storageOps.updateSession(sessionId, { isStreaming: true });
6310
6777
  const updated = storageOps.getSession(sessionId);
6311
- if (updated) sendEvent2("session-changed", { session: updated });
6312
- sendEvent2("prompt-start", { sessionId });
6778
+ if (updated) sendEvent3("session-changed", { session: updated });
6779
+ sendEvent3("prompt-start", { sessionId });
6313
6780
  if (ilinkBridge?.isConnected && sessionId === ilinkActiveSessionId) {
6314
6781
  const userId = ilinkBridge.userId;
6315
6782
  if (userId) {
@@ -6339,14 +6806,14 @@ var backendHandlers = {
6339
6806
  promptError = err instanceof Error ? err.message : String(err);
6340
6807
  throw err;
6341
6808
  } finally {
6342
- sendEvent2("prompt-end", {
6809
+ sendEvent3("prompt-end", {
6343
6810
  sessionId,
6344
6811
  stopReason: promptResponse?.stopReason,
6345
6812
  error: promptError
6346
6813
  });
6347
6814
  storageOps.updateSession(sessionId, { isStreaming: false });
6348
6815
  const updated2 = storageOps.getSession(sessionId);
6349
- if (updated2) sendEvent2("session-changed", { session: updated2 });
6816
+ if (updated2) sendEvent3("session-changed", { session: updated2 });
6350
6817
  if (ilinkBridge?.isConnected && sessionId === ilinkActiveSessionId) {
6351
6818
  const userId = ilinkBridge.userId;
6352
6819
  if (userId) {
@@ -6415,7 +6882,7 @@ var backendHandlers = {
6415
6882
  clearTimeout(pending.timeoutId);
6416
6883
  pending.resolve({ value, reason: reason ?? null });
6417
6884
  pendingAskUserRequests.delete(askUserId);
6418
- sendEvent2("ask-user-response", {
6885
+ sendEvent3("ask-user-response", {
6419
6886
  sessionId,
6420
6887
  askUserId,
6421
6888
  value,
@@ -6428,7 +6895,7 @@ var backendHandlers = {
6428
6895
  async updateSession({ sessionId, ...updates }) {
6429
6896
  storageOps.updateSession(sessionId, updates);
6430
6897
  const session = storageOps.getSession(sessionId);
6431
- if (session) sendEvent2("session-changed", { session });
6898
+ if (session) sendEvent3("session-changed", { session });
6432
6899
  },
6433
6900
  async changeWorkDir() {
6434
6901
  return { ok: false, cwd: null };
@@ -6461,7 +6928,7 @@ var backendHandlers = {
6461
6928
  } catch (error) {
6462
6929
  console.warn("[iLink] Failed to clear persisted active session:", error);
6463
6930
  }
6464
- sendEvent2("ilink-active-session-changed", { sessionId: null });
6931
+ sendEvent3("ilink-active-session-changed", { sessionId: null });
6465
6932
  }
6466
6933
  if (session) {
6467
6934
  try {
@@ -6476,7 +6943,7 @@ var backendHandlers = {
6476
6943
  );
6477
6944
  }
6478
6945
  }
6479
- sendEvent2("sessions-changed", void 0);
6946
+ sendEvent3("sessions-changed", void 0);
6480
6947
  },
6481
6948
  async getSystemFilePath({ projectId, path: inputPath, isAbsolute: isAbsolute4 }) {
6482
6949
  const project = storageOps.getProject(projectId);
@@ -6491,14 +6958,14 @@ var backendHandlers = {
6491
6958
  if (!project) throw new Error("Project not found");
6492
6959
  const cwd = destDir || project.cwd;
6493
6960
  const fileName = basename3(sourcePath);
6494
- let destPath = join7(cwd, fileName);
6961
+ let destPath = join9(cwd, fileName);
6495
6962
  let counter = 1;
6496
6963
  while (true) {
6497
6964
  const info = await stat3(destPath).catch(() => null);
6498
6965
  if (!info) break;
6499
6966
  const ext = extname2(fileName);
6500
6967
  const name = basename3(fileName, ext);
6501
- destPath = join7(cwd, `${name}(${counter})${ext}`);
6968
+ destPath = join9(cwd, `${name}(${counter})${ext}`);
6502
6969
  counter++;
6503
6970
  }
6504
6971
  await copyFile(sourcePath, destPath);
@@ -6569,7 +7036,7 @@ var backendHandlers = {
6569
7036
  session.models.currentModelId = modelId;
6570
7037
  storageOps.updateSession(session.id, { models: session.models });
6571
7038
  const updated = storageOps.getSession(session.id);
6572
- if (updated) sendEvent2("session-changed", { session: updated });
7039
+ if (updated) sendEvent3("session-changed", { session: updated });
6573
7040
  }
6574
7041
  },
6575
7042
  async getModes({ sessionId }) {
@@ -6591,7 +7058,7 @@ var backendHandlers = {
6591
7058
  session.modes.currentModeId = modeId;
6592
7059
  storageOps.updateSession(session.id, { modes: session.modes });
6593
7060
  const updated = storageOps.getSession(session.id);
6594
- if (updated) sendEvent2("session-changed", { session: updated });
7061
+ if (updated) sendEvent3("session-changed", { session: updated });
6595
7062
  }
6596
7063
  },
6597
7064
  async searchFiles({ projectId, query }) {
@@ -6603,7 +7070,7 @@ var backendHandlers = {
6603
7070
  const entries = await readdir(cwd).catch(() => []);
6604
7071
  const results = [];
6605
7072
  for (const name of entries) {
6606
- const full = join7(cwd, name);
7073
+ const full = join9(cwd, name);
6607
7074
  if (isIgnorePath(full, cwd)) continue;
6608
7075
  if (fileScene.has(full)) continue;
6609
7076
  fileScene.add(full);
@@ -6645,7 +7112,7 @@ var backendHandlers = {
6645
7112
  const entries = await readdir(startPath).catch(() => []);
6646
7113
  const results = [];
6647
7114
  for (const name of entries) {
6648
- const full = join7(startPath, name);
7115
+ const full = join9(startPath, name);
6649
7116
  const s = await stat3(full).catch(() => null);
6650
7117
  if (!s) continue;
6651
7118
  if (isIgnorePath(full, cwd)) continue;
@@ -6669,7 +7136,7 @@ var backendHandlers = {
6669
7136
  if (isFolder) {
6670
7137
  await mkdir2(targetPath, { recursive: true });
6671
7138
  } else {
6672
- await mkdir2(dirname4(targetPath), { recursive: true });
7139
+ await mkdir2(dirname6(targetPath), { recursive: true });
6673
7140
  await writeFile3(targetPath, "");
6674
7141
  }
6675
7142
  markProjectFsDirty(projectId);
@@ -6740,7 +7207,7 @@ var backendHandlers = {
6740
7207
  const ext = extname2(fileName);
6741
7208
  const base = basename3(fileName, ext);
6742
7209
  let counter = 0;
6743
- let currentDest = join7(destDir, fileName);
7210
+ let currentDest = join9(destDir, fileName);
6744
7211
  while (true) {
6745
7212
  const existing = await stat3(currentDest).catch(() => null);
6746
7213
  if (!existing) break;
@@ -6748,7 +7215,7 @@ var backendHandlers = {
6748
7215
  throw new Error("Cannot overwrite a folder with a file");
6749
7216
  }
6750
7217
  counter++;
6751
- currentDest = join7(destDir, `${base}(${counter})${ext}`);
7218
+ currentDest = join9(destDir, `${base}(${counter})${ext}`);
6752
7219
  }
6753
7220
  const buffer = Buffer.from(base64, "base64");
6754
7221
  await mkdir2(destDir, { recursive: true });
@@ -6867,7 +7334,7 @@ var backendHandlers = {
6867
7334
  const project = storageOps.getProject(projectId);
6868
7335
  if (!project) throw new Error("Project not found");
6869
7336
  const targetPath = resolveSafePath(project.cwd, relativePath);
6870
- const cwd = dirname4(targetPath);
7337
+ const cwd = dirname6(targetPath);
6871
7338
  const relPath = relative5(cwd, targetPath);
6872
7339
  const { stdout } = await execFileAsync("git", ["show", `HEAD:./${relPath}`], {
6873
7340
  cwd,
@@ -6910,14 +7377,14 @@ var backendHandlers = {
6910
7377
  ilinkActiveSessionId = null;
6911
7378
  ilinkReplyBuffer = "";
6912
7379
  await writeActiveSessionId(null);
6913
- sendEvent2("ilink-active-session-changed", { sessionId: null });
7380
+ sendEvent3("ilink-active-session-changed", { sessionId: null });
6914
7381
  },
6915
7382
  async setActiveIlinkSession({ sessionId }) {
6916
7383
  if (!sessionId) {
6917
7384
  ilinkActiveSessionId = null;
6918
7385
  ilinkReplyBuffer = "";
6919
7386
  await writeActiveSessionId(null);
6920
- sendEvent2("ilink-active-session-changed", { sessionId: null });
7387
+ sendEvent3("ilink-active-session-changed", { sessionId: null });
6921
7388
  return;
6922
7389
  }
6923
7390
  const session = storageOps.getSession(sessionId);
@@ -6925,7 +7392,7 @@ var backendHandlers = {
6925
7392
  ilinkActiveSessionId = sessionId;
6926
7393
  ilinkReplyBuffer = "";
6927
7394
  await writeActiveSessionId(sessionId);
6928
- sendEvent2("ilink-active-session-changed", { sessionId });
7395
+ sendEvent3("ilink-active-session-changed", { sessionId });
6929
7396
  },
6930
7397
  async getActiveIlinkSession() {
6931
7398
  if (ilinkActiveSessionId) {
@@ -6940,11 +7407,80 @@ var backendHandlers = {
6940
7407
  } catch {
6941
7408
  }
6942
7409
  return { sessionId: null };
7410
+ },
7411
+ // ── Automation Handlers ───────────────────────────────────────────
7412
+ async listSchedules() {
7413
+ return store.listSchedules().map((s) => ({ ...s, nextRunAt: getNextRun(s) }));
7414
+ },
7415
+ async getServerTimezone() {
7416
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
7417
+ },
7418
+ async createSchedule(params) {
7419
+ const scheduleId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
7420
+ const now = Date.now();
7421
+ const schedule = {
7422
+ id: scheduleId,
7423
+ name: params.name,
7424
+ agentId: params.agentId,
7425
+ prompt: params.prompt,
7426
+ cron: { type: params.cron.type, expr: params.cron.expr },
7427
+ createdAt: now,
7428
+ updatedAt: now,
7429
+ lastRunAt: null,
7430
+ features: (params.features ?? []).filter((f) => f !== "ask_user"),
7431
+ mcpServers: params.mcpServers ?? []
7432
+ };
7433
+ store.saveSchedule(schedule);
7434
+ if (schedule.cron.type === "cron" && schedule.cron.expr) {
7435
+ scheduleCron(schedule);
7436
+ }
7437
+ sendEvent3("schedules-changed", void 0);
7438
+ return schedule;
7439
+ },
7440
+ async updateSchedule({ scheduleId, updates }) {
7441
+ const schedule = store.getSchedule(scheduleId);
7442
+ if (!schedule) throw new Error("Schedule not found");
7443
+ Object.assign(schedule, updates);
7444
+ schedule.updatedAt = Date.now();
7445
+ store.saveSchedule(schedule);
7446
+ if (schedule.cron.type === "cron" && schedule.cron.expr) {
7447
+ scheduleCron(schedule);
7448
+ } else {
7449
+ unscheduleCron(scheduleId);
7450
+ }
7451
+ sendEvent3("schedules-changed", void 0);
7452
+ return schedule;
7453
+ },
7454
+ async deleteSchedule({ scheduleId }) {
7455
+ unscheduleCron(scheduleId);
7456
+ store.deleteSchedule(scheduleId);
7457
+ sendEvent3("schedules-changed", void 0);
7458
+ },
7459
+ async triggerSchedule({ scheduleId }) {
7460
+ return executeTask(scheduleId);
7461
+ },
7462
+ async getTasks({ scheduleId }) {
7463
+ return store.listTasks(scheduleId);
7464
+ },
7465
+ async getTaskFiles({ scheduleId, taskId }) {
7466
+ return store.listTaskFiles(scheduleId, taskId);
7467
+ },
7468
+ async readTaskFile({ scheduleId, taskId, filePath }) {
7469
+ return store.readTaskFile(scheduleId, taskId, filePath);
7470
+ },
7471
+ async getTaskFileSystemPath({ scheduleId, taskId, filePath }) {
7472
+ const base = store.taskDir(scheduleId, taskId);
7473
+ const fullPath = join9(base, filePath);
7474
+ if (!fullPath.startsWith(base + "/") && fullPath !== base) throw new Error("Invalid file path");
7475
+ return fullPath;
7476
+ },
7477
+ async deleteTask({ scheduleId, taskId }) {
7478
+ store.deleteTask(scheduleId, taskId);
6943
7479
  }
6944
7480
  };
6945
7481
 
6946
7482
  // package.json
6947
- var version = "0.1.28";
7483
+ var version = "0.1.29";
6948
7484
 
6949
7485
  // src/server/main.ts
6950
7486
  var port;