@workflow-code/cli 0.2.4-20261012005.codex-studio-integration → 0.2.4-20261012007.codex-studio-integration
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/index.js +1 -1
- package/dist/web/kanban-workspace.css +76 -10
- package/dist/web/kanban-workspace.js +201 -10
- package/dist/web/resource-preview.css +2172 -0
- package/dist/web/resource-preview.js +16515 -0
- package/dist/web/worker.mjs +3 -3
- package/dist/web/workflow-ui.css +76 -10
- package/dist/web/workflow-ui.js +337 -31
- package/dist/web/workflow-workspace.css +76 -10
- package/dist/web/workflow-workspace.js +329 -29
- package/dist/{web-6SWHMU2A.js → web-ROA37F4H.js} +514 -167
- package/package.json +3 -3
|
@@ -100,6 +100,8 @@ var messages_zh_cn_default = {
|
|
|
100
100
|
"Configuration name": "\u914D\u7F6E\u540D\u79F0",
|
|
101
101
|
"Connecting to project": "\u6B63\u5728\u8FDE\u63A5\u9879\u76EE",
|
|
102
102
|
"Connection changed.": "\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002",
|
|
103
|
+
"Copy timed out.": "\u590D\u5236\u8D85\u65F6\u3002",
|
|
104
|
+
"Host clipboard is unavailable.": "\u5BBF\u4E3B\u526A\u8D34\u677F\u4E0D\u53EF\u7528\u3002",
|
|
103
105
|
"Connection interrupted": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD",
|
|
104
106
|
"Connection interrupted.": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002",
|
|
105
107
|
"Connection not verified": "\u8FDE\u63A5\u672A\u9A8C\u8BC1",
|
|
@@ -340,7 +342,33 @@ var messages_zh_cn_default = {
|
|
|
340
342
|
"Write file": "\u5199\u5165\u6587\u4EF6",
|
|
341
343
|
Yes: "\u662F",
|
|
342
344
|
"Zoom in": "\u653E\u5927",
|
|
343
|
-
"Zoom out": "\u7F29\u5C0F"
|
|
345
|
+
"Zoom out": "\u7F29\u5C0F",
|
|
346
|
+
"Open resource": "\u6253\u5F00\u8D44\u6E90",
|
|
347
|
+
"Show in conversation": "\u5728\u5BF9\u8BDD\u4E2D\u5C55\u793A",
|
|
348
|
+
"Hide preview": "\u6536\u8D77\u9884\u89C8",
|
|
349
|
+
"Open in new window": "\u5728\u65B0\u7A97\u53E3\u4E2D\u6253\u5F00",
|
|
350
|
+
"Tool call details": "\u5DE5\u5177\u8C03\u7528\u8BE6\u60C5",
|
|
351
|
+
"Please sign in to preview this server resource.": "\u8BF7\u767B\u5F55\u540E\u9884\u89C8\u670D\u52A1\u5668\u8D44\u6E90\u3002",
|
|
352
|
+
"You do not have access to this resource.": "\u4F60\u6CA1\u6709\u6B64\u8D44\u6E90\u7684\u8BBF\u95EE\u6743\u9650\u3002",
|
|
353
|
+
"The resource project is unavailable.": "\u8D44\u6E90\u6240\u5C5E\u9879\u76EE\u4E0D\u53EF\u7528\u3002",
|
|
354
|
+
"The resource project is unavailable. Import or open the target project first.": "\u8D44\u6E90\u6240\u5C5E\u9879\u76EE\u4E0D\u53EF\u7528\uFF0C\u8BF7\u5148\u5BFC\u5165\u6216\u6253\u5F00\u76EE\u6807\u9879\u76EE\u3002",
|
|
355
|
+
"The project does not allow this storage location.": "\u9879\u76EE\u4E0D\u5141\u8BB8\u4F7F\u7528\u6B64\u5B58\u50A8\u4F4D\u7F6E\u3002",
|
|
356
|
+
"This resource no longer exists.": "\u6B64\u8D44\u6E90\u5DF2\u4E0D\u5B58\u5728\u3002",
|
|
357
|
+
"Server source files cannot be previewed by this card.": "\u6B64\u5361\u7247\u4E0D\u652F\u6301\u9884\u89C8\u670D\u52A1\u5668\u6E90\u7801\u6587\u4EF6\u3002",
|
|
358
|
+
"File is outside this project.": "\u6587\u4EF6\u4E0D\u5728\u6B64\u9879\u76EE\u4E2D\u3002",
|
|
359
|
+
"File preview exceeds 2 MiB.": "\u6587\u4EF6\u9884\u89C8\u5927\u5C0F\u8D85\u8FC7 2 MiB\u3002",
|
|
360
|
+
"This project does not provide a Kanban preview.": "\u6B64\u9879\u76EE\u4E0D\u63D0\u4F9B\u770B\u677F\u9884\u89C8\u3002",
|
|
361
|
+
"Invalid resource preview URL.": "\u8D44\u6E90\u9884\u89C8\u5730\u5740\u65E0\u6548\u3002",
|
|
362
|
+
"This preview configuration no longer exists.": "\u6B64\u9884\u89C8\u914D\u7F6E\u5DF2\u4E0D\u5B58\u5728\u3002",
|
|
363
|
+
"Resource access changed. Open the card again.": "\u8D44\u6E90\u8BBF\u95EE\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5361\u7247\u3002",
|
|
364
|
+
"This resource preview is read-only.": "\u6B64\u8D44\u6E90\u9884\u89C8\u4E3A\u53EA\u8BFB\u3002",
|
|
365
|
+
"Invalid resource action.": "\u8D44\u6E90\u64CD\u4F5C\u65E0\u6548\u3002",
|
|
366
|
+
"Run no longer exists.": "\u6B64\u8FD0\u884C\u5DF2\u4E0D\u5B58\u5728\u3002",
|
|
367
|
+
"Conversation changed.": "\u4F1A\u8BDD\u5DF2\u53D8\u5316\u3002",
|
|
368
|
+
"Unable to load this preview. Retry or open it separately.": "\u65E0\u6CD5\u52A0\u8F7D\u6B64\u9884\u89C8\uFF0C\u8BF7\u91CD\u8BD5\u6216\u5355\u72EC\u6253\u5F00\u3002",
|
|
369
|
+
"Open in browser": "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00",
|
|
370
|
+
"No resources": "\u6682\u65E0\u8D44\u6E90",
|
|
371
|
+
"Allow popups to open this resource in a new window.": "\u8BF7\u5141\u8BB8\u5F39\u51FA\u7A97\u53E3\u4EE5\u5355\u72EC\u6253\u5F00\u6B64\u8D44\u6E90\u3002"
|
|
344
372
|
};
|
|
345
373
|
|
|
346
374
|
// src/web/localization.ts
|
|
@@ -2143,115 +2171,7 @@ var KanbanController = class {
|
|
|
2143
2171
|
}
|
|
2144
2172
|
};
|
|
2145
2173
|
|
|
2146
|
-
// src/web/resource-cards.ts
|
|
2147
|
-
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2148
|
-
var string = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2149
|
-
function conversationResourceCards(items, projectId, conversationId) {
|
|
2150
|
-
const cards = /* @__PURE__ */ new Map();
|
|
2151
|
-
for (const [index, item] of items.entries()) {
|
|
2152
|
-
if (!["agent-part", "opencode-part"].includes(String(item.metadata?.kind)) || item.metadata?.partType !== "tool") {
|
|
2153
|
-
continue;
|
|
2154
|
-
}
|
|
2155
|
-
const content = record(item.content);
|
|
2156
|
-
if (content?.status !== "success") {
|
|
2157
|
-
continue;
|
|
2158
|
-
}
|
|
2159
|
-
let output = record(content.output);
|
|
2160
|
-
const details = record(output?.details);
|
|
2161
|
-
if (Array.isArray(output?.content) && output.content.length === 1) {
|
|
2162
|
-
try {
|
|
2163
|
-
output = record(JSON.parse(String(record(output.content[0])?.text)));
|
|
2164
|
-
} catch {
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
let descriptor = record(details?.resourceCard) ?? record(record(output?.details)?.resourceCard);
|
|
2168
|
-
const input = record(content.input) ?? {};
|
|
2169
|
-
const tool = string(content.tool) ?? "";
|
|
2170
|
-
if (tool === "workflow-ui_present_card") {
|
|
2171
|
-
const presentation = output?.action ? output : input;
|
|
2172
|
-
if (presentation.action === "remove") {
|
|
2173
|
-
cards.delete(String(presentation.cardId));
|
|
2174
|
-
continue;
|
|
2175
|
-
}
|
|
2176
|
-
if (presentation.action !== "upsert") {
|
|
2177
|
-
continue;
|
|
2178
|
-
}
|
|
2179
|
-
descriptor = presentation;
|
|
2180
|
-
}
|
|
2181
|
-
if (!descriptor) {
|
|
2182
|
-
const target2 = { source: "local", projectId, conversationId };
|
|
2183
|
-
let kind2;
|
|
2184
|
-
if (tool === "workflow-kv_kv_store" || /workflow-desktop_(?:read|list|write)_project_kv/.test(tool)) {
|
|
2185
|
-
kind2 = "kv";
|
|
2186
|
-
Object.assign(target2, { projectId: input.projectId ?? projectId, key: input.key, scope: input.scope ?? "project", conversationId: input.conversationId ?? conversationId });
|
|
2187
|
-
} else if (tool === "workflow-knowledge_knowledge_documents" || /workflow-desktop_(?:query|mutate)_project_knowledge/.test(tool)) {
|
|
2188
|
-
kind2 = "knowledge";
|
|
2189
|
-
Object.assign(target2, { projectId: input.projectId ?? projectId, documentId: input.documentId ?? record(output?.document)?.id });
|
|
2190
|
-
} else if (tool === "read" || tool === "workflow-desktop_open_dock_tool" && input.tool === "files") {
|
|
2191
|
-
kind2 = "file";
|
|
2192
|
-
target2.filePath = input.filePath ?? input.path;
|
|
2193
|
-
} else if (tool === "workflow-desktop_open_project" && input.kind === "project") {
|
|
2194
|
-
kind2 = "project";
|
|
2195
|
-
target2.projectId = input.targetId;
|
|
2196
|
-
} else if (tool === "workflow-desktop_open_dock_tool") {
|
|
2197
|
-
if (input.tool === "browser") {
|
|
2198
|
-
kind2 = "browser";
|
|
2199
|
-
target2.url = input.url;
|
|
2200
|
-
}
|
|
2201
|
-
if (input.tool === "preview") {
|
|
2202
|
-
kind2 = "kanban";
|
|
2203
|
-
}
|
|
2204
|
-
if (input.tool === "open-project" && input.openedProjectKind === "project") {
|
|
2205
|
-
kind2 = "project";
|
|
2206
|
-
target2.projectId = input.openedProjectId;
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
if (!kind2) {
|
|
2210
|
-
continue;
|
|
2211
|
-
}
|
|
2212
|
-
descriptor = { kind: kind2, target: target2, title: record(output?.document)?.title };
|
|
2213
|
-
}
|
|
2214
|
-
const kind = descriptor.kind;
|
|
2215
|
-
if (!["kv", "knowledge", "project", "browser", "file", "kanban"].includes(kind)) {
|
|
2216
|
-
continue;
|
|
2217
|
-
}
|
|
2218
|
-
const target = Object.fromEntries(Object.entries(record(descriptor.target) ?? {}).filter((entry) => typeof entry[1] === "string" && entry[1].length < 8192));
|
|
2219
|
-
if (target.source === "current") {
|
|
2220
|
-
target.source = "local";
|
|
2221
|
-
target.projectId = projectId;
|
|
2222
|
-
}
|
|
2223
|
-
if (target.source && !["local", "server"].includes(target.source)) {
|
|
2224
|
-
continue;
|
|
2225
|
-
}
|
|
2226
|
-
if (kind === "browser") {
|
|
2227
|
-
try {
|
|
2228
|
-
const url = new URL(target.url);
|
|
2229
|
-
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) {
|
|
2230
|
-
continue;
|
|
2231
|
-
}
|
|
2232
|
-
} catch {
|
|
2233
|
-
continue;
|
|
2234
|
-
}
|
|
2235
|
-
} else {
|
|
2236
|
-
target.source ??= "local";
|
|
2237
|
-
target.projectId ??= projectId;
|
|
2238
|
-
if (target.scope === "conversation") {
|
|
2239
|
-
target.conversationId ??= conversationId;
|
|
2240
|
-
}
|
|
2241
|
-
if (kind === "file" && (!target.filePath || /^(?:[\\/]|[a-z]:)|(?:^|[\\/])\.\.(?:[\\/]|$)/i.test(target.filePath))) {
|
|
2242
|
-
continue;
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
const id = string(descriptor.cardId) ?? `${kind}:${JSON.stringify(target)}`;
|
|
2246
|
-
const title = (string(descriptor.title) ?? target.key ?? target.documentId ?? target.filePath ?? target.url ?? target.projectId ?? kind).slice(0, 200);
|
|
2247
|
-
cards.set(id, { id, kind, title, target, itemId: item.id ?? String(index) });
|
|
2248
|
-
}
|
|
2249
|
-
return [...cards.values()];
|
|
2250
|
-
}
|
|
2251
|
-
|
|
2252
2174
|
// src/web/workflow-actions.ts
|
|
2253
|
-
import { readFile as readFile4, realpath as realpath3 } from "fs/promises";
|
|
2254
|
-
import path4 from "path";
|
|
2255
2175
|
function createWorkflowActions(controller, runtime, project, revision, changed) {
|
|
2256
2176
|
const states = /* @__PURE__ */ new Map();
|
|
2257
2177
|
const epochs = /* @__PURE__ */ new Map();
|
|
@@ -2436,28 +2356,6 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
2436
2356
|
replies.push({ type: "attachments", entrypointId: id, runId: message.runId, requestId: message.requestId, paramName: message.paramName, files });
|
|
2437
2357
|
break;
|
|
2438
2358
|
}
|
|
2439
|
-
case "resource": {
|
|
2440
|
-
const run = controller.runs.find((record2) => record2.id === message.runId);
|
|
2441
|
-
if (!run) {
|
|
2442
|
-
throw new Error("Run no longer exists.");
|
|
2443
|
-
}
|
|
2444
|
-
const card = conversationResourceCards(run.items, project.id, run.scopeId).find((card2) => card2.id === message.cardId);
|
|
2445
|
-
if (!card || card.target.projectId && card.target.projectId !== project.id || card.target.source === "server") {
|
|
2446
|
-
throw new Error("Resource is outside this project.");
|
|
2447
|
-
}
|
|
2448
|
-
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
2449
|
-
replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, run.scopeId, card.kind, card.target) });
|
|
2450
|
-
} else if (card.kind === "file") {
|
|
2451
|
-
const root = await realpath3(project.folderPath), filename = await realpath3(path4.resolve(root, card.target.filePath)), relative = path4.relative(root, filename);
|
|
2452
|
-
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
2453
|
-
throw new Error("File is outside this project.");
|
|
2454
|
-
}
|
|
2455
|
-
replies.push({ type: "preview", title: card.title, content: await readFile4(filename, "utf8") });
|
|
2456
|
-
} else {
|
|
2457
|
-
throw new Error("This resource requires an editor host.");
|
|
2458
|
-
}
|
|
2459
|
-
break;
|
|
2460
|
-
}
|
|
2461
2359
|
default:
|
|
2462
2360
|
throw new Error(`Unsupported workflow action: ${message.type}`);
|
|
2463
2361
|
}
|
|
@@ -2469,26 +2367,26 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
2469
2367
|
// src/web/runtime.ts
|
|
2470
2368
|
import { spawn } from "child_process";
|
|
2471
2369
|
import { existsSync, readFileSync } from "fs";
|
|
2472
|
-
import
|
|
2370
|
+
import path4 from "path";
|
|
2473
2371
|
import { fileURLToPath } from "url";
|
|
2474
2372
|
import { WORKFLOW_RUNTIME_LOCALE_ENV } from "workflow-code";
|
|
2475
2373
|
function cliRoot() {
|
|
2476
|
-
let directory =
|
|
2374
|
+
let directory = path4.dirname(fileURLToPath(import.meta.url));
|
|
2477
2375
|
for (let depth = 0; depth < 8; depth++) {
|
|
2478
2376
|
try {
|
|
2479
|
-
if (JSON.parse(readFileSync(
|
|
2377
|
+
if (JSON.parse(readFileSync(path4.join(directory, "package.json"), "utf8")).name === "@workflow-code/cli") {
|
|
2480
2378
|
return directory;
|
|
2481
2379
|
}
|
|
2482
2380
|
} catch {
|
|
2483
2381
|
}
|
|
2484
|
-
directory =
|
|
2382
|
+
directory = path4.dirname(directory);
|
|
2485
2383
|
}
|
|
2486
2384
|
throw new Error("Cannot locate the CLI installation.");
|
|
2487
2385
|
}
|
|
2488
2386
|
function createWebRuntime(projectDir, storageDir, connection, relatedProjects = () => []) {
|
|
2489
2387
|
const root = cliRoot();
|
|
2490
|
-
const builtWorker =
|
|
2491
|
-
const worker = existsSync(builtWorker) ? builtWorker :
|
|
2388
|
+
const builtWorker = path4.join(root, "dist/web/worker.mjs");
|
|
2389
|
+
const worker = existsSync(builtWorker) ? builtWorker : path4.join(root, "src/web/worker.mjs");
|
|
2492
2390
|
const children = /* @__PURE__ */ new Set();
|
|
2493
2391
|
const active = /* @__PURE__ */ new Map();
|
|
2494
2392
|
let disposed = false;
|
|
@@ -2512,7 +2410,7 @@ function createWebRuntime(projectDir, storageDir, connection, relatedProjects =
|
|
|
2512
2410
|
WORKFLOW_SERVER_TOKEN: "",
|
|
2513
2411
|
WORKFLOW_LLM_SERVER_URL: auth?.serverUrl ?? "",
|
|
2514
2412
|
WORKFLOW_LLM_SERVER_TOKEN: auth?.accessToken ?? "",
|
|
2515
|
-
WORKFLOW_CLI_AUTH_DIR:
|
|
2413
|
+
WORKFLOW_CLI_AUTH_DIR: path4.join(storageDir, "runtime-auth")
|
|
2516
2414
|
};
|
|
2517
2415
|
for (const key of [
|
|
2518
2416
|
"TSX_TSCONFIG_PATH",
|
|
@@ -2635,6 +2533,125 @@ function createWebRuntime(projectDir, storageDir, connection, relatedProjects =
|
|
|
2635
2533
|
};
|
|
2636
2534
|
}
|
|
2637
2535
|
|
|
2536
|
+
// src/web/resource-cards.ts
|
|
2537
|
+
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2538
|
+
var string = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2539
|
+
function conversationResourceCards(items, projectId, conversationId, storageSource = "local") {
|
|
2540
|
+
const cards = /* @__PURE__ */ new Map();
|
|
2541
|
+
for (const [index, item] of items.entries()) {
|
|
2542
|
+
if (!["agent-part", "opencode-part"].includes(String(item.metadata?.kind)) || item.metadata?.partType !== "tool") {
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
const content = record(item.content);
|
|
2546
|
+
if (content?.status !== "success") {
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
let output = record(content.output);
|
|
2550
|
+
const details = record(output?.details);
|
|
2551
|
+
if (Array.isArray(output?.content) && output.content.length === 1) {
|
|
2552
|
+
try {
|
|
2553
|
+
output = record(JSON.parse(String(record(output.content[0])?.text)));
|
|
2554
|
+
} catch {
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
let descriptor = record(details?.resourceCard) ?? record(record(output?.details)?.resourceCard);
|
|
2558
|
+
const input = record(content.input) ?? {};
|
|
2559
|
+
const tool = string(content.tool) ?? "";
|
|
2560
|
+
if (tool === "workflow-ui_present_card") {
|
|
2561
|
+
const presentation = output?.action ? output : input;
|
|
2562
|
+
if (presentation.action === "remove") {
|
|
2563
|
+
cards.delete(String(presentation.cardId));
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
if (presentation.action !== "upsert") {
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
descriptor = presentation;
|
|
2570
|
+
}
|
|
2571
|
+
if (!descriptor) {
|
|
2572
|
+
const target2 = { source: "local", projectId, conversationId };
|
|
2573
|
+
let kind2;
|
|
2574
|
+
if (tool === "workflow-kv_kv_store" || /workflow-desktop_(?:read|list|write)_project_kv/.test(tool)) {
|
|
2575
|
+
kind2 = "kv";
|
|
2576
|
+
Object.assign(target2, { projectId: input.projectId ?? projectId, key: input.key, scope: input.scope ?? "project", conversationId: input.conversationId ?? conversationId });
|
|
2577
|
+
} else if (tool === "workflow-knowledge_knowledge_documents" || /workflow-desktop_(?:query|mutate)_project_knowledge/.test(tool)) {
|
|
2578
|
+
kind2 = "knowledge";
|
|
2579
|
+
Object.assign(target2, { projectId: input.projectId ?? projectId, documentId: input.documentId ?? record(output?.document)?.id });
|
|
2580
|
+
} else if (tool === "read" || tool === "workflow-desktop_open_dock_tool" && input.tool === "files") {
|
|
2581
|
+
kind2 = "file";
|
|
2582
|
+
target2.filePath = input.filePath ?? input.path;
|
|
2583
|
+
} else if (tool === "workflow-desktop_open_project" && input.kind === "project") {
|
|
2584
|
+
kind2 = "project";
|
|
2585
|
+
target2.projectId = input.targetId;
|
|
2586
|
+
} else if (tool === "workflow-desktop_open_dock_tool") {
|
|
2587
|
+
if (input.tool === "browser") {
|
|
2588
|
+
kind2 = "browser";
|
|
2589
|
+
target2.url = input.url;
|
|
2590
|
+
}
|
|
2591
|
+
if (input.tool === "preview") {
|
|
2592
|
+
kind2 = "kanban";
|
|
2593
|
+
}
|
|
2594
|
+
if (input.tool === "open-project" && input.openedProjectKind === "project") {
|
|
2595
|
+
kind2 = "project";
|
|
2596
|
+
target2.projectId = input.openedProjectId;
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
if (!kind2) {
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
2602
|
+
if (kind2 === "kv" || kind2 === "knowledge") {
|
|
2603
|
+
target2.source = input.source ?? storageSource;
|
|
2604
|
+
}
|
|
2605
|
+
descriptor = { kind: kind2, target: target2, title: record(output?.document)?.title };
|
|
2606
|
+
}
|
|
2607
|
+
const kind = descriptor.kind;
|
|
2608
|
+
if (!["kv", "knowledge", "project", "browser", "file", "kanban"].includes(kind)) {
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
const target = Object.fromEntries(Object.entries(record(descriptor.target) ?? {}).filter((entry) => typeof entry[1] === "string" && entry[1].length < 8192));
|
|
2612
|
+
if (target.source === "current") {
|
|
2613
|
+
target.source = kind === "kv" || kind === "knowledge" ? storageSource : "local";
|
|
2614
|
+
target.projectId = projectId;
|
|
2615
|
+
}
|
|
2616
|
+
if (target.source && !["local", "server"].includes(target.source)) {
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
if (kind === "browser") {
|
|
2620
|
+
try {
|
|
2621
|
+
const url = new URL(target.url);
|
|
2622
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) {
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
} catch {
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
} else {
|
|
2629
|
+
target.source ??= "local";
|
|
2630
|
+
target.projectId ??= projectId;
|
|
2631
|
+
if (target.scope === "conversation") {
|
|
2632
|
+
target.conversationId ??= conversationId;
|
|
2633
|
+
}
|
|
2634
|
+
if (kind === "file" && (!target.filePath || /^(?:[\\/]|[a-z]:)|(?:^|[\\/])\.\.(?:[\\/]|$)/i.test(target.filePath))) {
|
|
2635
|
+
continue;
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
const id = string(descriptor.cardId) ?? `${kind}:${JSON.stringify(target)}`;
|
|
2639
|
+
const title = (string(descriptor.title) ?? string(record(output?.document)?.title) ?? target.key ?? target.documentId ?? target.filePath ?? target.url ?? target.projectId ?? kind).slice(0, 200);
|
|
2640
|
+
const kanban = record(descriptor.kanban), values = record(kanban?.values);
|
|
2641
|
+
cards.set(id, {
|
|
2642
|
+
id,
|
|
2643
|
+
kind,
|
|
2644
|
+
title,
|
|
2645
|
+
target,
|
|
2646
|
+
itemId: item.id ?? String(index),
|
|
2647
|
+
...typeof descriptor.preview === "boolean" ? { preview: descriptor.preview } : {},
|
|
2648
|
+
...typeof descriptor.pinned === "boolean" ? { pinned: descriptor.pinned } : {},
|
|
2649
|
+
...kind === "kanban" && ["preview", "save"].includes(String(kanban?.mode)) && values && Object.keys(values).length <= 100 && Object.values(values).every((value) => typeof value === "string") ? { kanban: { mode: kanban.mode, values } } : {}
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
return [...cards.values()];
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2638
2655
|
// src/web/account-auth.ts
|
|
2639
2656
|
async function loginWebAccount(serverUrl, email, password) {
|
|
2640
2657
|
if (!email.trim() || !password) {
|
|
@@ -2718,6 +2735,268 @@ async function inspectWebProject(runtime, projectDir, host) {
|
|
|
2718
2735
|
};
|
|
2719
2736
|
}
|
|
2720
2737
|
|
|
2738
|
+
// src/web/resource-preview.ts
|
|
2739
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2740
|
+
import { readFile as readFile4, realpath as realpath3, stat as stat3 } from "fs/promises";
|
|
2741
|
+
import path5 from "path";
|
|
2742
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2743
|
+
var ResourcePreviews = class {
|
|
2744
|
+
constructor(owner, runtime, storageDir, connection, related, resourceProjects = () => []) {
|
|
2745
|
+
this.owner = owner;
|
|
2746
|
+
this.runtime = runtime;
|
|
2747
|
+
this.storageDir = storageDir;
|
|
2748
|
+
this.connection = connection;
|
|
2749
|
+
this.related = related;
|
|
2750
|
+
this.resourceProjects = resourceProjects;
|
|
2751
|
+
}
|
|
2752
|
+
owner;
|
|
2753
|
+
runtime;
|
|
2754
|
+
storageDir;
|
|
2755
|
+
connection;
|
|
2756
|
+
related;
|
|
2757
|
+
resourceProjects;
|
|
2758
|
+
entries = /* @__PURE__ */ new Map();
|
|
2759
|
+
generation = 0;
|
|
2760
|
+
clear() {
|
|
2761
|
+
this.generation++;
|
|
2762
|
+
this.entries.clear();
|
|
2763
|
+
}
|
|
2764
|
+
async server(id, suffix, init = {}) {
|
|
2765
|
+
const connection = this.connection();
|
|
2766
|
+
if (!connection) {
|
|
2767
|
+
throw new Error(t("Please sign in to preview this server resource."));
|
|
2768
|
+
}
|
|
2769
|
+
const response = await fetch(`${connection.serverUrl}/api/workflows/${encodeURIComponent(id)}${suffix}`, {
|
|
2770
|
+
...init,
|
|
2771
|
+
headers: { Authorization: `Bearer ${connection.accessToken}`, "Content-Type": "application/json" },
|
|
2772
|
+
signal: AbortSignal.timeout(3e4),
|
|
2773
|
+
redirect: "error"
|
|
2774
|
+
});
|
|
2775
|
+
const result = await response.json();
|
|
2776
|
+
if (!response.ok || result.errCode !== 0) {
|
|
2777
|
+
throw new Error(result.errMessage || t("You do not have access to this resource."));
|
|
2778
|
+
}
|
|
2779
|
+
return result.data;
|
|
2780
|
+
}
|
|
2781
|
+
async resolve(card) {
|
|
2782
|
+
if (card.kind === "browser") {
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
const id = card.target.projectId ?? this.owner.id;
|
|
2786
|
+
if (card.target.source === "server") {
|
|
2787
|
+
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
2788
|
+
return { id, uri: `workflow-server:${id}`, folderPath: "", name: id, projectType: "workflow", fields: [], entrypoints: [] };
|
|
2789
|
+
}
|
|
2790
|
+
const snapshot = await this.server(id, "/structure"), structure = snapshot.structure;
|
|
2791
|
+
return {
|
|
2792
|
+
...structure,
|
|
2793
|
+
id,
|
|
2794
|
+
uri: `workflow-server:${id}`,
|
|
2795
|
+
folderPath: "",
|
|
2796
|
+
name: snapshot.workflowName ?? card.title,
|
|
2797
|
+
projectType: structure.projectType,
|
|
2798
|
+
fields: structure.params ?? [],
|
|
2799
|
+
entrypoints: structure.entrypoints ?? []
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
if (id === this.owner.id) {
|
|
2803
|
+
return this.owner;
|
|
2804
|
+
}
|
|
2805
|
+
for (const directory of /* @__PURE__ */ new Set([...this.related().map((binding) => binding.projectDir), ...this.resourceProjects()])) {
|
|
2806
|
+
let root, pkg;
|
|
2807
|
+
try {
|
|
2808
|
+
root = await realpath3(directory);
|
|
2809
|
+
pkg = JSON.parse(await readFile4(path5.join(root, "package.json"), "utf8"));
|
|
2810
|
+
} catch {
|
|
2811
|
+
continue;
|
|
2812
|
+
}
|
|
2813
|
+
if (String(pkg.id).toLowerCase() !== id.toLowerCase()) {
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
const inspected = await this.runtime.inspect([root]);
|
|
2817
|
+
const project = inspected.projects.find((project2) => project2.id.toLowerCase() === id.toLowerCase());
|
|
2818
|
+
if (!project) {
|
|
2819
|
+
throw new Error(t("The resource project is unavailable."));
|
|
2820
|
+
}
|
|
2821
|
+
return { ...project, uri: pathToFileURL2(root).href };
|
|
2822
|
+
}
|
|
2823
|
+
throw new Error(t("The resource project is unavailable. Import or open the target project first."));
|
|
2824
|
+
}
|
|
2825
|
+
async open(card) {
|
|
2826
|
+
const generation = this.generation;
|
|
2827
|
+
const project = await this.resolve(card);
|
|
2828
|
+
const id = randomUUID3(), data = { card, project }, entry = { data };
|
|
2829
|
+
const target = card.target, scopeId = target.conversationId ?? "";
|
|
2830
|
+
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
2831
|
+
if (target.source === "server") {
|
|
2832
|
+
const query = new URLSearchParams({ scope: target.scope ?? "project" });
|
|
2833
|
+
if (target.conversationId) {
|
|
2834
|
+
query.set("conversationId", target.conversationId);
|
|
2835
|
+
}
|
|
2836
|
+
if (target.key) {
|
|
2837
|
+
query.set("key", target.key);
|
|
2838
|
+
}
|
|
2839
|
+
data.content = await this.server(project.id, card.kind === "knowledge" ? `/knowledge-documents${target.documentId ? `/${encodeURIComponent(target.documentId)}` : ""}` : `/kv${target.key ? "/entry" : ""}?${query}`);
|
|
2840
|
+
} else {
|
|
2841
|
+
const info = readWorkflowProjectInfo(JSON.parse(await readFile4(path5.join(project.folderPath, "package.json"), "utf8")));
|
|
2842
|
+
if (info.dataStorage && !isWorkflowProjectDataStorageLocationAllowed(info, "local")) {
|
|
2843
|
+
throw new Error(t("The project does not allow this storage location."));
|
|
2844
|
+
}
|
|
2845
|
+
data.content = await this.runtime.readResource(project, scopeId, card.kind, target);
|
|
2846
|
+
}
|
|
2847
|
+
if (card.kind === "knowledge" && (data.content === null || data.content === void 0)) {
|
|
2848
|
+
throw new Error(t("This resource no longer exists."));
|
|
2849
|
+
}
|
|
2850
|
+
data.format = card.kind === "knowledge" && typeof data.content.markdown === "string" ? "markdown" : "json";
|
|
2851
|
+
} else if (card.kind === "file") {
|
|
2852
|
+
if (target.source === "server") {
|
|
2853
|
+
throw new Error(t("Server source files cannot be previewed by this card."));
|
|
2854
|
+
}
|
|
2855
|
+
const root = await realpath3(project.assistant?.workdir ?? project.folderPath);
|
|
2856
|
+
const filename = await realpath3(path5.resolve(root, target.filePath));
|
|
2857
|
+
const relative = path5.relative(root, filename);
|
|
2858
|
+
if (relative === ".." || relative.startsWith(`..${path5.sep}`) || path5.isAbsolute(relative)) {
|
|
2859
|
+
throw new Error(t("File is outside this project."));
|
|
2860
|
+
}
|
|
2861
|
+
if (!(await stat3(filename)).isFile() || (await stat3(filename)).size > 2 * 1024 * 1024) {
|
|
2862
|
+
throw new Error(t("File preview exceeds 2 MiB."));
|
|
2863
|
+
}
|
|
2864
|
+
data.content = await readFile4(filename, "utf8");
|
|
2865
|
+
data.format = /\.mdx?$/i.test(filename) ? "markdown" : "text";
|
|
2866
|
+
} else if (card.kind === "browser") {
|
|
2867
|
+
data.format = "browser";
|
|
2868
|
+
data.content = target.url;
|
|
2869
|
+
} else if (card.kind === "kanban" || project?.projectType === "kanban" && card.preview !== false) {
|
|
2870
|
+
if (project?.projectType !== "kanban") {
|
|
2871
|
+
throw new Error(t("This project does not provide a Kanban preview."));
|
|
2872
|
+
}
|
|
2873
|
+
let params, configurations, activeId;
|
|
2874
|
+
if (target.source === "server") {
|
|
2875
|
+
const workspace = await this.server(project.id, "/kanban");
|
|
2876
|
+
params = workspace.manifest?.params ?? [];
|
|
2877
|
+
configurations = workspace.configurations;
|
|
2878
|
+
activeId = workspace.activeConfigurationId;
|
|
2879
|
+
const connection = this.connection(), url = new URL(workspace.preview.url, `${connection.serverUrl}/`);
|
|
2880
|
+
const expected = `/api/workflows/${encodeURIComponent(project.id)}/kanban/previews/`;
|
|
2881
|
+
if (url.origin !== new URL(connection.serverUrl).origin || !url.pathname.startsWith(expected) || url.search || url.hash) {
|
|
2882
|
+
throw new Error(t("Invalid resource preview URL."));
|
|
2883
|
+
}
|
|
2884
|
+
const segments = url.pathname.slice(expected.length).split("/");
|
|
2885
|
+
const assetPrefix = `${expected}${segments.slice(0, 3).join("/")}/`;
|
|
2886
|
+
entry.remote = { url, prefix: assetPrefix, connection };
|
|
2887
|
+
data.kanban = { url: `/resource-assets/${id}/${url.pathname.slice(assetPrefix.length)}`, nonce: workspace.preview.nonce, configuration: { id: "", name: "", params: {} } };
|
|
2888
|
+
} else {
|
|
2889
|
+
params = project.fields;
|
|
2890
|
+
try {
|
|
2891
|
+
params = parseKanbanManifestText(await readFile4(path5.join(project.folderPath, "kanban.json"), "utf8")).params;
|
|
2892
|
+
} catch (error) {
|
|
2893
|
+
if (error.code !== "ENOENT") {
|
|
2894
|
+
throw error;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
let workspace = {};
|
|
2898
|
+
try {
|
|
2899
|
+
workspace = JSON.parse(await readFile4(path5.join(webProjectDataDirectory(this.storageDir, project.folderPath), "kanban.json"), "utf8"));
|
|
2900
|
+
} catch (error) {
|
|
2901
|
+
if (error.code !== "ENOENT") {
|
|
2902
|
+
throw error;
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
configurations = workspace.configurations ?? [];
|
|
2906
|
+
activeId = workspace.activeConfigurationId;
|
|
2907
|
+
}
|
|
2908
|
+
const selected = configurations.find((item) => item.id === (target.configurationId ?? activeId));
|
|
2909
|
+
if (target.configurationId && !selected) {
|
|
2910
|
+
throw new Error(t("This preview configuration no longer exists."));
|
|
2911
|
+
}
|
|
2912
|
+
const values = sanitizeKanbanValues(params, { ...selected?.values ?? createDefaultKanbanValues(params), ...card.kanban?.mode === "preview" ? card.kanban.values : {} });
|
|
2913
|
+
const configuration = { id: selected?.id ?? id, name: selected?.name ?? card.title, params: createInjectedKanbanParams(params, values) };
|
|
2914
|
+
if (!entry.remote) {
|
|
2915
|
+
if (!project.kanban) {
|
|
2916
|
+
throw new Error(t("This project does not provide a Kanban preview."));
|
|
2917
|
+
}
|
|
2918
|
+
entry.local = new KanbanPreview(project.folderPath, project.kanban, () => configuration);
|
|
2919
|
+
data.kanban = { url: entry.local.url, nonce: entry.local.nonce, configuration };
|
|
2920
|
+
} else {
|
|
2921
|
+
data.kanban.configuration = configuration;
|
|
2922
|
+
}
|
|
2923
|
+
data.format = "kanban";
|
|
2924
|
+
} else {
|
|
2925
|
+
data.format = "project";
|
|
2926
|
+
data.content = project;
|
|
2927
|
+
}
|
|
2928
|
+
if (generation !== this.generation) {
|
|
2929
|
+
throw new Error(t("Resource access changed. Open the card again."));
|
|
2930
|
+
}
|
|
2931
|
+
if (this.entries.size >= 64) {
|
|
2932
|
+
this.entries.delete(this.entries.keys().next().value);
|
|
2933
|
+
}
|
|
2934
|
+
this.entries.set(id, entry);
|
|
2935
|
+
return { id, data, url: `/resource/${id}?workflowLocale=${locale()}` };
|
|
2936
|
+
}
|
|
2937
|
+
get(id) {
|
|
2938
|
+
const entry = this.entries.get(id);
|
|
2939
|
+
if (!entry) {
|
|
2940
|
+
throw new Error(t("Resource access changed. Open the card again."));
|
|
2941
|
+
}
|
|
2942
|
+
return entry.data;
|
|
2943
|
+
}
|
|
2944
|
+
async asset(url) {
|
|
2945
|
+
if (url.pathname.startsWith("/kanban/")) {
|
|
2946
|
+
for (const entry of this.entries.values()) {
|
|
2947
|
+
if (entry.local && url.pathname.startsWith(`/kanban/${entry.local.nonce}/`)) {
|
|
2948
|
+
return entry.local.asset(url);
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
const match = /^\/resource-assets\/([^/]+)\/(.+)$/.exec(url.pathname);
|
|
2954
|
+
const remote = match && this.entries.get(match[1])?.remote;
|
|
2955
|
+
if (!remote || remote.connection !== this.connection()) {
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
const asset = decodeURIComponent(match[2]);
|
|
2959
|
+
if (asset.includes("\\") || asset.split("/").some((part) => part === ".." || !part) || !/\.[a-z\d]+$/i.test(asset)) {
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
const address = new URL(`${remote.prefix}${match[2]}`, remote.url.origin);
|
|
2963
|
+
const response = await fetch(address, { headers: { Authorization: `Bearer ${remote.connection.accessToken}` }, redirect: "error", signal: AbortSignal.timeout(3e4) });
|
|
2964
|
+
if (!response.ok) {
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
return { content: Buffer.from(await response.arrayBuffer()), headers: {
|
|
2968
|
+
"Content-Type": response.headers.get("content-type") ?? "application/octet-stream",
|
|
2969
|
+
"Content-Security-Policy": `${response.headers.get("content-security-policy") ?? "default-src 'none'"}; frame-ancestors 'self' vscode-webview: vscode-file: http://127.0.0.1:* http://localhost:*`,
|
|
2970
|
+
"Cache-Control": "no-store",
|
|
2971
|
+
"Access-Control-Allow-Origin": "*",
|
|
2972
|
+
"X-Content-Type-Options": "nosniff"
|
|
2973
|
+
} };
|
|
2974
|
+
}
|
|
2975
|
+
async bridge(id, value) {
|
|
2976
|
+
const data = this.get(id), project = data.project, call = parseKanbanBridgeCall(value);
|
|
2977
|
+
if (data.format !== "kanban" || !project) {
|
|
2978
|
+
throw new Error(t("This project does not provide a Kanban preview."));
|
|
2979
|
+
}
|
|
2980
|
+
if (!["runtime.getLocale", "kv.getValue", "relatedProject.kv.getValue", "knowledge.listDocuments", "knowledge.getDocument", "knowledge.searchDocuments", "knowledge.readDocumentLines", "files.readFile"].includes(call.operation)) {
|
|
2981
|
+
throw new Error(t("This resource preview is read-only."));
|
|
2982
|
+
}
|
|
2983
|
+
if (call.operation === "runtime.getLocale") {
|
|
2984
|
+
return locale();
|
|
2985
|
+
}
|
|
2986
|
+
if (data.card.target.source === "server" || project.projectInfo?.dataStorage?.mode === "server") {
|
|
2987
|
+
return this.server(project.id, "/kanban/bridge", { method: "POST", body: JSON.stringify({ call }) });
|
|
2988
|
+
}
|
|
2989
|
+
if (call.operation === "files.readFile") {
|
|
2990
|
+
const file = await this.runtime.readFile(project, String(call.input.fileId));
|
|
2991
|
+
return { file: file.reference, base64: (await readFile4(file.path)).toString("base64") };
|
|
2992
|
+
}
|
|
2993
|
+
if (!this.runtime.kanbanBridge) {
|
|
2994
|
+
throw new Error(t("The resource project is unavailable."));
|
|
2995
|
+
}
|
|
2996
|
+
return this.runtime.kanbanBridge(project, call);
|
|
2997
|
+
}
|
|
2998
|
+
};
|
|
2999
|
+
|
|
2721
3000
|
// src/web/server.ts
|
|
2722
3001
|
async function startWebServer(options) {
|
|
2723
3002
|
const projectDir = await realpath4(path6.resolve(options.projectDir));
|
|
@@ -2736,6 +3015,7 @@ async function startWebServer(options) {
|
|
|
2736
3015
|
let authCheck;
|
|
2737
3016
|
const sensitiveTokens = /* @__PURE__ */ new Set();
|
|
2738
3017
|
let relatedProjects = options.host?.relatedProjects ?? [];
|
|
3018
|
+
let resourceProjects = options.host?.resourceProjects ?? [];
|
|
2739
3019
|
const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection, () => relatedProjects);
|
|
2740
3020
|
let inspected;
|
|
2741
3021
|
try {
|
|
@@ -2757,6 +3037,7 @@ async function startWebServer(options) {
|
|
|
2757
3037
|
throw new Error("Unsupported Workflow project type.");
|
|
2758
3038
|
}
|
|
2759
3039
|
const project = descriptor;
|
|
3040
|
+
const resources = new ResourcePreviews(project, runtime, storageDir, () => connection, () => relatedProjects, () => resourceProjects);
|
|
2760
3041
|
const key = createHash3("sha256").update(project.assistant ? `assistant:${project.id}:${project.assistant.workdir}` : projectDir).digest("hex").slice(0, 24);
|
|
2761
3042
|
const historyPath = path6.join(storageDir, key, "conversations.json");
|
|
2762
3043
|
let saved = {};
|
|
@@ -2916,6 +3197,7 @@ ${token}`;
|
|
|
2916
3197
|
authIdentity = identity;
|
|
2917
3198
|
const candidate = !signedOut && serverUrl && token ? { serverUrl: serverUrl.replace(/\/+$/, ""), accessToken: token } : void 0;
|
|
2918
3199
|
if (changed) {
|
|
3200
|
+
resources.clear();
|
|
2919
3201
|
connection = void 0;
|
|
2920
3202
|
authRevision++;
|
|
2921
3203
|
parameterStates.clear();
|
|
@@ -2957,6 +3239,7 @@ ${token}`;
|
|
|
2957
3239
|
return;
|
|
2958
3240
|
}
|
|
2959
3241
|
connection = void 0;
|
|
3242
|
+
resources.clear();
|
|
2960
3243
|
kanbanController?.authenticationChanged();
|
|
2961
3244
|
if (!changed) {
|
|
2962
3245
|
authRevision++;
|
|
@@ -3027,6 +3310,62 @@ ${token}`;
|
|
|
3027
3310
|
if (message.projectUri && message.projectUri !== project.uri) {
|
|
3028
3311
|
throw new Error("Project changed.");
|
|
3029
3312
|
}
|
|
3313
|
+
if (message.type === "resource-data") {
|
|
3314
|
+
return [{ type: "resource-data", requestId: message.requestId, data: resources.get(String(message.id)) }];
|
|
3315
|
+
}
|
|
3316
|
+
if (message.type === "resource-refresh" || message.type === "resource-select") {
|
|
3317
|
+
const card = resources.get(String(message.id)).card;
|
|
3318
|
+
const selection = message.type === "resource-select" ? message.selection : void 0;
|
|
3319
|
+
const target = { ...card.target };
|
|
3320
|
+
if (selection && ["kv", "knowledge"].includes(card.kind)) {
|
|
3321
|
+
for (const key2 of card.kind === "knowledge" ? ["documentId"] : ["key", "scope", "conversationId"]) {
|
|
3322
|
+
if (typeof selection[key2] === "string" && selection[key2].length <= 8192) {
|
|
3323
|
+
target[key2] = selection[key2];
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
const preview = await resources.open({ ...card, target });
|
|
3328
|
+
return [{ type: "resource-data", requestId: message.requestId, id: preview.id, data: preview.data }];
|
|
3329
|
+
}
|
|
3330
|
+
if (message.type === "resource-bridge") {
|
|
3331
|
+
return [{ type: "resource-bridge", requestId: message.requestId, value: await resources.bridge(String(message.id), message.call) }];
|
|
3332
|
+
}
|
|
3333
|
+
if (message.type === "resource") {
|
|
3334
|
+
if (!["open", "inline", "window", "project", void 0].includes(message.action)) {
|
|
3335
|
+
throw new Error(t("Invalid resource action."));
|
|
3336
|
+
}
|
|
3337
|
+
let items, scopeId;
|
|
3338
|
+
if (workflowController) {
|
|
3339
|
+
const run = workflowController.runs.find((run2) => run2.id === message.runId);
|
|
3340
|
+
if (!run) {
|
|
3341
|
+
throw new Error(t("Run no longer exists."));
|
|
3342
|
+
}
|
|
3343
|
+
items = run.items;
|
|
3344
|
+
scopeId = run.scopeId;
|
|
3345
|
+
} else {
|
|
3346
|
+
const current2 = conversationController?.current;
|
|
3347
|
+
if (!current2 || message.sessionId && message.sessionId !== current2.id) {
|
|
3348
|
+
throw new Error(t("Conversation changed."));
|
|
3349
|
+
}
|
|
3350
|
+
items = current2.messages.find((answer) => answer.id === message.messageId)?.items ?? [];
|
|
3351
|
+
scopeId = current2.id;
|
|
3352
|
+
}
|
|
3353
|
+
const card = conversationResourceCards(items, project.id, scopeId, project.projectInfo?.dataStorage?.mode === "server" ? "server" : "local").find((card2) => card2.id === message.cardId);
|
|
3354
|
+
if (!card) {
|
|
3355
|
+
throw new Error(t("This resource no longer exists."));
|
|
3356
|
+
}
|
|
3357
|
+
const preview = await resources.open(card);
|
|
3358
|
+
return [{
|
|
3359
|
+
type: "resource-preview",
|
|
3360
|
+
requestId: message.requestId,
|
|
3361
|
+
cardId: card.id,
|
|
3362
|
+
action: message.action ?? "open",
|
|
3363
|
+
id: preview.id,
|
|
3364
|
+
url: preview.url,
|
|
3365
|
+
card,
|
|
3366
|
+
project: preview.data.project
|
|
3367
|
+
}];
|
|
3368
|
+
}
|
|
3030
3369
|
if (kanbanController && !["ready", "login", "verify", "logout", "usage"].includes(message.type)) {
|
|
3031
3370
|
return kanbanController.handle(message);
|
|
3032
3371
|
}
|
|
@@ -3189,27 +3528,6 @@ ${token}`;
|
|
|
3189
3528
|
}
|
|
3190
3529
|
break;
|
|
3191
3530
|
}
|
|
3192
|
-
case "resource": {
|
|
3193
|
-
const answer = current.messages.find((entry) => entry.id === message.messageId);
|
|
3194
|
-
const card = conversationResourceCards(answer?.items ?? [], project.id, current.id).find((entry) => entry.id === message.cardId);
|
|
3195
|
-
if (!card || card.target.projectId && card.target.projectId !== project.id || card.target.source === "server") {
|
|
3196
|
-
throw new Error("Resource is outside this project.");
|
|
3197
|
-
}
|
|
3198
|
-
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
3199
|
-
replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, current.id, card.kind, card.target) });
|
|
3200
|
-
} else if (card.kind === "file") {
|
|
3201
|
-
const root = await realpath4(project.assistant?.workdir ?? projectDir);
|
|
3202
|
-
const filename = await realpath4(path6.resolve(root, card.target.filePath));
|
|
3203
|
-
const relative = path6.relative(root, filename);
|
|
3204
|
-
if (relative === ".." || relative.startsWith(`..${path6.sep}`) || path6.isAbsolute(relative)) {
|
|
3205
|
-
throw new Error("File is outside this project.");
|
|
3206
|
-
}
|
|
3207
|
-
replies.push({ type: "preview", title: card.title, content: await readFile5(filename, "utf8") });
|
|
3208
|
-
} else {
|
|
3209
|
-
throw new Error("This resource requires an editor host.");
|
|
3210
|
-
}
|
|
3211
|
-
break;
|
|
3212
|
-
}
|
|
3213
3531
|
case "usage": {
|
|
3214
3532
|
if (!connection) {
|
|
3215
3533
|
throw new Error("Workflow Server login is not configured.");
|
|
@@ -3307,13 +3625,14 @@ ${token}`;
|
|
|
3307
3625
|
const host = new URL(`http://${request.headers.host ?? ""}`);
|
|
3308
3626
|
const forwarded = !!options.host && ["127.0.0.1", "localhost", "[::1]"].includes(host.hostname) && !host.username && !host.password && host.pathname === "/" && !host.search && !host.hash;
|
|
3309
3627
|
const url = new URL(request.url ?? "/", origin);
|
|
3310
|
-
const
|
|
3628
|
+
const resourceAsset = url.pathname.startsWith("/kanban/") || url.pathname.startsWith("/resource-assets/");
|
|
3629
|
+
const sandboxAsset = resourceAsset && ["GET", "HEAD"].includes(request.method ?? "") && request.headers.origin === "null";
|
|
3311
3630
|
if (request.headers.host !== new URL(origin).host && !forwarded || request.headers.origin && request.headers.origin !== (forwarded ? host.origin : origin) && !sandboxAsset) {
|
|
3312
3631
|
json(response, 403, { message: "Invalid request origin." });
|
|
3313
3632
|
return;
|
|
3314
3633
|
}
|
|
3315
|
-
if (
|
|
3316
|
-
const asset = await kanbanController
|
|
3634
|
+
if (resourceAsset && ["GET", "HEAD"].includes(request.method ?? "")) {
|
|
3635
|
+
const asset = await kanbanController?.preview.asset(url) ?? await resources.asset(url);
|
|
3317
3636
|
if (!asset) {
|
|
3318
3637
|
json(response, 404, { message: "Kanban asset not found." });
|
|
3319
3638
|
return;
|
|
@@ -3347,6 +3666,10 @@ ${token}`;
|
|
|
3347
3666
|
json(response, 200, { connected: true });
|
|
3348
3667
|
return;
|
|
3349
3668
|
}
|
|
3669
|
+
if (url.pathname === "/api/resource-state" && request.method === "GET") {
|
|
3670
|
+
json(response, 200, { type: "state", authenticationRevision: authRevision });
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3350
3673
|
if (url.pathname === "/api/events" && request.method === "GET") {
|
|
3351
3674
|
const disconnectKanban = kanbanController?.methods.connect(url.searchParams.get("clientId") ?? "");
|
|
3352
3675
|
response.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
|
|
@@ -3368,7 +3691,7 @@ ${token}`;
|
|
|
3368
3691
|
const operation = () => handleMessage(message).then((value2) => {
|
|
3369
3692
|
messages = value2;
|
|
3370
3693
|
});
|
|
3371
|
-
const concurrent = ["resolve", "ready", "kanban-bridge", "kanban-method-state", "kanban-method-reply", "kanban-methods"].includes(message.type);
|
|
3694
|
+
const concurrent = ["resolve", "ready", "resource", "resource-data", "resource-refresh", "resource-select", "resource-bridge", "kanban-bridge", "kanban-method-state", "kanban-method-reply", "kanban-methods"].includes(message.type);
|
|
3372
3695
|
const task = concurrent ? operation() : mutations.catch(() => void 0).then(operation);
|
|
3373
3696
|
if (!concurrent) {
|
|
3374
3697
|
mutations = task;
|
|
@@ -3397,10 +3720,10 @@ ${token}`;
|
|
|
3397
3720
|
response.end();
|
|
3398
3721
|
return;
|
|
3399
3722
|
}
|
|
3400
|
-
if (url.pathname === "/") {
|
|
3723
|
+
if (url.pathname === "/" || /^\/resource\/[a-f\d-]{36}$/.test(url.pathname)) {
|
|
3401
3724
|
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:";
|
|
3402
|
-
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'
|
|
3403
|
-
const ui = isKanban ? "kanban-workspace" : isWorkflow ? "workflow-workspace" : "workflow-ui";
|
|
3725
|
+
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' https: http:; object-src 'none'; base-uri 'none'; frame-ancestors ${ancestors}; form-action 'self'` });
|
|
3726
|
+
const ui = url.pathname !== "/" ? "resource-preview" : isKanban ? "kanban-workspace" : isWorkflow ? "workflow-workspace" : "workflow-ui";
|
|
3404
3727
|
response.end(`<!doctype html><html lang="${locale()}" 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"}" data-account-host="${hostManaged}"><div id="app"></div><script src="/assets/${ui}.js" defer></script></body></html>`);
|
|
3405
3728
|
return;
|
|
3406
3729
|
}
|
|
@@ -3409,6 +3732,8 @@ ${token}`;
|
|
|
3409
3732
|
assets["/assets/workflow-workspace.css"] = "text/css";
|
|
3410
3733
|
assets["/assets/kanban-workspace.js"] = "application/javascript";
|
|
3411
3734
|
assets["/assets/kanban-workspace.css"] = "text/css";
|
|
3735
|
+
assets["/assets/resource-preview.js"] = "application/javascript";
|
|
3736
|
+
assets["/assets/resource-preview.css"] = "text/css";
|
|
3412
3737
|
if (!assets[url.pathname]) {
|
|
3413
3738
|
json(response, 404, { message: "Not found." });
|
|
3414
3739
|
return;
|
|
@@ -3451,10 +3776,19 @@ ${token}`;
|
|
|
3451
3776
|
kanbanController,
|
|
3452
3777
|
setHostRelatedProjects: (value) => {
|
|
3453
3778
|
relatedProjects = value;
|
|
3779
|
+
resources.clear();
|
|
3780
|
+
},
|
|
3781
|
+
setHostResourceProjects: (value) => {
|
|
3782
|
+
resourceProjects = value;
|
|
3783
|
+
resources.clear();
|
|
3454
3784
|
},
|
|
3455
3785
|
setHostConnection: async (value) => {
|
|
3786
|
+
if (hostManaged && JSON.stringify(value) === JSON.stringify(hostConnection)) {
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3456
3789
|
authEpoch++;
|
|
3457
3790
|
authCheck?.abort();
|
|
3791
|
+
resources.clear();
|
|
3458
3792
|
hostConnection = value;
|
|
3459
3793
|
hostManaged = true;
|
|
3460
3794
|
signedOut = !value;
|
|
@@ -3490,6 +3824,15 @@ import { createInterface } from "readline";
|
|
|
3490
3824
|
|
|
3491
3825
|
// src/web/host-config.ts
|
|
3492
3826
|
import path7 from "path";
|
|
3827
|
+
function parseHostResourceProjects(value) {
|
|
3828
|
+
if (value === void 0) {
|
|
3829
|
+
return [];
|
|
3830
|
+
}
|
|
3831
|
+
if (!Array.isArray(value) || value.length > 100 || value.some((directory) => typeof directory !== "string" || !path7.isAbsolute(directory))) {
|
|
3832
|
+
throw new Error("Invalid host resource projects.");
|
|
3833
|
+
}
|
|
3834
|
+
return [...new Set(value)];
|
|
3835
|
+
}
|
|
3493
3836
|
function parseHostRelatedProjects(value) {
|
|
3494
3837
|
if (value === void 0) {
|
|
3495
3838
|
return [];
|
|
@@ -3545,7 +3888,8 @@ function parseHostConfiguration(value) {
|
|
|
3545
3888
|
assistant,
|
|
3546
3889
|
assistantProjectDir,
|
|
3547
3890
|
connection: parseHostConnection(configuration.connection),
|
|
3548
|
-
relatedProjects: parseHostRelatedProjects(configuration.relatedProjects)
|
|
3891
|
+
relatedProjects: parseHostRelatedProjects(configuration.relatedProjects),
|
|
3892
|
+
...configuration.resourceProjects === void 0 ? {} : { resourceProjects: parseHostResourceProjects(configuration.resourceProjects) }
|
|
3549
3893
|
};
|
|
3550
3894
|
}
|
|
3551
3895
|
|
|
@@ -3657,6 +4001,9 @@ async function main(argv) {
|
|
|
3657
4001
|
});
|
|
3658
4002
|
} else if (message.type === "related-projects") {
|
|
3659
4003
|
app.setHostRelatedProjects(parseHostRelatedProjects(message.relatedProjects));
|
|
4004
|
+
if (message.resourceProjects !== void 0) {
|
|
4005
|
+
app.setHostResourceProjects(parseHostResourceProjects(message.resourceProjects));
|
|
4006
|
+
}
|
|
3660
4007
|
} else if (message.type === "close") {
|
|
3661
4008
|
break;
|
|
3662
4009
|
} else {
|