@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.
- package/out/renderer/assets/{_baseUniq-C01H-TTl.js → _baseUniq-BApwm7pv.js} +1 -1
- package/out/renderer/assets/{arc-BXqrFYj-.js → arc-yKenDsmO.js} +1 -1
- package/out/renderer/assets/{architectureDiagram-Q4EWVU46-DlFz_4yO.js → architectureDiagram-Q4EWVU46-C31yFVWH.js} +5 -5
- package/out/renderer/assets/{blockDiagram-DXYQGD6D-B_jYpKkP.js → blockDiagram-DXYQGD6D-6liCvTXv.js} +6 -6
- package/out/renderer/assets/{c4Diagram-AHTNJAMY-DWRXz6-S.js → c4Diagram-AHTNJAMY-Co_b_6bH.js} +2 -2
- package/out/renderer/assets/{channel-iMh-jYBg.js → channel-Dc_jjyUr.js} +1 -1
- package/out/renderer/assets/{chunk-4BX2VUAB-CDXuLHIo.js → chunk-4BX2VUAB-Cg18SQei.js} +1 -1
- package/out/renderer/assets/{chunk-4TB4RGXK-Bs-z-5kA.js → chunk-4TB4RGXK-CvSqwINq.js} +5 -5
- package/out/renderer/assets/{chunk-55IACEB6-DBmLw2WJ.js → chunk-55IACEB6-CfBiPWtx.js} +1 -1
- package/out/renderer/assets/{chunk-EDXVE4YY-BhsDT27O.js → chunk-EDXVE4YY-DMjVfNdS.js} +1 -1
- package/out/renderer/assets/{chunk-FMBD7UC4-C_qJYK5q.js → chunk-FMBD7UC4-BbpFqyFG.js} +1 -1
- package/out/renderer/assets/{chunk-OYMX7WX6-DIh7s3uw.js → chunk-OYMX7WX6-Cf724x--.js} +3 -3
- package/out/renderer/assets/{chunk-QZHKN3VN-CYa45CSJ.js → chunk-QZHKN3VN-4ZNIwknr.js} +1 -1
- package/out/renderer/assets/{chunk-YZCP3GAM-BeF07Xv_.js → chunk-YZCP3GAM-D1j7xGGR.js} +1 -1
- package/out/renderer/assets/{classDiagram-6PBFFD2Q-B12_6ldG.js → classDiagram-6PBFFD2Q-OFMeoPav.js} +6 -6
- package/out/renderer/assets/{classDiagram-v2-HSJHXN6E-B12_6ldG.js → classDiagram-v2-HSJHXN6E-OFMeoPav.js} +6 -6
- package/out/renderer/assets/{clone-1GZJZPn3.js → clone-Cm3RO2Zl.js} +1 -1
- package/out/renderer/assets/{cose-bilkent-S5V4N54A-CuSnz9Ro.js → cose-bilkent-S5V4N54A-lCfWcP6y.js} +1 -1
- package/out/renderer/assets/{dagre-KV5264BT-LHPLEy9g.js → dagre-KV5264BT-4I0h4wyh.js} +6 -6
- package/out/renderer/assets/{diagram-5BDNPKRD-DWjP3Jzr.js → diagram-5BDNPKRD-C1mEpAlt.js} +6 -6
- package/out/renderer/assets/{diagram-G4DWMVQ6-CM3uASto.js → diagram-G4DWMVQ6-C_g_DZm9.js} +6 -6
- package/out/renderer/assets/{diagram-MMDJMWI5-X5oh5RON.js → diagram-MMDJMWI5-Di6wZV6-.js} +5 -5
- package/out/renderer/assets/{diagram-TYMM5635-iO4Xohj9.js → diagram-TYMM5635-aF8hIDx1.js} +5 -5
- package/out/renderer/assets/{erDiagram-SMLLAGMA-4xwE7Ysd.js → erDiagram-SMLLAGMA-Bk9QFZ27.js} +4 -4
- package/out/renderer/assets/{flowDiagram-DWJPFMVM-DoIqEJWm.js → flowDiagram-DWJPFMVM-Dfg0LLup.js} +6 -6
- package/out/renderer/assets/{ganttDiagram-T4ZO3ILL-C48vXSUK.js → ganttDiagram-T4ZO3ILL-lTjaJTs0.js} +2 -2
- package/out/renderer/assets/{gitGraphDiagram-UUTBAWPF-D3YiiEhK.js → gitGraphDiagram-UUTBAWPF-C74WBszH.js} +6 -6
- package/out/renderer/assets/{graph-CYPfPCZi.js → graph-DGEX1eV1.js} +2 -2
- package/out/renderer/assets/{highlighted-body-OFNGDK62-B6M-kMLU.js → highlighted-body-OFNGDK62-DQWoSdTY.js} +1 -1
- package/out/renderer/assets/{index-Dabo17L1.js → index-BZzw3jIU.js} +1506 -244
- package/out/renderer/assets/index-Cv3lJTlc.css +382 -0
- package/out/renderer/assets/{infoDiagram-42DDH7IO-ur7rRidE.js → infoDiagram-42DDH7IO-CR4ZLAM1.js} +4 -4
- package/out/renderer/assets/{ishikawaDiagram-UXIWVN3A-fJSipMV8.js → ishikawaDiagram-UXIWVN3A-uXL-4FGw.js} +1 -1
- package/out/renderer/assets/{journeyDiagram-VCZTEJTY-TxjKxXBo.js → journeyDiagram-VCZTEJTY-Ds2OL3Lo.js} +4 -4
- package/out/renderer/assets/{kanban-definition-6JOO6SKY-DJwjRjlE.js → kanban-definition-6JOO6SKY-CZ1CZVyP.js} +2 -2
- package/out/renderer/assets/{layout-BT1ASsLg.js → layout-CNGveWdi.js} +4 -4
- package/out/renderer/assets/{linear-ecuEcPGt.js → linear-CnHAiAJ3.js} +1 -1
- package/out/renderer/assets/{min-BYCDJeWK.js → min-D63AfN0e.js} +2 -2
- package/out/renderer/assets/{mindmap-definition-QFDTVHPH-D2MxZmzw.js → mindmap-definition-QFDTVHPH-C3U3xkIJ.js} +3 -3
- package/out/renderer/assets/{pieDiagram-DEJITSTG-Cew7QWxE.js → pieDiagram-DEJITSTG-C8AxK_-F.js} +6 -6
- package/out/renderer/assets/{quadrantDiagram-34T5L4WZ-CrZCtVJh.js → quadrantDiagram-34T5L4WZ-BdiZmi7w.js} +2 -2
- package/out/renderer/assets/{requirementDiagram-MS252O5E-C69IGxW8.js → requirementDiagram-MS252O5E-BHIfl5Tz.js} +3 -3
- package/out/renderer/assets/{sankeyDiagram-XADWPNL6-B2tuS_nG.js → sankeyDiagram-XADWPNL6-SthTR8cL.js} +1 -1
- package/out/renderer/assets/{sequenceDiagram-FGHM5R23-Dv_lOZN1.js → sequenceDiagram-FGHM5R23-COKEbCYM.js} +3 -3
- package/out/renderer/assets/{stateDiagram-FHFEXIEX-D9dJAqtV.js → stateDiagram-FHFEXIEX-7zRGBf7O.js} +8 -8
- package/out/renderer/assets/{stateDiagram-v2-QKLJ7IA2-CQXoaDY1.js → stateDiagram-v2-QKLJ7IA2-BqHJ_ZBX.js} +4 -4
- package/out/renderer/assets/{timeline-definition-GMOUNBTQ-DnnX0Ml0.js → timeline-definition-GMOUNBTQ-AvZAT5J-.js} +2 -2
- package/out/renderer/assets/{vennDiagram-DHZGUBPP-BCIC8HTk.js → vennDiagram-DHZGUBPP-oSTndBDY.js} +1 -1
- package/out/renderer/assets/{wardley-RL74JXVD-B35jxdMa.js → wardley-RL74JXVD-C8hIuf19.js} +3 -3
- package/out/renderer/assets/{wardleyDiagram-NUSXRM2D-D3rjXear.js → wardleyDiagram-NUSXRM2D-Dv2kSOMM.js} +5 -5
- package/out/renderer/assets/{xychartDiagram-5P7HB3ND-095YR9W9.js → xychartDiagram-5P7HB3ND-J9z4uuMK.js} +2 -2
- package/out/renderer/index.html +2 -2
- package/out/server/main.js +695 -159
- package/package.json +3 -1
- package/out/renderer/assets/index-D8H7Q_md.css +0 -382
package/out/server/main.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
// src/backend/backend.ts
|
|
4
4
|
import Fuse from "fuse.js";
|
|
5
|
-
import { homedir as
|
|
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
|
|
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
|
|
23
|
-
import * as
|
|
24
|
-
import { fileURLToPath as
|
|
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
|
-
|
|
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/
|
|
4417
|
-
import
|
|
4418
|
-
import {
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
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
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
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
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
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
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
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
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
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
|
-
|
|
4481
|
-
changes.clear();
|
|
4482
|
-
changes.add(".");
|
|
4550
|
+
result.push(join6(prefix, entry));
|
|
4483
4551
|
}
|
|
4484
4552
|
}
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
const
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
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
|
|
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 =
|
|
4880
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
5464
|
+
const skillFile = join9(skillDir, SKILL_FILENAME);
|
|
5001
5465
|
let body = "";
|
|
5002
5466
|
try {
|
|
5003
|
-
const text =
|
|
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 =
|
|
5522
|
+
const askUserId = randomUUID5();
|
|
5059
5523
|
const request = { ...options, askUserId };
|
|
5060
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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(
|
|
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(),
|
|
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 =
|
|
5851
|
-
const helperPath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6113
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
6636
|
+
sendEvent3("session-changed", { session });
|
|
6170
6637
|
}
|
|
6171
6638
|
const existingSocketServer = sessionSocketServers.get(session.id);
|
|
6172
|
-
const socketPath = existingSocketServer ? existingSocketServer.socketPath : generateSessionSocketPath(
|
|
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
|
-
|
|
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(
|
|
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)
|
|
6312
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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)
|
|
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)
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
7483
|
+
var version = "0.1.29";
|
|
6948
7484
|
|
|
6949
7485
|
// src/server/main.ts
|
|
6950
7486
|
var port;
|