@workflow-code/cli 0.2.4-20261011004.codex-cli-web → 0.2.4-20261011006.codex-cli-web
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/dist/chunk-HLMW6LAQ.js +908 -0
- package/dist/{chunk-G7PZDTYW.js → chunk-MNY562W5.js} +1 -1
- package/dist/{chunk-BP2VDZQV.js → chunk-P6K3NB5S.js} +1 -0
- package/dist/{chunk-T4FXQ7AQ.js → chunk-WN7VAGK6.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/{local-7HMZSBG2.js → local-QLYFH4WT.js} +3 -3
- package/dist/{mcp-5H6U7H4S.js → mcp-LQ2IV6VA.js} +2 -2
- package/dist/web/kanban-data.mjs +613 -0
- package/dist/web/kanban-workspace.css +2354 -0
- package/dist/web/kanban-workspace.js +4526 -0
- package/dist/web/worker.mjs +7 -1
- package/dist/web/workflow-ui.css +41 -3
- package/dist/web/workflow-ui.js +92 -14
- package/dist/web/workflow-workspace.css +41 -3
- package/dist/web/workflow-workspace.js +124 -23
- package/dist/{web-O6FZ5URK.js → web-KEM7Y2ZR.js} +753 -63
- package/dist/{workspace-G7SB6MS5.js → workspace-V3UXVFK6.js} +9 -76
- package/package.json +4 -3
|
@@ -1,29 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
assertLocalCliCoreCompatible
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WN7VAGK6.js";
|
|
5
|
+
import {
|
|
6
|
+
assertKanbanMethodJson,
|
|
7
|
+
consumeKanbanRelatedProjectRevisionStream,
|
|
8
|
+
createDefaultKanbanValues,
|
|
9
|
+
createInjectedKanbanParams,
|
|
10
|
+
createKanbanBridgeError,
|
|
11
|
+
createKanbanContentSecurityPolicy,
|
|
12
|
+
encodeKanbanAssetUrlPath,
|
|
13
|
+
injectKanbanBridge,
|
|
14
|
+
isSafeKanbanPublicAssetPath,
|
|
15
|
+
parseKanbanBridgeCall,
|
|
16
|
+
parseKanbanManifestText,
|
|
17
|
+
parseKanbanMethodDefinitions,
|
|
18
|
+
parseKanbanMethodInvocation,
|
|
19
|
+
readKanbanArtifactConfig,
|
|
20
|
+
readKanbanAssetMimeType,
|
|
21
|
+
sanitizeKanbanValues
|
|
22
|
+
} from "./chunk-HLMW6LAQ.js";
|
|
5
23
|
import "./chunk-J2JMC4MZ.js";
|
|
6
|
-
import "./chunk-
|
|
24
|
+
import "./chunk-MNY562W5.js";
|
|
7
25
|
import {
|
|
8
26
|
clearCliAuthConfig,
|
|
9
27
|
readCliAuthConfig,
|
|
10
28
|
writeCliAuthConfig
|
|
11
29
|
} from "./chunk-PGM3IPWA.js";
|
|
12
|
-
import "./chunk-BP2VDZQV.js";
|
|
13
30
|
import {
|
|
31
|
+
isWorkflowProjectDataStorageLocationAllowed,
|
|
32
|
+
readWorkflowProjectInfo,
|
|
33
|
+
resolveWorkflowRelatedProjectAccess
|
|
34
|
+
} from "./chunk-P6K3NB5S.js";
|
|
35
|
+
import {
|
|
36
|
+
getCurrentLocale,
|
|
14
37
|
i18n
|
|
15
38
|
} from "./chunk-7WEXRL5A.js";
|
|
16
39
|
import "./chunk-NSPRIPOP.js";
|
|
17
40
|
|
|
18
41
|
// src/web.ts
|
|
19
42
|
import { spawn as spawn3 } from "child_process";
|
|
20
|
-
import
|
|
43
|
+
import path8 from "path";
|
|
21
44
|
|
|
22
45
|
// src/web/server.ts
|
|
23
46
|
import { createServer } from "http";
|
|
24
|
-
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
25
|
-
import { mkdir, readFile as
|
|
26
|
-
import
|
|
47
|
+
import { createHash as createHash3, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
48
|
+
import { mkdir as mkdir2, readFile as readFile5, rename as rename2, writeFile as writeFile2, realpath as realpath4 } from "fs/promises";
|
|
49
|
+
import path6 from "path";
|
|
27
50
|
import os from "os";
|
|
28
51
|
import { spawn as spawn2 } from "child_process";
|
|
29
52
|
|
|
@@ -991,7 +1014,8 @@ var WorkflowController = class {
|
|
|
991
1014
|
this.confirmInput(record2, record2.submittingInput);
|
|
992
1015
|
}
|
|
993
1016
|
if (report.status === "failed") {
|
|
994
|
-
|
|
1017
|
+
const data = report.result?.data;
|
|
1018
|
+
record2.error = report.result?.error?.message || (data && typeof data === "object" && "errMessage" in data && typeof data.errMessage === "string" ? data.errMessage : "") || "Workflow failed.";
|
|
995
1019
|
}
|
|
996
1020
|
if (record2.submittingInput && pending && report.status === "failed") {
|
|
997
1021
|
record2.status = "waiting_for_input";
|
|
@@ -1143,6 +1167,626 @@ var WorkflowController = class {
|
|
|
1143
1167
|
}
|
|
1144
1168
|
};
|
|
1145
1169
|
|
|
1170
|
+
// src/web/kanban-controller.ts
|
|
1171
|
+
import { randomUUID as randomUUID2, createHash as createHash2 } from "crypto";
|
|
1172
|
+
import { mkdir, readFile as readFile3, readdir as readdir2, rename, writeFile, stat as stat2 } from "fs/promises";
|
|
1173
|
+
import path3 from "path";
|
|
1174
|
+
import { watch } from "fs";
|
|
1175
|
+
|
|
1176
|
+
// src/web/kanban-preview.ts
|
|
1177
|
+
import { randomBytes } from "crypto";
|
|
1178
|
+
import { lstat, realpath, readFile, stat } from "fs/promises";
|
|
1179
|
+
import path from "path";
|
|
1180
|
+
function injectTheme(html, nonce) {
|
|
1181
|
+
const script = `<script nonce=${JSON.stringify(nonce)}>(() => {
|
|
1182
|
+
addEventListener('message', event => {
|
|
1183
|
+
const message = event.data;
|
|
1184
|
+
if (event.source !== parent || message?.nonce !== ${JSON.stringify(nonce)} || message.type !== 'workflow-code:kanban-theme' || !['light', 'dark'].includes(message.theme)) return;
|
|
1185
|
+
document.documentElement.dataset.theme = message.theme;
|
|
1186
|
+
document.documentElement.style.colorScheme = message.theme;
|
|
1187
|
+
dispatchEvent(new CustomEvent('workflow-code:kanban-theme-change', { detail: { theme: message.theme } }));
|
|
1188
|
+
});
|
|
1189
|
+
})();</script>`;
|
|
1190
|
+
const head = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
1191
|
+
if (!head || head.index === void 0) {
|
|
1192
|
+
return `${html}${script}`;
|
|
1193
|
+
}
|
|
1194
|
+
const index = head.index + head[0].length;
|
|
1195
|
+
return `${html.slice(0, index)}${script}${html.slice(index)}`;
|
|
1196
|
+
}
|
|
1197
|
+
var KanbanPreview = class {
|
|
1198
|
+
constructor(projectRoot, artifact, configuration) {
|
|
1199
|
+
this.projectRoot = projectRoot;
|
|
1200
|
+
this.artifact = artifact;
|
|
1201
|
+
this.configuration = configuration;
|
|
1202
|
+
}
|
|
1203
|
+
projectRoot;
|
|
1204
|
+
artifact;
|
|
1205
|
+
configuration;
|
|
1206
|
+
nonce = randomBytes(24).toString("base64url");
|
|
1207
|
+
get url() {
|
|
1208
|
+
return `/kanban/${this.nonce}/${encodeKanbanAssetUrlPath(this.artifact.entry)}`;
|
|
1209
|
+
}
|
|
1210
|
+
async asset(url) {
|
|
1211
|
+
const prefix = `/kanban/${this.nonce}/`;
|
|
1212
|
+
if (!url.pathname.startsWith(prefix)) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
try {
|
|
1216
|
+
const asset = decodeURIComponent(url.pathname.slice(prefix.length));
|
|
1217
|
+
if (asset.includes("\\") || !isSafeKanbanPublicAssetPath(asset, this.artifact.entry)) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const projectRoot = await realpath(this.projectRoot);
|
|
1221
|
+
let artifactRoot = projectRoot;
|
|
1222
|
+
if (this.artifact.artifactDir !== ".") {
|
|
1223
|
+
for (const segment of this.artifact.artifactDir.split("/")) {
|
|
1224
|
+
artifactRoot = path.join(artifactRoot, segment);
|
|
1225
|
+
if ((await lstat(artifactRoot)).isSymbolicLink()) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
let filename = artifactRoot;
|
|
1231
|
+
for (const segment of asset.split("/")) {
|
|
1232
|
+
filename = path.join(filename, segment);
|
|
1233
|
+
if ((await lstat(filename)).isSymbolicLink()) {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
const canonical = await realpath(filename), relative = path.relative(artifactRoot, canonical);
|
|
1238
|
+
if (!relative || relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative)) {
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
const info = await stat(canonical);
|
|
1242
|
+
if (!info.isFile() || info.size > 20 * 1024 * 1024) {
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const headers = { "Content-Type": readKanbanAssetMimeType(asset), "Content-Security-Policy": `${createKanbanContentSecurityPolicy(this.nonce)}; form-action 'none'; frame-ancestors 'self' vscode-webview: vscode-file: https://*.vscode-cdn.net https://*.vscode-webview.net http://127.0.0.1:* http://localhost:*`, "Cache-Control": "no-store", "Access-Control-Allow-Origin": "*", "Referrer-Policy": "no-referrer", "X-Content-Type-Options": "nosniff" };
|
|
1246
|
+
return { headers, content: asset === this.artifact.entry ? injectKanbanBridge(injectTheme(await readFile(canonical, "utf8"), this.nonce), this.configuration(), this.nonce) : await readFile(canonical) };
|
|
1247
|
+
} catch {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
// src/web/kanban-methods.ts
|
|
1254
|
+
import { randomUUID } from "crypto";
|
|
1255
|
+
var KanbanMethodHub = class {
|
|
1256
|
+
constructor(configuration, emit) {
|
|
1257
|
+
this.configuration = configuration;
|
|
1258
|
+
this.emit = emit;
|
|
1259
|
+
}
|
|
1260
|
+
configuration;
|
|
1261
|
+
emit;
|
|
1262
|
+
clients = /* @__PURE__ */ new Map();
|
|
1263
|
+
instances = /* @__PURE__ */ new Map();
|
|
1264
|
+
pending = /* @__PURE__ */ new Map();
|
|
1265
|
+
connect(clientId) {
|
|
1266
|
+
if (!/^[a-zA-Z0-9-]{1,200}$/.test(clientId)) {
|
|
1267
|
+
throw new Error("Invalid Kanban client.");
|
|
1268
|
+
}
|
|
1269
|
+
this.disconnect(clientId);
|
|
1270
|
+
const connection = {};
|
|
1271
|
+
this.clients.set(clientId, connection);
|
|
1272
|
+
return () => {
|
|
1273
|
+
if (this.clients.get(clientId) === connection) {
|
|
1274
|
+
this.disconnect(clientId);
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
remove(instanceId) {
|
|
1279
|
+
this.instances.delete(instanceId);
|
|
1280
|
+
for (const [requestId, request] of this.pending) {
|
|
1281
|
+
if (request.instanceId !== instanceId) {
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
request.finish(new Error("Kanban preview changed or closed."));
|
|
1285
|
+
this.emit({ type: "kanban-method-cancel", requestId, instanceId });
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
disconnect(clientId) {
|
|
1289
|
+
this.clients.delete(clientId);
|
|
1290
|
+
for (const [instanceId, instance] of this.instances) {
|
|
1291
|
+
if (instance.clientId === clientId) {
|
|
1292
|
+
this.remove(instanceId);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
publish(value) {
|
|
1297
|
+
if (!this.clients.has(value.clientId) || typeof value.instanceId !== "string" || !value.instanceId || value.instanceId.length > 200 || value.configurationId !== this.configuration().id || typeof value.ready !== "boolean" || !["fullscreen", "phone", "tablet", "card"].includes(value.presentation?.mode)) {
|
|
1298
|
+
throw new Error("Invalid Kanban method instance.");
|
|
1299
|
+
}
|
|
1300
|
+
const existing = this.instances.get(value.instanceId);
|
|
1301
|
+
if (existing && existing.clientId !== value.clientId) {
|
|
1302
|
+
throw new Error("Kanban method instance belongs to another client.");
|
|
1303
|
+
}
|
|
1304
|
+
this.instances.set(value.instanceId, {
|
|
1305
|
+
ready: value.ready,
|
|
1306
|
+
configurationId: value.configurationId,
|
|
1307
|
+
configurationName: this.configuration().name,
|
|
1308
|
+
presentation: value.presentation,
|
|
1309
|
+
methods: parseKanbanMethodDefinitions(value.methods),
|
|
1310
|
+
seen: Date.now(),
|
|
1311
|
+
clientId: value.clientId
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
list() {
|
|
1315
|
+
for (const [id, instance] of this.instances) {
|
|
1316
|
+
if (Date.now() - instance.seen > 15e3) {
|
|
1317
|
+
this.remove(id);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return [...this.instances].map(([instanceId, { seen: _, clientId: __, ...state }]) => ({ instanceId, ...state }));
|
|
1321
|
+
}
|
|
1322
|
+
async invoke(value) {
|
|
1323
|
+
const call = parseKanbanMethodInvocation(value);
|
|
1324
|
+
this.list();
|
|
1325
|
+
const instance = this.instances.get(call.instanceId);
|
|
1326
|
+
if (!instance?.ready || instance.configurationId !== call.configurationId || call.configurationId !== this.configuration().id) {
|
|
1327
|
+
throw new Error("Kanban method instance changed. List instances again.");
|
|
1328
|
+
}
|
|
1329
|
+
if (!instance.methods.some((method) => method.name === call.method)) {
|
|
1330
|
+
throw new Error("Kanban page did not register this method.");
|
|
1331
|
+
}
|
|
1332
|
+
if ([...this.pending.values()].filter((item) => item.instanceId === call.instanceId).length >= 4) {
|
|
1333
|
+
throw new Error("Too many Kanban method calls.");
|
|
1334
|
+
}
|
|
1335
|
+
const requestId = randomUUID();
|
|
1336
|
+
return new Promise((resolve, reject) => {
|
|
1337
|
+
const timer = setTimeout(() => {
|
|
1338
|
+
this.pending.get(requestId)?.finish(new Error("Kanban method timed out. Inspect state before retrying."));
|
|
1339
|
+
this.emit({ type: "kanban-method-cancel", requestId, instanceId: call.instanceId });
|
|
1340
|
+
}, 15e3);
|
|
1341
|
+
this.pending.set(requestId, { instanceId: call.instanceId, finish: (error, result) => {
|
|
1342
|
+
if (!this.pending.delete(requestId)) {
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
clearTimeout(timer);
|
|
1346
|
+
error ? reject(error) : resolve(result);
|
|
1347
|
+
} });
|
|
1348
|
+
this.emit({ type: "kanban-method-invoke", requestId, ...call });
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
reply(message) {
|
|
1352
|
+
const request = this.pending.get(message.requestId);
|
|
1353
|
+
if (!request || request.instanceId !== message.instanceId || this.instances.get(message.instanceId)?.clientId !== message.clientId) {
|
|
1354
|
+
throw new Error("Kanban method request changed.");
|
|
1355
|
+
}
|
|
1356
|
+
if (message.ok === true) {
|
|
1357
|
+
assertKanbanMethodJson(message.value);
|
|
1358
|
+
request.finish(void 0, message.value);
|
|
1359
|
+
} else {
|
|
1360
|
+
request.finish(new Error(typeof message.error === "string" ? message.error.slice(0, 2e3) : "Kanban method failed."));
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
clear() {
|
|
1364
|
+
this.instances.clear();
|
|
1365
|
+
for (const [requestId, request] of this.pending) {
|
|
1366
|
+
request.finish(new Error("Kanban preview changed or closed."));
|
|
1367
|
+
this.emit({ type: "kanban-method-cancel", requestId, instanceId: request.instanceId });
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
// src/web/kanban-data.ts
|
|
1373
|
+
import { readFile as readFile2, readdir, realpath as realpath2 } from "fs/promises";
|
|
1374
|
+
import { createHash } from "crypto";
|
|
1375
|
+
import path2 from "path";
|
|
1376
|
+
var readKanbanProjectInfo = readWorkflowProjectInfo;
|
|
1377
|
+
var webProjectDataDirectory = (storageDir, projectDir) => path2.join(storageDir, createHash("sha256").update(projectDir).digest("hex").slice(0, 24));
|
|
1378
|
+
async function localKanbanRelatedProjects(project) {
|
|
1379
|
+
const info = readWorkflowProjectInfo(JSON.parse(await readFile2(path2.join(project.folderPath, "package.json"), "utf8")));
|
|
1380
|
+
const relations = info.relatedProjects;
|
|
1381
|
+
const projects = /* @__PURE__ */ new Map();
|
|
1382
|
+
if (!relations.length) {
|
|
1383
|
+
return { info, projects };
|
|
1384
|
+
}
|
|
1385
|
+
const parent = path2.dirname(project.folderPath);
|
|
1386
|
+
const directories = (await readdir(parent, { withFileTypes: true })).filter((item) => item.isDirectory() && !item.name.startsWith(".") && item.name !== "node_modules").slice(0, 500);
|
|
1387
|
+
await Promise.all(directories.map(async (item) => {
|
|
1388
|
+
try {
|
|
1389
|
+
const folderPath = await realpath2(path2.join(parent, item.name));
|
|
1390
|
+
const pkg = JSON.parse(await readFile2(path2.join(folderPath, "package.json"), "utf8"));
|
|
1391
|
+
const relation = relations.find((relation2) => relation2.projectId.toLowerCase() === String(pkg.id).toLowerCase());
|
|
1392
|
+
if (!relation) {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const projectInfo = readWorkflowProjectInfo(pkg);
|
|
1396
|
+
const access = resolveWorkflowRelatedProjectAccess({ consumerProjectId: project.id, consumerProjectInfo: info, targetProjectId: pkg.id, targetProjectInfo: projectInfo, alias: relation.alias });
|
|
1397
|
+
if (access && isWorkflowProjectDataStorageLocationAllowed(projectInfo, "local")) {
|
|
1398
|
+
if (projects.has(relation.alias)) {
|
|
1399
|
+
throw new Error(`Duplicate related project UUID: ${pkg.id}`);
|
|
1400
|
+
}
|
|
1401
|
+
projects.set(relation.alias, { folderPath, id: pkg.id, projectInfo });
|
|
1402
|
+
}
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
if (String(error).includes("Duplicate related project UUID")) {
|
|
1405
|
+
throw error;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}));
|
|
1409
|
+
return { info, projects };
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/web/kanban-controller.ts
|
|
1413
|
+
var KanbanController = class {
|
|
1414
|
+
constructor(project, runtime, storageDir, hooks) {
|
|
1415
|
+
this.project = project;
|
|
1416
|
+
this.runtime = runtime;
|
|
1417
|
+
this.storageDir = storageDir;
|
|
1418
|
+
this.hooks = hooks;
|
|
1419
|
+
this.historyPath = path3.join(webProjectDataDirectory(storageDir, project.folderPath), "kanban.json");
|
|
1420
|
+
this.methods = new KanbanMethodHub(() => this.configuration(), (message) => hooks.emit(message));
|
|
1421
|
+
}
|
|
1422
|
+
project;
|
|
1423
|
+
runtime;
|
|
1424
|
+
storageDir;
|
|
1425
|
+
hooks;
|
|
1426
|
+
workspace = { version: 1, revision: 0, configurations: [] };
|
|
1427
|
+
params = [];
|
|
1428
|
+
preview;
|
|
1429
|
+
relatedProjectRevisions = [];
|
|
1430
|
+
missingRelatedProjectAliases = [];
|
|
1431
|
+
relatedProjectWarning = "";
|
|
1432
|
+
pendingSave = Promise.resolve();
|
|
1433
|
+
poll;
|
|
1434
|
+
feed;
|
|
1435
|
+
retry;
|
|
1436
|
+
disposed = false;
|
|
1437
|
+
generation = 0;
|
|
1438
|
+
polling = false;
|
|
1439
|
+
watcher;
|
|
1440
|
+
refreshTimer;
|
|
1441
|
+
refreshing;
|
|
1442
|
+
requests = /* @__PURE__ */ new Set();
|
|
1443
|
+
historyPath;
|
|
1444
|
+
methods;
|
|
1445
|
+
get current() {
|
|
1446
|
+
return this.workspace.configurations.find((item) => item.id === this.workspace.activeConfigurationId);
|
|
1447
|
+
}
|
|
1448
|
+
configuration() {
|
|
1449
|
+
const active = this.current;
|
|
1450
|
+
return active ? { id: active.id, name: active.name, params: createInjectedKanbanParams(this.params, active.values) } : { id: "", name: "", params: {} };
|
|
1451
|
+
}
|
|
1452
|
+
state() {
|
|
1453
|
+
return { ...this.workspace, params: this.params, configuration: this.configuration(), preview: { url: this.preview.url, nonce: this.preview.nonce }, relatedProjectRevisions: this.relatedProjectRevisions, missingRelatedProjectAliases: this.missingRelatedProjectAliases, relatedProjectWarning: this.relatedProjectWarning };
|
|
1454
|
+
}
|
|
1455
|
+
async initialize() {
|
|
1456
|
+
try {
|
|
1457
|
+
const saved = JSON.parse(await readFile3(this.historyPath, "utf8"));
|
|
1458
|
+
if (saved.version !== 1 || !Array.isArray(saved.configurations) || saved.configurations.some((item) => typeof item.id !== "string" || typeof item.name !== "string" || !item.values || typeof item.values !== "object")) {
|
|
1459
|
+
throw new Error("Invalid Kanban configuration history.");
|
|
1460
|
+
}
|
|
1461
|
+
this.workspace = { ...saved, revision: Number.isSafeInteger(saved.revision) ? saved.revision : 0 };
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
if (error.code !== "ENOENT") {
|
|
1464
|
+
throw error;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
await this.refresh();
|
|
1468
|
+
try {
|
|
1469
|
+
this.watcher = watch(this.project.folderPath, { recursive: true }, (_event, filename) => {
|
|
1470
|
+
const file = String(filename).replaceAll("\\", "/");
|
|
1471
|
+
const data = path3.relative(this.project.folderPath, webProjectDataDirectory(this.storageDir, this.project.folderPath)).replaceAll("\\", "/");
|
|
1472
|
+
if (!file || file.split("/").some((part) => part.startsWith(".") || part === "node_modules") || file.startsWith(`${data}/`)) {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
const artifact = this.project.kanban.artifactDir;
|
|
1476
|
+
if (file !== "package.json" && file !== "kanban.json" && artifact !== "." && file !== artifact && !file.startsWith(`${artifact}/`)) {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
clearTimeout(this.refreshTimer);
|
|
1480
|
+
this.refreshTimer = setTimeout(() => void this.refresh().catch((error) => this.hooks.log(String(error))), 400);
|
|
1481
|
+
});
|
|
1482
|
+
this.watcher.on("error", (error) => this.hooks.log(String(error)));
|
|
1483
|
+
} catch (error) {
|
|
1484
|
+
this.hooks.log(String(error));
|
|
1485
|
+
}
|
|
1486
|
+
this.poll = setInterval(() => {
|
|
1487
|
+
if (!this.polling && this.project.projectInfo?.dataStorage?.mode !== "server") {
|
|
1488
|
+
this.polling = true;
|
|
1489
|
+
void this.updateLocalRevisions().catch((error) => this.hooks.log(String(error))).finally(() => {
|
|
1490
|
+
this.polling = false;
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
}, 2e3);
|
|
1494
|
+
this.poll.unref();
|
|
1495
|
+
}
|
|
1496
|
+
refresh() {
|
|
1497
|
+
clearTimeout(this.refreshTimer);
|
|
1498
|
+
return this.refreshing ??= this.load().finally(() => {
|
|
1499
|
+
this.refreshing = void 0;
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
async load() {
|
|
1503
|
+
const pkg = JSON.parse(await readFile3(path3.join(this.project.folderPath, "package.json"), "utf8"));
|
|
1504
|
+
const artifact = readKanbanArtifactConfig(pkg);
|
|
1505
|
+
this.project.kanban = artifact;
|
|
1506
|
+
this.project.projectInfo = readKanbanProjectInfo(pkg);
|
|
1507
|
+
try {
|
|
1508
|
+
this.params = parseKanbanManifestText(await readFile3(path3.join(this.project.folderPath, "kanban.json"), "utf8")).params;
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
if (error.code !== "ENOENT") {
|
|
1511
|
+
throw error;
|
|
1512
|
+
}
|
|
1513
|
+
this.params = [];
|
|
1514
|
+
}
|
|
1515
|
+
this.project.fields = this.params;
|
|
1516
|
+
for (const item of this.workspace.configurations) {
|
|
1517
|
+
item.values = sanitizeKanbanValues(this.params, item.values);
|
|
1518
|
+
}
|
|
1519
|
+
if (!this.workspace.configurations.length && this.params.length) {
|
|
1520
|
+
this.create();
|
|
1521
|
+
}
|
|
1522
|
+
this.workspace.activeConfigurationId = this.current?.id ?? this.workspace.configurations[0]?.id;
|
|
1523
|
+
this.workspace.revision++;
|
|
1524
|
+
this.newPreview();
|
|
1525
|
+
await this.persist();
|
|
1526
|
+
this.authenticationChanged();
|
|
1527
|
+
await this.updateLocalRevisions();
|
|
1528
|
+
this.hooks.changed();
|
|
1529
|
+
}
|
|
1530
|
+
newPreview() {
|
|
1531
|
+
this.generation++;
|
|
1532
|
+
this.methods.clear();
|
|
1533
|
+
this.preview = new KanbanPreview(this.project.folderPath, this.project.kanban, () => this.configuration());
|
|
1534
|
+
}
|
|
1535
|
+
name() {
|
|
1536
|
+
let index = 1;
|
|
1537
|
+
while (this.workspace.configurations.some((item) => item.name === `\u914D\u7F6E ${index}`)) {
|
|
1538
|
+
index++;
|
|
1539
|
+
}
|
|
1540
|
+
return `\u914D\u7F6E ${index}`;
|
|
1541
|
+
}
|
|
1542
|
+
create(values = createDefaultKanbanValues(this.params), name = this.name()) {
|
|
1543
|
+
const date = (/* @__PURE__ */ new Date()).toISOString(), item = { id: randomUUID2(), name, values: { ...values }, createdAt: date, updatedAt: date };
|
|
1544
|
+
this.workspace.configurations.push(item);
|
|
1545
|
+
this.workspace.activeConfigurationId = item.id;
|
|
1546
|
+
return item;
|
|
1547
|
+
}
|
|
1548
|
+
async persist() {
|
|
1549
|
+
const content = JSON.stringify(this.workspace);
|
|
1550
|
+
this.pendingSave = this.pendingSave.catch(() => void 0).then(async () => {
|
|
1551
|
+
await mkdir(path3.dirname(this.historyPath), { recursive: true });
|
|
1552
|
+
const temp = `${this.historyPath}.${process.pid}.tmp`;
|
|
1553
|
+
await writeFile(temp, content, { mode: 384 });
|
|
1554
|
+
await rename(temp, this.historyPath);
|
|
1555
|
+
});
|
|
1556
|
+
return this.pendingSave;
|
|
1557
|
+
}
|
|
1558
|
+
async handle(message) {
|
|
1559
|
+
if (message.type === "kanban-method-state") {
|
|
1560
|
+
if (message.nonce !== this.preview.nonce) {
|
|
1561
|
+
throw new Error("Kanban preview changed.");
|
|
1562
|
+
}
|
|
1563
|
+
this.methods.publish(message);
|
|
1564
|
+
return [];
|
|
1565
|
+
}
|
|
1566
|
+
if (message.type === "kanban-method-reply") {
|
|
1567
|
+
this.methods.reply(message);
|
|
1568
|
+
return [];
|
|
1569
|
+
}
|
|
1570
|
+
if (message.type === "kanban-methods") {
|
|
1571
|
+
return [{ type: "kanban-methods-result", requestId: message.requestId, value: message.action === "list" ? this.methods.list() : message.action === "invoke" ? await this.methods.invoke(message.call) : (() => {
|
|
1572
|
+
throw new Error("Invalid Kanban methods action.");
|
|
1573
|
+
})() }];
|
|
1574
|
+
}
|
|
1575
|
+
if (message.type === "kanban-bridge") {
|
|
1576
|
+
try {
|
|
1577
|
+
if (message.nonce !== this.preview.nonce) {
|
|
1578
|
+
throw Object.assign(new Error("Kanban preview changed."), { code: "KANBAN_BRIDGE_SESSION_INVALID" });
|
|
1579
|
+
}
|
|
1580
|
+
const value = await this.bridge(parseKanbanBridgeCall(message.call));
|
|
1581
|
+
return [{ type: "kanban-bridge-result", requestId: message.requestId, ok: true, value }];
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
return [{ type: "kanban-bridge-result", requestId: message.requestId, ok: false, error: createKanbanBridgeError(error) }];
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
if (message.type === "kanban-refresh") {
|
|
1587
|
+
await this.refresh();
|
|
1588
|
+
return [{ type: "kanban-updated", requestId: message.requestId, kanban: this.state() }];
|
|
1589
|
+
}
|
|
1590
|
+
if (message.type !== "kanban-configuration") {
|
|
1591
|
+
throw new Error(`Unsupported Kanban action: ${message.type}`);
|
|
1592
|
+
}
|
|
1593
|
+
if (message.revision !== this.workspace.revision) {
|
|
1594
|
+
throw new Error("Kanban configuration changed. Try again.");
|
|
1595
|
+
}
|
|
1596
|
+
const action = String(message.action), item = this.workspace.configurations.find((item2) => item2.id === message.id);
|
|
1597
|
+
if (!["create", "duplicate", "rename", "save", "select", "delete"].includes(action)) {
|
|
1598
|
+
throw new Error("Unknown Kanban configuration action.");
|
|
1599
|
+
}
|
|
1600
|
+
if (action !== "create" && !item) {
|
|
1601
|
+
throw new Error("Kanban configuration no longer exists.");
|
|
1602
|
+
}
|
|
1603
|
+
switch (action) {
|
|
1604
|
+
case "create":
|
|
1605
|
+
if (!this.params.length) {
|
|
1606
|
+
throw new Error("The project does not define parameters.");
|
|
1607
|
+
}
|
|
1608
|
+
this.create();
|
|
1609
|
+
this.newPreview();
|
|
1610
|
+
break;
|
|
1611
|
+
case "duplicate":
|
|
1612
|
+
this.create(sanitizeKanbanValues(this.params, item.values), `${item.name} (copy)`);
|
|
1613
|
+
this.newPreview();
|
|
1614
|
+
break;
|
|
1615
|
+
case "rename": {
|
|
1616
|
+
if (typeof message.name !== "string" || !message.name.trim() || message.name.length > 200) {
|
|
1617
|
+
throw new Error("Invalid configuration name.");
|
|
1618
|
+
}
|
|
1619
|
+
item.name = message.name.trim();
|
|
1620
|
+
item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1621
|
+
break;
|
|
1622
|
+
}
|
|
1623
|
+
case "save": {
|
|
1624
|
+
if (!message.values || typeof message.values !== "object" || Array.isArray(message.values) || Object.values(message.values).some((value) => typeof value !== "string")) {
|
|
1625
|
+
throw new Error("Invalid Kanban parameters.");
|
|
1626
|
+
}
|
|
1627
|
+
item.values = sanitizeKanbanValues(this.params, message.values);
|
|
1628
|
+
item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
case "select":
|
|
1632
|
+
this.workspace.activeConfigurationId = item.id;
|
|
1633
|
+
this.newPreview();
|
|
1634
|
+
break;
|
|
1635
|
+
case "delete": {
|
|
1636
|
+
if (this.workspace.configurations.length <= 1) {
|
|
1637
|
+
throw new Error("The last configuration cannot be deleted.");
|
|
1638
|
+
}
|
|
1639
|
+
this.workspace.configurations = this.workspace.configurations.filter((value) => value !== item);
|
|
1640
|
+
if (item.id === this.workspace.activeConfigurationId) {
|
|
1641
|
+
this.workspace.activeConfigurationId = this.workspace.configurations[0].id;
|
|
1642
|
+
this.newPreview();
|
|
1643
|
+
}
|
|
1644
|
+
break;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
this.workspace.revision++;
|
|
1648
|
+
await this.persist();
|
|
1649
|
+
this.hooks.changed();
|
|
1650
|
+
return [{ type: "kanban-updated", requestId: message.requestId, kanban: this.state() }];
|
|
1651
|
+
}
|
|
1652
|
+
async bridge(call) {
|
|
1653
|
+
if (this.disposed) {
|
|
1654
|
+
throw new Error("Kanban has closed.");
|
|
1655
|
+
}
|
|
1656
|
+
if (call.operation === "runtime.getLocale") {
|
|
1657
|
+
return getCurrentLocale();
|
|
1658
|
+
}
|
|
1659
|
+
const generation = this.generation;
|
|
1660
|
+
let result;
|
|
1661
|
+
if (this.project.projectInfo?.dataStorage?.mode === "server" || call.operation.startsWith("files.")) {
|
|
1662
|
+
const connection = this.hooks.connection();
|
|
1663
|
+
if (!connection) {
|
|
1664
|
+
throw Object.assign(new Error("Workflow Server login is required."), { code: call.operation.startsWith("files.") ? "KANBAN_FILE_UNAVAILABLE" : "KANBAN_BRIDGE_UNAVAILABLE" });
|
|
1665
|
+
}
|
|
1666
|
+
const abort = new AbortController();
|
|
1667
|
+
this.requests.add(abort);
|
|
1668
|
+
try {
|
|
1669
|
+
const response = await fetch(`${connection.serverUrl}/api/workflows/${encodeURIComponent(this.project.id)}/kanban/bridge`, { method: "POST", headers: { Authorization: `Bearer ${connection.accessToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ call }), redirect: "error", signal: AbortSignal.any([abort.signal, AbortSignal.timeout(call.operation.startsWith("files.") ? 12e4 : 15e3)]) });
|
|
1670
|
+
const value = await response.json();
|
|
1671
|
+
if (!response.ok || value.errCode !== 0) {
|
|
1672
|
+
throw Object.assign(new Error("Kanban Server operation failed."), { code: call.operation.startsWith("files.") ? "KANBAN_FILE_UNAVAILABLE" : "KANBAN_DATA_OPERATION_FAILED" });
|
|
1673
|
+
}
|
|
1674
|
+
result = value.data ?? null;
|
|
1675
|
+
} finally {
|
|
1676
|
+
this.requests.delete(abort);
|
|
1677
|
+
}
|
|
1678
|
+
} else {
|
|
1679
|
+
if (!this.runtime.kanbanBridge) {
|
|
1680
|
+
throw new Error("Project Core does not support Kanban data.");
|
|
1681
|
+
}
|
|
1682
|
+
result = await this.runtime.kanbanBridge(this.project, call);
|
|
1683
|
+
}
|
|
1684
|
+
if (generation !== this.generation || this.disposed) {
|
|
1685
|
+
throw Object.assign(new Error("Kanban preview changed."), { code: "KANBAN_BRIDGE_SESSION_INVALID" });
|
|
1686
|
+
}
|
|
1687
|
+
return result;
|
|
1688
|
+
}
|
|
1689
|
+
authenticationChanged() {
|
|
1690
|
+
this.generation++;
|
|
1691
|
+
this.methods.clear();
|
|
1692
|
+
for (const request of this.requests) {
|
|
1693
|
+
request.abort();
|
|
1694
|
+
}
|
|
1695
|
+
this.feed?.abort();
|
|
1696
|
+
clearTimeout(this.retry);
|
|
1697
|
+
this.relatedProjectRevisions = [];
|
|
1698
|
+
this.relatedProjectWarning = "";
|
|
1699
|
+
this.connectServerRevisions();
|
|
1700
|
+
}
|
|
1701
|
+
connectServerRevisions() {
|
|
1702
|
+
if (this.project.projectInfo?.dataStorage?.mode !== "server" || this.disposed) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
this.missingRelatedProjectAliases = [];
|
|
1706
|
+
const connection = this.hooks.connection();
|
|
1707
|
+
if (!connection) {
|
|
1708
|
+
this.relatedProjectWarning = "\u8BF7\u767B\u5F55 Workflow Server \u4EE5\u8FDE\u63A5\u770B\u677F\u6570\u636E\u3002";
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
const abort = this.feed = new AbortController();
|
|
1712
|
+
void (async () => {
|
|
1713
|
+
try {
|
|
1714
|
+
const response = await fetch(`${connection.serverUrl}/api/workflows/${encodeURIComponent(this.project.id)}/kanban/related-project-revisions`, { headers: { Authorization: `Bearer ${connection.accessToken}` }, signal: abort.signal, redirect: "error" });
|
|
1715
|
+
if (!response.ok || !response.body) {
|
|
1716
|
+
throw new Error("Kanban related project updates are unavailable.");
|
|
1717
|
+
}
|
|
1718
|
+
const apply = (revisions) => {
|
|
1719
|
+
if (!abort.signal.aborted) {
|
|
1720
|
+
this.relatedProjectRevisions = revisions;
|
|
1721
|
+
this.relatedProjectWarning = "";
|
|
1722
|
+
this.hooks.changed();
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
await consumeKanbanRelatedProjectRevisionStream(response.body, { onSnapshot: (value) => apply(value.revisions), onRevision: (value) => apply([...this.relatedProjectRevisions.filter((item) => item.alias !== value.alias), value]), onUnavailable: () => {
|
|
1726
|
+
throw new Error("Related projects are unavailable.");
|
|
1727
|
+
} }, abort.signal);
|
|
1728
|
+
} catch {
|
|
1729
|
+
if (!abort.signal.aborted) {
|
|
1730
|
+
this.relatedProjectWarning = "\u5173\u8054\u9879\u76EE\u66F4\u65B0\u8FDE\u63A5\u5DF2\u4E2D\u65AD\uFF0C\u6B63\u5728\u91CD\u8FDE\u3002";
|
|
1731
|
+
this.hooks.changed();
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
if (!abort.signal.aborted && !this.disposed) {
|
|
1735
|
+
this.retry = setTimeout(() => this.connectServerRevisions(), 5e3);
|
|
1736
|
+
this.retry.unref();
|
|
1737
|
+
}
|
|
1738
|
+
})();
|
|
1739
|
+
}
|
|
1740
|
+
async updateLocalRevisions() {
|
|
1741
|
+
if (this.project.projectInfo?.dataStorage?.mode === "server" || this.disposed) {
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
const generation = this.generation, { info, projects } = await localKanbanRelatedProjects(this.project);
|
|
1745
|
+
const missing = info.relatedProjects.filter((item) => !projects.has(item.alias)).map((item) => item.alias);
|
|
1746
|
+
const revisions = await Promise.all([...projects].sort(([left], [right]) => left.localeCompare(right)).map(async ([alias, target]) => {
|
|
1747
|
+
const hash = createHash2("sha256"), root = path3.join(webProjectDataDirectory(this.storageDir, target.folderPath), "kv");
|
|
1748
|
+
const visit = async (directory) => {
|
|
1749
|
+
for (const item of (await readdir2(directory, { withFileTypes: true }).catch(() => [])).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1750
|
+
if (item.name.startsWith(".") || item.name.endsWith(".lock") || item.name.endsWith(".tmp")) {
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
const file = path3.join(directory, item.name);
|
|
1754
|
+
if (item.isDirectory()) {
|
|
1755
|
+
await visit(file);
|
|
1756
|
+
} else if (item.isFile()) {
|
|
1757
|
+
const info2 = await stat2(file).catch(() => void 0);
|
|
1758
|
+
hash.update(`${file}:${info2?.size}:${info2?.mtimeMs}`);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1762
|
+
await visit(root);
|
|
1763
|
+
return { alias, revision: hash.digest("hex") };
|
|
1764
|
+
}));
|
|
1765
|
+
if (generation !== this.generation || this.disposed) {
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
if (JSON.stringify([missing, revisions]) !== JSON.stringify([this.missingRelatedProjectAliases, this.relatedProjectRevisions])) {
|
|
1769
|
+
this.missingRelatedProjectAliases = missing;
|
|
1770
|
+
this.relatedProjectRevisions = revisions;
|
|
1771
|
+
this.hooks.changed();
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
async close() {
|
|
1775
|
+
this.disposed = true;
|
|
1776
|
+
this.watcher?.close();
|
|
1777
|
+
clearTimeout(this.refreshTimer);
|
|
1778
|
+
this.methods.clear();
|
|
1779
|
+
clearInterval(this.poll);
|
|
1780
|
+
clearTimeout(this.retry);
|
|
1781
|
+
this.feed?.abort();
|
|
1782
|
+
for (const request of this.requests) {
|
|
1783
|
+
request.abort();
|
|
1784
|
+
}
|
|
1785
|
+
await this.refreshing?.catch(() => void 0);
|
|
1786
|
+
await this.pendingSave;
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1146
1790
|
// src/web/resource-cards.ts
|
|
1147
1791
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1148
1792
|
var string = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -1249,8 +1893,8 @@ function conversationResourceCards(items, projectId, conversationId) {
|
|
|
1249
1893
|
}
|
|
1250
1894
|
|
|
1251
1895
|
// src/web/workflow-actions.ts
|
|
1252
|
-
import { readFile, realpath } from "fs/promises";
|
|
1253
|
-
import
|
|
1896
|
+
import { readFile as readFile4, realpath as realpath3 } from "fs/promises";
|
|
1897
|
+
import path4 from "path";
|
|
1254
1898
|
function createWorkflowActions(controller, runtime, project, revision, changed) {
|
|
1255
1899
|
const states = /* @__PURE__ */ new Map();
|
|
1256
1900
|
const epochs = /* @__PURE__ */ new Map();
|
|
@@ -1439,11 +2083,11 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
1439
2083
|
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
1440
2084
|
replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, run.scopeId, card.kind, card.target) });
|
|
1441
2085
|
} else if (card.kind === "file") {
|
|
1442
|
-
const root = await
|
|
1443
|
-
if (relative.startsWith("..") ||
|
|
2086
|
+
const root = await realpath3(project.folderPath), filename = await realpath3(path4.resolve(root, card.target.filePath)), relative = path4.relative(root, filename);
|
|
2087
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
1444
2088
|
throw new Error("File is outside this project.");
|
|
1445
2089
|
}
|
|
1446
|
-
replies.push({ type: "preview", title: card.title, content: await
|
|
2090
|
+
replies.push({ type: "preview", title: card.title, content: await readFile4(filename, "utf8") });
|
|
1447
2091
|
} else {
|
|
1448
2092
|
throw new Error("This resource requires an editor host.");
|
|
1449
2093
|
}
|
|
@@ -1460,25 +2104,25 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
1460
2104
|
// src/web/runtime.ts
|
|
1461
2105
|
import { spawn } from "child_process";
|
|
1462
2106
|
import { existsSync, readFileSync } from "fs";
|
|
1463
|
-
import
|
|
2107
|
+
import path5 from "path";
|
|
1464
2108
|
import { fileURLToPath } from "url";
|
|
1465
2109
|
function cliRoot() {
|
|
1466
|
-
let directory =
|
|
2110
|
+
let directory = path5.dirname(fileURLToPath(import.meta.url));
|
|
1467
2111
|
for (let depth = 0; depth < 8; depth++) {
|
|
1468
2112
|
try {
|
|
1469
|
-
if (JSON.parse(readFileSync(
|
|
2113
|
+
if (JSON.parse(readFileSync(path5.join(directory, "package.json"), "utf8")).name === "@workflow-code/cli") {
|
|
1470
2114
|
return directory;
|
|
1471
2115
|
}
|
|
1472
2116
|
} catch {
|
|
1473
2117
|
}
|
|
1474
|
-
directory =
|
|
2118
|
+
directory = path5.dirname(directory);
|
|
1475
2119
|
}
|
|
1476
2120
|
throw new Error("Cannot locate the CLI installation.");
|
|
1477
2121
|
}
|
|
1478
2122
|
function createWebRuntime(projectDir, storageDir, connection) {
|
|
1479
2123
|
const root = cliRoot();
|
|
1480
|
-
const builtWorker =
|
|
1481
|
-
const worker = existsSync(builtWorker) ? builtWorker :
|
|
2124
|
+
const builtWorker = path5.join(root, "dist/web/worker.mjs");
|
|
2125
|
+
const worker = existsSync(builtWorker) ? builtWorker : path5.join(root, "src/web/worker.mjs");
|
|
1482
2126
|
const children = /* @__PURE__ */ new Set();
|
|
1483
2127
|
const active = /* @__PURE__ */ new Map();
|
|
1484
2128
|
let disposed = false;
|
|
@@ -1500,7 +2144,7 @@ function createWebRuntime(projectDir, storageDir, connection) {
|
|
|
1500
2144
|
WORKFLOW_SERVER_TOKEN: "",
|
|
1501
2145
|
WORKFLOW_LLM_SERVER_URL: auth?.serverUrl ?? "",
|
|
1502
2146
|
WORKFLOW_LLM_SERVER_TOKEN: auth?.accessToken ?? "",
|
|
1503
|
-
WORKFLOW_CLI_AUTH_DIR:
|
|
2147
|
+
WORKFLOW_CLI_AUTH_DIR: path5.join(storageDir, "runtime-auth")
|
|
1504
2148
|
};
|
|
1505
2149
|
for (const key of [
|
|
1506
2150
|
"TSX_TSCONFIG_PATH",
|
|
@@ -1589,6 +2233,7 @@ function createWebRuntime(projectDir, storageDir, connection) {
|
|
|
1589
2233
|
});
|
|
1590
2234
|
return {
|
|
1591
2235
|
available: true,
|
|
2236
|
+
kanbanBridge: (project, call) => invoke({ method: "kanban-bridge", project, call }),
|
|
1592
2237
|
inspect: (paths) => invoke({ method: "inspect", paths }),
|
|
1593
2238
|
resolveParams: (project, request, entrypointId) => invoke({ method: "resolve", project, request, entrypointId }),
|
|
1594
2239
|
run: (request, onEvent) => invoke({ method: "run", ...request }, onEvent),
|
|
@@ -1707,8 +2352,8 @@ async function inspectWebProject(runtime, projectDir, host) {
|
|
|
1707
2352
|
|
|
1708
2353
|
// src/web/server.ts
|
|
1709
2354
|
async function startWebServer(options) {
|
|
1710
|
-
const projectDir = await
|
|
1711
|
-
const storageDir =
|
|
2355
|
+
const projectDir = await realpath4(path6.resolve(options.projectDir));
|
|
2356
|
+
const storageDir = path6.resolve(options.dataDir ?? path6.join(os.homedir(), ".workflow-code", "web"));
|
|
1712
2357
|
const clients = /* @__PURE__ */ new Set();
|
|
1713
2358
|
let connection;
|
|
1714
2359
|
let account = { status: "signed-out", serverUrl: "http://localhost:7126", revision: 0 };
|
|
@@ -1738,26 +2383,29 @@ async function startWebServer(options) {
|
|
|
1738
2383
|
if (!options.runtime) {
|
|
1739
2384
|
assertLocalCliCoreCompatible(projectDir, descriptor.runtime.coreVersion);
|
|
1740
2385
|
}
|
|
1741
|
-
if (!["conversation", "workflow"].includes(descriptor.projectType)) {
|
|
2386
|
+
if (!["conversation", "workflow", "kanban"].includes(descriptor.projectType)) {
|
|
1742
2387
|
runtime.dispose();
|
|
1743
|
-
throw new Error("
|
|
2388
|
+
throw new Error("Unsupported Workflow project type.");
|
|
1744
2389
|
}
|
|
1745
2390
|
const project = descriptor;
|
|
1746
|
-
const key =
|
|
1747
|
-
const historyPath =
|
|
2391
|
+
const key = createHash3("sha256").update(project.assistant ? `assistant:${project.id}:${project.assistant.workdir}` : projectDir).digest("hex").slice(0, 24);
|
|
2392
|
+
const historyPath = path6.join(storageDir, key, "conversations.json");
|
|
1748
2393
|
let saved = {};
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
2394
|
+
if (project.projectType !== "kanban") {
|
|
2395
|
+
try {
|
|
2396
|
+
saved = JSON.parse(await readFile5(historyPath, "utf8"));
|
|
2397
|
+
} catch (error) {
|
|
2398
|
+
if (error.code !== "ENOENT") {
|
|
2399
|
+
runtime.dispose();
|
|
2400
|
+
throw new Error(`Cannot read conversation history: ${String(error)}`);
|
|
2401
|
+
}
|
|
1755
2402
|
}
|
|
1756
2403
|
}
|
|
1757
2404
|
let pendingSave = Promise.resolve();
|
|
1758
2405
|
let broadcastTimer;
|
|
1759
2406
|
let authRevision = 0;
|
|
1760
2407
|
const isWorkflow = project.projectType === "workflow";
|
|
2408
|
+
const isKanban = project.projectType === "kanban";
|
|
1761
2409
|
const parameterStates = /* @__PURE__ */ new Map();
|
|
1762
2410
|
const parameterEpochs = /* @__PURE__ */ new Map();
|
|
1763
2411
|
const publicFile = (file) => ({ ...file, previewUrl: `/api/file/${encodeURIComponent(file.id)}` });
|
|
@@ -1777,7 +2425,7 @@ async function startWebServer(options) {
|
|
|
1777
2425
|
const state = () => publicValue(redactWorkflowData({
|
|
1778
2426
|
type: "state",
|
|
1779
2427
|
project,
|
|
1780
|
-
...isWorkflow ? workflowActions.state() : { sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {} },
|
|
2428
|
+
...isKanban ? { kanban: kanbanController.state() } : isWorkflow ? workflowActions.state() : { sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {} },
|
|
1781
2429
|
account,
|
|
1782
2430
|
authenticationRevision: authRevision
|
|
1783
2431
|
}, [...sensitiveTokens]));
|
|
@@ -1795,27 +2443,27 @@ async function startWebServer(options) {
|
|
|
1795
2443
|
}
|
|
1796
2444
|
}, 30);
|
|
1797
2445
|
};
|
|
1798
|
-
const conversationController = isWorkflow ? void 0 : new ConversationController(project, runtime, saved, {
|
|
2446
|
+
const conversationController = isWorkflow || isKanban ? void 0 : new ConversationController(project, runtime, saved, {
|
|
1799
2447
|
changed: broadcast,
|
|
1800
2448
|
log: (message) => process.stderr.write(`${message}
|
|
1801
2449
|
`),
|
|
1802
2450
|
save: (sessions, activeId) => {
|
|
1803
2451
|
const content = JSON.stringify({ sessions, activeId });
|
|
1804
2452
|
pendingSave = pendingSave.catch(() => void 0).then(async () => {
|
|
1805
|
-
await
|
|
2453
|
+
await mkdir2(path6.dirname(historyPath), { recursive: true });
|
|
1806
2454
|
const temp = `${historyPath}.${process.pid}.tmp`;
|
|
1807
|
-
await
|
|
1808
|
-
await
|
|
2455
|
+
await writeFile2(temp, content, { mode: 384 });
|
|
2456
|
+
await rename2(temp, historyPath);
|
|
1809
2457
|
});
|
|
1810
2458
|
return pendingSave;
|
|
1811
2459
|
}
|
|
1812
2460
|
});
|
|
1813
2461
|
const controller = conversationController;
|
|
1814
|
-
const runsPath =
|
|
2462
|
+
const runsPath = path6.join(storageDir, key, "runs.json");
|
|
1815
2463
|
let workspace;
|
|
1816
2464
|
if (isWorkflow) {
|
|
1817
2465
|
try {
|
|
1818
|
-
workspace = JSON.parse(await
|
|
2466
|
+
workspace = JSON.parse(await readFile5(runsPath, "utf8"));
|
|
1819
2467
|
if (workspace?.version !== 1 || !Array.isArray(workspace.runs) || !workspace.drafts || typeof workspace.drafts !== "object") {
|
|
1820
2468
|
throw new Error("Invalid Workflow history.");
|
|
1821
2469
|
}
|
|
@@ -1835,15 +2483,32 @@ async function startWebServer(options) {
|
|
|
1835
2483
|
save: (workspace2) => {
|
|
1836
2484
|
const content = JSON.stringify(workspace2);
|
|
1837
2485
|
pendingSave = pendingSave.catch(() => void 0).then(async () => {
|
|
1838
|
-
await
|
|
2486
|
+
await mkdir2(path6.dirname(runsPath), { recursive: true });
|
|
1839
2487
|
const temp = `${runsPath}.${process.pid}.tmp`;
|
|
1840
|
-
await
|
|
1841
|
-
await
|
|
2488
|
+
await writeFile2(temp, content, { mode: 384 });
|
|
2489
|
+
await rename2(temp, runsPath);
|
|
1842
2490
|
});
|
|
1843
2491
|
return pendingSave;
|
|
1844
2492
|
}
|
|
1845
2493
|
}) : void 0;
|
|
1846
2494
|
const workflowActions = workflowController ? createWorkflowActions(workflowController, runtime, project, () => authRevision, broadcast) : void 0;
|
|
2495
|
+
const kanbanController = isKanban ? new KanbanController(project, runtime, storageDir, { changed: broadcast, emit: (message) => {
|
|
2496
|
+
for (const client of clients) {
|
|
2497
|
+
client.write(`data: ${JSON.stringify(message)}
|
|
2498
|
+
|
|
2499
|
+
`);
|
|
2500
|
+
}
|
|
2501
|
+
}, connection: () => connection, log: (message) => process.stderr.write(`${message}
|
|
2502
|
+
`) }) : void 0;
|
|
2503
|
+
if (kanbanController) {
|
|
2504
|
+
try {
|
|
2505
|
+
await kanbanController.initialize();
|
|
2506
|
+
} catch (error) {
|
|
2507
|
+
await kanbanController.close();
|
|
2508
|
+
runtime.dispose();
|
|
2509
|
+
throw error;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
1847
2512
|
if (workflowController) {
|
|
1848
2513
|
await workflowController.persist();
|
|
1849
2514
|
}
|
|
@@ -1886,6 +2551,7 @@ ${token}`;
|
|
|
1886
2551
|
authRevision++;
|
|
1887
2552
|
parameterStates.clear();
|
|
1888
2553
|
workflowActions?.invalidate();
|
|
2554
|
+
kanbanController?.authenticationChanged();
|
|
1889
2555
|
if (authenticationInitialized) {
|
|
1890
2556
|
await stopForAuthentication();
|
|
1891
2557
|
}
|
|
@@ -1911,6 +2577,7 @@ ${token}`;
|
|
|
1911
2577
|
workflowActions?.invalidate();
|
|
1912
2578
|
}
|
|
1913
2579
|
connection = candidate;
|
|
2580
|
+
kanbanController?.authenticationChanged();
|
|
1914
2581
|
const savedConnection = !hostManaged && candidate.serverUrl === config.serverUrl.replace(/\/+$/, "") && candidate.accessToken === config.apiKey;
|
|
1915
2582
|
account = { ...account, status: "authenticated", user: profile, expiresAt: result.data.principal?.expiresAt ?? (savedConnection ? config.expiresAt : void 0) };
|
|
1916
2583
|
} catch (error) {
|
|
@@ -1918,6 +2585,7 @@ ${token}`;
|
|
|
1918
2585
|
return;
|
|
1919
2586
|
}
|
|
1920
2587
|
connection = void 0;
|
|
2588
|
+
kanbanController?.authenticationChanged();
|
|
1921
2589
|
if (!changed) {
|
|
1922
2590
|
authRevision++;
|
|
1923
2591
|
parameterStates.clear();
|
|
@@ -1935,8 +2603,8 @@ ${token}`;
|
|
|
1935
2603
|
const authPoll = setInterval(() => void refreshAuth().catch((error) => process.stderr.write(`${String(error)}
|
|
1936
2604
|
`)), 5e3);
|
|
1937
2605
|
authPoll.unref();
|
|
1938
|
-
const capability =
|
|
1939
|
-
const cookie = `workflow_web_${
|
|
2606
|
+
const capability = randomBytes2(32).toString("hex");
|
|
2607
|
+
const cookie = `workflow_web_${randomBytes2(8).toString("hex")}`;
|
|
1940
2608
|
let origin = "";
|
|
1941
2609
|
const json = (response, status, value) => {
|
|
1942
2610
|
response.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
|
@@ -1984,6 +2652,9 @@ ${token}`;
|
|
|
1984
2652
|
if (message.projectUri && message.projectUri !== project.uri) {
|
|
1985
2653
|
throw new Error("Project changed.");
|
|
1986
2654
|
}
|
|
2655
|
+
if (kanbanController && !["ready", "login", "verify", "logout", "usage"].includes(message.type)) {
|
|
2656
|
+
return kanbanController.handle(message);
|
|
2657
|
+
}
|
|
1987
2658
|
if (workflowActions && !["ready", "login", "verify", "logout", "usage"].includes(message.type)) {
|
|
1988
2659
|
return publicValue(await workflowActions.handle(message));
|
|
1989
2660
|
}
|
|
@@ -2152,13 +2823,13 @@ ${token}`;
|
|
|
2152
2823
|
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
2153
2824
|
replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, current.id, card.kind, card.target) });
|
|
2154
2825
|
} else if (card.kind === "file") {
|
|
2155
|
-
const root = await
|
|
2156
|
-
const filename = await
|
|
2157
|
-
const relative =
|
|
2158
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
2826
|
+
const root = await realpath4(project.assistant?.workdir ?? projectDir);
|
|
2827
|
+
const filename = await realpath4(path6.resolve(root, card.target.filePath));
|
|
2828
|
+
const relative = path6.relative(root, filename);
|
|
2829
|
+
if (relative === ".." || relative.startsWith(`..${path6.sep}`) || path6.isAbsolute(relative)) {
|
|
2159
2830
|
throw new Error("File is outside this project.");
|
|
2160
2831
|
}
|
|
2161
|
-
replies.push({ type: "preview", title: card.title, content: await
|
|
2832
|
+
replies.push({ type: "preview", title: card.title, content: await readFile5(filename, "utf8") });
|
|
2162
2833
|
} else {
|
|
2163
2834
|
throw new Error("This resource requires an editor host.");
|
|
2164
2835
|
}
|
|
@@ -2215,7 +2886,7 @@ ${token}`;
|
|
|
2215
2886
|
await refreshAuth(true);
|
|
2216
2887
|
break;
|
|
2217
2888
|
}
|
|
2218
|
-
login = spawn2(process.execPath, [
|
|
2889
|
+
login = spawn2(process.execPath, [path6.join(cliRoot(), "dist/index.js"), "workspace", "login", "--server", url.toString().replace(/\/$/, "")], { shell: false, stdio: ["ignore", "ignore", "pipe"] });
|
|
2219
2890
|
login.stderr?.on("data", (chunk) => process.stderr.write(chunk));
|
|
2220
2891
|
login.once("error", (error) => {
|
|
2221
2892
|
login = void 0;
|
|
@@ -2253,11 +2924,22 @@ ${token}`;
|
|
|
2253
2924
|
response.setHeader("Referrer-Policy", "no-referrer");
|
|
2254
2925
|
const host = new URL(`http://${request.headers.host ?? ""}`);
|
|
2255
2926
|
const forwarded = !!options.host && ["127.0.0.1", "localhost", "[::1]"].includes(host.hostname) && !host.username && !host.password && host.pathname === "/" && !host.search && !host.hash;
|
|
2256
|
-
|
|
2927
|
+
const url = new URL(request.url ?? "/", origin);
|
|
2928
|
+
const sandboxAsset = !!kanbanController && url.pathname.startsWith("/kanban/") && ["GET", "HEAD"].includes(request.method ?? "") && request.headers.origin === "null";
|
|
2929
|
+
if (request.headers.host !== new URL(origin).host && !forwarded || request.headers.origin && request.headers.origin !== (forwarded ? host.origin : origin) && !sandboxAsset) {
|
|
2257
2930
|
json(response, 403, { message: "Invalid request origin." });
|
|
2258
2931
|
return;
|
|
2259
2932
|
}
|
|
2260
|
-
|
|
2933
|
+
if (kanbanController && url.pathname.startsWith("/kanban/") && ["GET", "HEAD"].includes(request.method ?? "")) {
|
|
2934
|
+
const asset = await kanbanController.preview.asset(url);
|
|
2935
|
+
if (!asset) {
|
|
2936
|
+
json(response, 404, { message: "Kanban asset not found." });
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
response.writeHead(200, asset.headers);
|
|
2940
|
+
response.end(request.method === "HEAD" ? void 0 : asset.content);
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2261
2943
|
if (url.pathname === "/api/session" && request.method === "POST") {
|
|
2262
2944
|
if (!authMatches(String(request.headers.authorization ?? "").replace(/^Bearer /, ""))) {
|
|
2263
2945
|
json(response, 401, { message: "Invalid session." });
|
|
@@ -2284,6 +2966,7 @@ ${token}`;
|
|
|
2284
2966
|
return;
|
|
2285
2967
|
}
|
|
2286
2968
|
if (url.pathname === "/api/events" && request.method === "GET") {
|
|
2969
|
+
const disconnectKanban = kanbanController?.methods.connect(url.searchParams.get("clientId") ?? "");
|
|
2287
2970
|
response.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
|
|
2288
2971
|
response.write(`data: ${JSON.stringify(state())}
|
|
2289
2972
|
|
|
@@ -2293,6 +2976,7 @@ ${token}`;
|
|
|
2293
2976
|
response.once("close", () => {
|
|
2294
2977
|
clearInterval(heartbeat);
|
|
2295
2978
|
clients.delete(response);
|
|
2979
|
+
disconnectKanban?.();
|
|
2296
2980
|
});
|
|
2297
2981
|
return;
|
|
2298
2982
|
}
|
|
@@ -2302,8 +2986,9 @@ ${token}`;
|
|
|
2302
2986
|
const operation = () => handleMessage(message).then((value2) => {
|
|
2303
2987
|
messages = value2;
|
|
2304
2988
|
});
|
|
2305
|
-
const
|
|
2306
|
-
|
|
2989
|
+
const concurrent = ["resolve", "ready", "kanban-bridge", "kanban-method-state", "kanban-method-reply", "kanban-methods"].includes(message.type);
|
|
2990
|
+
const task = concurrent ? operation() : mutations.catch(() => void 0).then(operation);
|
|
2991
|
+
if (!concurrent) {
|
|
2307
2992
|
mutations = task;
|
|
2308
2993
|
}
|
|
2309
2994
|
await task;
|
|
@@ -2312,7 +2997,7 @@ ${token}`;
|
|
|
2312
2997
|
}
|
|
2313
2998
|
if (url.pathname.startsWith("/api/file/") && request.method === "GET") {
|
|
2314
2999
|
const file = await runtime.readFile(project, decodeURIComponent(url.pathname.slice("/api/file/".length)));
|
|
2315
|
-
const bytes = await
|
|
3000
|
+
const bytes = await readFile5(file.path);
|
|
2316
3001
|
const safeType = /^(?:image\/(?:png|jpeg|gif|webp|avif)|audio\/[a-z0-9.+-]+|video\/[a-z0-9.+-]+)$/.test(file.reference.mimeType);
|
|
2317
3002
|
response.writeHead(200, { "Content-Type": safeType ? file.reference.mimeType : "application/octet-stream", "Content-Disposition": `${safeType ? "inline" : "attachment"}; filename*=UTF-8''${encodeURIComponent(file.reference.name)}`, "Cache-Control": "no-store" });
|
|
2318
3003
|
response.end(bytes);
|
|
@@ -2332,19 +3017,21 @@ ${token}`;
|
|
|
2332
3017
|
}
|
|
2333
3018
|
if (url.pathname === "/") {
|
|
2334
3019
|
const ancestors = options.host ? "'self' vscode-webview: vscode-file: https://*.vscode-cdn.net https://*.vscode-webview.net http://127.0.0.1:* http://localhost:*" : "'self' vscode-webview:";
|
|
2335
|
-
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Content-Security-Policy": `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: http:; font-src 'self'; connect-src 'self'; media-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors ${ancestors}; form-action 'self'` });
|
|
2336
|
-
const ui = isWorkflow ? "workflow-workspace" : "workflow-ui";
|
|
3020
|
+
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Content-Security-Policy": `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: http:; font-src 'self'; connect-src 'self'; media-src 'self' blob:; frame-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors ${ancestors}; form-action 'self'` });
|
|
3021
|
+
const ui = isKanban ? "kanban-workspace" : isWorkflow ? "workflow-workspace" : "workflow-ui";
|
|
2337
3022
|
response.end(`<!doctype html><html lang="zh-CN" data-theme="${options.theme ?? "auto"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="workflow-project" content="${key}"><title>Workflow</title><link rel="stylesheet" href="/assets/codicons/codicon.css"><link rel="stylesheet" href="/assets/${ui}.css"><link rel="stylesheet" href="/assets/theme.css"></head><body data-surface="${options.host?.surface ?? "editor"}"><div id="app"></div><script src="/assets/${ui}.js" defer></script></body></html>`);
|
|
2338
3023
|
return;
|
|
2339
3024
|
}
|
|
2340
3025
|
const assets = { "/assets/workflow-ui.js": "application/javascript", "/assets/workflow-ui.css": "text/css", "/assets/theme.css": "text/css", "/assets/codicons/codicon.css": "text/css", "/assets/codicons/codicon.ttf": "font/ttf" };
|
|
2341
3026
|
assets["/assets/workflow-workspace.js"] = "application/javascript";
|
|
2342
3027
|
assets["/assets/workflow-workspace.css"] = "text/css";
|
|
3028
|
+
assets["/assets/kanban-workspace.js"] = "application/javascript";
|
|
3029
|
+
assets["/assets/kanban-workspace.css"] = "text/css";
|
|
2343
3030
|
if (!assets[url.pathname]) {
|
|
2344
3031
|
json(response, 404, { message: "Not found." });
|
|
2345
3032
|
return;
|
|
2346
3033
|
}
|
|
2347
|
-
const content = await
|
|
3034
|
+
const content = await readFile5(path6.join(cliRoot(), "dist/web", url.pathname.slice("/assets/".length)));
|
|
2348
3035
|
response.writeHead(200, { "Content-Type": assets[url.pathname], "Cache-Control": "no-cache" });
|
|
2349
3036
|
response.end(content);
|
|
2350
3037
|
})().catch((error) => {
|
|
@@ -2367,6 +3054,7 @@ ${token}`;
|
|
|
2367
3054
|
clearInterval(authPoll);
|
|
2368
3055
|
conversationController?.dispose();
|
|
2369
3056
|
workflowController?.dispose();
|
|
3057
|
+
await kanbanController?.close();
|
|
2370
3058
|
runtime.dispose();
|
|
2371
3059
|
throw error;
|
|
2372
3060
|
}
|
|
@@ -2378,6 +3066,7 @@ ${token}`;
|
|
|
2378
3066
|
project,
|
|
2379
3067
|
controller,
|
|
2380
3068
|
workflowController,
|
|
3069
|
+
kanbanController,
|
|
2381
3070
|
setHostConnection: async (value) => {
|
|
2382
3071
|
authEpoch++;
|
|
2383
3072
|
authCheck?.abort();
|
|
@@ -2397,6 +3086,7 @@ ${token}`;
|
|
|
2397
3086
|
conversationController?.dispose();
|
|
2398
3087
|
workflowController?.dispose();
|
|
2399
3088
|
runtime.dispose();
|
|
3089
|
+
await kanbanController?.close();
|
|
2400
3090
|
for (const client of clients) {
|
|
2401
3091
|
client.end();
|
|
2402
3092
|
}
|
|
@@ -2414,7 +3104,7 @@ ${token}`;
|
|
|
2414
3104
|
import { createInterface } from "readline";
|
|
2415
3105
|
|
|
2416
3106
|
// src/web/host-config.ts
|
|
2417
|
-
import
|
|
3107
|
+
import path7 from "path";
|
|
2418
3108
|
function parseHostConnection(value) {
|
|
2419
3109
|
if (value === null || value === void 0) {
|
|
2420
3110
|
return;
|
|
@@ -2438,11 +3128,11 @@ function parseHostConfiguration(value) {
|
|
|
2438
3128
|
throw new Error("Invalid web host configuration.");
|
|
2439
3129
|
}
|
|
2440
3130
|
const assistant = configuration.assistant;
|
|
2441
|
-
if (assistant && (typeof assistant.workdir !== "string" || !
|
|
3131
|
+
if (assistant && (typeof assistant.workdir !== "string" || !path7.isAbsolute(assistant.workdir) || !Array.isArray(assistant.roots) || !assistant.roots.length || assistant.roots.length > 100 || assistant.standalone !== void 0 && typeof assistant.standalone !== "boolean" || assistant.roots.some((root) => typeof root.uri !== "string" || typeof root.name !== "string" || typeof root.folderPath !== "string" || !path7.isAbsolute(root.folderPath)) || !assistant.roots.some((root) => root.folderPath === assistant.workdir))) {
|
|
2442
3132
|
throw new Error("Invalid assistant workspace.");
|
|
2443
3133
|
}
|
|
2444
3134
|
const assistantProjectDir = configuration.assistantProjectDir;
|
|
2445
|
-
if (configuration.surface === "assistant" && (!assistant || typeof assistantProjectDir !== "string" || !
|
|
3135
|
+
if (configuration.surface === "assistant" && (!assistant || typeof assistantProjectDir !== "string" || !path7.isAbsolute(assistantProjectDir))) {
|
|
2446
3136
|
throw new Error("A dedicated assistant project and its workspace are required.");
|
|
2447
3137
|
}
|
|
2448
3138
|
if (configuration.surface !== "assistant" && (assistant || assistantProjectDir !== void 0)) {
|
|
@@ -2483,12 +3173,12 @@ function parseWebArgs(argv) {
|
|
|
2483
3173
|
}
|
|
2484
3174
|
options.theme = value;
|
|
2485
3175
|
} else {
|
|
2486
|
-
options.dataDir =
|
|
3176
|
+
options.dataDir = path8.resolve(value);
|
|
2487
3177
|
}
|
|
2488
3178
|
} else if (arg.startsWith("-") || projectSet) {
|
|
2489
3179
|
throw new Error(`Unknown web argument: ${arg}`);
|
|
2490
3180
|
} else {
|
|
2491
|
-
options.projectDir =
|
|
3181
|
+
options.projectDir = path8.resolve(arg);
|
|
2492
3182
|
projectSet = true;
|
|
2493
3183
|
}
|
|
2494
3184
|
}
|