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