@workflow-code/cli 0.2.4-20261011005.codex-cli-web → 0.2.4-20261011007.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 +4530 -0
- package/dist/web/worker.mjs +7 -1
- package/dist/web/workflow-ui.js +6 -2
- package/dist/web/workflow-workspace.js +30 -4
- package/dist/{web-A3HVQRKB.js → web-KEM7Y2ZR.js} +751 -62
- package/dist/{workspace-G7SB6MS5.js → workspace-V3UXVFK6.js} +9 -76
- package/package.json +3 -2
|
@@ -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
|
|
|
@@ -1144,6 +1167,626 @@ var WorkflowController = class {
|
|
|
1144
1167
|
}
|
|
1145
1168
|
};
|
|
1146
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
|
+
|
|
1147
1790
|
// src/web/resource-cards.ts
|
|
1148
1791
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1149
1792
|
var string = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -1250,8 +1893,8 @@ function conversationResourceCards(items, projectId, conversationId) {
|
|
|
1250
1893
|
}
|
|
1251
1894
|
|
|
1252
1895
|
// src/web/workflow-actions.ts
|
|
1253
|
-
import { readFile, realpath } from "fs/promises";
|
|
1254
|
-
import
|
|
1896
|
+
import { readFile as readFile4, realpath as realpath3 } from "fs/promises";
|
|
1897
|
+
import path4 from "path";
|
|
1255
1898
|
function createWorkflowActions(controller, runtime, project, revision, changed) {
|
|
1256
1899
|
const states = /* @__PURE__ */ new Map();
|
|
1257
1900
|
const epochs = /* @__PURE__ */ new Map();
|
|
@@ -1440,11 +2083,11 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
1440
2083
|
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
1441
2084
|
replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, run.scopeId, card.kind, card.target) });
|
|
1442
2085
|
} else if (card.kind === "file") {
|
|
1443
|
-
const root = await
|
|
1444
|
-
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)) {
|
|
1445
2088
|
throw new Error("File is outside this project.");
|
|
1446
2089
|
}
|
|
1447
|
-
replies.push({ type: "preview", title: card.title, content: await
|
|
2090
|
+
replies.push({ type: "preview", title: card.title, content: await readFile4(filename, "utf8") });
|
|
1448
2091
|
} else {
|
|
1449
2092
|
throw new Error("This resource requires an editor host.");
|
|
1450
2093
|
}
|
|
@@ -1461,25 +2104,25 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
1461
2104
|
// src/web/runtime.ts
|
|
1462
2105
|
import { spawn } from "child_process";
|
|
1463
2106
|
import { existsSync, readFileSync } from "fs";
|
|
1464
|
-
import
|
|
2107
|
+
import path5 from "path";
|
|
1465
2108
|
import { fileURLToPath } from "url";
|
|
1466
2109
|
function cliRoot() {
|
|
1467
|
-
let directory =
|
|
2110
|
+
let directory = path5.dirname(fileURLToPath(import.meta.url));
|
|
1468
2111
|
for (let depth = 0; depth < 8; depth++) {
|
|
1469
2112
|
try {
|
|
1470
|
-
if (JSON.parse(readFileSync(
|
|
2113
|
+
if (JSON.parse(readFileSync(path5.join(directory, "package.json"), "utf8")).name === "@workflow-code/cli") {
|
|
1471
2114
|
return directory;
|
|
1472
2115
|
}
|
|
1473
2116
|
} catch {
|
|
1474
2117
|
}
|
|
1475
|
-
directory =
|
|
2118
|
+
directory = path5.dirname(directory);
|
|
1476
2119
|
}
|
|
1477
2120
|
throw new Error("Cannot locate the CLI installation.");
|
|
1478
2121
|
}
|
|
1479
2122
|
function createWebRuntime(projectDir, storageDir, connection) {
|
|
1480
2123
|
const root = cliRoot();
|
|
1481
|
-
const builtWorker =
|
|
1482
|
-
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");
|
|
1483
2126
|
const children = /* @__PURE__ */ new Set();
|
|
1484
2127
|
const active = /* @__PURE__ */ new Map();
|
|
1485
2128
|
let disposed = false;
|
|
@@ -1501,7 +2144,7 @@ function createWebRuntime(projectDir, storageDir, connection) {
|
|
|
1501
2144
|
WORKFLOW_SERVER_TOKEN: "",
|
|
1502
2145
|
WORKFLOW_LLM_SERVER_URL: auth?.serverUrl ?? "",
|
|
1503
2146
|
WORKFLOW_LLM_SERVER_TOKEN: auth?.accessToken ?? "",
|
|
1504
|
-
WORKFLOW_CLI_AUTH_DIR:
|
|
2147
|
+
WORKFLOW_CLI_AUTH_DIR: path5.join(storageDir, "runtime-auth")
|
|
1505
2148
|
};
|
|
1506
2149
|
for (const key of [
|
|
1507
2150
|
"TSX_TSCONFIG_PATH",
|
|
@@ -1590,6 +2233,7 @@ function createWebRuntime(projectDir, storageDir, connection) {
|
|
|
1590
2233
|
});
|
|
1591
2234
|
return {
|
|
1592
2235
|
available: true,
|
|
2236
|
+
kanbanBridge: (project, call) => invoke({ method: "kanban-bridge", project, call }),
|
|
1593
2237
|
inspect: (paths) => invoke({ method: "inspect", paths }),
|
|
1594
2238
|
resolveParams: (project, request, entrypointId) => invoke({ method: "resolve", project, request, entrypointId }),
|
|
1595
2239
|
run: (request, onEvent) => invoke({ method: "run", ...request }, onEvent),
|
|
@@ -1708,8 +2352,8 @@ async function inspectWebProject(runtime, projectDir, host) {
|
|
|
1708
2352
|
|
|
1709
2353
|
// src/web/server.ts
|
|
1710
2354
|
async function startWebServer(options) {
|
|
1711
|
-
const projectDir = await
|
|
1712
|
-
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"));
|
|
1713
2357
|
const clients = /* @__PURE__ */ new Set();
|
|
1714
2358
|
let connection;
|
|
1715
2359
|
let account = { status: "signed-out", serverUrl: "http://localhost:7126", revision: 0 };
|
|
@@ -1739,26 +2383,29 @@ async function startWebServer(options) {
|
|
|
1739
2383
|
if (!options.runtime) {
|
|
1740
2384
|
assertLocalCliCoreCompatible(projectDir, descriptor.runtime.coreVersion);
|
|
1741
2385
|
}
|
|
1742
|
-
if (!["conversation", "workflow"].includes(descriptor.projectType)) {
|
|
2386
|
+
if (!["conversation", "workflow", "kanban"].includes(descriptor.projectType)) {
|
|
1743
2387
|
runtime.dispose();
|
|
1744
|
-
throw new Error("
|
|
2388
|
+
throw new Error("Unsupported Workflow project type.");
|
|
1745
2389
|
}
|
|
1746
2390
|
const project = descriptor;
|
|
1747
|
-
const key =
|
|
1748
|
-
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");
|
|
1749
2393
|
let saved = {};
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
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
|
+
}
|
|
1756
2402
|
}
|
|
1757
2403
|
}
|
|
1758
2404
|
let pendingSave = Promise.resolve();
|
|
1759
2405
|
let broadcastTimer;
|
|
1760
2406
|
let authRevision = 0;
|
|
1761
2407
|
const isWorkflow = project.projectType === "workflow";
|
|
2408
|
+
const isKanban = project.projectType === "kanban";
|
|
1762
2409
|
const parameterStates = /* @__PURE__ */ new Map();
|
|
1763
2410
|
const parameterEpochs = /* @__PURE__ */ new Map();
|
|
1764
2411
|
const publicFile = (file) => ({ ...file, previewUrl: `/api/file/${encodeURIComponent(file.id)}` });
|
|
@@ -1778,7 +2425,7 @@ async function startWebServer(options) {
|
|
|
1778
2425
|
const state = () => publicValue(redactWorkflowData({
|
|
1779
2426
|
type: "state",
|
|
1780
2427
|
project,
|
|
1781
|
-
...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 ?? {} },
|
|
1782
2429
|
account,
|
|
1783
2430
|
authenticationRevision: authRevision
|
|
1784
2431
|
}, [...sensitiveTokens]));
|
|
@@ -1796,27 +2443,27 @@ async function startWebServer(options) {
|
|
|
1796
2443
|
}
|
|
1797
2444
|
}, 30);
|
|
1798
2445
|
};
|
|
1799
|
-
const conversationController = isWorkflow ? void 0 : new ConversationController(project, runtime, saved, {
|
|
2446
|
+
const conversationController = isWorkflow || isKanban ? void 0 : new ConversationController(project, runtime, saved, {
|
|
1800
2447
|
changed: broadcast,
|
|
1801
2448
|
log: (message) => process.stderr.write(`${message}
|
|
1802
2449
|
`),
|
|
1803
2450
|
save: (sessions, activeId) => {
|
|
1804
2451
|
const content = JSON.stringify({ sessions, activeId });
|
|
1805
2452
|
pendingSave = pendingSave.catch(() => void 0).then(async () => {
|
|
1806
|
-
await
|
|
2453
|
+
await mkdir2(path6.dirname(historyPath), { recursive: true });
|
|
1807
2454
|
const temp = `${historyPath}.${process.pid}.tmp`;
|
|
1808
|
-
await
|
|
1809
|
-
await
|
|
2455
|
+
await writeFile2(temp, content, { mode: 384 });
|
|
2456
|
+
await rename2(temp, historyPath);
|
|
1810
2457
|
});
|
|
1811
2458
|
return pendingSave;
|
|
1812
2459
|
}
|
|
1813
2460
|
});
|
|
1814
2461
|
const controller = conversationController;
|
|
1815
|
-
const runsPath =
|
|
2462
|
+
const runsPath = path6.join(storageDir, key, "runs.json");
|
|
1816
2463
|
let workspace;
|
|
1817
2464
|
if (isWorkflow) {
|
|
1818
2465
|
try {
|
|
1819
|
-
workspace = JSON.parse(await
|
|
2466
|
+
workspace = JSON.parse(await readFile5(runsPath, "utf8"));
|
|
1820
2467
|
if (workspace?.version !== 1 || !Array.isArray(workspace.runs) || !workspace.drafts || typeof workspace.drafts !== "object") {
|
|
1821
2468
|
throw new Error("Invalid Workflow history.");
|
|
1822
2469
|
}
|
|
@@ -1836,15 +2483,32 @@ async function startWebServer(options) {
|
|
|
1836
2483
|
save: (workspace2) => {
|
|
1837
2484
|
const content = JSON.stringify(workspace2);
|
|
1838
2485
|
pendingSave = pendingSave.catch(() => void 0).then(async () => {
|
|
1839
|
-
await
|
|
2486
|
+
await mkdir2(path6.dirname(runsPath), { recursive: true });
|
|
1840
2487
|
const temp = `${runsPath}.${process.pid}.tmp`;
|
|
1841
|
-
await
|
|
1842
|
-
await
|
|
2488
|
+
await writeFile2(temp, content, { mode: 384 });
|
|
2489
|
+
await rename2(temp, runsPath);
|
|
1843
2490
|
});
|
|
1844
2491
|
return pendingSave;
|
|
1845
2492
|
}
|
|
1846
2493
|
}) : void 0;
|
|
1847
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
|
+
}
|
|
1848
2512
|
if (workflowController) {
|
|
1849
2513
|
await workflowController.persist();
|
|
1850
2514
|
}
|
|
@@ -1887,6 +2551,7 @@ ${token}`;
|
|
|
1887
2551
|
authRevision++;
|
|
1888
2552
|
parameterStates.clear();
|
|
1889
2553
|
workflowActions?.invalidate();
|
|
2554
|
+
kanbanController?.authenticationChanged();
|
|
1890
2555
|
if (authenticationInitialized) {
|
|
1891
2556
|
await stopForAuthentication();
|
|
1892
2557
|
}
|
|
@@ -1912,6 +2577,7 @@ ${token}`;
|
|
|
1912
2577
|
workflowActions?.invalidate();
|
|
1913
2578
|
}
|
|
1914
2579
|
connection = candidate;
|
|
2580
|
+
kanbanController?.authenticationChanged();
|
|
1915
2581
|
const savedConnection = !hostManaged && candidate.serverUrl === config.serverUrl.replace(/\/+$/, "") && candidate.accessToken === config.apiKey;
|
|
1916
2582
|
account = { ...account, status: "authenticated", user: profile, expiresAt: result.data.principal?.expiresAt ?? (savedConnection ? config.expiresAt : void 0) };
|
|
1917
2583
|
} catch (error) {
|
|
@@ -1919,6 +2585,7 @@ ${token}`;
|
|
|
1919
2585
|
return;
|
|
1920
2586
|
}
|
|
1921
2587
|
connection = void 0;
|
|
2588
|
+
kanbanController?.authenticationChanged();
|
|
1922
2589
|
if (!changed) {
|
|
1923
2590
|
authRevision++;
|
|
1924
2591
|
parameterStates.clear();
|
|
@@ -1936,8 +2603,8 @@ ${token}`;
|
|
|
1936
2603
|
const authPoll = setInterval(() => void refreshAuth().catch((error) => process.stderr.write(`${String(error)}
|
|
1937
2604
|
`)), 5e3);
|
|
1938
2605
|
authPoll.unref();
|
|
1939
|
-
const capability =
|
|
1940
|
-
const cookie = `workflow_web_${
|
|
2606
|
+
const capability = randomBytes2(32).toString("hex");
|
|
2607
|
+
const cookie = `workflow_web_${randomBytes2(8).toString("hex")}`;
|
|
1941
2608
|
let origin = "";
|
|
1942
2609
|
const json = (response, status, value) => {
|
|
1943
2610
|
response.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
|
@@ -1985,6 +2652,9 @@ ${token}`;
|
|
|
1985
2652
|
if (message.projectUri && message.projectUri !== project.uri) {
|
|
1986
2653
|
throw new Error("Project changed.");
|
|
1987
2654
|
}
|
|
2655
|
+
if (kanbanController && !["ready", "login", "verify", "logout", "usage"].includes(message.type)) {
|
|
2656
|
+
return kanbanController.handle(message);
|
|
2657
|
+
}
|
|
1988
2658
|
if (workflowActions && !["ready", "login", "verify", "logout", "usage"].includes(message.type)) {
|
|
1989
2659
|
return publicValue(await workflowActions.handle(message));
|
|
1990
2660
|
}
|
|
@@ -2153,13 +2823,13 @@ ${token}`;
|
|
|
2153
2823
|
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
2154
2824
|
replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, current.id, card.kind, card.target) });
|
|
2155
2825
|
} else if (card.kind === "file") {
|
|
2156
|
-
const root = await
|
|
2157
|
-
const filename = await
|
|
2158
|
-
const relative =
|
|
2159
|
-
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)) {
|
|
2160
2830
|
throw new Error("File is outside this project.");
|
|
2161
2831
|
}
|
|
2162
|
-
replies.push({ type: "preview", title: card.title, content: await
|
|
2832
|
+
replies.push({ type: "preview", title: card.title, content: await readFile5(filename, "utf8") });
|
|
2163
2833
|
} else {
|
|
2164
2834
|
throw new Error("This resource requires an editor host.");
|
|
2165
2835
|
}
|
|
@@ -2216,7 +2886,7 @@ ${token}`;
|
|
|
2216
2886
|
await refreshAuth(true);
|
|
2217
2887
|
break;
|
|
2218
2888
|
}
|
|
2219
|
-
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"] });
|
|
2220
2890
|
login.stderr?.on("data", (chunk) => process.stderr.write(chunk));
|
|
2221
2891
|
login.once("error", (error) => {
|
|
2222
2892
|
login = void 0;
|
|
@@ -2254,11 +2924,22 @@ ${token}`;
|
|
|
2254
2924
|
response.setHeader("Referrer-Policy", "no-referrer");
|
|
2255
2925
|
const host = new URL(`http://${request.headers.host ?? ""}`);
|
|
2256
2926
|
const forwarded = !!options.host && ["127.0.0.1", "localhost", "[::1]"].includes(host.hostname) && !host.username && !host.password && host.pathname === "/" && !host.search && !host.hash;
|
|
2257
|
-
|
|
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) {
|
|
2258
2930
|
json(response, 403, { message: "Invalid request origin." });
|
|
2259
2931
|
return;
|
|
2260
2932
|
}
|
|
2261
|
-
|
|
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
|
+
}
|
|
2262
2943
|
if (url.pathname === "/api/session" && request.method === "POST") {
|
|
2263
2944
|
if (!authMatches(String(request.headers.authorization ?? "").replace(/^Bearer /, ""))) {
|
|
2264
2945
|
json(response, 401, { message: "Invalid session." });
|
|
@@ -2285,6 +2966,7 @@ ${token}`;
|
|
|
2285
2966
|
return;
|
|
2286
2967
|
}
|
|
2287
2968
|
if (url.pathname === "/api/events" && request.method === "GET") {
|
|
2969
|
+
const disconnectKanban = kanbanController?.methods.connect(url.searchParams.get("clientId") ?? "");
|
|
2288
2970
|
response.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
|
|
2289
2971
|
response.write(`data: ${JSON.stringify(state())}
|
|
2290
2972
|
|
|
@@ -2294,6 +2976,7 @@ ${token}`;
|
|
|
2294
2976
|
response.once("close", () => {
|
|
2295
2977
|
clearInterval(heartbeat);
|
|
2296
2978
|
clients.delete(response);
|
|
2979
|
+
disconnectKanban?.();
|
|
2297
2980
|
});
|
|
2298
2981
|
return;
|
|
2299
2982
|
}
|
|
@@ -2303,8 +2986,9 @@ ${token}`;
|
|
|
2303
2986
|
const operation = () => handleMessage(message).then((value2) => {
|
|
2304
2987
|
messages = value2;
|
|
2305
2988
|
});
|
|
2306
|
-
const
|
|
2307
|
-
|
|
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) {
|
|
2308
2992
|
mutations = task;
|
|
2309
2993
|
}
|
|
2310
2994
|
await task;
|
|
@@ -2313,7 +2997,7 @@ ${token}`;
|
|
|
2313
2997
|
}
|
|
2314
2998
|
if (url.pathname.startsWith("/api/file/") && request.method === "GET") {
|
|
2315
2999
|
const file = await runtime.readFile(project, decodeURIComponent(url.pathname.slice("/api/file/".length)));
|
|
2316
|
-
const bytes = await
|
|
3000
|
+
const bytes = await readFile5(file.path);
|
|
2317
3001
|
const safeType = /^(?:image\/(?:png|jpeg|gif|webp|avif)|audio\/[a-z0-9.+-]+|video\/[a-z0-9.+-]+)$/.test(file.reference.mimeType);
|
|
2318
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" });
|
|
2319
3003
|
response.end(bytes);
|
|
@@ -2333,19 +3017,21 @@ ${token}`;
|
|
|
2333
3017
|
}
|
|
2334
3018
|
if (url.pathname === "/") {
|
|
2335
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:";
|
|
2336
|
-
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'` });
|
|
2337
|
-
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";
|
|
2338
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>`);
|
|
2339
3023
|
return;
|
|
2340
3024
|
}
|
|
2341
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" };
|
|
2342
3026
|
assets["/assets/workflow-workspace.js"] = "application/javascript";
|
|
2343
3027
|
assets["/assets/workflow-workspace.css"] = "text/css";
|
|
3028
|
+
assets["/assets/kanban-workspace.js"] = "application/javascript";
|
|
3029
|
+
assets["/assets/kanban-workspace.css"] = "text/css";
|
|
2344
3030
|
if (!assets[url.pathname]) {
|
|
2345
3031
|
json(response, 404, { message: "Not found." });
|
|
2346
3032
|
return;
|
|
2347
3033
|
}
|
|
2348
|
-
const content = await
|
|
3034
|
+
const content = await readFile5(path6.join(cliRoot(), "dist/web", url.pathname.slice("/assets/".length)));
|
|
2349
3035
|
response.writeHead(200, { "Content-Type": assets[url.pathname], "Cache-Control": "no-cache" });
|
|
2350
3036
|
response.end(content);
|
|
2351
3037
|
})().catch((error) => {
|
|
@@ -2368,6 +3054,7 @@ ${token}`;
|
|
|
2368
3054
|
clearInterval(authPoll);
|
|
2369
3055
|
conversationController?.dispose();
|
|
2370
3056
|
workflowController?.dispose();
|
|
3057
|
+
await kanbanController?.close();
|
|
2371
3058
|
runtime.dispose();
|
|
2372
3059
|
throw error;
|
|
2373
3060
|
}
|
|
@@ -2379,6 +3066,7 @@ ${token}`;
|
|
|
2379
3066
|
project,
|
|
2380
3067
|
controller,
|
|
2381
3068
|
workflowController,
|
|
3069
|
+
kanbanController,
|
|
2382
3070
|
setHostConnection: async (value) => {
|
|
2383
3071
|
authEpoch++;
|
|
2384
3072
|
authCheck?.abort();
|
|
@@ -2398,6 +3086,7 @@ ${token}`;
|
|
|
2398
3086
|
conversationController?.dispose();
|
|
2399
3087
|
workflowController?.dispose();
|
|
2400
3088
|
runtime.dispose();
|
|
3089
|
+
await kanbanController?.close();
|
|
2401
3090
|
for (const client of clients) {
|
|
2402
3091
|
client.end();
|
|
2403
3092
|
}
|
|
@@ -2415,7 +3104,7 @@ ${token}`;
|
|
|
2415
3104
|
import { createInterface } from "readline";
|
|
2416
3105
|
|
|
2417
3106
|
// src/web/host-config.ts
|
|
2418
|
-
import
|
|
3107
|
+
import path7 from "path";
|
|
2419
3108
|
function parseHostConnection(value) {
|
|
2420
3109
|
if (value === null || value === void 0) {
|
|
2421
3110
|
return;
|
|
@@ -2439,11 +3128,11 @@ function parseHostConfiguration(value) {
|
|
|
2439
3128
|
throw new Error("Invalid web host configuration.");
|
|
2440
3129
|
}
|
|
2441
3130
|
const assistant = configuration.assistant;
|
|
2442
|
-
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))) {
|
|
2443
3132
|
throw new Error("Invalid assistant workspace.");
|
|
2444
3133
|
}
|
|
2445
3134
|
const assistantProjectDir = configuration.assistantProjectDir;
|
|
2446
|
-
if (configuration.surface === "assistant" && (!assistant || typeof assistantProjectDir !== "string" || !
|
|
3135
|
+
if (configuration.surface === "assistant" && (!assistant || typeof assistantProjectDir !== "string" || !path7.isAbsolute(assistantProjectDir))) {
|
|
2447
3136
|
throw new Error("A dedicated assistant project and its workspace are required.");
|
|
2448
3137
|
}
|
|
2449
3138
|
if (configuration.surface !== "assistant" && (assistant || assistantProjectDir !== void 0)) {
|
|
@@ -2484,12 +3173,12 @@ function parseWebArgs(argv) {
|
|
|
2484
3173
|
}
|
|
2485
3174
|
options.theme = value;
|
|
2486
3175
|
} else {
|
|
2487
|
-
options.dataDir =
|
|
3176
|
+
options.dataDir = path8.resolve(value);
|
|
2488
3177
|
}
|
|
2489
3178
|
} else if (arg.startsWith("-") || projectSet) {
|
|
2490
3179
|
throw new Error(`Unknown web argument: ${arg}`);
|
|
2491
3180
|
} else {
|
|
2492
|
-
options.projectDir =
|
|
3181
|
+
options.projectDir = path8.resolve(arg);
|
|
2493
3182
|
projectSet = true;
|
|
2494
3183
|
}
|
|
2495
3184
|
}
|