@workflow-code/cli 0.2.4-20261012006.codex-studio-integration → 0.2.4-20261013001.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-data.mjs +18 -2
- 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 +16518 -0
- package/dist/web/worker.mjs +7 -6
- 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-JXC3WIJK.js} +534 -170
- package/package.json +2 -2
|
@@ -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
|
|
@@ -1706,7 +1732,6 @@ var KanbanMethodHub = class {
|
|
|
1706
1732
|
import { readFile as readFile2, readdir, realpath as realpath2 } from "fs/promises";
|
|
1707
1733
|
import { createHash } from "crypto";
|
|
1708
1734
|
import path2 from "path";
|
|
1709
|
-
var readKanbanProjectInfo = readWorkflowProjectInfo;
|
|
1710
1735
|
var webProjectDataDirectory = (storageDir, projectDir) => path2.join(storageDir, createHash("sha256").update(projectDir).digest("hex").slice(0, 24));
|
|
1711
1736
|
async function localKanbanRelatedProjects(project) {
|
|
1712
1737
|
const info = readWorkflowProjectInfo(JSON.parse(await readFile2(path2.join(project.folderPath, "package.json"), "utf8")));
|
|
@@ -1846,7 +1871,7 @@ var KanbanController = class {
|
|
|
1846
1871
|
async load() {
|
|
1847
1872
|
const pkg = JSON.parse(await readFile3(path3.join(this.project.folderPath, "package.json"), "utf8"));
|
|
1848
1873
|
const artifact = readKanbanArtifactConfig(pkg);
|
|
1849
|
-
const projectInfo =
|
|
1874
|
+
const projectInfo = readWorkflowProjectInfo(pkg);
|
|
1850
1875
|
let params;
|
|
1851
1876
|
try {
|
|
1852
1877
|
params = parseKanbanManifestText(await readFile3(path3.join(this.project.folderPath, "kanban.json"), "utf8")).params;
|
|
@@ -2145,115 +2170,7 @@ var KanbanController = class {
|
|
|
2145
2170
|
}
|
|
2146
2171
|
};
|
|
2147
2172
|
|
|
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
2173
|
// src/web/workflow-actions.ts
|
|
2255
|
-
import { readFile as readFile4, realpath as realpath3 } from "fs/promises";
|
|
2256
|
-
import path4 from "path";
|
|
2257
2174
|
function createWorkflowActions(controller, runtime, project, revision, changed) {
|
|
2258
2175
|
const states = /* @__PURE__ */ new Map();
|
|
2259
2176
|
const epochs = /* @__PURE__ */ new Map();
|
|
@@ -2438,28 +2355,6 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
2438
2355
|
replies.push({ type: "attachments", entrypointId: id, runId: message.runId, requestId: message.requestId, paramName: message.paramName, files });
|
|
2439
2356
|
break;
|
|
2440
2357
|
}
|
|
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
2358
|
default:
|
|
2464
2359
|
throw new Error(`Unsupported workflow action: ${message.type}`);
|
|
2465
2360
|
}
|
|
@@ -2471,26 +2366,26 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
|
|
|
2471
2366
|
// src/web/runtime.ts
|
|
2472
2367
|
import { spawn } from "child_process";
|
|
2473
2368
|
import { existsSync, readFileSync } from "fs";
|
|
2474
|
-
import
|
|
2369
|
+
import path4 from "path";
|
|
2475
2370
|
import { fileURLToPath } from "url";
|
|
2476
2371
|
import { WORKFLOW_RUNTIME_LOCALE_ENV } from "workflow-code";
|
|
2477
2372
|
function cliRoot() {
|
|
2478
|
-
let directory =
|
|
2373
|
+
let directory = path4.dirname(fileURLToPath(import.meta.url));
|
|
2479
2374
|
for (let depth = 0; depth < 8; depth++) {
|
|
2480
2375
|
try {
|
|
2481
|
-
if (JSON.parse(readFileSync(
|
|
2376
|
+
if (JSON.parse(readFileSync(path4.join(directory, "package.json"), "utf8")).name === "@workflow-code/cli") {
|
|
2482
2377
|
return directory;
|
|
2483
2378
|
}
|
|
2484
2379
|
} catch {
|
|
2485
2380
|
}
|
|
2486
|
-
directory =
|
|
2381
|
+
directory = path4.dirname(directory);
|
|
2487
2382
|
}
|
|
2488
2383
|
throw new Error("Cannot locate the CLI installation.");
|
|
2489
2384
|
}
|
|
2490
2385
|
function createWebRuntime(projectDir, storageDir, connection, relatedProjects = () => []) {
|
|
2491
2386
|
const root = cliRoot();
|
|
2492
|
-
const builtWorker =
|
|
2493
|
-
const worker = existsSync(builtWorker) ? builtWorker :
|
|
2387
|
+
const builtWorker = path4.join(root, "dist/web/worker.mjs");
|
|
2388
|
+
const worker = existsSync(builtWorker) ? builtWorker : path4.join(root, "src/web/worker.mjs");
|
|
2494
2389
|
const children = /* @__PURE__ */ new Set();
|
|
2495
2390
|
const active = /* @__PURE__ */ new Map();
|
|
2496
2391
|
let disposed = false;
|
|
@@ -2514,7 +2409,7 @@ function createWebRuntime(projectDir, storageDir, connection, relatedProjects =
|
|
|
2514
2409
|
WORKFLOW_SERVER_TOKEN: "",
|
|
2515
2410
|
WORKFLOW_LLM_SERVER_URL: auth?.serverUrl ?? "",
|
|
2516
2411
|
WORKFLOW_LLM_SERVER_TOKEN: auth?.accessToken ?? "",
|
|
2517
|
-
WORKFLOW_CLI_AUTH_DIR:
|
|
2412
|
+
WORKFLOW_CLI_AUTH_DIR: path4.join(storageDir, "runtime-auth")
|
|
2518
2413
|
};
|
|
2519
2414
|
for (const key of [
|
|
2520
2415
|
"TSX_TSCONFIG_PATH",
|
|
@@ -2637,6 +2532,125 @@ function createWebRuntime(projectDir, storageDir, connection, relatedProjects =
|
|
|
2637
2532
|
};
|
|
2638
2533
|
}
|
|
2639
2534
|
|
|
2535
|
+
// src/web/resource-cards.ts
|
|
2536
|
+
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2537
|
+
var string = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2538
|
+
function conversationResourceCards(items, projectId, conversationId, storageSource = "local") {
|
|
2539
|
+
const cards = /* @__PURE__ */ new Map();
|
|
2540
|
+
for (const [index, item] of items.entries()) {
|
|
2541
|
+
if (!["agent-part", "opencode-part"].includes(String(item.metadata?.kind)) || item.metadata?.partType !== "tool") {
|
|
2542
|
+
continue;
|
|
2543
|
+
}
|
|
2544
|
+
const content = record(item.content);
|
|
2545
|
+
if (content?.status !== "success") {
|
|
2546
|
+
continue;
|
|
2547
|
+
}
|
|
2548
|
+
let output = record(content.output);
|
|
2549
|
+
const details = record(output?.details);
|
|
2550
|
+
if (Array.isArray(output?.content) && output.content.length === 1) {
|
|
2551
|
+
try {
|
|
2552
|
+
output = record(JSON.parse(String(record(output.content[0])?.text)));
|
|
2553
|
+
} catch {
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
let descriptor = record(details?.resourceCard) ?? record(record(output?.details)?.resourceCard);
|
|
2557
|
+
const input = record(content.input) ?? {};
|
|
2558
|
+
const tool = string(content.tool) ?? "";
|
|
2559
|
+
if (tool === "workflow-ui_present_card") {
|
|
2560
|
+
const presentation = output?.action ? output : input;
|
|
2561
|
+
if (presentation.action === "remove") {
|
|
2562
|
+
cards.delete(String(presentation.cardId));
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
if (presentation.action !== "upsert") {
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
descriptor = presentation;
|
|
2569
|
+
}
|
|
2570
|
+
if (!descriptor) {
|
|
2571
|
+
const target2 = { source: "local", projectId, conversationId };
|
|
2572
|
+
let kind2;
|
|
2573
|
+
if (tool === "workflow-kv_kv_store" || /workflow-desktop_(?:read|list|write)_project_kv/.test(tool)) {
|
|
2574
|
+
kind2 = "kv";
|
|
2575
|
+
Object.assign(target2, { projectId: input.projectId ?? projectId, key: input.key, scope: input.scope ?? "project", conversationId: input.conversationId ?? conversationId });
|
|
2576
|
+
} else if (tool === "workflow-knowledge_knowledge_documents" || /workflow-desktop_(?:query|mutate)_project_knowledge/.test(tool)) {
|
|
2577
|
+
kind2 = "knowledge";
|
|
2578
|
+
Object.assign(target2, { projectId: input.projectId ?? projectId, documentId: input.documentId ?? record(output?.document)?.id });
|
|
2579
|
+
} else if (tool === "read" || tool === "workflow-desktop_open_dock_tool" && input.tool === "files") {
|
|
2580
|
+
kind2 = "file";
|
|
2581
|
+
target2.filePath = input.filePath ?? input.path;
|
|
2582
|
+
} else if (tool === "workflow-desktop_open_project" && input.kind === "project") {
|
|
2583
|
+
kind2 = "project";
|
|
2584
|
+
target2.projectId = input.targetId;
|
|
2585
|
+
} else if (tool === "workflow-desktop_open_dock_tool") {
|
|
2586
|
+
if (input.tool === "browser") {
|
|
2587
|
+
kind2 = "browser";
|
|
2588
|
+
target2.url = input.url;
|
|
2589
|
+
}
|
|
2590
|
+
if (input.tool === "preview") {
|
|
2591
|
+
kind2 = "kanban";
|
|
2592
|
+
}
|
|
2593
|
+
if (input.tool === "open-project" && input.openedProjectKind === "project") {
|
|
2594
|
+
kind2 = "project";
|
|
2595
|
+
target2.projectId = input.openedProjectId;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
if (!kind2) {
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
if (kind2 === "kv" || kind2 === "knowledge") {
|
|
2602
|
+
target2.source = input.source ?? storageSource;
|
|
2603
|
+
}
|
|
2604
|
+
descriptor = { kind: kind2, target: target2, title: record(output?.document)?.title };
|
|
2605
|
+
}
|
|
2606
|
+
const kind = descriptor.kind;
|
|
2607
|
+
if (!["kv", "knowledge", "project", "browser", "file", "kanban"].includes(kind)) {
|
|
2608
|
+
continue;
|
|
2609
|
+
}
|
|
2610
|
+
const target = Object.fromEntries(Object.entries(record(descriptor.target) ?? {}).filter((entry) => typeof entry[1] === "string" && entry[1].length < 8192));
|
|
2611
|
+
if (target.source === "current") {
|
|
2612
|
+
target.source = kind === "kv" || kind === "knowledge" ? storageSource : "local";
|
|
2613
|
+
target.projectId = projectId;
|
|
2614
|
+
}
|
|
2615
|
+
if (target.source && !["local", "server"].includes(target.source)) {
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
if (kind === "browser") {
|
|
2619
|
+
try {
|
|
2620
|
+
const url = new URL(target.url);
|
|
2621
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) {
|
|
2622
|
+
continue;
|
|
2623
|
+
}
|
|
2624
|
+
} catch {
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2627
|
+
} else {
|
|
2628
|
+
target.source ??= "local";
|
|
2629
|
+
target.projectId ??= projectId;
|
|
2630
|
+
if (target.scope === "conversation") {
|
|
2631
|
+
target.conversationId ??= conversationId;
|
|
2632
|
+
}
|
|
2633
|
+
if (kind === "file" && (!target.filePath || /^(?:[\\/]|[a-z]:)|(?:^|[\\/])\.\.(?:[\\/]|$)/i.test(target.filePath))) {
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
const id = string(descriptor.cardId) ?? `${kind}:${JSON.stringify(target)}`;
|
|
2638
|
+
const title = (string(descriptor.title) ?? string(record(output?.document)?.title) ?? target.key ?? target.documentId ?? target.filePath ?? target.url ?? target.projectId ?? kind).slice(0, 200);
|
|
2639
|
+
const kanban = record(descriptor.kanban), values = record(kanban?.values);
|
|
2640
|
+
cards.set(id, {
|
|
2641
|
+
id,
|
|
2642
|
+
kind,
|
|
2643
|
+
title,
|
|
2644
|
+
target,
|
|
2645
|
+
itemId: item.id ?? String(index),
|
|
2646
|
+
...typeof descriptor.preview === "boolean" ? { preview: descriptor.preview } : {},
|
|
2647
|
+
...typeof descriptor.pinned === "boolean" ? { pinned: descriptor.pinned } : {},
|
|
2648
|
+
...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 } } : {}
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
return [...cards.values()];
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2640
2654
|
// src/web/account-auth.ts
|
|
2641
2655
|
async function loginWebAccount(serverUrl, email, password) {
|
|
2642
2656
|
if (!email.trim() || !password) {
|
|
@@ -2720,6 +2734,287 @@ async function inspectWebProject(runtime, projectDir, host) {
|
|
|
2720
2734
|
};
|
|
2721
2735
|
}
|
|
2722
2736
|
|
|
2737
|
+
// src/web/resource-preview.ts
|
|
2738
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2739
|
+
import { readFile as readFile4, realpath as realpath3, stat as stat3 } from "fs/promises";
|
|
2740
|
+
import path5 from "path";
|
|
2741
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2742
|
+
var ResourcePreviews = class {
|
|
2743
|
+
constructor(owner, runtime, storageDir, connection, related, resourceProjects = () => []) {
|
|
2744
|
+
this.owner = owner;
|
|
2745
|
+
this.runtime = runtime;
|
|
2746
|
+
this.storageDir = storageDir;
|
|
2747
|
+
this.connection = connection;
|
|
2748
|
+
this.related = related;
|
|
2749
|
+
this.resourceProjects = resourceProjects;
|
|
2750
|
+
}
|
|
2751
|
+
owner;
|
|
2752
|
+
runtime;
|
|
2753
|
+
storageDir;
|
|
2754
|
+
connection;
|
|
2755
|
+
related;
|
|
2756
|
+
resourceProjects;
|
|
2757
|
+
entries = /* @__PURE__ */ new Map();
|
|
2758
|
+
generation = 0;
|
|
2759
|
+
clear() {
|
|
2760
|
+
this.generation++;
|
|
2761
|
+
this.entries.clear();
|
|
2762
|
+
}
|
|
2763
|
+
get revision() {
|
|
2764
|
+
return this.generation;
|
|
2765
|
+
}
|
|
2766
|
+
async server(id, suffix, init = {}) {
|
|
2767
|
+
const connection = this.connection();
|
|
2768
|
+
if (!connection) {
|
|
2769
|
+
throw new Error(t("Please sign in to preview this server resource."));
|
|
2770
|
+
}
|
|
2771
|
+
const response = await fetch(`${connection.serverUrl}/api/workflows/${encodeURIComponent(id)}${suffix}`, {
|
|
2772
|
+
...init,
|
|
2773
|
+
headers: { Authorization: `Bearer ${connection.accessToken}`, "Content-Type": "application/json" },
|
|
2774
|
+
signal: AbortSignal.timeout(3e4),
|
|
2775
|
+
redirect: "error"
|
|
2776
|
+
});
|
|
2777
|
+
const result = await response.json();
|
|
2778
|
+
if (!response.ok || result.errCode !== 0) {
|
|
2779
|
+
throw new Error(result.errMessage || t("You do not have access to this resource."));
|
|
2780
|
+
}
|
|
2781
|
+
return result.data;
|
|
2782
|
+
}
|
|
2783
|
+
async resolve(card) {
|
|
2784
|
+
if (card.kind === "browser") {
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
const id = card.target.projectId ?? this.owner.id;
|
|
2788
|
+
if (card.target.source === "server") {
|
|
2789
|
+
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
2790
|
+
if (id.toLowerCase() === this.owner.id.toLowerCase()) {
|
|
2791
|
+
return this.owner;
|
|
2792
|
+
}
|
|
2793
|
+
return { id, uri: `workflow-server:${id}`, folderPath: "", name: id, projectType: "workflow", fields: [], entrypoints: [] };
|
|
2794
|
+
}
|
|
2795
|
+
const snapshot = await this.server(id, "/structure"), structure = snapshot.structure;
|
|
2796
|
+
return {
|
|
2797
|
+
...structure,
|
|
2798
|
+
id,
|
|
2799
|
+
uri: `workflow-server:${id}`,
|
|
2800
|
+
folderPath: "",
|
|
2801
|
+
name: snapshot.workflowName ?? card.title,
|
|
2802
|
+
projectType: structure.projectType,
|
|
2803
|
+
fields: structure.params ?? [],
|
|
2804
|
+
entrypoints: structure.entrypoints ?? []
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
if (id === this.owner.id) {
|
|
2808
|
+
return this.owner;
|
|
2809
|
+
}
|
|
2810
|
+
for (const directory of /* @__PURE__ */ new Set([...this.related().map((binding) => binding.projectDir), ...this.resourceProjects()])) {
|
|
2811
|
+
let root, pkg;
|
|
2812
|
+
try {
|
|
2813
|
+
root = await realpath3(directory);
|
|
2814
|
+
pkg = JSON.parse(await readFile4(path5.join(root, "package.json"), "utf8"));
|
|
2815
|
+
} catch {
|
|
2816
|
+
continue;
|
|
2817
|
+
}
|
|
2818
|
+
if (String(pkg.id).toLowerCase() !== id.toLowerCase()) {
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2821
|
+
const inspected = await this.runtime.inspect([root]);
|
|
2822
|
+
const project = inspected.projects.find((project2) => project2.id.toLowerCase() === id.toLowerCase());
|
|
2823
|
+
if (!project) {
|
|
2824
|
+
throw new Error(t("The resource project is unavailable."));
|
|
2825
|
+
}
|
|
2826
|
+
return { ...project, uri: pathToFileURL2(root).href };
|
|
2827
|
+
}
|
|
2828
|
+
throw new Error(t("The resource project is unavailable. Import or open the target project first."));
|
|
2829
|
+
}
|
|
2830
|
+
async open(card) {
|
|
2831
|
+
const generation = this.generation;
|
|
2832
|
+
const project = await this.resolve(card);
|
|
2833
|
+
const id = randomUUID3(), data = { card, project }, entry = { data };
|
|
2834
|
+
const target = card.target, scopeId = target.conversationId ?? "";
|
|
2835
|
+
if (card.kind === "kv" || card.kind === "knowledge") {
|
|
2836
|
+
if (target.source === "server") {
|
|
2837
|
+
const query = new URLSearchParams({ scope: target.scope ?? "project" });
|
|
2838
|
+
if (target.conversationId) {
|
|
2839
|
+
query.set("conversationId", target.conversationId);
|
|
2840
|
+
}
|
|
2841
|
+
if (target.key) {
|
|
2842
|
+
query.set("key", target.key);
|
|
2843
|
+
}
|
|
2844
|
+
data.content = await this.server(project.id, card.kind === "knowledge" ? `/knowledge-documents${target.documentId ? `/${encodeURIComponent(target.documentId)}` : ""}` : `/kv${target.key ? "/entry" : ""}?${query}`);
|
|
2845
|
+
} else {
|
|
2846
|
+
const info = readWorkflowProjectInfo(JSON.parse(await readFile4(path5.join(project.folderPath, "package.json"), "utf8")));
|
|
2847
|
+
if (info.dataStorage && !isWorkflowProjectDataStorageLocationAllowed(info, "local")) {
|
|
2848
|
+
throw new Error(t("The project does not allow this storage location."));
|
|
2849
|
+
}
|
|
2850
|
+
data.content = await this.runtime.readResource(project, scopeId, card.kind, target);
|
|
2851
|
+
}
|
|
2852
|
+
if (card.kind === "knowledge" && (data.content === null || data.content === void 0)) {
|
|
2853
|
+
throw new Error(t("This resource no longer exists."));
|
|
2854
|
+
}
|
|
2855
|
+
data.format = card.kind === "knowledge" && typeof data.content.markdown === "string" ? "markdown" : "json";
|
|
2856
|
+
} else if (card.kind === "file") {
|
|
2857
|
+
if (target.source === "server") {
|
|
2858
|
+
throw new Error(t("Server source files cannot be previewed by this card."));
|
|
2859
|
+
}
|
|
2860
|
+
const root = await realpath3(project.assistant?.workdir ?? project.folderPath);
|
|
2861
|
+
const filename = await realpath3(path5.resolve(root, target.filePath));
|
|
2862
|
+
const relative = path5.relative(root, filename);
|
|
2863
|
+
if (relative === ".." || relative.startsWith(`..${path5.sep}`) || path5.isAbsolute(relative)) {
|
|
2864
|
+
throw new Error(t("File is outside this project."));
|
|
2865
|
+
}
|
|
2866
|
+
if (!(await stat3(filename)).isFile() || (await stat3(filename)).size > 2 * 1024 * 1024) {
|
|
2867
|
+
throw new Error(t("File preview exceeds 2 MiB."));
|
|
2868
|
+
}
|
|
2869
|
+
data.content = await readFile4(filename, "utf8");
|
|
2870
|
+
data.format = /\.mdx?$/i.test(filename) ? "markdown" : "text";
|
|
2871
|
+
} else if (card.kind === "browser") {
|
|
2872
|
+
data.format = "browser";
|
|
2873
|
+
data.content = target.url;
|
|
2874
|
+
} else if (card.kind === "kanban" || project?.projectType === "kanban" && card.preview !== false) {
|
|
2875
|
+
if (project?.projectType !== "kanban") {
|
|
2876
|
+
throw new Error(t("This project does not provide a Kanban preview."));
|
|
2877
|
+
}
|
|
2878
|
+
let params, configurations, activeId;
|
|
2879
|
+
if (target.source === "server") {
|
|
2880
|
+
const workspace = await this.server(project.id, "/kanban");
|
|
2881
|
+
params = workspace.manifest?.params ?? [];
|
|
2882
|
+
configurations = workspace.configurations;
|
|
2883
|
+
activeId = workspace.activeConfigurationId;
|
|
2884
|
+
const connection = this.connection(), url = new URL(workspace.preview.url, `${connection.serverUrl}/`);
|
|
2885
|
+
const expected = `/api/workflows/${encodeURIComponent(project.id)}/kanban/previews/`;
|
|
2886
|
+
if (url.origin !== new URL(connection.serverUrl).origin || !url.pathname.startsWith(expected) || url.search || url.hash) {
|
|
2887
|
+
throw new Error(t("Invalid resource preview URL."));
|
|
2888
|
+
}
|
|
2889
|
+
const segments = url.pathname.slice(expected.length).split("/");
|
|
2890
|
+
const assetPrefix = `${expected}${segments.slice(0, 3).join("/")}/`;
|
|
2891
|
+
entry.remote = { url, prefix: assetPrefix, connection };
|
|
2892
|
+
data.kanban = { url: `/resource-assets/${id}/${url.pathname.slice(assetPrefix.length)}`, nonce: workspace.preview.nonce, configuration: { id: "", name: "", params: {} } };
|
|
2893
|
+
} else {
|
|
2894
|
+
params = project.fields;
|
|
2895
|
+
try {
|
|
2896
|
+
params = parseKanbanManifestText(await readFile4(path5.join(project.folderPath, "kanban.json"), "utf8")).params;
|
|
2897
|
+
} catch (error) {
|
|
2898
|
+
if (error.code !== "ENOENT") {
|
|
2899
|
+
throw error;
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
let workspace = {};
|
|
2903
|
+
try {
|
|
2904
|
+
workspace = JSON.parse(await readFile4(path5.join(webProjectDataDirectory(this.storageDir, project.folderPath), "kanban.json"), "utf8"));
|
|
2905
|
+
} catch (error) {
|
|
2906
|
+
if (error.code !== "ENOENT") {
|
|
2907
|
+
throw error;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
configurations = workspace.configurations ?? [];
|
|
2911
|
+
activeId = workspace.activeConfigurationId;
|
|
2912
|
+
}
|
|
2913
|
+
const selected = configurations.find((item) => item.id === (target.configurationId ?? activeId));
|
|
2914
|
+
if (target.configurationId && !selected) {
|
|
2915
|
+
throw new Error(t("This preview configuration no longer exists."));
|
|
2916
|
+
}
|
|
2917
|
+
const values = sanitizeKanbanValues(params, { ...selected?.values ?? createDefaultKanbanValues(params), ...card.kanban?.mode === "preview" ? card.kanban.values : {} });
|
|
2918
|
+
const configuration = { id: selected?.id ?? id, name: selected?.name ?? card.title, params: createInjectedKanbanParams(params, values) };
|
|
2919
|
+
if (!entry.remote) {
|
|
2920
|
+
if (!project.kanban) {
|
|
2921
|
+
throw new Error(t("This project does not provide a Kanban preview."));
|
|
2922
|
+
}
|
|
2923
|
+
entry.local = new KanbanPreview(project.folderPath, project.kanban, () => configuration);
|
|
2924
|
+
data.kanban = { url: entry.local.url, nonce: entry.local.nonce, configuration };
|
|
2925
|
+
} else {
|
|
2926
|
+
data.kanban.configuration = configuration;
|
|
2927
|
+
}
|
|
2928
|
+
data.format = "kanban";
|
|
2929
|
+
} else {
|
|
2930
|
+
data.format = "project";
|
|
2931
|
+
data.content = project;
|
|
2932
|
+
}
|
|
2933
|
+
if (generation !== this.generation) {
|
|
2934
|
+
throw new Error(t("Resource access changed. Open the card again."));
|
|
2935
|
+
}
|
|
2936
|
+
if (this.entries.size >= 64) {
|
|
2937
|
+
this.entries.delete(this.entries.keys().next().value);
|
|
2938
|
+
}
|
|
2939
|
+
this.entries.set(id, entry);
|
|
2940
|
+
return { id, data, url: `/resource/${id}?workflowLocale=${locale()}` };
|
|
2941
|
+
}
|
|
2942
|
+
get(id) {
|
|
2943
|
+
const entry = this.entries.get(id);
|
|
2944
|
+
if (!entry) {
|
|
2945
|
+
throw new Error(t("Resource access changed. Open the card again."));
|
|
2946
|
+
}
|
|
2947
|
+
return entry.data;
|
|
2948
|
+
}
|
|
2949
|
+
async asset(url) {
|
|
2950
|
+
const generation = this.generation;
|
|
2951
|
+
if (url.pathname.startsWith("/kanban/")) {
|
|
2952
|
+
for (const entry of this.entries.values()) {
|
|
2953
|
+
if (entry.local && url.pathname.startsWith(`/kanban/${entry.local.nonce}/`)) {
|
|
2954
|
+
const asset2 = await entry.local.asset(url);
|
|
2955
|
+
return generation === this.generation ? asset2 : void 0;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
const match = /^\/resource-assets\/([^/]+)\/(.+)$/.exec(url.pathname);
|
|
2961
|
+
const remote = match && this.entries.get(match[1])?.remote;
|
|
2962
|
+
const connection = this.connection();
|
|
2963
|
+
if (!remote || remote.connection.serverUrl !== connection?.serverUrl || remote.connection.accessToken !== connection?.accessToken) {
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
const asset = decodeURIComponent(match[2]);
|
|
2967
|
+
if (asset.includes("\\") || asset.split("/").some((part) => part === ".." || !part) || !/\.[a-z\d]+$/i.test(asset)) {
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
const address = new URL(`${remote.prefix}${match[2]}`, remote.url.origin);
|
|
2971
|
+
const response = await fetch(address, { headers: { Authorization: `Bearer ${remote.connection.accessToken}` }, redirect: "error", signal: AbortSignal.timeout(3e4) });
|
|
2972
|
+
if (!response.ok) {
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
const content = Buffer.from(await response.arrayBuffer());
|
|
2976
|
+
if (generation !== this.generation) {
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
return { content, headers: {
|
|
2980
|
+
"Content-Type": response.headers.get("content-type") ?? "application/octet-stream",
|
|
2981
|
+
"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:*`,
|
|
2982
|
+
"Cache-Control": "no-store",
|
|
2983
|
+
"Access-Control-Allow-Origin": "*",
|
|
2984
|
+
"X-Content-Type-Options": "nosniff"
|
|
2985
|
+
} };
|
|
2986
|
+
}
|
|
2987
|
+
async bridge(id, value) {
|
|
2988
|
+
const generation = this.generation;
|
|
2989
|
+
const data = this.get(id), project = data.project, call = parseKanbanBridgeCall(value);
|
|
2990
|
+
if (data.format !== "kanban" || !project) {
|
|
2991
|
+
throw new Error(t("This project does not provide a Kanban preview."));
|
|
2992
|
+
}
|
|
2993
|
+
if (!["runtime.getLocale", "kv.getValue", "relatedProject.kv.getValue", "knowledge.listDocuments", "knowledge.getDocument", "knowledge.searchDocuments", "knowledge.readDocumentLines", "files.readFile"].includes(call.operation)) {
|
|
2994
|
+
throw new Error(t("This resource preview is read-only."));
|
|
2995
|
+
}
|
|
2996
|
+
if (call.operation === "runtime.getLocale") {
|
|
2997
|
+
return locale();
|
|
2998
|
+
}
|
|
2999
|
+
let result;
|
|
3000
|
+
if (data.card.target.source === "server" || project.projectInfo?.dataStorage?.mode === "server") {
|
|
3001
|
+
result = await this.server(project.id, "/kanban/bridge", { method: "POST", body: JSON.stringify({ call }) });
|
|
3002
|
+
} else if (call.operation === "files.readFile") {
|
|
3003
|
+
const file = await this.runtime.readFile(project, String(call.input.fileId));
|
|
3004
|
+
result = { file: file.reference, base64: (await readFile4(file.path)).toString("base64") };
|
|
3005
|
+
} else {
|
|
3006
|
+
if (!this.runtime.kanbanBridge) {
|
|
3007
|
+
throw new Error(t("The resource project is unavailable."));
|
|
3008
|
+
}
|
|
3009
|
+
result = await this.runtime.kanbanBridge(project, call);
|
|
3010
|
+
}
|
|
3011
|
+
if (generation !== this.generation) {
|
|
3012
|
+
throw new Error(t("Resource access changed. Open the card again."));
|
|
3013
|
+
}
|
|
3014
|
+
return result;
|
|
3015
|
+
}
|
|
3016
|
+
};
|
|
3017
|
+
|
|
2723
3018
|
// src/web/server.ts
|
|
2724
3019
|
async function startWebServer(options) {
|
|
2725
3020
|
const projectDir = await realpath4(path6.resolve(options.projectDir));
|
|
@@ -2738,6 +3033,7 @@ async function startWebServer(options) {
|
|
|
2738
3033
|
let authCheck;
|
|
2739
3034
|
const sensitiveTokens = /* @__PURE__ */ new Set();
|
|
2740
3035
|
let relatedProjects = options.host?.relatedProjects ?? [];
|
|
3036
|
+
let resourceProjects = options.host?.resourceProjects ?? [];
|
|
2741
3037
|
const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection, () => relatedProjects);
|
|
2742
3038
|
let inspected;
|
|
2743
3039
|
try {
|
|
@@ -2759,6 +3055,7 @@ async function startWebServer(options) {
|
|
|
2759
3055
|
throw new Error("Unsupported Workflow project type.");
|
|
2760
3056
|
}
|
|
2761
3057
|
const project = descriptor;
|
|
3058
|
+
const resources = new ResourcePreviews(project, runtime, storageDir, () => connection, () => relatedProjects, () => resourceProjects);
|
|
2762
3059
|
const key = createHash3("sha256").update(project.assistant ? `assistant:${project.id}:${project.assistant.workdir}` : projectDir).digest("hex").slice(0, 24);
|
|
2763
3060
|
const historyPath = path6.join(storageDir, key, "conversations.json");
|
|
2764
3061
|
let saved = {};
|
|
@@ -2798,7 +3095,8 @@ async function startWebServer(options) {
|
|
|
2798
3095
|
project,
|
|
2799
3096
|
...isKanban ? { kanban: kanbanController.state() } : isWorkflow ? workflowActions.state() : { sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {} },
|
|
2800
3097
|
account: { ...account, user: account.user && { userId: account.user.userId, email: account.user.email, displayName: account.user.displayName, avatarUrl: account.user.avatarUrl } },
|
|
2801
|
-
authenticationRevision: authRevision
|
|
3098
|
+
authenticationRevision: authRevision,
|
|
3099
|
+
resourceRevision: resources.revision
|
|
2802
3100
|
}, [...sensitiveTokens], false));
|
|
2803
3101
|
const broadcast = () => {
|
|
2804
3102
|
if (broadcastTimer) {
|
|
@@ -2918,6 +3216,7 @@ ${token}`;
|
|
|
2918
3216
|
authIdentity = identity;
|
|
2919
3217
|
const candidate = !signedOut && serverUrl && token ? { serverUrl: serverUrl.replace(/\/+$/, ""), accessToken: token } : void 0;
|
|
2920
3218
|
if (changed) {
|
|
3219
|
+
resources.clear();
|
|
2921
3220
|
connection = void 0;
|
|
2922
3221
|
authRevision++;
|
|
2923
3222
|
parameterStates.clear();
|
|
@@ -2959,6 +3258,7 @@ ${token}`;
|
|
|
2959
3258
|
return;
|
|
2960
3259
|
}
|
|
2961
3260
|
connection = void 0;
|
|
3261
|
+
resources.clear();
|
|
2962
3262
|
kanbanController?.authenticationChanged();
|
|
2963
3263
|
if (!changed) {
|
|
2964
3264
|
authRevision++;
|
|
@@ -3029,6 +3329,62 @@ ${token}`;
|
|
|
3029
3329
|
if (message.projectUri && message.projectUri !== project.uri) {
|
|
3030
3330
|
throw new Error("Project changed.");
|
|
3031
3331
|
}
|
|
3332
|
+
if (message.type === "resource-data") {
|
|
3333
|
+
return [{ type: "resource-data", requestId: message.requestId, data: resources.get(String(message.id)) }];
|
|
3334
|
+
}
|
|
3335
|
+
if (message.type === "resource-refresh" || message.type === "resource-select") {
|
|
3336
|
+
const card = resources.get(String(message.id)).card;
|
|
3337
|
+
const selection = message.type === "resource-select" ? message.selection : void 0;
|
|
3338
|
+
const target = { ...card.target };
|
|
3339
|
+
if (selection && ["kv", "knowledge"].includes(card.kind)) {
|
|
3340
|
+
for (const key2 of card.kind === "knowledge" ? ["documentId"] : ["key", "scope", "conversationId"]) {
|
|
3341
|
+
if (typeof selection[key2] === "string" && selection[key2].length <= 8192) {
|
|
3342
|
+
target[key2] = selection[key2];
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
const preview = await resources.open({ ...card, target });
|
|
3347
|
+
return [{ type: "resource-data", requestId: message.requestId, id: preview.id, data: preview.data }];
|
|
3348
|
+
}
|
|
3349
|
+
if (message.type === "resource-bridge") {
|
|
3350
|
+
return [{ type: "resource-bridge", requestId: message.requestId, value: await resources.bridge(String(message.id), message.call) }];
|
|
3351
|
+
}
|
|
3352
|
+
if (message.type === "resource") {
|
|
3353
|
+
if (!["open", "inline", "window", "project", void 0].includes(message.action)) {
|
|
3354
|
+
throw new Error(t("Invalid resource action."));
|
|
3355
|
+
}
|
|
3356
|
+
let items, scopeId;
|
|
3357
|
+
if (workflowController) {
|
|
3358
|
+
const run = workflowController.runs.find((run2) => run2.id === message.runId);
|
|
3359
|
+
if (!run) {
|
|
3360
|
+
throw new Error(t("Run no longer exists."));
|
|
3361
|
+
}
|
|
3362
|
+
items = run.items;
|
|
3363
|
+
scopeId = run.scopeId;
|
|
3364
|
+
} else {
|
|
3365
|
+
const current2 = conversationController?.current;
|
|
3366
|
+
if (!current2 || message.sessionId && message.sessionId !== current2.id) {
|
|
3367
|
+
throw new Error(t("Conversation changed."));
|
|
3368
|
+
}
|
|
3369
|
+
items = current2.messages.find((answer) => answer.id === message.messageId)?.items ?? [];
|
|
3370
|
+
scopeId = current2.id;
|
|
3371
|
+
}
|
|
3372
|
+
const card = conversationResourceCards(items, project.id, scopeId, project.projectInfo?.dataStorage?.mode === "server" ? "server" : "local").find((card2) => card2.id === message.cardId);
|
|
3373
|
+
if (!card) {
|
|
3374
|
+
throw new Error(t("This resource no longer exists."));
|
|
3375
|
+
}
|
|
3376
|
+
const preview = await resources.open(card);
|
|
3377
|
+
return [{
|
|
3378
|
+
type: "resource-preview",
|
|
3379
|
+
requestId: message.requestId,
|
|
3380
|
+
cardId: card.id,
|
|
3381
|
+
action: message.action ?? "open",
|
|
3382
|
+
id: preview.id,
|
|
3383
|
+
url: preview.url,
|
|
3384
|
+
card,
|
|
3385
|
+
project: preview.data.project
|
|
3386
|
+
}];
|
|
3387
|
+
}
|
|
3032
3388
|
if (kanbanController && !["ready", "login", "verify", "logout", "usage"].includes(message.type)) {
|
|
3033
3389
|
return kanbanController.handle(message);
|
|
3034
3390
|
}
|
|
@@ -3191,27 +3547,6 @@ ${token}`;
|
|
|
3191
3547
|
}
|
|
3192
3548
|
break;
|
|
3193
3549
|
}
|
|
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
3550
|
case "usage": {
|
|
3216
3551
|
if (!connection) {
|
|
3217
3552
|
throw new Error("Workflow Server login is not configured.");
|
|
@@ -3309,13 +3644,14 @@ ${token}`;
|
|
|
3309
3644
|
const host = new URL(`http://${request.headers.host ?? ""}`);
|
|
3310
3645
|
const forwarded = !!options.host && ["127.0.0.1", "localhost", "[::1]"].includes(host.hostname) && !host.username && !host.password && host.pathname === "/" && !host.search && !host.hash;
|
|
3311
3646
|
const url = new URL(request.url ?? "/", origin);
|
|
3312
|
-
const
|
|
3647
|
+
const resourceAsset = url.pathname.startsWith("/kanban/") || url.pathname.startsWith("/resource-assets/");
|
|
3648
|
+
const sandboxAsset = resourceAsset && ["GET", "HEAD"].includes(request.method ?? "") && request.headers.origin === "null";
|
|
3313
3649
|
if (request.headers.host !== new URL(origin).host && !forwarded || request.headers.origin && request.headers.origin !== (forwarded ? host.origin : origin) && !sandboxAsset) {
|
|
3314
3650
|
json(response, 403, { message: "Invalid request origin." });
|
|
3315
3651
|
return;
|
|
3316
3652
|
}
|
|
3317
|
-
if (
|
|
3318
|
-
const asset = await kanbanController
|
|
3653
|
+
if (resourceAsset && ["GET", "HEAD"].includes(request.method ?? "")) {
|
|
3654
|
+
const asset = await kanbanController?.preview.asset(url) ?? await resources.asset(url);
|
|
3319
3655
|
if (!asset) {
|
|
3320
3656
|
json(response, 404, { message: "Kanban asset not found." });
|
|
3321
3657
|
return;
|
|
@@ -3349,6 +3685,10 @@ ${token}`;
|
|
|
3349
3685
|
json(response, 200, { connected: true });
|
|
3350
3686
|
return;
|
|
3351
3687
|
}
|
|
3688
|
+
if (url.pathname === "/api/resource-state" && request.method === "GET") {
|
|
3689
|
+
json(response, 200, { type: "state", authenticationRevision: authRevision, resourceRevision: resources.revision });
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3352
3692
|
if (url.pathname === "/api/events" && request.method === "GET") {
|
|
3353
3693
|
const disconnectKanban = kanbanController?.methods.connect(url.searchParams.get("clientId") ?? "");
|
|
3354
3694
|
response.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
|
|
@@ -3370,7 +3710,7 @@ ${token}`;
|
|
|
3370
3710
|
const operation = () => handleMessage(message).then((value2) => {
|
|
3371
3711
|
messages = value2;
|
|
3372
3712
|
});
|
|
3373
|
-
const concurrent = ["resolve", "ready", "kanban-bridge", "kanban-method-state", "kanban-method-reply", "kanban-methods"].includes(message.type);
|
|
3713
|
+
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
3714
|
const task = concurrent ? operation() : mutations.catch(() => void 0).then(operation);
|
|
3375
3715
|
if (!concurrent) {
|
|
3376
3716
|
mutations = task;
|
|
@@ -3399,10 +3739,10 @@ ${token}`;
|
|
|
3399
3739
|
response.end();
|
|
3400
3740
|
return;
|
|
3401
3741
|
}
|
|
3402
|
-
if (url.pathname === "/") {
|
|
3742
|
+
if (url.pathname === "/" || /^\/resource\/[a-f\d-]{36}$/.test(url.pathname)) {
|
|
3403
3743
|
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";
|
|
3744
|
+
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'` });
|
|
3745
|
+
const ui = url.pathname !== "/" ? "resource-preview" : isKanban ? "kanban-workspace" : isWorkflow ? "workflow-workspace" : "workflow-ui";
|
|
3406
3746
|
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
3747
|
return;
|
|
3408
3748
|
}
|
|
@@ -3411,6 +3751,8 @@ ${token}`;
|
|
|
3411
3751
|
assets["/assets/workflow-workspace.css"] = "text/css";
|
|
3412
3752
|
assets["/assets/kanban-workspace.js"] = "application/javascript";
|
|
3413
3753
|
assets["/assets/kanban-workspace.css"] = "text/css";
|
|
3754
|
+
assets["/assets/resource-preview.js"] = "application/javascript";
|
|
3755
|
+
assets["/assets/resource-preview.css"] = "text/css";
|
|
3414
3756
|
if (!assets[url.pathname]) {
|
|
3415
3757
|
json(response, 404, { message: "Not found." });
|
|
3416
3758
|
return;
|
|
@@ -3453,10 +3795,19 @@ ${token}`;
|
|
|
3453
3795
|
kanbanController,
|
|
3454
3796
|
setHostRelatedProjects: (value) => {
|
|
3455
3797
|
relatedProjects = value;
|
|
3798
|
+
resources.clear();
|
|
3799
|
+
},
|
|
3800
|
+
setHostResourceProjects: (value) => {
|
|
3801
|
+
resourceProjects = value;
|
|
3802
|
+
resources.clear();
|
|
3456
3803
|
},
|
|
3457
3804
|
setHostConnection: async (value) => {
|
|
3805
|
+
if (hostManaged && JSON.stringify(value) === JSON.stringify(hostConnection)) {
|
|
3806
|
+
return;
|
|
3807
|
+
}
|
|
3458
3808
|
authEpoch++;
|
|
3459
3809
|
authCheck?.abort();
|
|
3810
|
+
resources.clear();
|
|
3460
3811
|
hostConnection = value;
|
|
3461
3812
|
hostManaged = true;
|
|
3462
3813
|
signedOut = !value;
|
|
@@ -3492,6 +3843,15 @@ import { createInterface } from "readline";
|
|
|
3492
3843
|
|
|
3493
3844
|
// src/web/host-config.ts
|
|
3494
3845
|
import path7 from "path";
|
|
3846
|
+
function parseHostResourceProjects(value) {
|
|
3847
|
+
if (value === void 0) {
|
|
3848
|
+
return [];
|
|
3849
|
+
}
|
|
3850
|
+
if (!Array.isArray(value) || value.length > 100 || value.some((directory) => typeof directory !== "string" || !path7.isAbsolute(directory))) {
|
|
3851
|
+
throw new Error("Invalid host resource projects.");
|
|
3852
|
+
}
|
|
3853
|
+
return [...new Set(value)];
|
|
3854
|
+
}
|
|
3495
3855
|
function parseHostRelatedProjects(value) {
|
|
3496
3856
|
if (value === void 0) {
|
|
3497
3857
|
return [];
|
|
@@ -3547,7 +3907,8 @@ function parseHostConfiguration(value) {
|
|
|
3547
3907
|
assistant,
|
|
3548
3908
|
assistantProjectDir,
|
|
3549
3909
|
connection: parseHostConnection(configuration.connection),
|
|
3550
|
-
relatedProjects: parseHostRelatedProjects(configuration.relatedProjects)
|
|
3910
|
+
relatedProjects: parseHostRelatedProjects(configuration.relatedProjects),
|
|
3911
|
+
...configuration.resourceProjects === void 0 ? {} : { resourceProjects: parseHostResourceProjects(configuration.resourceProjects) }
|
|
3551
3912
|
};
|
|
3552
3913
|
}
|
|
3553
3914
|
|
|
@@ -3659,6 +4020,9 @@ async function main(argv) {
|
|
|
3659
4020
|
});
|
|
3660
4021
|
} else if (message.type === "related-projects") {
|
|
3661
4022
|
app.setHostRelatedProjects(parseHostRelatedProjects(message.relatedProjects));
|
|
4023
|
+
if (message.resourceProjects !== void 0) {
|
|
4024
|
+
app.setHostResourceProjects(parseHostResourceProjects(message.resourceProjects));
|
|
4025
|
+
}
|
|
3662
4026
|
} else if (message.type === "close") {
|
|
3663
4027
|
break;
|
|
3664
4028
|
} else {
|