@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.
@@ -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 path5 from "path";
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 = path5.dirname(fileURLToPath(import.meta.url));
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(path5.join(directory, "package.json"), "utf8")).name === "@workflow-code/cli") {
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 = path5.dirname(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 = path5.join(root, "dist/web/worker.mjs");
2493
- const worker = existsSync(builtWorker) ? builtWorker : path5.join(root, "src/web/worker.mjs");
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: path5.join(storageDir, "runtime-auth")
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 sandboxAsset = !!kanbanController && url.pathname.startsWith("/kanban/") && ["GET", "HEAD"].includes(request.method ?? "") && request.headers.origin === "null";
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 (kanbanController && url.pathname.startsWith("/kanban/") && ["GET", "HEAD"].includes(request.method ?? "")) {
3318
- const asset = await kanbanController.preview.asset(url);
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'; object-src 'none'; base-uri 'none'; frame-ancestors ${ancestors}; form-action '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 {