@workflow-code/cli 0.2.4-20261012006.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 +119 -8
- 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 +251 -27
- package/dist/web/workflow-workspace.css +76 -10
- package/dist/web/workflow-workspace.js +247 -27
- package/dist/{web-FDREEISF.js → web-ROA37F4H.js} +512 -167
- package/package.json +3 -3
|
@@ -342,7 +342,33 @@ var messages_zh_cn_default = {
|
|
|
342
342
|
"Write file": "\u5199\u5165\u6587\u4EF6",
|
|
343
343
|
Yes: "\u662F",
|
|
344
344
|
"Zoom in": "\u653E\u5927",
|
|
345
|
-
"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"
|
|
346
372
|
};
|
|
347
373
|
|
|
348
374
|
// src/web/localization.ts
|
|
@@ -2145,115 +2171,7 @@ var KanbanController = class {
|
|
|
2145
2171
|
}
|
|
2146
2172
|
};
|
|
2147
2173
|
|
|
2148
|
-
// src/web/resource-cards.ts
|
|
2149
|
-
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2150
|
-
var string = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2151
|
-
function conversationResourceCards(items, projectId, conversationId) {
|
|
2152
|
-
const cards = /* @__PURE__ */ new Map();
|
|
2153
|
-
for (const [index, item] of items.entries()) {
|
|
2154
|
-
if (!["agent-part", "opencode-part"].includes(String(item.metadata?.kind)) || item.metadata?.partType !== "tool") {
|
|
2155
|
-
continue;
|
|
2156
|
-
}
|
|
2157
|
-
const content = record(item.content);
|
|
2158
|
-
if (content?.status !== "success") {
|
|
2159
|
-
continue;
|
|
2160
|
-
}
|
|
2161
|
-
let output = record(content.output);
|
|
2162
|
-
const details = record(output?.details);
|
|
2163
|
-
if (Array.isArray(output?.content) && output.content.length === 1) {
|
|
2164
|
-
try {
|
|
2165
|
-
output = record(JSON.parse(String(record(output.content[0])?.text)));
|
|
2166
|
-
} catch {
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
let descriptor = record(details?.resourceCard) ?? record(record(output?.details)?.resourceCard);
|
|
2170
|
-
const input = record(content.input) ?? {};
|
|
2171
|
-
const tool = string(content.tool) ?? "";
|
|
2172
|
-
if (tool === "workflow-ui_present_card") {
|
|
2173
|
-
const presentation = output?.action ? output : input;
|
|
2174
|
-
if (presentation.action === "remove") {
|
|
2175
|
-
cards.delete(String(presentation.cardId));
|
|
2176
|
-
continue;
|
|
2177
|
-
}
|
|
2178
|
-
if (presentation.action !== "upsert") {
|
|
2179
|
-
continue;
|
|
2180
|
-
}
|
|
2181
|
-
descriptor = presentation;
|
|
2182
|
-
}
|
|
2183
|
-
if (!descriptor) {
|
|
2184
|
-
const target2 = { source: "local", projectId, conversationId };
|
|
2185
|
-
let kind2;
|
|
2186
|
-
if (tool === "workflow-kv_kv_store" || /workflow-desktop_(?:read|list|write)_project_kv/.test(tool)) {
|
|
2187
|
-
kind2 = "kv";
|
|
2188
|
-
Object.assign(target2, { projectId: input.projectId ?? projectId, key: input.key, scope: input.scope ?? "project", conversationId: input.conversationId ?? conversationId });
|
|
2189
|
-
} else if (tool === "workflow-knowledge_knowledge_documents" || /workflow-desktop_(?:query|mutate)_project_knowledge/.test(tool)) {
|
|
2190
|
-
kind2 = "knowledge";
|
|
2191
|
-
Object.assign(target2, { projectId: input.projectId ?? projectId, documentId: input.documentId ?? record(output?.document)?.id });
|
|
2192
|
-
} else if (tool === "read" || tool === "workflow-desktop_open_dock_tool" && input.tool === "files") {
|
|
2193
|
-
kind2 = "file";
|
|
2194
|
-
target2.filePath = input.filePath ?? input.path;
|
|
2195
|
-
} else if (tool === "workflow-desktop_open_project" && input.kind === "project") {
|
|
2196
|
-
kind2 = "project";
|
|
2197
|
-
target2.projectId = input.targetId;
|
|
2198
|
-
} else if (tool === "workflow-desktop_open_dock_tool") {
|
|
2199
|
-
if (input.tool === "browser") {
|
|
2200
|
-
kind2 = "browser";
|
|
2201
|
-
target2.url = input.url;
|
|
2202
|
-
}
|
|
2203
|
-
if (input.tool === "preview") {
|
|
2204
|
-
kind2 = "kanban";
|
|
2205
|
-
}
|
|
2206
|
-
if (input.tool === "open-project" && input.openedProjectKind === "project") {
|
|
2207
|
-
kind2 = "project";
|
|
2208
|
-
target2.projectId = input.openedProjectId;
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
if (!kind2) {
|
|
2212
|
-
continue;
|
|
2213
|
-
}
|
|
2214
|
-
descriptor = { kind: kind2, target: target2, title: record(output?.document)?.title };
|
|
2215
|
-
}
|
|
2216
|
-
const kind = descriptor.kind;
|
|
2217
|
-
if (!["kv", "knowledge", "project", "browser", "file", "kanban"].includes(kind)) {
|
|
2218
|
-
continue;
|
|
2219
|
-
}
|
|
2220
|
-
const target = Object.fromEntries(Object.entries(record(descriptor.target) ?? {}).filter((entry) => typeof entry[1] === "string" && entry[1].length < 8192));
|
|
2221
|
-
if (target.source === "current") {
|
|
2222
|
-
target.source = "local";
|
|
2223
|
-
target.projectId = projectId;
|
|
2224
|
-
}
|
|
2225
|
-
if (target.source && !["local", "server"].includes(target.source)) {
|
|
2226
|
-
continue;
|
|
2227
|
-
}
|
|
2228
|
-
if (kind === "browser") {
|
|
2229
|
-
try {
|
|
2230
|
-
const url = new URL(target.url);
|
|
2231
|
-
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) {
|
|
2232
|
-
continue;
|
|
2233
|
-
}
|
|
2234
|
-
} catch {
|
|
2235
|
-
continue;
|
|
2236
|
-
}
|
|
2237
|
-
} else {
|
|
2238
|
-
target.source ??= "local";
|
|
2239
|
-
target.projectId ??= projectId;
|
|
2240
|
-
if (target.scope === "conversation") {
|
|
2241
|
-
target.conversationId ??= conversationId;
|
|
2242
|
-
}
|
|
2243
|
-
if (kind === "file" && (!target.filePath || /^(?:[\\/]|[a-z]:)|(?:^|[\\/])\.\.(?:[\\/]|$)/i.test(target.filePath))) {
|
|
2244
|
-
continue;
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
const id = string(descriptor.cardId) ?? `${kind}:${JSON.stringify(target)}`;
|
|
2248
|
-
const title = (string(descriptor.title) ?? target.key ?? target.documentId ?? target.filePath ?? target.url ?? target.projectId ?? kind).slice(0, 200);
|
|
2249
|
-
cards.set(id, { id, kind, title, target, itemId: item.id ?? String(index) });
|
|
2250
|
-
}
|
|
2251
|
-
return [...cards.values()];
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
2174
|
// src/web/workflow-actions.ts
|
|
2255
|
-
import { readFile as readFile4, realpath as realpath3 } from "fs/promises";
|
|
2256
|
-
import path4 from "path";
|
|
2257
2175
|
function createWorkflowActions(controller, runtime, project, revision, changed) {
|
|
2258
2176
|
const states = /* @__PURE__ */ new Map();
|
|
2259
2177
|
const epochs = /* @__PURE__ */ new Map();
|
|
@@ -2438,28 +2356,6 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
2438
2356
|
replies.push({ type: "attachments", entrypointId: id, runId: message.runId, requestId: message.requestId, paramName: message.paramName, files });
|
|
2439
2357
|
break;
|
|
2440
2358
|
}
|
|
2441
|
-
case "resource": {
|
|
2442
|
-
const run = controller.runs.find((record2) => record2.id === message.runId);
|
|
2443
|
-
if (!run) {
|
|
2444
|
-
throw new Error("Run no longer exists.");
|
|
2445
|
-
}
|
|
2446
|
-
const card = conversationResourceCards(run.items, project.id, run.scopeId).find((card2) => card2.id === message.cardId);
|
|
2447
|
-
if (!card || card.target.projectId && card.target.projectId !== project.id || card.target.source === "server") {
|
|
2448
|
-
throw new Error("Resource is outside this project.");
|
|
2449
|
-
}
|
|
2450
|
-
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
2451
|
-
replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, run.scopeId, card.kind, card.target) });
|
|
2452
|
-
} else if (card.kind === "file") {
|
|
2453
|
-
const root = await realpath3(project.folderPath), filename = await realpath3(path4.resolve(root, card.target.filePath)), relative = path4.relative(root, filename);
|
|
2454
|
-
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
2455
|
-
throw new Error("File is outside this project.");
|
|
2456
|
-
}
|
|
2457
|
-
replies.push({ type: "preview", title: card.title, content: await readFile4(filename, "utf8") });
|
|
2458
|
-
} else {
|
|
2459
|
-
throw new Error("This resource requires an editor host.");
|
|
2460
|
-
}
|
|
2461
|
-
break;
|
|
2462
|
-
}
|
|
2463
2359
|
default:
|
|
2464
2360
|
throw new Error(`Unsupported workflow action: ${message.type}`);
|
|
2465
2361
|
}
|
|
@@ -2471,26 +2367,26 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
2471
2367
|
// src/web/runtime.ts
|
|
2472
2368
|
import { spawn } from "child_process";
|
|
2473
2369
|
import { existsSync, readFileSync } from "fs";
|
|
2474
|
-
import
|
|
2370
|
+
import path4 from "path";
|
|
2475
2371
|
import { fileURLToPath } from "url";
|
|
2476
2372
|
import { WORKFLOW_RUNTIME_LOCALE_ENV } from "workflow-code";
|
|
2477
2373
|
function cliRoot() {
|
|
2478
|
-
let directory =
|
|
2374
|
+
let directory = path4.dirname(fileURLToPath(import.meta.url));
|
|
2479
2375
|
for (let depth = 0; depth < 8; depth++) {
|
|
2480
2376
|
try {
|
|
2481
|
-
if (JSON.parse(readFileSync(
|
|
2377
|
+
if (JSON.parse(readFileSync(path4.join(directory, "package.json"), "utf8")).name === "@workflow-code/cli") {
|
|
2482
2378
|
return directory;
|
|
2483
2379
|
}
|
|
2484
2380
|
} catch {
|
|
2485
2381
|
}
|
|
2486
|
-
directory =
|
|
2382
|
+
directory = path4.dirname(directory);
|
|
2487
2383
|
}
|
|
2488
2384
|
throw new Error("Cannot locate the CLI installation.");
|
|
2489
2385
|
}
|
|
2490
2386
|
function createWebRuntime(projectDir, storageDir, connection, relatedProjects = () => []) {
|
|
2491
2387
|
const root = cliRoot();
|
|
2492
|
-
const builtWorker =
|
|
2493
|
-
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");
|
|
2494
2390
|
const children = /* @__PURE__ */ new Set();
|
|
2495
2391
|
const active = /* @__PURE__ */ new Map();
|
|
2496
2392
|
let disposed = false;
|
|
@@ -2514,7 +2410,7 @@ function createWebRuntime(projectDir, storageDir, connection, relatedProjects =
|
|
|
2514
2410
|
WORKFLOW_SERVER_TOKEN: "",
|
|
2515
2411
|
WORKFLOW_LLM_SERVER_URL: auth?.serverUrl ?? "",
|
|
2516
2412
|
WORKFLOW_LLM_SERVER_TOKEN: auth?.accessToken ?? "",
|
|
2517
|
-
WORKFLOW_CLI_AUTH_DIR:
|
|
2413
|
+
WORKFLOW_CLI_AUTH_DIR: path4.join(storageDir, "runtime-auth")
|
|
2518
2414
|
};
|
|
2519
2415
|
for (const key of [
|
|
2520
2416
|
"TSX_TSCONFIG_PATH",
|
|
@@ -2637,6 +2533,125 @@ function createWebRuntime(projectDir, storageDir, connection, relatedProjects =
|
|
|
2637
2533
|
};
|
|
2638
2534
|
}
|
|
2639
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
|
+
|
|
2640
2655
|
// src/web/account-auth.ts
|
|
2641
2656
|
async function loginWebAccount(serverUrl, email, password) {
|
|
2642
2657
|
if (!email.trim() || !password) {
|
|
@@ -2720,6 +2735,268 @@ async function inspectWebProject(runtime, projectDir, host) {
|
|
|
2720
2735
|
};
|
|
2721
2736
|
}
|
|
2722
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
|
+
|
|
2723
3000
|
// src/web/server.ts
|
|
2724
3001
|
async function startWebServer(options) {
|
|
2725
3002
|
const projectDir = await realpath4(path6.resolve(options.projectDir));
|
|
@@ -2738,6 +3015,7 @@ async function startWebServer(options) {
|
|
|
2738
3015
|
let authCheck;
|
|
2739
3016
|
const sensitiveTokens = /* @__PURE__ */ new Set();
|
|
2740
3017
|
let relatedProjects = options.host?.relatedProjects ?? [];
|
|
3018
|
+
let resourceProjects = options.host?.resourceProjects ?? [];
|
|
2741
3019
|
const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection, () => relatedProjects);
|
|
2742
3020
|
let inspected;
|
|
2743
3021
|
try {
|
|
@@ -2759,6 +3037,7 @@ async function startWebServer(options) {
|
|
|
2759
3037
|
throw new Error("Unsupported Workflow project type.");
|
|
2760
3038
|
}
|
|
2761
3039
|
const project = descriptor;
|
|
3040
|
+
const resources = new ResourcePreviews(project, runtime, storageDir, () => connection, () => relatedProjects, () => resourceProjects);
|
|
2762
3041
|
const key = createHash3("sha256").update(project.assistant ? `assistant:${project.id}:${project.assistant.workdir}` : projectDir).digest("hex").slice(0, 24);
|
|
2763
3042
|
const historyPath = path6.join(storageDir, key, "conversations.json");
|
|
2764
3043
|
let saved = {};
|
|
@@ -2918,6 +3197,7 @@ ${token}`;
|
|
|
2918
3197
|
authIdentity = identity;
|
|
2919
3198
|
const candidate = !signedOut && serverUrl && token ? { serverUrl: serverUrl.replace(/\/+$/, ""), accessToken: token } : void 0;
|
|
2920
3199
|
if (changed) {
|
|
3200
|
+
resources.clear();
|
|
2921
3201
|
connection = void 0;
|
|
2922
3202
|
authRevision++;
|
|
2923
3203
|
parameterStates.clear();
|
|
@@ -2959,6 +3239,7 @@ ${token}`;
|
|
|
2959
3239
|
return;
|
|
2960
3240
|
}
|
|
2961
3241
|
connection = void 0;
|
|
3242
|
+
resources.clear();
|
|
2962
3243
|
kanbanController?.authenticationChanged();
|
|
2963
3244
|
if (!changed) {
|
|
2964
3245
|
authRevision++;
|
|
@@ -3029,6 +3310,62 @@ ${token}`;
|
|
|
3029
3310
|
if (message.projectUri && message.projectUri !== project.uri) {
|
|
3030
3311
|
throw new Error("Project changed.");
|
|
3031
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
|
+
}
|
|
3032
3369
|
if (kanbanController && !["ready", "login", "verify", "logout", "usage"].includes(message.type)) {
|
|
3033
3370
|
return kanbanController.handle(message);
|
|
3034
3371
|
}
|
|
@@ -3191,27 +3528,6 @@ ${token}`;
|
|
|
3191
3528
|
}
|
|
3192
3529
|
break;
|
|
3193
3530
|
}
|
|
3194
|
-
case "resource": {
|
|
3195
|
-
const answer = current.messages.find((entry) => entry.id === message.messageId);
|
|
3196
|
-
const card = conversationResourceCards(answer?.items ?? [], project.id, current.id).find((entry) => entry.id === message.cardId);
|
|
3197
|
-
if (!card || card.target.projectId && card.target.projectId !== project.id || card.target.source === "server") {
|
|
3198
|
-
throw new Error("Resource is outside this project.");
|
|
3199
|
-
}
|
|
3200
|
-
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
3201
|
-
replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, current.id, card.kind, card.target) });
|
|
3202
|
-
} else if (card.kind === "file") {
|
|
3203
|
-
const root = await realpath4(project.assistant?.workdir ?? projectDir);
|
|
3204
|
-
const filename = await realpath4(path6.resolve(root, card.target.filePath));
|
|
3205
|
-
const relative = path6.relative(root, filename);
|
|
3206
|
-
if (relative === ".." || relative.startsWith(`..${path6.sep}`) || path6.isAbsolute(relative)) {
|
|
3207
|
-
throw new Error("File is outside this project.");
|
|
3208
|
-
}
|
|
3209
|
-
replies.push({ type: "preview", title: card.title, content: await readFile5(filename, "utf8") });
|
|
3210
|
-
} else {
|
|
3211
|
-
throw new Error("This resource requires an editor host.");
|
|
3212
|
-
}
|
|
3213
|
-
break;
|
|
3214
|
-
}
|
|
3215
3531
|
case "usage": {
|
|
3216
3532
|
if (!connection) {
|
|
3217
3533
|
throw new Error("Workflow Server login is not configured.");
|
|
@@ -3309,13 +3625,14 @@ ${token}`;
|
|
|
3309
3625
|
const host = new URL(`http://${request.headers.host ?? ""}`);
|
|
3310
3626
|
const forwarded = !!options.host && ["127.0.0.1", "localhost", "[::1]"].includes(host.hostname) && !host.username && !host.password && host.pathname === "/" && !host.search && !host.hash;
|
|
3311
3627
|
const url = new URL(request.url ?? "/", origin);
|
|
3312
|
-
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";
|
|
3313
3630
|
if (request.headers.host !== new URL(origin).host && !forwarded || request.headers.origin && request.headers.origin !== (forwarded ? host.origin : origin) && !sandboxAsset) {
|
|
3314
3631
|
json(response, 403, { message: "Invalid request origin." });
|
|
3315
3632
|
return;
|
|
3316
3633
|
}
|
|
3317
|
-
if (
|
|
3318
|
-
const asset = await kanbanController
|
|
3634
|
+
if (resourceAsset && ["GET", "HEAD"].includes(request.method ?? "")) {
|
|
3635
|
+
const asset = await kanbanController?.preview.asset(url) ?? await resources.asset(url);
|
|
3319
3636
|
if (!asset) {
|
|
3320
3637
|
json(response, 404, { message: "Kanban asset not found." });
|
|
3321
3638
|
return;
|
|
@@ -3349,6 +3666,10 @@ ${token}`;
|
|
|
3349
3666
|
json(response, 200, { connected: true });
|
|
3350
3667
|
return;
|
|
3351
3668
|
}
|
|
3669
|
+
if (url.pathname === "/api/resource-state" && request.method === "GET") {
|
|
3670
|
+
json(response, 200, { type: "state", authenticationRevision: authRevision });
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3352
3673
|
if (url.pathname === "/api/events" && request.method === "GET") {
|
|
3353
3674
|
const disconnectKanban = kanbanController?.methods.connect(url.searchParams.get("clientId") ?? "");
|
|
3354
3675
|
response.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
|
|
@@ -3370,7 +3691,7 @@ ${token}`;
|
|
|
3370
3691
|
const operation = () => handleMessage(message).then((value2) => {
|
|
3371
3692
|
messages = value2;
|
|
3372
3693
|
});
|
|
3373
|
-
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);
|
|
3374
3695
|
const task = concurrent ? operation() : mutations.catch(() => void 0).then(operation);
|
|
3375
3696
|
if (!concurrent) {
|
|
3376
3697
|
mutations = task;
|
|
@@ -3399,10 +3720,10 @@ ${token}`;
|
|
|
3399
3720
|
response.end();
|
|
3400
3721
|
return;
|
|
3401
3722
|
}
|
|
3402
|
-
if (url.pathname === "/") {
|
|
3723
|
+
if (url.pathname === "/" || /^\/resource\/[a-f\d-]{36}$/.test(url.pathname)) {
|
|
3403
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:";
|
|
3404
|
-
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'
|
|
3405
|
-
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";
|
|
3406
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>`);
|
|
3407
3728
|
return;
|
|
3408
3729
|
}
|
|
@@ -3411,6 +3732,8 @@ ${token}`;
|
|
|
3411
3732
|
assets["/assets/workflow-workspace.css"] = "text/css";
|
|
3412
3733
|
assets["/assets/kanban-workspace.js"] = "application/javascript";
|
|
3413
3734
|
assets["/assets/kanban-workspace.css"] = "text/css";
|
|
3735
|
+
assets["/assets/resource-preview.js"] = "application/javascript";
|
|
3736
|
+
assets["/assets/resource-preview.css"] = "text/css";
|
|
3414
3737
|
if (!assets[url.pathname]) {
|
|
3415
3738
|
json(response, 404, { message: "Not found." });
|
|
3416
3739
|
return;
|
|
@@ -3453,10 +3776,19 @@ ${token}`;
|
|
|
3453
3776
|
kanbanController,
|
|
3454
3777
|
setHostRelatedProjects: (value) => {
|
|
3455
3778
|
relatedProjects = value;
|
|
3779
|
+
resources.clear();
|
|
3780
|
+
},
|
|
3781
|
+
setHostResourceProjects: (value) => {
|
|
3782
|
+
resourceProjects = value;
|
|
3783
|
+
resources.clear();
|
|
3456
3784
|
},
|
|
3457
3785
|
setHostConnection: async (value) => {
|
|
3786
|
+
if (hostManaged && JSON.stringify(value) === JSON.stringify(hostConnection)) {
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3458
3789
|
authEpoch++;
|
|
3459
3790
|
authCheck?.abort();
|
|
3791
|
+
resources.clear();
|
|
3460
3792
|
hostConnection = value;
|
|
3461
3793
|
hostManaged = true;
|
|
3462
3794
|
signedOut = !value;
|
|
@@ -3492,6 +3824,15 @@ import { createInterface } from "readline";
|
|
|
3492
3824
|
|
|
3493
3825
|
// src/web/host-config.ts
|
|
3494
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
|
+
}
|
|
3495
3836
|
function parseHostRelatedProjects(value) {
|
|
3496
3837
|
if (value === void 0) {
|
|
3497
3838
|
return [];
|
|
@@ -3547,7 +3888,8 @@ function parseHostConfiguration(value) {
|
|
|
3547
3888
|
assistant,
|
|
3548
3889
|
assistantProjectDir,
|
|
3549
3890
|
connection: parseHostConnection(configuration.connection),
|
|
3550
|
-
relatedProjects: parseHostRelatedProjects(configuration.relatedProjects)
|
|
3891
|
+
relatedProjects: parseHostRelatedProjects(configuration.relatedProjects),
|
|
3892
|
+
...configuration.resourceProjects === void 0 ? {} : { resourceProjects: parseHostResourceProjects(configuration.resourceProjects) }
|
|
3551
3893
|
};
|
|
3552
3894
|
}
|
|
3553
3895
|
|
|
@@ -3659,6 +4001,9 @@ async function main(argv) {
|
|
|
3659
4001
|
});
|
|
3660
4002
|
} else if (message.type === "related-projects") {
|
|
3661
4003
|
app.setHostRelatedProjects(parseHostRelatedProjects(message.relatedProjects));
|
|
4004
|
+
if (message.resourceProjects !== void 0) {
|
|
4005
|
+
app.setHostResourceProjects(parseHostResourceProjects(message.resourceProjects));
|
|
4006
|
+
}
|
|
3662
4007
|
} else if (message.type === "close") {
|
|
3663
4008
|
break;
|
|
3664
4009
|
} else {
|