@workflow-code/cli 0.2.4-20261012005.codex-studio-integration → 0.2.4-20261012007.codex-studio-integration

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12643,6 +12643,8 @@
12643
12643
  "Configuration name": "\u914D\u7F6E\u540D\u79F0",
12644
12644
  "Connecting to project": "\u6B63\u5728\u8FDE\u63A5\u9879\u76EE",
12645
12645
  "Connection changed.": "\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002",
12646
+ "Copy timed out.": "\u590D\u5236\u8D85\u65F6\u3002",
12647
+ "Host clipboard is unavailable.": "\u5BBF\u4E3B\u526A\u8D34\u677F\u4E0D\u53EF\u7528\u3002",
12646
12648
  "Connection interrupted": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD",
12647
12649
  "Connection interrupted.": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002",
12648
12650
  "Connection not verified": "\u8FDE\u63A5\u672A\u9A8C\u8BC1",
@@ -12883,7 +12885,33 @@
12883
12885
  "Write file": "\u5199\u5165\u6587\u4EF6",
12884
12886
  Yes: "\u662F",
12885
12887
  "Zoom in": "\u653E\u5927",
12886
- "Zoom out": "\u7F29\u5C0F"
12888
+ "Zoom out": "\u7F29\u5C0F",
12889
+ "Open resource": "\u6253\u5F00\u8D44\u6E90",
12890
+ "Show in conversation": "\u5728\u5BF9\u8BDD\u4E2D\u5C55\u793A",
12891
+ "Hide preview": "\u6536\u8D77\u9884\u89C8",
12892
+ "Open in new window": "\u5728\u65B0\u7A97\u53E3\u4E2D\u6253\u5F00",
12893
+ "Tool call details": "\u5DE5\u5177\u8C03\u7528\u8BE6\u60C5",
12894
+ "Please sign in to preview this server resource.": "\u8BF7\u767B\u5F55\u540E\u9884\u89C8\u670D\u52A1\u5668\u8D44\u6E90\u3002",
12895
+ "You do not have access to this resource.": "\u4F60\u6CA1\u6709\u6B64\u8D44\u6E90\u7684\u8BBF\u95EE\u6743\u9650\u3002",
12896
+ "The resource project is unavailable.": "\u8D44\u6E90\u6240\u5C5E\u9879\u76EE\u4E0D\u53EF\u7528\u3002",
12897
+ "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",
12898
+ "The project does not allow this storage location.": "\u9879\u76EE\u4E0D\u5141\u8BB8\u4F7F\u7528\u6B64\u5B58\u50A8\u4F4D\u7F6E\u3002",
12899
+ "This resource no longer exists.": "\u6B64\u8D44\u6E90\u5DF2\u4E0D\u5B58\u5728\u3002",
12900
+ "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",
12901
+ "File is outside this project.": "\u6587\u4EF6\u4E0D\u5728\u6B64\u9879\u76EE\u4E2D\u3002",
12902
+ "File preview exceeds 2 MiB.": "\u6587\u4EF6\u9884\u89C8\u5927\u5C0F\u8D85\u8FC7 2 MiB\u3002",
12903
+ "This project does not provide a Kanban preview.": "\u6B64\u9879\u76EE\u4E0D\u63D0\u4F9B\u770B\u677F\u9884\u89C8\u3002",
12904
+ "Invalid resource preview URL.": "\u8D44\u6E90\u9884\u89C8\u5730\u5740\u65E0\u6548\u3002",
12905
+ "This preview configuration no longer exists.": "\u6B64\u9884\u89C8\u914D\u7F6E\u5DF2\u4E0D\u5B58\u5728\u3002",
12906
+ "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",
12907
+ "This resource preview is read-only.": "\u6B64\u8D44\u6E90\u9884\u89C8\u4E3A\u53EA\u8BFB\u3002",
12908
+ "Invalid resource action.": "\u8D44\u6E90\u64CD\u4F5C\u65E0\u6548\u3002",
12909
+ "Run no longer exists.": "\u6B64\u8FD0\u884C\u5DF2\u4E0D\u5B58\u5728\u3002",
12910
+ "Conversation changed.": "\u4F1A\u8BDD\u5DF2\u53D8\u5316\u3002",
12911
+ "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",
12912
+ "Open in browser": "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00",
12913
+ "No resources": "\u6682\u65E0\u8D44\u6E90",
12914
+ "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"
12887
12915
  };
12888
12916
 
12889
12917
  // src/web/localization.ts
@@ -16138,7 +16166,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
16138
16166
  // src/web/resource-cards.ts
16139
16167
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
16140
16168
  var string = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
16141
- function conversationResourceCards(items, projectId, conversationId) {
16169
+ function conversationResourceCards(items, projectId, conversationId, storageSource = "local") {
16142
16170
  const cards = /* @__PURE__ */ new Map();
16143
16171
  for (const [index, item] of items.entries()) {
16144
16172
  if (!["agent-part", "opencode-part"].includes(String(item.metadata?.kind)) || item.metadata?.partType !== "tool") {
@@ -16201,6 +16229,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
16201
16229
  if (!kind2) {
16202
16230
  continue;
16203
16231
  }
16232
+ if (kind2 === "kv" || kind2 === "knowledge") {
16233
+ target2.source = input.source ?? storageSource;
16234
+ }
16204
16235
  descriptor = { kind: kind2, target: target2, title: record(output?.document)?.title };
16205
16236
  }
16206
16237
  const kind = descriptor.kind;
@@ -16209,7 +16240,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
16209
16240
  }
16210
16241
  const target = Object.fromEntries(Object.entries(record(descriptor.target) ?? {}).filter((entry) => typeof entry[1] === "string" && entry[1].length < 8192));
16211
16242
  if (target.source === "current") {
16212
- target.source = "local";
16243
+ target.source = kind === "kv" || kind === "knowledge" ? storageSource : "local";
16213
16244
  target.projectId = projectId;
16214
16245
  }
16215
16246
  if (target.source && !["local", "server"].includes(target.source)) {
@@ -16235,8 +16266,18 @@ Please report this to https://github.com/markedjs/marked.`, e) {
16235
16266
  }
16236
16267
  }
16237
16268
  const id = string(descriptor.cardId) ?? `${kind}:${JSON.stringify(target)}`;
16238
- const title = (string(descriptor.title) ?? target.key ?? target.documentId ?? target.filePath ?? target.url ?? target.projectId ?? kind).slice(0, 200);
16239
- cards.set(id, { id, kind, title, target, itemId: item.id ?? String(index) });
16269
+ const title = (string(descriptor.title) ?? string(record(output?.document)?.title) ?? target.key ?? target.documentId ?? target.filePath ?? target.url ?? target.projectId ?? kind).slice(0, 200);
16270
+ const kanban = record(descriptor.kanban), values = record(kanban?.values);
16271
+ cards.set(id, {
16272
+ id,
16273
+ kind,
16274
+ title,
16275
+ target,
16276
+ itemId: item.id ?? String(index),
16277
+ ...typeof descriptor.preview === "boolean" ? { preview: descriptor.preview } : {},
16278
+ ...typeof descriptor.pinned === "boolean" ? { pinned: descriptor.pinned } : {},
16279
+ ...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 } } : {}
16280
+ });
16240
16281
  }
16241
16282
  return [...cards.values()];
16242
16283
  }
@@ -16430,15 +16471,53 @@ ${JSON.stringify(output.details, null, 2)}` : "");
16430
16471
  this.root = root;
16431
16472
  this.options = options;
16432
16473
  root.className = "message-output";
16474
+ window.addEventListener("message", this.resourceReply);
16475
+ this.resourceTheme.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
16433
16476
  }
16434
16477
  root;
16435
16478
  options;
16436
16479
  views = /* @__PURE__ */ new Map();
16437
16480
  groups = /* @__PURE__ */ new Map();
16438
16481
  activities = /* @__PURE__ */ new Map();
16439
- update(message, projectId, conversationId, files) {
16482
+ resourceTheme = new MutationObserver(() => this.syncResourceTheme());
16483
+ syncResourceTheme() {
16484
+ for (const frame of this.root.querySelectorAll(".resource-inline > iframe")) {
16485
+ frame.contentWindow?.postMessage({ type: "workflow-host", theme: document.documentElement.dataset.theme }, location.origin);
16486
+ }
16487
+ }
16488
+ resourceReply = (event) => {
16489
+ const reply = event.data;
16490
+ if (event.source !== null || !reply?.requestId) {
16491
+ return;
16492
+ }
16493
+ const card = Array.from(this.root.querySelectorAll(".resource-card")).find((card2) => card2.dataset.requestId === reply.requestId);
16494
+ if (!card) {
16495
+ return;
16496
+ }
16497
+ const preview = card.querySelector(".resource-inline");
16498
+ if (reply.type === "error") {
16499
+ preview.textContent = reply.message;
16500
+ preview.setAttribute("role", "alert");
16501
+ }
16502
+ if (reply.type === "resource-preview" && reply.action === "inline") {
16503
+ const frame = document.createElement("iframe");
16504
+ frame.title = card.querySelector(".resource-title").textContent ?? "";
16505
+ const address = new URL(reply.url, location.href);
16506
+ address.searchParams.set("resourcePresentation", "card");
16507
+ frame.src = address.href;
16508
+ frame.sandbox.add("allow-scripts", "allow-same-origin");
16509
+ frame.addEventListener("load", () => this.syncResourceTheme());
16510
+ preview.replaceChildren(frame);
16511
+ preview.removeAttribute("role");
16512
+ }
16513
+ };
16514
+ dispose() {
16515
+ window.removeEventListener("message", this.resourceReply);
16516
+ this.resourceTheme.disconnect();
16517
+ }
16518
+ update(message, projectId, conversationId, files, storageSource = "local") {
16440
16519
  const items = message.items ?? [];
16441
- const cards = conversationResourceCards(items, projectId, conversationId);
16520
+ const cards = conversationResourceCards(items, projectId, conversationId, storageSource);
16442
16521
  const cardByItem = new Map(cards.map((card) => [card.itemId, card]));
16443
16522
  const answers = [...new Map([...message.resolvedInputs ?? [], ...message.submittingInput ? [message.submittingInput] : []].map((answer) => [answer.requestId, answer])).values()];
16444
16523
  const ordered = (message.timers ?? []).map((timer) => ({ key: `timer:${timer.timerId}`, timer }));
@@ -16458,14 +16537,18 @@ ${JSON.stringify(output.details, null, 2)}` : "");
16458
16537
  const blocks = [];
16459
16538
  for (const entry of ordered) {
16460
16539
  const content = entry.item?.content;
16461
- if (content?.tool === "workflow-ui_present_card" && !entry.card) {
16540
+ if (content?.tool === "workflow-ui_present_card" && content.status === "success" && !entry.card) {
16462
16541
  continue;
16463
16542
  }
16464
- if (entry.item && content?.tool !== "workflow-ui_present_card") {
16543
+ if (entry.item && (content?.tool !== "workflow-ui_present_card" || content.status !== "success")) {
16465
16544
  entry.activity = agentActivity(entry.item, message);
16466
16545
  }
16467
- if (entry.activity?.kind === "read" && !entry.card) {
16468
- entry.card = conversationResourceCards([entry.item], projectId, conversationId)[0];
16546
+ if (entry.item && content?.tool !== "workflow-ui_present_card" && !entry.card) {
16547
+ entry.card = conversationResourceCards([entry.item], projectId, conversationId, storageSource)[0];
16548
+ }
16549
+ if (entry.card && entry.card.kind !== "file") {
16550
+ blocks.push({ key: `resource:${entry.key}`, card: entry.card, item: entry.item });
16551
+ continue;
16469
16552
  }
16470
16553
  if (entry.activity) {
16471
16554
  const last = blocks.at(-1);
@@ -16492,7 +16575,7 @@ ${JSON.stringify(output.details, null, 2)}` : "");
16492
16575
  view.signature = signature;
16493
16576
  }
16494
16577
  } else if (!view || view.signature !== signature) {
16495
- const root = entry.answer ? this.answer(entry.answer, message.submittingInput?.requestId === entry.answer.requestId) : entry.timer ? this.timer(entry.timer) : activity ? this.activity(activity, entry.key, entry.card) : entry.card ? this.card(entry.card) : entry.item ? this.item(entry.item, files) : this.markdown(entry.text ?? "", files);
16578
+ const root = entry.answer ? this.answer(entry.answer, message.submittingInput?.requestId === entry.answer.requestId) : entry.timer ? this.timer(entry.timer) : activity ? this.activity(activity, entry.key, entry.card) : entry.card ? this.card(entry.card, entry.item) : entry.item ? this.item(entry.item, files) : this.markdown(entry.text ?? "", files);
16496
16579
  if (view?.root instanceof HTMLDetailsElement && root instanceof HTMLDetailsElement) {
16497
16580
  root.open = view.root.open;
16498
16581
  }
@@ -16826,13 +16909,67 @@ ${JSON.stringify(output.details, null, 2)}` : "");
16826
16909
  root.append(entry);
16827
16910
  return root;
16828
16911
  }
16829
- card(card) {
16912
+ card(card, item) {
16913
+ const root = document.createElement("section");
16914
+ root.className = "resource-card";
16915
+ root.dataset.cardId = card.id;
16916
+ const row = document.createElement("div");
16917
+ row.className = "resource-card-row";
16830
16918
  const button = document.createElement("button");
16831
- button.className = "resource-card";
16832
- button.title = card.title;
16833
- button.innerHTML = `${iconHtml(card.kind === "browser" ? "globe" : card.kind === "file" ? "file" : card.kind === "kv" ? "database" : card.kind === "knowledge" ? "book" : "folder")}<span>${escapeHtml(card.title)}</span>${iconHtml("arrow-up-right")}`;
16834
- button.addEventListener("click", () => this.options.resource(card));
16835
- return button;
16919
+ button.className = "resource-target";
16920
+ button.title = t2("Open resource");
16921
+ button.innerHTML = `${iconHtml(card.kind === "browser" ? "globe" : card.kind === "file" ? "file" : card.kind === "kv" ? "database" : card.kind === "knowledge" ? "book" : card.kind === "kanban" ? "layout" : "folder")}<span class="resource-title">${escapeHtml(card.title)}</span>`;
16922
+ button.addEventListener("click", () => this.options.resource(card, "open"));
16923
+ const preview = document.createElement("div");
16924
+ preview.className = "resource-inline";
16925
+ preview.hidden = true;
16926
+ const toggle = iconButton("eye", t2("Show in conversation"), () => {
16927
+ preview.hidden = !preview.hidden;
16928
+ toggle.setAttribute("aria-expanded", String(!preview.hidden));
16929
+ toggle.title = preview.hidden ? t2("Show in conversation") : t2("Hide preview");
16930
+ if (preview.hidden) {
16931
+ preview.replaceChildren();
16932
+ delete root.dataset.requestId;
16933
+ return;
16934
+ }
16935
+ const requestId = crypto.randomUUID();
16936
+ root.dataset.requestId = requestId;
16937
+ preview.textContent = t2("Loading...");
16938
+ preview.setAttribute("role", "status");
16939
+ this.options.resource(card, "inline", requestId);
16940
+ });
16941
+ toggle.setAttribute("aria-expanded", "false");
16942
+ toggle.dataset.action = "inline";
16943
+ const separate = iconButton("open-preview", t2("Open in new window"), () => this.options.resource(card, "window"));
16944
+ separate.dataset.action = "window";
16945
+ row.append(button, toggle, separate);
16946
+ root.append(row, preview);
16947
+ if (card.kind === "project" || card.kind === "kanban") {
16948
+ row.append(iconButton("folder-opened", t2("Open project"), () => this.options.resource(card, "project")));
16949
+ }
16950
+ if (item) {
16951
+ const details = document.createElement("div");
16952
+ details.className = "resource-call-details";
16953
+ details.hidden = true;
16954
+ const data = document.createElement("pre");
16955
+ data.textContent = JSON.stringify(item.content, null, 2);
16956
+ details.append(data);
16957
+ const inspect = iconButton("info", t2("Tool call details"), () => {
16958
+ details.hidden = !details.hidden;
16959
+ inspect.setAttribute("aria-expanded", String(!details.hidden));
16960
+ });
16961
+ inspect.setAttribute("aria-expanded", "false");
16962
+ row.append(inspect);
16963
+ root.append(details);
16964
+ }
16965
+ if (card.preview && ["project", "kanban", "browser"].includes(card.kind)) {
16966
+ queueMicrotask(() => {
16967
+ if (root.isConnected) {
16968
+ toggle.click();
16969
+ }
16970
+ });
16971
+ }
16972
+ return root;
16836
16973
  }
16837
16974
  };
16838
16975
 
@@ -17024,11 +17161,14 @@ ${state.user?.avatarUrl ?? ""}`;
17024
17161
  var ConnectionError = class extends Error {
17025
17162
  };
17026
17163
  var BrowserConnection = class {
17027
- constructor(emit) {
17164
+ constructor(emit, live = true) {
17028
17165
  this.emit = emit;
17166
+ this.live = live;
17029
17167
  if (this.token) {
17168
+ const query = new URLSearchParams(location.search);
17169
+ query.delete("session");
17030
17170
  sessionStorage.setItem("workflow-web-session", this.token);
17031
- history.replaceState(null, "", `${location.pathname}${location.search}`);
17171
+ history.replaceState(null, "", `${location.pathname}${query.size ? `?${query}` : ""}`);
17032
17172
  }
17033
17173
  queueMicrotask(() => {
17034
17174
  this.banner = document.createElement("div");
@@ -17062,6 +17202,7 @@ ${state.user?.avatarUrl ?? ""}`;
17062
17202
  });
17063
17203
  }
17064
17204
  emit;
17205
+ live;
17065
17206
  clientId = crypto.randomUUID();
17066
17207
  source;
17067
17208
  banner;
@@ -17069,10 +17210,11 @@ ${state.user?.avatarUrl ?? ""}`;
17069
17210
  handshake;
17070
17211
  epoch = 0;
17071
17212
  retry;
17213
+ snapshotTimer;
17072
17214
  status = "connecting";
17073
17215
  attempts = 0;
17074
17216
  closed = false;
17075
- token = new URLSearchParams(location.hash.slice(1)).get("session") ?? sessionStorage.getItem("workflow-web-session");
17217
+ token = new URLSearchParams(location.hash.slice(1)).get("session") ?? new URLSearchParams(location.search).get("session") ?? sessionStorage.getItem("workflow-web-session");
17076
17218
  update(status) {
17077
17219
  this.status = status;
17078
17220
  this.emit({ type: "connection", status });
@@ -17095,6 +17237,7 @@ ${state.user?.avatarUrl ?? ""}`;
17095
17237
  this.source?.close();
17096
17238
  this.source = void 0;
17097
17239
  clearTimeout(this.retry);
17240
+ clearTimeout(this.snapshotTimer);
17098
17241
  }
17099
17242
  interrupted() {
17100
17243
  this.disconnect();
@@ -17148,6 +17291,10 @@ ${state.user?.avatarUrl ?? ""}`;
17148
17291
  if (!session.ok) {
17149
17292
  throw new Error("Cannot connect to the project.");
17150
17293
  }
17294
+ if (!this.live) {
17295
+ await this.snapshot(epoch);
17296
+ return;
17297
+ }
17151
17298
  const eventsUrl = new URL(this.fileUrl("/api/events"), location.href);
17152
17299
  eventsUrl.searchParams.set("clientId", this.clientId);
17153
17300
  const source = this.source = new EventSource(eventsUrl.href);
@@ -17184,6 +17331,39 @@ ${state.user?.avatarUrl ?? ""}`;
17184
17331
  });
17185
17332
  return this.connecting;
17186
17333
  }
17334
+ async snapshot(epoch) {
17335
+ if (epoch !== this.epoch || this.closed) {
17336
+ return;
17337
+ }
17338
+ const response = await fetch("/api/resource-state", { headers: this.headers(), signal: AbortSignal.timeout(1e4) });
17339
+ if (epoch !== this.epoch || this.closed) {
17340
+ return;
17341
+ }
17342
+ if (response.status === 401) {
17343
+ this.disconnect();
17344
+ this.update("expired");
17345
+ return;
17346
+ }
17347
+ if (!response.ok) {
17348
+ throw new Error("Cannot connect to the project.");
17349
+ }
17350
+ const state = await response.json();
17351
+ if (epoch !== this.epoch || this.closed) {
17352
+ return;
17353
+ }
17354
+ if (this.status !== "connected") {
17355
+ this.attempts = 0;
17356
+ this.update("connected");
17357
+ }
17358
+ this.emit(state);
17359
+ this.snapshotTimer = setTimeout(() => {
17360
+ void this.snapshot(epoch).catch(() => {
17361
+ if (epoch === this.epoch && !this.closed) {
17362
+ this.interrupted();
17363
+ }
17364
+ });
17365
+ }, 5e3);
17366
+ }
17187
17367
  async request(message) {
17188
17368
  await this.connecting;
17189
17369
  if (this.status !== "connected") {
@@ -17229,7 +17409,12 @@ ${state.user?.avatarUrl ?? ""}`;
17229
17409
  return this.token ? { Authorization: `Bearer ${this.token}` } : {};
17230
17410
  }
17231
17411
  fileUrl(path) {
17232
- return this.token ? `${path}?session=${encodeURIComponent(this.token)}` : path;
17412
+ if (!this.token) {
17413
+ return path;
17414
+ }
17415
+ const url = new URL(path, location.href);
17416
+ url.searchParams.set("session", this.token);
17417
+ return `${url.pathname}${url.search}${url.hash}`;
17233
17418
  }
17234
17419
  publicFiles(value) {
17235
17420
  if (Array.isArray(value)) {
@@ -17386,8 +17571,71 @@ ${state.user?.avatarUrl ?? ""}`;
17386
17571
  }
17387
17572
  };
17388
17573
 
17574
+ // src/web/ui/clipboard-bridge.ts
17575
+ var ClipboardBridge = class {
17576
+ constructor(send, browserWrite, requiresHost = false) {
17577
+ this.send = send;
17578
+ this.browserWrite = browserWrite;
17579
+ this.requiresHost = requiresHost;
17580
+ }
17581
+ send;
17582
+ browserWrite;
17583
+ requiresHost;
17584
+ enabled = false;
17585
+ pending = /* @__PURE__ */ new Map();
17586
+ enable(version) {
17587
+ this.enabled = version === 1;
17588
+ }
17589
+ async writeText(text3) {
17590
+ if (!this.enabled) {
17591
+ if (this.requiresHost) {
17592
+ throw new Error(t2("Host clipboard is unavailable."));
17593
+ }
17594
+ return this.browserWrite(text3);
17595
+ }
17596
+ const requestId = crypto.randomUUID();
17597
+ return new Promise((resolve, reject) => {
17598
+ const timer = setTimeout(() => {
17599
+ this.pending.delete(requestId);
17600
+ reject(new Error(t2("Copy timed out.")));
17601
+ }, 1e4);
17602
+ this.pending.set(requestId, { resolve, reject, timer });
17603
+ try {
17604
+ this.send({ type: "workflow-clipboard", version: 1, command: "writeText", requestId, text: text3 });
17605
+ } catch (error) {
17606
+ clearTimeout(timer);
17607
+ this.pending.delete(requestId);
17608
+ reject(error);
17609
+ }
17610
+ });
17611
+ }
17612
+ receive(reply) {
17613
+ if (reply?.version !== 1 || typeof reply.requestId !== "string" || typeof reply.ok !== "boolean" || reply.error !== void 0 && typeof reply.error !== "string") {
17614
+ return;
17615
+ }
17616
+ const pending = this.pending.get(reply.requestId);
17617
+ if (!pending) {
17618
+ return;
17619
+ }
17620
+ this.pending.delete(reply.requestId);
17621
+ clearTimeout(pending.timer);
17622
+ if (reply.ok) {
17623
+ pending.resolve();
17624
+ } else {
17625
+ pending.reject(new Error(reply.error || t2("Host clipboard is unavailable.")));
17626
+ }
17627
+ }
17628
+ dispose() {
17629
+ for (const pending of this.pending.values()) {
17630
+ clearTimeout(pending.timer);
17631
+ pending.reject(new Error(t2("Connection changed.")));
17632
+ }
17633
+ this.pending.clear();
17634
+ }
17635
+ };
17636
+
17389
17637
  // src/web/ui/host.ts
17390
- function acquireWorkflowHost() {
17638
+ function acquireWorkflowHost(options = {}) {
17391
17639
  const embedded = globalThis.acquireVsCodeApi;
17392
17640
  if (embedded) {
17393
17641
  return embedded();
@@ -17407,13 +17655,20 @@ ${state.user?.avatarUrl ?? ""}`;
17407
17655
  window.parent.postMessage(message, "*");
17408
17656
  }
17409
17657
  });
17658
+ let resourceHost = false;
17659
+ const clipboard = new ClipboardBridge(
17660
+ (message) => window.parent.postMessage(message, "*"),
17661
+ (text3) => navigator.clipboard.writeText(text3),
17662
+ window.parent !== window && document.body.dataset.accountHost === "true"
17663
+ );
17664
+ addEventListener("pagehide", () => clipboard.dispose());
17410
17665
  const connection = new BrowserConnection((message) => {
17411
17666
  if (message.type === "state" && message.account) {
17412
17667
  accountPanel.update(message.account);
17413
17668
  }
17414
17669
  trace.receive(message);
17415
17670
  emit(message);
17416
- });
17671
+ }, options.live);
17417
17672
  const request = async (message) => {
17418
17673
  for (const entry of await connection.request(message)) {
17419
17674
  handleReply(entry);
@@ -17429,6 +17684,16 @@ ${state.user?.avatarUrl ?? ""}`;
17429
17684
  if (event.data.traceVersion !== void 0) {
17430
17685
  trace.enable(event.data.traceVersion);
17431
17686
  }
17687
+ if (event.data.clipboardVersion !== void 0) {
17688
+ clipboard.enable(event.data.clipboardVersion);
17689
+ }
17690
+ if (event.data.resourceVersion === 1) {
17691
+ resourceHost = true;
17692
+ }
17693
+ if (event.data.resourceError) {
17694
+ emit(event.data.resourceError);
17695
+ }
17696
+ clipboard.receive(event.data.clipboard);
17432
17697
  const traceCommand = trace.command(event.data.traceCommand);
17433
17698
  if (traceCommand) {
17434
17699
  void request(traceCommand).catch((error) => trace.receive({ type: "trace-run", runId: traceCommand.id, error: error.message }));
@@ -17445,7 +17710,14 @@ ${state.user?.avatarUrl ?? ""}`;
17445
17710
  }
17446
17711
  return;
17447
17712
  }
17448
- if (reply.type === "download") {
17713
+ if (reply.type === "resource-preview") {
17714
+ reply.url = connection.fileUrl(reply.url);
17715
+ if (reply.action === "inline") {
17716
+ emit(reply);
17717
+ } else {
17718
+ window.open(reply.url, "_blank", "noopener");
17719
+ }
17720
+ } else if (reply.type === "download") {
17449
17721
  const url = URL.createObjectURL(new Blob([reply.content], { type: reply.mimeType ?? "application/json" }));
17450
17722
  const link = document.createElement("a");
17451
17723
  link.href = url;
@@ -17513,6 +17785,29 @@ ${state.user?.avatarUrl ?? ""}`;
17513
17785
  if (message.type === "ready") {
17514
17786
  return;
17515
17787
  }
17788
+ if (message.type === "resource" && message.action !== "inline" && resourceHost) {
17789
+ window.parent.postMessage({ type: "workflow-resource", version: 1, request: message }, "*");
17790
+ return;
17791
+ }
17792
+ if (message.type === "resource" && message.action !== "inline") {
17793
+ const popup = window.open("", "_blank");
17794
+ try {
17795
+ const reply = (await connection.request(message)).find((reply2) => reply2.type === "resource-preview");
17796
+ if (reply && popup) {
17797
+ popup.opener = null;
17798
+ popup.location.href = connection.fileUrl(reply.url);
17799
+ } else {
17800
+ popup?.close();
17801
+ if (reply) {
17802
+ emit({ type: "error", message: t2("Allow popups to open this resource in a new window.") });
17803
+ }
17804
+ }
17805
+ } catch (error) {
17806
+ popup?.close();
17807
+ emit({ type: "error", requestId: message.requestId, message: error.message });
17808
+ }
17809
+ return;
17810
+ }
17516
17811
  if (message.type === "trace") {
17517
17812
  if (trace.open(message)) {
17518
17813
  return;
@@ -17522,8 +17817,13 @@ ${state.user?.avatarUrl ?? ""}`;
17522
17817
  }
17523
17818
  }
17524
17819
  if (message.type === "copy") {
17525
- void navigator.clipboard.writeText(message.text).catch((error) => emit({ type: "error", message: error.message }));
17526
- return;
17820
+ try {
17821
+ await clipboard.writeText(message.text);
17822
+ } catch (error) {
17823
+ emit({ type: "error", message: error.message });
17824
+ return false;
17825
+ }
17826
+ return true;
17527
17827
  }
17528
17828
  if (message.type === "attach") {
17529
17829
  chooseFiles(message);
@@ -18249,8 +18549,10 @@ ${state.user?.avatarUrl ?? ""}`;
18249
18549
  error.textContent = message;
18250
18550
  error.hidden = false;
18251
18551
  }
18252
- function copy2(text3, button) {
18253
- vscode.postMessage({ type: "copy", text: text3 });
18552
+ async function copy2(text3, button) {
18553
+ if (await vscode.postMessage({ type: "copy", text: text3 }) === false) {
18554
+ return;
18555
+ }
18254
18556
  button.innerHTML = iconHtml("check");
18255
18557
  button.title = t2("Copied");
18256
18558
  setTimeout(() => {
@@ -18285,7 +18587,7 @@ ${state.user?.avatarUrl ?? ""}`;
18285
18587
  plan: (text3) => dialogs.plan(text3),
18286
18588
  file: (id) => post({ type: "open-file", id }),
18287
18589
  image: (src, title) => dialogs.image(src, title),
18288
- resource: (card) => post({ type: "resource", messageId: view.message.id, cardId: card.id })
18590
+ resource: (card, action, requestId2) => post({ type: "resource", messageId: view.message.id, cardId: card.id, action, requestId: requestId2 })
18289
18591
  });
18290
18592
  const actions = root.querySelector(".message-actions");
18291
18593
  actions.prepend(iconButton("history", t2("Trace"), () => post({ type: "trace", messageId: view.message.id, runId: view.message.runId })));
@@ -18316,7 +18618,7 @@ ${state.user?.avatarUrl ?? ""}`;
18316
18618
  if (message.role === "user") {
18317
18619
  view.content.textContent = message.content;
18318
18620
  } else {
18319
- view.output.update(message, state.project.id, state.activeId, (currentSession()?.messages ?? []).flatMap((entry) => [...entry.files ?? [], ...entry.attachments ?? []]));
18621
+ view.output.update(message, state.project.id, state.activeId, (currentSession()?.messages ?? []).flatMap((entry) => [...entry.files ?? [], ...entry.attachments ?? []]), state.project.projectInfo?.dataStorage?.mode === "server" ? "server" : "local");
18320
18622
  }
18321
18623
  view.error.textContent = message.error ?? "";
18322
18624
  view.error.hidden = !message.error;
@@ -18407,6 +18709,9 @@ ${state.user?.avatarUrl ?? ""}`;
18407
18709
  draftFiles = session.draft?.attachments ?? [];
18408
18710
  editingQueueId = session.draft?.editingQueueId;
18409
18711
  renderedSession = next.activeId;
18712
+ for (const view of messages.values()) {
18713
+ view.output?.dispose();
18714
+ }
18410
18715
  messages.clear();
18411
18716
  transcript.replaceChildren();
18412
18717
  document.querySelector("#error").hidden = true;
@@ -18449,6 +18754,7 @@ ${state.user?.avatarUrl ?? ""}`;
18449
18754
  });
18450
18755
  for (const [id, view] of messages) {
18451
18756
  if (!active.has(id)) {
18757
+ view.output?.dispose();
18452
18758
  view.root.remove();
18453
18759
  messages.delete(id);
18454
18760
  }
@@ -1072,23 +1072,89 @@ main {
1072
1072
  margin-top: 12px;
1073
1073
  }
1074
1074
  .resource-card {
1075
- display: flex;
1076
- align-items: center;
1077
- gap: 8px;
1075
+ display: block;
1076
+ width: 100%;
1078
1077
  min-width: 0;
1079
- max-width: 100%;
1080
- min-height: 36px;
1081
- padding: 6px 10px;
1078
+ margin: 8px 0;
1082
1079
  border: 1px solid var(--border);
1083
1080
  border-radius: 6px;
1084
1081
  background: var(--vscode-editor-background);
1082
+ overflow: hidden;
1083
+ container-type: inline-size;
1084
+ }
1085
+ .resource-card-row {
1086
+ display: flex;
1087
+ align-items: center;
1088
+ gap: 4px;
1089
+ min-height: 44px;
1090
+ padding: 4px 8px;
1091
+ }
1092
+ .resource-target {
1093
+ display: flex;
1094
+ align-items: center;
1095
+ gap: 10px;
1096
+ flex: 1;
1097
+ min-width: 0;
1098
+ min-height: 36px;
1099
+ padding: 6px 4px;
1100
+ border: 0;
1101
+ background: transparent;
1085
1102
  text-align: left;
1086
1103
  }
1087
- .resource-card > span:not(.codicon) {
1104
+ .resource-target > .codicon {
1105
+ flex: none;
1106
+ color: var(--muted);
1107
+ }
1108
+ .resource-title {
1088
1109
  min-width: 0;
1089
- overflow: hidden;
1090
- text-overflow: ellipsis;
1091
- white-space: nowrap;
1110
+ white-space: normal;
1111
+ overflow-wrap: anywhere;
1112
+ line-height: 1.5;
1113
+ }
1114
+ .resource-card-row > .icon-button {
1115
+ flex: none;
1116
+ width: 28px;
1117
+ height: 28px;
1118
+ }
1119
+ @container (max-width: 280px) {
1120
+ .resource-card-row {
1121
+ flex-wrap: wrap;
1122
+ }
1123
+ .resource-target {
1124
+ flex-basis: 100%;
1125
+ }
1126
+ }
1127
+ .resource-inline {
1128
+ height: 360px;
1129
+ max-height: 70vh;
1130
+ min-height: 160px;
1131
+ border-top: 1px solid var(--border);
1132
+ overflow: auto;
1133
+ }
1134
+ .resource-inline[role] {
1135
+ padding: 12px;
1136
+ color: var(--muted);
1137
+ }
1138
+ .resource-inline[role=alert] {
1139
+ color: var(--vscode-errorForeground);
1140
+ }
1141
+ .resource-inline iframe {
1142
+ display: block;
1143
+ width: 100%;
1144
+ height: 100%;
1145
+ border: 0;
1146
+ }
1147
+ .resource-call-details {
1148
+ padding: 8px 12px;
1149
+ border-top: 1px solid var(--border);
1150
+ max-height: 320px;
1151
+ overflow: auto;
1152
+ }
1153
+ .resource-call-details pre {
1154
+ margin: 0;
1155
+ font-size: 11px;
1156
+ white-space: pre-wrap;
1157
+ overflow-wrap: anywhere;
1092
1158
  }
1093
1159
  .input-prompt {
1094
1160
  margin: 16px 0;