@workflow-code/cli 0.2.4-20261011009.codex-cli-web → 0.2.4-20261012001

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  assertLocalCliCoreCompatible
4
- } from "./chunk-WN7VAGK6.js";
4
+ } from "./chunk-5DWX3LL6.js";
5
5
  import {
6
6
  assertKanbanMethodJson,
7
7
  consumeKanbanRelatedProjectRevisionStream,
@@ -21,12 +21,13 @@ import {
21
21
  sanitizeKanbanValues
22
22
  } from "./chunk-HLMW6LAQ.js";
23
23
  import "./chunk-J2JMC4MZ.js";
24
- import "./chunk-MNY562W5.js";
24
+ import "./chunk-5FPIZZE4.js";
25
25
  import {
26
26
  clearCliAuthConfig,
27
+ createEmptyWorkflowAuthConfig,
27
28
  readCliAuthConfig,
28
29
  writeCliAuthConfig
29
- } from "./chunk-PGM3IPWA.js";
30
+ } from "./chunk-SGVXPYCT.js";
30
31
  import {
31
32
  isWorkflowProjectDataStorageLocationAllowed,
32
33
  readWorkflowProjectInfo,
@@ -34,7 +35,9 @@ import {
34
35
  } from "./chunk-P6K3NB5S.js";
35
36
  import {
36
37
  getCurrentLocale,
37
- i18n
38
+ i18n,
39
+ initI18n,
40
+ normalizeLocale
38
41
  } from "./chunk-7WEXRL5A.js";
39
42
  import "./chunk-NSPRIPOP.js";
40
43
 
@@ -42,6 +45,348 @@ import "./chunk-NSPRIPOP.js";
42
45
  import { spawn as spawn3 } from "child_process";
43
46
  import path8 from "path";
44
47
 
48
+ // src/web/messages.zh-cn.json
49
+ var messages_zh_cn_default = {
50
+ " and others": "\u7B49",
51
+ ", ": "\u3001",
52
+ "{0}": "\u5DF2{0}",
53
+ "{0} \xB7 {1}/{2} items": "{0} \xB7 {1}/{2} \u9879",
54
+ "{0} days": "{0} \u5929",
55
+ "{0} failed": "{0}\u5931\u8D25",
56
+ "{0} hours": "{0} \u5C0F\u65F6",
57
+ "{0} min": "{0} \u5206",
58
+ "{0} must be a valid boolean.": "{0} \u9700\u8981\u662F\u6709\u6548\u5E03\u5C14\u503C\u3002",
59
+ "{0} must be a valid number.": "{0} \u9700\u8981\u662F\u6709\u6548\u6570\u5B57\u3002",
60
+ "{0} must be valid JSON.": "{0} \u9700\u8981\u662F\u6709\u6548\u7684 JSON\u3002",
61
+ "{0} selected": "\u5DF2\u9009 {0} \u9879",
62
+ "{0} tools": "{0} \u4E2A\u5DE5\u5177",
63
+ "{0}...": "\u6B63\u5728{0}",
64
+ "{0}{1} \xB7 {2} items": "\u5DF2{0}{1} \xB7 {2} \u9879",
65
+ "{1} in {0}": "{0} \u4E2D {1}",
66
+ "Account balance ${0}": "\u8D26\u6237\u4F59\u989D ${0}",
67
+ "Add attachment": "\u6DFB\u52A0\u9644\u4EF6",
68
+ "Agent run": "Agent \u8FD0\u884C",
69
+ "All chats": "\u5168\u90E8\u804A\u5929",
70
+ "All conversations": "\u5168\u90E8\u5BF9\u8BDD",
71
+ Apply: "\u5E94\u7528",
72
+ "Apply changes": "\u5E94\u7528\u4FEE\u6539",
73
+ "Approve execution": "\u6279\u51C6\u6267\u884C",
74
+ Ask: "\u63D0\u95EE",
75
+ Attachment: "\u9644\u4EF6",
76
+ "Auto-approve": "\u81EA\u52A8\u6279\u51C6",
77
+ "Awaiting confirmation": "\u7B49\u5F85\u786E\u8BA4",
78
+ "Awaiting confirmation: {0}": "\u7B49\u5F85\u786E\u8BA4{0}",
79
+ "Back to {0}": "\u8FD4\u56DE {0}",
80
+ Branch: "\u5206\u652F",
81
+ "Browser sign-in": "\u6D4F\u89C8\u5668\u767B\u5F55",
82
+ "Call tool": "\u8C03\u7528\u5DE5\u5177",
83
+ Cancel: "\u53D6\u6D88",
84
+ "Cancel editing": "\u53D6\u6D88\u7F16\u8F91",
85
+ Card: "\u5361\u7247",
86
+ Chat: "\u804A\u5929",
87
+ "Choose files": "\u9009\u62E9\u6587\u4EF6",
88
+ Close: "\u5173\u95ED",
89
+ "Close account": "\u5173\u95ED\u8D26\u53F7",
90
+ "Close conversation history": "\u5173\u95ED\u5BF9\u8BDD\u5386\u53F2",
91
+ "Close parameters": "\u5173\u95ED\u53C2\u6570",
92
+ "Close search": "\u5173\u95ED\u641C\u7D22",
93
+ Code: "\u4EE3\u7801",
94
+ Command: "\u547D\u4EE4",
95
+ "Compact context": "\u538B\u7F29\u4E0A\u4E0B\u6587",
96
+ Complete: "\u5B8C\u6210",
97
+ Completed: "\u5DF2\u5B8C\u6210",
98
+ "Configuration {0}": "\u914D\u7F6E {0}",
99
+ "Configuration actions": "\u914D\u7F6E\u64CD\u4F5C",
100
+ "Configuration name": "\u914D\u7F6E\u540D\u79F0",
101
+ "Connecting to project": "\u6B63\u5728\u8FDE\u63A5\u9879\u76EE",
102
+ "Connection changed.": "\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002",
103
+ "Copy timed out.": "\u590D\u5236\u8D85\u65F6\u3002",
104
+ "Host clipboard is unavailable.": "\u5BBF\u4E3B\u526A\u8D34\u677F\u4E0D\u53EF\u7528\u3002",
105
+ "Connection interrupted": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD",
106
+ "Connection interrupted.": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002",
107
+ "Connection not verified": "\u8FDE\u63A5\u672A\u9A8C\u8BC1",
108
+ Conversation: "\u5BF9\u8BDD",
109
+ "Conversation actions": "\u5BF9\u8BDD\u64CD\u4F5C",
110
+ "Conversation history": "\u5BF9\u8BDD\u5386\u53F2",
111
+ "Conversation messages": "\u5BF9\u8BDD\u6D88\u606F",
112
+ "Conversation name": "\u5BF9\u8BDD\u540D\u79F0",
113
+ Conversations: "\u5BF9\u8BDD\u5217\u8868",
114
+ Copied: "\u5DF2\u590D\u5236",
115
+ Copy: "\u590D\u5236",
116
+ "Copy {0}": "\u590D\u5236{0}",
117
+ "Copy code": "\u590D\u5236\u4EE3\u7801",
118
+ "Copy message": "\u590D\u5236\u6D88\u606F",
119
+ "Current configuration": "\u5F53\u524D\u914D\u7F6E",
120
+ Daily: "\u6BCF\u65E5",
121
+ Default: "\u9ED8\u8BA4",
122
+ Delete: "\u5220\u9664",
123
+ "Delete conversation": "\u5220\u9664\u5BF9\u8BDD",
124
+ "Delete record": "\u5220\u9664\u8BB0\u5F55",
125
+ "Delete this configuration?": "\u5220\u9664\u8FD9\u4E2A\u914D\u7F6E\uFF1F",
126
+ "Delete this conversation?": "\u5220\u9664\u8FD9\u4E2A\u5BF9\u8BDD\uFF1F",
127
+ "Delete this run?": "\u5220\u9664\u8FD9\u6761\u8FD0\u884C\u8BB0\u5F55\uFF1F",
128
+ Deny: "\u62D2\u7EDD",
129
+ Details: "\u8BE6\u60C5",
130
+ "Download file": "\u4E0B\u8F7D\u6587\u4EF6",
131
+ "Duration {0}{1}s": "\u7528\u65F6 {0}{1}s",
132
+ Edit: "\u7F16\u8F91",
133
+ "Edit file": "\u7F16\u8F91\u6587\u4EF6",
134
+ "Edit queued message": "\u7F16\u8F91\u5F85\u53D1\u6D88\u606F",
135
+ Email: "\u90AE\u7BB1",
136
+ Enable: "\u542F\u7528",
137
+ "End run": "\u7ED3\u675F\u8FD0\u884C",
138
+ "Ended: {0}": "\u7ED3\u675F\uFF1A{0}",
139
+ "Enter your email and password.": "\u8BF7\u8F93\u5165\u90AE\u7BB1\u548C\u5BC6\u7801\u3002",
140
+ "Entrypoint parameters": "\u5165\u53E3\u53C2\u6570",
141
+ Entrypoints: "\u8FD0\u884C\u5165\u53E3",
142
+ Error: "\u9519\u8BEF",
143
+ Execute: "\u6267\u884C",
144
+ "Execute plan": "\u6309\u8BA1\u5212\u6267\u884C",
145
+ "Export conversation": "\u5BFC\u51FA\u5BF9\u8BDD",
146
+ "Export record": "\u5BFC\u51FA\u8BB0\u5F55",
147
+ "Export run": "\u5BFC\u51FA\u8FD0\u884C\u8BB0\u5F55",
148
+ Failed: "\u5931\u8D25",
149
+ "Failed to load Kanban": "\u770B\u677F\u52A0\u8F7D\u5931\u8D25",
150
+ File: "\u6587\u4EF6",
151
+ "File exceeds 50 MiB.": "\u6587\u4EF6\u8D85\u8FC7 50 MiB\u3002",
152
+ "Files in {0}": "{0} \u4E2D\u7684\u6587\u4EF6",
153
+ "Fit graph": "\u9002\u914D\u6D41\u7A0B",
154
+ Form: "\u8868\u5355",
155
+ "Full screen": "\u5168\u5C4F",
156
+ "Generated file": "\u751F\u6210\u6587\u4EF6",
157
+ "Go to latest message": "\u56DE\u5230\u6700\u65B0\u6D88\u606F",
158
+ Graph: "\u6D41\u7A0B",
159
+ Height: "\u9AD8\u5EA6",
160
+ "Hide conversations": "\u9690\u85CF\u5BF9\u8BDD\u5217\u8868",
161
+ "Hide Kanban configurations": "\u9690\u85CF\u770B\u677F\u914D\u7F6E",
162
+ "Hide password": "\u9690\u85CF\u5BC6\u7801",
163
+ "Hide runs": "\u9690\u85CF\u8FD0\u884C\u8BB0\u5F55",
164
+ Image: "\u56FE\u7247",
165
+ "Incorrect email or password, or sign-in expired.": "\u90AE\u7BB1\u6216\u5BC6\u7801\u4E0D\u6B63\u786E\uFF0C\u6216\u767B\u5F55\u5DF2\u5931\u6548\u3002",
166
+ Interrupted: "\u5DF2\u4E2D\u65AD",
167
+ "JSON argument array": "JSON \u53C2\u6570\u6570\u7EC4",
168
+ "Just now": "\u521A\u521A",
169
+ Kanban: "\u770B\u677F",
170
+ "Kanban configurations": "\u770B\u677F\u914D\u7F6E",
171
+ List: "\u5217\u51FA",
172
+ "List files": "\u5217\u51FA\u6587\u4EF6",
173
+ "Load more": "\u52A0\u8F7D\u66F4\u591A",
174
+ "Loading Kanban": "\u6B63\u5728\u52A0\u8F7D\u770B\u677F",
175
+ "Loading parameters...": "\u52A0\u8F7D\u53C2\u6570...",
176
+ "Loading...": "\u52A0\u8F7D\u4E2D...",
177
+ "Lock title": "\u9501\u5B9A\u6807\u9898",
178
+ Logs: "\u65E5\u5FD7",
179
+ Loop: "\u5FAA\u73AF",
180
+ "Loop end": "\u5FAA\u73AF\u7ED3\u675F",
181
+ "Managed by server": "\u7531\u670D\u52A1\u5668\u7BA1\u7406",
182
+ "Manage this account through Studio.": "\u8BF7\u5728 Studio \u4E2D\u7BA1\u7406\u6B64\u8D26\u53F7\u3002",
183
+ Merge: "\u6C47\u5408",
184
+ Message: "\u6D88\u606F",
185
+ Monthly: "\u6BCF\u6708",
186
+ "Move up": "\u4E0A\u79FB",
187
+ "New configuration": "\u65B0\u5EFA\u914D\u7F6E",
188
+ "New conversation": "\u65B0\u5BF9\u8BDD",
189
+ "New run": "\u65B0\u5EFA\u8FD0\u884C",
190
+ Next: "\u4E0B\u4E00\u4E2A",
191
+ "Next question": "\u4E0B\u4E00\u95EE",
192
+ No: "\u5426",
193
+ "No active subscriptions": "\u6682\u65E0\u5DF2\u8BA2\u9605\u5957\u9910",
194
+ "No additional details": "\u6682\u65E0\u989D\u5916\u8BE6\u60C5",
195
+ "No available options": "\u6682\u65E0\u53EF\u7528\u9009\u9879",
196
+ "No conversations": "\u6682\u65E0\u5BF9\u8BDD",
197
+ "No logs": "\u6682\u65E0\u65E5\u5FD7",
198
+ "No matching conversations": "\u6CA1\u6709\u5339\u914D\u7684\u5BF9\u8BDD",
199
+ "No options": "\u6682\u65E0\u9009\u9879",
200
+ "No page methods registered": "\u9875\u9762\u6682\u672A\u6CE8\u518C\u65B9\u6CD5",
201
+ "No runs": "\u6682\u65E0\u8FD0\u884C\u8BB0\u5F55",
202
+ "No static workflow structure is available. Showing recorded execution nodes.": "\u672A\u89E3\u6790\u5230\u9759\u6001\u6D41\u7A0B\u7ED3\u6784\uFF0C\u663E\u793A\u5DF2\u6709\u8FD0\u884C\u8282\u70B9\u3002",
203
+ "No tools declared by this project": "\u9879\u76EE\u672A\u58F0\u660E\u5DE5\u5177",
204
+ "Node {0} does not match the static structure.": "\u8282\u70B9 {0} \u672A\u5339\u914D\u9759\u6001\u7ED3\u6784\u3002",
205
+ "Not selected": "\u672A\u9009\u62E9",
206
+ "Open {0}": "\u6253\u5F00 {0}",
207
+ "Page methods": "\u9875\u9762\u65B9\u6CD5",
208
+ "Parameter mode": "\u53C2\u6570\u6A21\u5F0F",
209
+ Parameters: "\u53C2\u6570",
210
+ "Parameters JSON": "\u53C2\u6570 JSON",
211
+ Password: "\u5BC6\u7801",
212
+ "Pending approval": "\u5F85\u786E\u8BA4",
213
+ Phone: "\u624B\u673A",
214
+ Plan: "\u8BA1\u5212",
215
+ "Please confirm {0}": "\u8BF7\u786E\u8BA4 {0}",
216
+ "Please enter {0}.": "\u8BF7\u586B\u5199 {0}\u3002",
217
+ "Preview size": "\u9884\u89C8\u5C3A\u5BF8",
218
+ Previous: "\u4E0A\u4E00\u4E2A",
219
+ "Previous question": "\u4E0A\u4E00\u95EE",
220
+ "Project connection expired": "\u9879\u76EE\u8FDE\u63A5\u5DF2\u5931\u6548",
221
+ "Project connection expired.": "\u9879\u76EE\u8FDE\u63A5\u5DF2\u5931\u6548\u3002",
222
+ "Queued messages ({0})": "\u5F85\u53D1\u6D88\u606F ({0})",
223
+ Read: "\u8BFB\u53D6",
224
+ "Read data": "\u8BFB\u53D6\u6570\u636E",
225
+ "Read file": "\u8BFB\u53D6\u6587\u4EF6",
226
+ "Read knowledge": "\u8BFB\u53D6\u77E5\u8BC6",
227
+ "Read web page": "\u8BFB\u53D6\u7F51\u9875",
228
+ Reasoning: "\u601D\u8003\u8FC7\u7A0B",
229
+ Reconnect: "\u91CD\u65B0\u8FDE\u63A5",
230
+ "Reconnecting...": "\u6B63\u5728\u91CD\u65B0\u8FDE\u63A5...",
231
+ "Reconnection will start when the network is available.": "\u7F51\u7EDC\u6062\u590D\u540E\u5C06\u81EA\u52A8\u91CD\u8FDE\u3002",
232
+ "Refresh Kanban": "\u5237\u65B0\u770B\u677F",
233
+ "Refresh parameters": "\u5237\u65B0\u53C2\u6570",
234
+ "Refresh usage": "\u5237\u65B0\u7528\u91CF",
235
+ "Related projects are unavailable: {0}": "\u5173\u8054\u9879\u76EE\u4E0D\u53EF\u7528\uFF1A{0}",
236
+ "Remove attachment": "\u79FB\u9664\u9644\u4EF6",
237
+ "Remove file": "\u79FB\u9664\u6587\u4EF6",
238
+ Rename: "\u91CD\u547D\u540D",
239
+ "Rename configuration": "\u91CD\u547D\u540D\u914D\u7F6E",
240
+ "Rename conversation": "\u91CD\u547D\u540D\u5BF9\u8BDD",
241
+ "Reopen using the project's run URL.": "\u8BF7\u4F7F\u7528\u9879\u76EE\u8FD0\u884C\u5730\u5740\u91CD\u65B0\u6253\u5F00\u3002",
242
+ Rerun: "\u91CD\u65B0\u8FD0\u884C",
243
+ Reset: "\u91CD\u7F6E",
244
+ "Reset options": "\u91CD\u7F6E\u9009\u9879",
245
+ "Resize preview": "\u8C03\u6574\u9884\u89C8\u5C3A\u5BF8",
246
+ Responding: "\u56DE\u590D\u4E2D",
247
+ "Responding...": "\u6B63\u5728\u56DE\u590D...",
248
+ Result: "\u7ED3\u679C",
249
+ "Resume queue": "\u7EE7\u7EED\u53D1\u9001\u961F\u5217",
250
+ "Resume run": "\u7EE7\u7EED\u8FD0\u884C",
251
+ Retry: "\u91CD\u8BD5",
252
+ "Retry loading parameters": "\u91CD\u8BD5\u52A0\u8F7D\u53C2\u6570",
253
+ "Retry save": "\u91CD\u8BD5\u4FDD\u5B58",
254
+ "Revise instructions": "\u4FEE\u6539\u8981\u6C42",
255
+ "Revise plan": "\u4FEE\u6539\u8BA1\u5212",
256
+ Run: "\u8FD0\u884C",
257
+ "Run arguments must be an array of strings.": "\u8FD0\u884C\u53C2\u6570\u9700\u8981\u662F\u5B57\u7B26\u4E32\u6570\u7EC4\u3002",
258
+ "Run command": "\u8FD0\u884C\u547D\u4EE4",
259
+ "Run failed": "\u8FD0\u884C\u5931\u8D25",
260
+ "Run interrupted": "\u8FD0\u884C\u5DF2\u4E2D\u65AD",
261
+ "Run logs": "\u8FD0\u884C\u65E5\u5FD7",
262
+ "Run parameters": "\u8FD0\u884C\u53C2\u6570",
263
+ "Run records \xB7 {0} items, {1} failed": "\u8FD0\u884C\u8BB0\u5F55 \xB7 {0} \u9879\uFF0C{1} \u9879\u5931\u8D25",
264
+ "Run subtask": "\u8FD0\u884C\u5B50\u4EFB\u52A1",
265
+ "Run timed out": "\u8FD0\u884C\u8D85\u65F6",
266
+ Running: "\u8FD0\u884C\u4E2D",
267
+ "Running...": "\u6B63\u5728\u8FD0\u884C...",
268
+ Runs: "\u8FD0\u884C\u8BB0\u5F55",
269
+ Save: "\u4FDD\u5B58",
270
+ "Save queued message": "\u4FDD\u5B58\u5F85\u53D1\u6D88\u606F",
271
+ Search: "\u641C\u7D22",
272
+ "Search all conversations": "\u641C\u7D22\u5168\u90E8\u5BF9\u8BDD",
273
+ "Search content": "\u641C\u7D22\u5185\u5BB9",
274
+ "Search conversation content": "\u641C\u7D22\u5BF9\u8BDD\u5185\u5BB9",
275
+ "Search conversations": "\u641C\u7D22\u5BF9\u8BDD",
276
+ "Search knowledge": "\u641C\u7D22\u77E5\u8BC6",
277
+ "Search runs": "\u641C\u7D22\u8FD0\u884C\u8BB0\u5F55",
278
+ "Search web": "\u641C\u7D22\u7F51\u9875",
279
+ "Select up to 32 files.": "\u6700\u591A\u9009\u62E9 32 \u4E2A\u6587\u4EF6\u3002",
280
+ "Select...": "\u9009\u62E9...",
281
+ Send: "\u53D1\u9001",
282
+ "Send a message...": "\u53D1\u9001\u6D88\u606F...",
283
+ "Send now": "\u7ACB\u5373\u53D1\u9001",
284
+ Server: "\u670D\u52A1\u5668",
285
+ "Show conversations": "\u663E\u793A\u5BF9\u8BDD\u5217\u8868",
286
+ "Show Kanban configurations": "\u663E\u793A\u770B\u677F\u914D\u7F6E",
287
+ "Show password": "\u663E\u793A\u5BC6\u7801",
288
+ "Show runs": "\u663E\u793A\u8FD0\u884C\u8BB0\u5F55",
289
+ "Sign in": "\u767B\u5F55",
290
+ "Sign in to Workflow": "\u767B\u5F55 Workflow",
291
+ "Sign in to Workflow Server to connect Kanban data.": "\u8BF7\u767B\u5F55 Workflow Server \u4EE5\u8FDE\u63A5\u770B\u677F\u6570\u636E\u3002",
292
+ "Sign in to Workflow Server, then refresh parameters.": "\u8BF7\u5148\u767B\u5F55 Workflow Server\uFF0C\u7136\u540E\u5237\u65B0\u53C2\u6570\u3002",
293
+ "Sign out": "\u9000\u51FA\u767B\u5F55",
294
+ "Sign-in expired": "\u767B\u5F55\u5DF2\u5931\u6548",
295
+ "Sign-in expired. Sign in to Workflow Server again.": "\u767B\u5F55\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 Workflow Server\u3002",
296
+ "Signed in; account verified": "\u5DF2\u767B\u5F55\uFF0C\u8D26\u53F7\u9A8C\u8BC1\u901A\u8FC7",
297
+ "Signed out": "\u672A\u767B\u5F55",
298
+ "Signing in...": "\u767B\u5F55\u4E2D...",
299
+ "Started: {0}": "\u5F00\u59CB\uFF1A{0}",
300
+ Stop: "\u505C\u6B62",
301
+ "Stop run": "\u505C\u6B62\u8FD0\u884C",
302
+ Stopped: "\u5DF2\u505C\u6B62",
303
+ "Stopped \xB7 {0}/{1} items completed": "\u5DF2\u505C\u6B62 \xB7 {0}/{1} \u9879\u5B8C\u6210",
304
+ "Stopped {0}": "\u5DF2\u505C\u6B62{0}",
305
+ "Submitting...": "\u6B63\u5728\u63D0\u4EA4...",
306
+ "System AI": "\u7CFB\u7EDF AI",
307
+ "System AI usage": "\u7CFB\u7EDF AI \u7528\u91CF",
308
+ Tablet: "\u5E73\u677F",
309
+ "The current session is running.": "\u5F53\u524D\u4F1A\u8BDD\u6B63\u5728\u8FD0\u884C\u3002",
310
+ "The Kanban connection is unavailable.": "\u770B\u677F\u8FDE\u63A5\u4E0D\u53EF\u7528\u3002",
311
+ "The options for {0} have changed. Please select again.": "{0} \u7684\u9009\u9879\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u3002",
312
+ "The project does not support this attachment type.": "\u9879\u76EE\u4E0D\u652F\u6301\u6B64\u7C7B\u9644\u4EF6\u3002",
313
+ "The related project connection was interrupted. Reconnecting.": "\u5173\u8054\u9879\u76EE\u66F4\u65B0\u8FDE\u63A5\u5DF2\u4E2D\u65AD\uFF0C\u6B63\u5728\u91CD\u8FDE\u3002",
314
+ "The request timed out. Check the current status.": "\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u5F53\u524D\u72B6\u6001\u3002",
315
+ Thinking: "\u601D\u8003",
316
+ "Thought complete": "\u601D\u8003\u5B8C\u6210",
317
+ "Timed out": "\u5DF2\u8D85\u65F6",
318
+ Tool: "\u5DE5\u5177",
319
+ "Tool input": "\u5DE5\u5177\u53C2\u6570",
320
+ "Tool permissions": "\u5DE5\u5177\u6743\u9650",
321
+ Trace: "\u8F68\u8FF9",
322
+ Unlimited: "\u4E0D\u9650",
323
+ "Unlock title": "\u89E3\u9664\u6807\u9898\u9501\u5B9A",
324
+ "Unsupported attachment type, or the 50 MiB per file / 32 attachment limit was exceeded.": "\u9644\u4EF6\u7C7B\u578B\u4E0D\u53D7\u652F\u6301\uFF0C\u6216\u8D85\u8FC7\u5355\u6587\u4EF6 50 MiB / 32 \u4E2A\u9644\u4EF6\u9650\u5236\u3002",
325
+ "Update data": "\u66F4\u65B0\u6570\u636E",
326
+ "Update knowledge": "\u66F4\u65B0\u77E5\u8BC6",
327
+ "Update tasks": "\u66F4\u65B0\u4EFB\u52A1\u5217\u8868",
328
+ Validity: "\u6709\u6548\u671F",
329
+ "Verify sign-in": "\u9A8C\u8BC1\u767B\u5F55",
330
+ "Verifying account...": "\u6B63\u5728\u9A8C\u8BC1\u8D26\u53F7...",
331
+ "View all ({0})": "\u67E5\u770B\u5168\u90E8\uFF08{0}\uFF09",
332
+ "Waiting for input": "\u7B49\u5F85\u8F93\u5165",
333
+ "Waiting for tool results...": "\u7B49\u5F85\u5DE5\u5177\u7ED3\u679C...",
334
+ Weekly: "\u6BCF\u5468",
335
+ Width: "\u5BBD\u5EA6",
336
+ Workflow: "\u5DE5\u4F5C\u6D41",
337
+ "Workflow Account": "Workflow \u8D26\u53F7",
338
+ "Workflow entrypoint": "\u5DE5\u4F5C\u6D41\u5165\u53E3",
339
+ "Workflow run": "\u5DE5\u4F5C\u6D41\u8FD0\u884C",
340
+ Write: "\u5199\u5165",
341
+ "Write anything": "\u968F\u5FC3\u8F93\u5165",
342
+ "Write file": "\u5199\u5165\u6587\u4EF6",
343
+ Yes: "\u662F",
344
+ "Zoom in": "\u653E\u5927",
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"
372
+ };
373
+
374
+ // src/web/localization.ts
375
+ if (typeof document !== "undefined" && document.documentElement) {
376
+ const requested = new URL(location.href).searchParams.get("workflowLocale");
377
+ initI18n({ preference: normalizeLocale(requested || document.documentElement.lang || navigator.language) });
378
+ } else if (!i18n.isInitialized) {
379
+ initI18n();
380
+ }
381
+ i18n.addResourceBundle("zh-CN", "cliWeb", messages_zh_cn_default);
382
+ i18n.addResourceBundle("en", "cliWeb", Object.fromEntries(Object.keys(messages_zh_cn_default).map((message) => [message, message])));
383
+ var locale = getCurrentLocale;
384
+ function t(message, ...args) {
385
+ const translate = i18n.t;
386
+ const text = translate(message, { ns: "cliWeb", keySeparator: false, nsSeparator: false, fallbackLng: false, defaultValue: message });
387
+ return text.replace(/\{(\d+)\}/g, (placeholder, index) => Number(index) < args.length ? String(args[Number(index)]) : placeholder);
388
+ }
389
+
45
390
  // src/web/server.ts
46
391
  import { createServer } from "http";
47
392
  import { createHash as createHash3, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
@@ -140,33 +485,33 @@ function validateWorkflowValues(fields, values, states = {}) {
140
485
  const value = workflowParamValue(field, values[field.name]);
141
486
  const label = field.label || field.name;
142
487
  if (field.required && (typeof value === "string" && !value.trim() || Array.isArray(value) && !value.length)) {
143
- throw new Error(`\u8BF7\u586B\u5199 ${label}\u3002`);
488
+ throw new Error(t("Please enter {0}.", label));
144
489
  }
145
490
  if (value === "" || Array.isArray(value) && !value.length) {
146
491
  continue;
147
492
  }
148
493
  if (field.type === "number" && (typeof value !== "string" || !Number.isFinite(Number(value)))) {
149
- throw new Error(`${label} \u9700\u8981\u662F\u6709\u6548\u6570\u5B57\u3002`);
494
+ throw new Error(t("{0} must be a valid number.", label));
150
495
  }
151
496
  if (field.type === "boolean" && value !== "true" && value !== "false") {
152
- throw new Error(`${label} \u9700\u8981\u662F\u6709\u6548\u5E03\u5C14\u503C\u3002`);
497
+ throw new Error(t("{0} must be a valid boolean.", label));
153
498
  }
154
499
  if (field.format === "json" || field.type === "file" || field.control === "audio") {
155
500
  let parsed;
156
501
  try {
157
502
  parsed = JSON.parse(typeof value === "string" ? value : JSON.stringify(value));
158
503
  } catch {
159
- throw new Error(`${label} \u9700\u8981\u662F\u6709\u6548\u7684 JSON\u3002`);
504
+ throw new Error(t("{0} must be valid JSON.", label));
160
505
  }
161
506
  if (field.required && (field.type === "file" || field.control === "audio") && (parsed === null || Array.isArray(parsed) && !parsed.length)) {
162
- throw new Error(`\u8BF7\u586B\u5199 ${label}\u3002`);
507
+ throw new Error(t("Please enter {0}.", label));
163
508
  }
164
509
  }
165
510
  const choices = states[field.name]?.resolvedOptions ?? field.options;
166
511
  if (choices && choices.length > 0) {
167
512
  const allowed = new Set(choices.filter((option) => states[field.name]?.options?.[option.value]?.visible !== false && !states[field.name]?.options?.[option.value]?.disabled).map((option) => option.value));
168
513
  if ((Array.isArray(value) ? value : [value]).some((entry) => !allowed.has(entry))) {
169
- throw new Error(`${label} \u7684\u9009\u9879\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u3002`);
514
+ throw new Error(t("The options for {0} have changed. Please select again.", label));
170
515
  }
171
516
  }
172
517
  }
@@ -226,7 +571,7 @@ var ConversationController = class {
226
571
  delete message.submittingInput;
227
572
  if (message.status === "running") {
228
573
  message.status = message.pendingInput ? "waiting_for_input" : "aborted";
229
- message.error = "\u8FD0\u884C\u5DF2\u4E2D\u65AD";
574
+ message.error = t("Run interrupted");
230
575
  }
231
576
  }
232
577
  }
@@ -250,7 +595,7 @@ var ConversationController = class {
250
595
  }
251
596
  blank() {
252
597
  const now = Date.now();
253
- return { id: globalThis.crypto.randomUUID(), title: "\u65B0\u5BF9\u8BDD", createdAt: now, updatedAt: now, messages: [], parameterValues: {}, toolPermissionPolicies: {}, queuedInputs: [], draft: { text: "", attachments: [] } };
598
+ return { id: globalThis.crypto.randomUUID(), title: t("New conversation"), createdAt: now, updatedAt: now, messages: [], parameterValues: {}, toolPermissionPolicies: {}, queuedInputs: [], draft: { text: "", attachments: [] } };
254
599
  }
255
600
  async persist() {
256
601
  const sessions = this.sessions.map((entry) => ({ ...entry, messages: entry.messages.map(({ submittingInput: _, ...message }) => message) }));
@@ -355,7 +700,7 @@ var ConversationController = class {
355
700
  const waiting = entry.messages.some((message) => message.pendingInput);
356
701
  if (entry.activeRunId || waiting || entry.queuedInputs?.length) {
357
702
  if (!this.project.conversation?.inputQueue?.enabled) {
358
- throw new Error("\u5F53\u524D\u4F1A\u8BDD\u6B63\u5728\u8FD0\u884C\u3002");
703
+ throw new Error(t("The current session is running."));
359
704
  }
360
705
  if (entry.draft?.editingQueueId) {
361
706
  const index = entry.queuedInputs.findIndex((item) => item.id === entry.draft?.editingQueueId);
@@ -399,7 +744,7 @@ var ConversationController = class {
399
744
  }
400
745
  if (!answer) {
401
746
  if (!entry.messages.length && !entry.titleLocked) {
402
- entry.title = (input.text || input.attachments[0]?.name || "\u5DE5\u4F5C\u6D41\u8FD0\u884C").slice(0, 80);
747
+ entry.title = (input.text || input.attachments[0]?.name || t("Workflow run")).slice(0, 80);
403
748
  }
404
749
  entry.messages.push({ id: globalThis.crypto.randomUUID(), role: "user", content: input.text || "", attachments: input.attachments, args, input, entrypointId: input.entrypointId, createdAt: Date.now() });
405
750
  answer = { id: globalThis.crypto.randomUUID(), role: "assistant", content: "", items: [], status: "running", createdAt: Date.now(), model: typeof input.params.model === "string" ? input.params.model : void 0, args, entrypointId: input.entrypointId, events: [] };
@@ -431,7 +776,7 @@ var ConversationController = class {
431
776
  rawArgs(text) {
432
777
  const raw = JSON.parse(text);
433
778
  if (!Array.isArray(raw) || !raw.every((arg) => typeof arg === "string")) {
434
- throw new Error("\u8FD0\u884C\u53C2\u6570\u9700\u8981\u662F\u5B57\u7B26\u4E32\u6570\u7EC4\u3002");
779
+ throw new Error(t("Run arguments must be an array of strings."));
435
780
  }
436
781
  return raw;
437
782
  }
@@ -440,7 +785,7 @@ var ConversationController = class {
440
785
  try {
441
786
  await this.persist();
442
787
  if (task.cancelled || this.disposed) {
443
- throw new Error("\u5DF2\u505C\u6B62");
788
+ throw new Error(t("Stopped"));
444
789
  }
445
790
  const report = await this.runtime.run(request, (event) => {
446
791
  if (task.cancelled || this.disposed || this.tasks.get(answer.runId) !== task) {
@@ -476,7 +821,7 @@ var ConversationController = class {
476
821
  this.changed();
477
822
  });
478
823
  if (task.cancelled || this.disposed) {
479
- throw new Error("\u5DF2\u505C\u6B62");
824
+ throw new Error(t("Stopped"));
480
825
  }
481
826
  answer.report = report;
482
827
  answer.status = report.status;
@@ -498,17 +843,17 @@ var ConversationController = class {
498
843
  answer.timers = report.timers ?? answer.timers;
499
844
  if (report.status === "failed") {
500
845
  const data = report.result?.data;
501
- answer.error = report.result?.error?.message || (data && typeof data === "object" && "errMessage" in data && typeof data.errMessage === "string" ? data.errMessage : "\u8FD0\u884C\u5931\u8D25");
846
+ answer.error = report.result?.error?.message || (data && typeof data === "object" && "errMessage" in data && typeof data.errMessage === "string" ? data.errMessage : t("Run failed"));
502
847
  }
503
848
  if (report.status === "aborted" || report.status === "timed_out") {
504
- answer.error = report.status === "aborted" ? "\u5DF2\u505C\u6B62" : "\u8FD0\u884C\u8D85\u65F6";
849
+ answer.error = report.status === "aborted" ? t("Stopped") : t("Run timed out");
505
850
  }
506
851
  if (answer.submittingInput && pending && report.status === "failed") {
507
852
  answer.status = "waiting_for_input";
508
853
  answer.pendingInput = pending;
509
854
  }
510
855
  if (!answer.content && !answer.items?.length && report.status === "success") {
511
- answer.content = report.result?.data === void 0 ? "\u5DF2\u5B8C\u6210" : JSON.stringify(report.result.data, null, 2);
856
+ answer.content = report.result?.data === void 0 ? t("Completed") : JSON.stringify(report.result.data, null, 2);
512
857
  }
513
858
  } catch (error) {
514
859
  answer.error = error instanceof Error ? error.message : String(error);
@@ -568,7 +913,7 @@ var ConversationController = class {
568
913
  if (pending) {
569
914
  pending.pendingInput = void 0;
570
915
  pending.status = "aborted";
571
- pending.error = "\u5DF2\u505C\u6B62";
916
+ pending.error = t("Stopped");
572
917
  this.changed();
573
918
  void this.persist().catch((error) => this.options.log(String(error)));
574
919
  }
@@ -790,7 +1135,7 @@ var WorkflowController = class {
790
1135
  delete record2.submittingInput;
791
1136
  if (record2.status === "running" || record2.status === "waiting_for_input") {
792
1137
  record2.status = "interrupted";
793
- record2.error = "\u8FD0\u884C\u5DF2\u4E2D\u65AD";
1138
+ record2.error = t("Run interrupted");
794
1139
  this.clearTimers(record2);
795
1140
  }
796
1141
  }
@@ -1565,10 +1910,10 @@ var KanbanController = class {
1565
1910
  create(workspace, params, values = createDefaultKanbanValues(params), name) {
1566
1911
  if (!name) {
1567
1912
  let index = 1;
1568
- while (workspace.configurations.some((item2) => item2.name === `\u914D\u7F6E ${index}`)) {
1913
+ while (workspace.configurations.some((item2) => item2.name === t("Configuration {0}", index))) {
1569
1914
  index++;
1570
1915
  }
1571
- name = `\u914D\u7F6E ${index}`;
1916
+ name = t("Configuration {0}", index);
1572
1917
  }
1573
1918
  const date = (/* @__PURE__ */ new Date()).toISOString(), item = { id: randomUUID2(), name, values: { ...values }, createdAt: date, updatedAt: date };
1574
1919
  workspace.configurations.push(item);
@@ -1744,7 +2089,7 @@ var KanbanController = class {
1744
2089
  this.missingRelatedProjectAliases = [];
1745
2090
  const connection = this.hooks.connection();
1746
2091
  if (!connection) {
1747
- this.relatedProjectWarning = "\u8BF7\u767B\u5F55 Workflow Server \u4EE5\u8FDE\u63A5\u770B\u677F\u6570\u636E\u3002";
2092
+ this.relatedProjectWarning = t("Sign in to Workflow Server to connect Kanban data.");
1748
2093
  return;
1749
2094
  }
1750
2095
  const abort = this.feed = new AbortController();
@@ -1766,7 +2111,7 @@ var KanbanController = class {
1766
2111
  } }, abort.signal);
1767
2112
  } catch {
1768
2113
  if (!abort.signal.aborted) {
1769
- this.relatedProjectWarning = "\u5173\u8054\u9879\u76EE\u66F4\u65B0\u8FDE\u63A5\u5DF2\u4E2D\u65AD\uFF0C\u6B63\u5728\u91CD\u8FDE\u3002";
2114
+ this.relatedProjectWarning = t("The related project connection was interrupted. Reconnecting.");
1770
2115
  this.hooks.changed();
1771
2116
  }
1772
2117
  }
@@ -1826,114 +2171,7 @@ var KanbanController = class {
1826
2171
  }
1827
2172
  };
1828
2173
 
1829
- // src/web/resource-cards.ts
1830
- var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1831
- var string = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
1832
- function conversationResourceCards(items, projectId, conversationId) {
1833
- const cards = /* @__PURE__ */ new Map();
1834
- for (const [index, item] of items.entries()) {
1835
- if (!["agent-part", "opencode-part"].includes(String(item.metadata?.kind)) || item.metadata?.partType !== "tool") {
1836
- continue;
1837
- }
1838
- const content = record(item.content);
1839
- if (content?.status !== "success") {
1840
- continue;
1841
- }
1842
- let output = record(content.output);
1843
- if (Array.isArray(output?.content) && output.content.length === 1) {
1844
- try {
1845
- output = record(JSON.parse(String(record(output.content[0])?.text)));
1846
- } catch {
1847
- }
1848
- }
1849
- let descriptor = record(record(output?.details)?.resourceCard);
1850
- const input = record(content.input) ?? {};
1851
- const tool = string(content.tool) ?? "";
1852
- if (tool === "workflow-ui_present_card") {
1853
- const presentation = output?.action ? output : input;
1854
- if (presentation.action === "remove") {
1855
- cards.delete(String(presentation.cardId));
1856
- continue;
1857
- }
1858
- if (presentation.action !== "upsert") {
1859
- continue;
1860
- }
1861
- descriptor = presentation;
1862
- }
1863
- if (!descriptor) {
1864
- const target2 = { source: "local", projectId, conversationId };
1865
- let kind2;
1866
- if (tool === "workflow-kv_kv_store" || /workflow-desktop_(?:read|list|write)_project_kv/.test(tool)) {
1867
- kind2 = "kv";
1868
- Object.assign(target2, { projectId: input.projectId ?? projectId, key: input.key, scope: input.scope ?? "project", conversationId: input.conversationId ?? conversationId });
1869
- } else if (tool === "workflow-knowledge_knowledge_documents" || /workflow-desktop_(?:query|mutate)_project_knowledge/.test(tool)) {
1870
- kind2 = "knowledge";
1871
- Object.assign(target2, { projectId: input.projectId ?? projectId, documentId: input.documentId ?? record(output?.document)?.id });
1872
- } else if (tool === "read" || tool === "workflow-desktop_open_dock_tool" && input.tool === "files") {
1873
- kind2 = "file";
1874
- target2.filePath = input.filePath ?? input.path;
1875
- } else if (tool === "workflow-desktop_open_project" && input.kind === "project") {
1876
- kind2 = "project";
1877
- target2.projectId = input.targetId;
1878
- } else if (tool === "workflow-desktop_open_dock_tool") {
1879
- if (input.tool === "browser") {
1880
- kind2 = "browser";
1881
- target2.url = input.url;
1882
- }
1883
- if (input.tool === "preview") {
1884
- kind2 = "kanban";
1885
- }
1886
- if (input.tool === "open-project" && input.openedProjectKind === "project") {
1887
- kind2 = "project";
1888
- target2.projectId = input.openedProjectId;
1889
- }
1890
- }
1891
- if (!kind2) {
1892
- continue;
1893
- }
1894
- descriptor = { kind: kind2, target: target2, title: record(output?.document)?.title };
1895
- }
1896
- const kind = descriptor.kind;
1897
- if (!["kv", "knowledge", "project", "browser", "file", "kanban"].includes(kind)) {
1898
- continue;
1899
- }
1900
- const target = Object.fromEntries(Object.entries(record(descriptor.target) ?? {}).filter((entry) => typeof entry[1] === "string" && entry[1].length < 8192));
1901
- if (target.source === "current") {
1902
- target.source = "local";
1903
- target.projectId = projectId;
1904
- }
1905
- if (target.source && !["local", "server"].includes(target.source)) {
1906
- continue;
1907
- }
1908
- if (kind === "browser") {
1909
- try {
1910
- const url = new URL(target.url);
1911
- if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) {
1912
- continue;
1913
- }
1914
- } catch {
1915
- continue;
1916
- }
1917
- } else {
1918
- target.source ??= "local";
1919
- target.projectId ??= projectId;
1920
- if (target.scope === "conversation") {
1921
- target.conversationId ??= conversationId;
1922
- }
1923
- if (kind === "file" && (!target.filePath || /^(?:[\\/]|[a-z]:)|(?:^|[\\/])\.\.(?:[\\/]|$)/i.test(target.filePath))) {
1924
- continue;
1925
- }
1926
- }
1927
- const id = string(descriptor.cardId) ?? `${kind}:${JSON.stringify(target)}`;
1928
- const title = (string(descriptor.title) ?? target.key ?? target.documentId ?? target.filePath ?? target.url ?? target.projectId ?? kind).slice(0, 200);
1929
- cards.set(id, { id, kind, title, target, itemId: item.id ?? String(index) });
1930
- }
1931
- return [...cards.values()];
1932
- }
1933
-
1934
2174
  // src/web/workflow-actions.ts
1935
- import { readFile as readFile4, realpath as realpath3 } from "fs/promises";
1936
- import path4 from "path";
1937
2175
  function createWorkflowActions(controller, runtime, project, revision, changed) {
1938
2176
  const states = /* @__PURE__ */ new Map();
1939
2177
  const epochs = /* @__PURE__ */ new Map();
@@ -2028,6 +2266,14 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
2028
2266
  case "workflow-select":
2029
2267
  await controller.select(message.id ? String(message.id) : void 0);
2030
2268
  break;
2269
+ case "workflow-trace": {
2270
+ const run = controller.runs.find((record2) => record2.id === message.id);
2271
+ if (!run) {
2272
+ throw new Error("Run no longer exists.");
2273
+ }
2274
+ replies.push({ type: "trace-run", runId: run.id, run });
2275
+ break;
2276
+ }
2031
2277
  case "workflow-draft": {
2032
2278
  const draft = { values: values(message.params ?? {}, id) };
2033
2279
  if (message.inputMode !== void 0) {
@@ -2110,28 +2356,6 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
2110
2356
  replies.push({ type: "attachments", entrypointId: id, runId: message.runId, requestId: message.requestId, paramName: message.paramName, files });
2111
2357
  break;
2112
2358
  }
2113
- case "resource": {
2114
- const run = controller.runs.find((record2) => record2.id === message.runId);
2115
- if (!run) {
2116
- throw new Error("Run no longer exists.");
2117
- }
2118
- const card = conversationResourceCards(run.items, project.id, run.scopeId).find((card2) => card2.id === message.cardId);
2119
- if (!card || card.target.projectId && card.target.projectId !== project.id || card.target.source === "server") {
2120
- throw new Error("Resource is outside this project.");
2121
- }
2122
- if (card.kind === "kv" || card.kind === "knowledge") {
2123
- replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, run.scopeId, card.kind, card.target) });
2124
- } else if (card.kind === "file") {
2125
- const root = await realpath3(project.folderPath), filename = await realpath3(path4.resolve(root, card.target.filePath)), relative = path4.relative(root, filename);
2126
- if (relative.startsWith("..") || path4.isAbsolute(relative)) {
2127
- throw new Error("File is outside this project.");
2128
- }
2129
- replies.push({ type: "preview", title: card.title, content: await readFile4(filename, "utf8") });
2130
- } else {
2131
- throw new Error("This resource requires an editor host.");
2132
- }
2133
- break;
2134
- }
2135
2359
  default:
2136
2360
  throw new Error(`Unsupported workflow action: ${message.type}`);
2137
2361
  }
@@ -2143,25 +2367,26 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
2143
2367
  // src/web/runtime.ts
2144
2368
  import { spawn } from "child_process";
2145
2369
  import { existsSync, readFileSync } from "fs";
2146
- import path5 from "path";
2370
+ import path4 from "path";
2147
2371
  import { fileURLToPath } from "url";
2372
+ import { WORKFLOW_RUNTIME_LOCALE_ENV } from "workflow-code";
2148
2373
  function cliRoot() {
2149
- let directory = path5.dirname(fileURLToPath(import.meta.url));
2374
+ let directory = path4.dirname(fileURLToPath(import.meta.url));
2150
2375
  for (let depth = 0; depth < 8; depth++) {
2151
2376
  try {
2152
- 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") {
2153
2378
  return directory;
2154
2379
  }
2155
2380
  } catch {
2156
2381
  }
2157
- directory = path5.dirname(directory);
2382
+ directory = path4.dirname(directory);
2158
2383
  }
2159
2384
  throw new Error("Cannot locate the CLI installation.");
2160
2385
  }
2161
- function createWebRuntime(projectDir, storageDir, connection) {
2386
+ function createWebRuntime(projectDir, storageDir, connection, relatedProjects = () => []) {
2162
2387
  const root = cliRoot();
2163
- const builtWorker = path5.join(root, "dist/web/worker.mjs");
2164
- 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");
2165
2390
  const children = /* @__PURE__ */ new Set();
2166
2391
  const active = /* @__PURE__ */ new Map();
2167
2392
  let disposed = false;
@@ -2178,12 +2403,14 @@ function createWebRuntime(projectDir, storageDir, connection) {
2178
2403
  const env = {
2179
2404
  ...process.env,
2180
2405
  WORKFLOW_RUNTIME_PLATFORM: "cli",
2406
+ [WORKFLOW_RUNTIME_LOCALE_ENV]: locale(),
2407
+ WORKFLOW_CODE_LOCALE: locale(),
2181
2408
  WORKFLOW_SERVER_URL: auth?.serverUrl ?? "",
2182
2409
  WORKFLOW_SERVER_ADMIN_KEY: auth?.accessToken ?? "",
2183
2410
  WORKFLOW_SERVER_TOKEN: "",
2184
2411
  WORKFLOW_LLM_SERVER_URL: auth?.serverUrl ?? "",
2185
2412
  WORKFLOW_LLM_SERVER_TOKEN: auth?.accessToken ?? "",
2186
- WORKFLOW_CLI_AUTH_DIR: path5.join(storageDir, "runtime-auth")
2413
+ WORKFLOW_CLI_AUTH_DIR: path4.join(storageDir, "runtime-auth")
2187
2414
  };
2188
2415
  for (const key of [
2189
2416
  "TSX_TSCONFIG_PATH",
@@ -2268,7 +2495,7 @@ function createWebRuntime(projectDir, storageDir, connection) {
2268
2495
  }
2269
2496
  });
2270
2497
  child.stdin.on("error", () => void 0);
2271
- child.stdin.end(JSON.stringify({ ...request, storageDir, runtimeProjectDir: projectDir }));
2498
+ child.stdin.end(JSON.stringify({ ...request, storageDir, runtimeProjectDir: projectDir, relatedProjects: relatedProjects() }));
2272
2499
  });
2273
2500
  return {
2274
2501
  available: true,
@@ -2306,10 +2533,129 @@ function createWebRuntime(projectDir, storageDir, connection) {
2306
2533
  };
2307
2534
  }
2308
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
+
2309
2655
  // src/web/account-auth.ts
2310
2656
  async function loginWebAccount(serverUrl, email, password) {
2311
2657
  if (!email.trim() || !password) {
2312
- throw new Error("\u8BF7\u8F93\u5165\u90AE\u7BB1\u548C\u5BC6\u7801\u3002");
2658
+ throw new Error(t("Enter your email and password."));
2313
2659
  }
2314
2660
  let token = "";
2315
2661
  let cookie = "";
@@ -2319,7 +2665,7 @@ async function loginWebAccount(serverUrl, email, password) {
2319
2665
  const response = await fetch(`${serverUrl}${endpoint}`, { ...options, redirect: "error", signal: AbortSignal.timeout(15e3) });
2320
2666
  const result = await response.json();
2321
2667
  if (!response.ok || result.errCode !== 0 || !result.data) {
2322
- const message = response.status === 401 ? "\u90AE\u7BB1\u6216\u5BC6\u7801\u4E0D\u6B63\u786E\uFF0C\u6216\u767B\u5F55\u5DF2\u5931\u6548\u3002" : result.errMessage || "Workflow Server login failed.";
2668
+ const message = response.status === 401 ? t("Incorrect email or password, or sign-in expired.") : result.errMessage || "Workflow Server login failed.";
2323
2669
  throw new Error(String(message).replaceAll(password, "[redacted]").replaceAll(token || "\0", "[redacted]"));
2324
2670
  }
2325
2671
  return { response, data: result.data };
@@ -2389,6 +2735,268 @@ async function inspectWebProject(runtime, projectDir, host) {
2389
2735
  };
2390
2736
  }
2391
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
+
2392
3000
  // src/web/server.ts
2393
3001
  async function startWebServer(options) {
2394
3002
  const projectDir = await realpath4(path6.resolve(options.projectDir));
@@ -2401,12 +3009,14 @@ async function startWebServer(options) {
2401
3009
  let login;
2402
3010
  let passwordLogin = false;
2403
3011
  let hostConnection = options.host?.connection;
2404
- let hostManaged = !!hostConnection;
3012
+ let hostManaged = options.host !== void 0;
2405
3013
  let authEpoch = 0;
2406
3014
  let authenticationInitialized = false;
2407
3015
  let authCheck;
2408
3016
  const sensitiveTokens = /* @__PURE__ */ new Set();
2409
- const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection);
3017
+ let relatedProjects = options.host?.relatedProjects ?? [];
3018
+ let resourceProjects = options.host?.resourceProjects ?? [];
3019
+ const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection, () => relatedProjects);
2410
3020
  let inspected;
2411
3021
  try {
2412
3022
  inspected = await inspectWebProject(runtime, projectDir, options.host);
@@ -2427,6 +3037,7 @@ async function startWebServer(options) {
2427
3037
  throw new Error("Unsupported Workflow project type.");
2428
3038
  }
2429
3039
  const project = descriptor;
3040
+ const resources = new ResourcePreviews(project, runtime, storageDir, () => connection, () => relatedProjects, () => resourceProjects);
2430
3041
  const key = createHash3("sha256").update(project.assistant ? `assistant:${project.id}:${project.assistant.workdir}` : projectDir).digest("hex").slice(0, 24);
2431
3042
  const historyPath = path6.join(storageDir, key, "conversations.json");
2432
3043
  let saved = {};
@@ -2564,7 +3175,7 @@ async function startWebServer(options) {
2564
3175
  return;
2565
3176
  }
2566
3177
  const readingEpoch = authEpoch;
2567
- const config = await readCliAuthConfig();
3178
+ const config = hostManaged ? createEmptyWorkflowAuthConfig() : await readCliAuthConfig();
2568
3179
  if (readingEpoch !== authEpoch) {
2569
3180
  return;
2570
3181
  }
@@ -2586,6 +3197,7 @@ ${token}`;
2586
3197
  authIdentity = identity;
2587
3198
  const candidate = !signedOut && serverUrl && token ? { serverUrl: serverUrl.replace(/\/+$/, ""), accessToken: token } : void 0;
2588
3199
  if (changed) {
3200
+ resources.clear();
2589
3201
  connection = void 0;
2590
3202
  authRevision++;
2591
3203
  parameterStates.clear();
@@ -2627,6 +3239,7 @@ ${token}`;
2627
3239
  return;
2628
3240
  }
2629
3241
  connection = void 0;
3242
+ resources.clear();
2630
3243
  kanbanController?.authenticationChanged();
2631
3244
  if (!changed) {
2632
3245
  authRevision++;
@@ -2697,6 +3310,62 @@ ${token}`;
2697
3310
  if (message.projectUri && message.projectUri !== project.uri) {
2698
3311
  throw new Error("Project changed.");
2699
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
+ }
2700
3369
  if (kanbanController && !["ready", "login", "verify", "logout", "usage"].includes(message.type)) {
2701
3370
  return kanbanController.handle(message);
2702
3371
  }
@@ -2855,28 +3524,7 @@ ${token}`;
2855
3524
  if (message.type === "export-message") {
2856
3525
  replies.push({ type: "download", name: `run-${answer.id}.json`, content: JSON.stringify(redactWorkflowData(answer, [...sensitiveTokens], false), null, 2) }, { type: "exported", sessionId: current.id, messageId: answer.id });
2857
3526
  } else {
2858
- replies.push({ type: "preview", title: message.type === "logs" ? "\u8FD0\u884C\u65E5\u5FD7" : "\u8FD0\u884C\u8DDF\u8E2A", content: message.type === "logs" ? answer.report?.logs ?? "" : answer.report });
2859
- }
2860
- break;
2861
- }
2862
- case "resource": {
2863
- const answer = current.messages.find((entry) => entry.id === message.messageId);
2864
- const card = conversationResourceCards(answer?.items ?? [], project.id, current.id).find((entry) => entry.id === message.cardId);
2865
- if (!card || card.target.projectId && card.target.projectId !== project.id || card.target.source === "server") {
2866
- throw new Error("Resource is outside this project.");
2867
- }
2868
- if (card.kind === "kv" || card.kind === "knowledge") {
2869
- replies.push({ type: "preview", title: card.title, content: await runtime.readResource(project, current.id, card.kind, card.target) });
2870
- } else if (card.kind === "file") {
2871
- const root = await realpath4(project.assistant?.workdir ?? projectDir);
2872
- const filename = await realpath4(path6.resolve(root, card.target.filePath));
2873
- const relative = path6.relative(root, filename);
2874
- if (relative === ".." || relative.startsWith(`..${path6.sep}`) || path6.isAbsolute(relative)) {
2875
- throw new Error("File is outside this project.");
2876
- }
2877
- replies.push({ type: "preview", title: card.title, content: await readFile5(filename, "utf8") });
2878
- } else {
2879
- throw new Error("This resource requires an editor host.");
3527
+ replies.push({ type: "preview", title: message.type === "logs" ? t("Run logs") : t("Trace"), content: message.type === "logs" ? answer.report?.logs ?? "" : { report: answer.report, events: answer.events, items: answer.items } });
2880
3528
  }
2881
3529
  break;
2882
3530
  }
@@ -2893,6 +3541,9 @@ ${token}`;
2893
3541
  break;
2894
3542
  }
2895
3543
  case "login": {
3544
+ if (hostManaged) {
3545
+ throw new Error(t("Manage this account through Studio."));
3546
+ }
2896
3547
  if (!options.host && process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
2897
3548
  throw new Error("This session is supplied by the host. Sign in through the host.");
2898
3549
  }
@@ -2948,15 +3599,19 @@ ${token}`;
2948
3599
  case "verify":
2949
3600
  await refreshAuth(true);
2950
3601
  break;
2951
- case "logout":
3602
+ case "logout": {
3603
+ if (hostManaged) {
3604
+ throw new Error(t("Manage this account through Studio."));
3605
+ }
2952
3606
  signedOut = true;
2953
3607
  login?.kill();
2954
- if (!hostManaged && process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
3608
+ if (process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
2955
3609
  await clearCliAuthConfig();
2956
3610
  }
2957
3611
  authIdentity = "signed-out";
2958
3612
  await refreshAuth();
2959
3613
  break;
3614
+ }
2960
3615
  default:
2961
3616
  throw new Error(`Unsupported action: ${message.type}`);
2962
3617
  }
@@ -2970,13 +3625,14 @@ ${token}`;
2970
3625
  const host = new URL(`http://${request.headers.host ?? ""}`);
2971
3626
  const forwarded = !!options.host && ["127.0.0.1", "localhost", "[::1]"].includes(host.hostname) && !host.username && !host.password && host.pathname === "/" && !host.search && !host.hash;
2972
3627
  const url = new URL(request.url ?? "/", origin);
2973
- 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";
2974
3630
  if (request.headers.host !== new URL(origin).host && !forwarded || request.headers.origin && request.headers.origin !== (forwarded ? host.origin : origin) && !sandboxAsset) {
2975
3631
  json(response, 403, { message: "Invalid request origin." });
2976
3632
  return;
2977
3633
  }
2978
- if (kanbanController && url.pathname.startsWith("/kanban/") && ["GET", "HEAD"].includes(request.method ?? "")) {
2979
- 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);
2980
3636
  if (!asset) {
2981
3637
  json(response, 404, { message: "Kanban asset not found." });
2982
3638
  return;
@@ -3010,6 +3666,10 @@ ${token}`;
3010
3666
  json(response, 200, { connected: true });
3011
3667
  return;
3012
3668
  }
3669
+ if (url.pathname === "/api/resource-state" && request.method === "GET") {
3670
+ json(response, 200, { type: "state", authenticationRevision: authRevision });
3671
+ return;
3672
+ }
3013
3673
  if (url.pathname === "/api/events" && request.method === "GET") {
3014
3674
  const disconnectKanban = kanbanController?.methods.connect(url.searchParams.get("clientId") ?? "");
3015
3675
  response.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
@@ -3031,7 +3691,7 @@ ${token}`;
3031
3691
  const operation = () => handleMessage(message).then((value2) => {
3032
3692
  messages = value2;
3033
3693
  });
3034
- 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);
3035
3695
  const task = concurrent ? operation() : mutations.catch(() => void 0).then(operation);
3036
3696
  if (!concurrent) {
3037
3697
  mutations = task;
@@ -3060,11 +3720,11 @@ ${token}`;
3060
3720
  response.end();
3061
3721
  return;
3062
3722
  }
3063
- if (url.pathname === "/") {
3723
+ if (url.pathname === "/" || /^\/resource\/[a-f\d-]{36}$/.test(url.pathname)) {
3064
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:";
3065
- 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'` });
3066
- const ui = isKanban ? "kanban-workspace" : isWorkflow ? "workflow-workspace" : "workflow-ui";
3067
- response.end(`<!doctype html><html lang="zh-CN" 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"}"><div id="app"></div><script src="/assets/${ui}.js" defer></script></body></html>`);
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";
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>`);
3068
3728
  return;
3069
3729
  }
3070
3730
  const assets = { "/assets/workflow-ui.js": "application/javascript", "/assets/workflow-ui.css": "text/css", "/assets/theme.css": "text/css", "/assets/codicons/codicon.css": "text/css", "/assets/codicons/codicon.ttf": "font/ttf" };
@@ -3072,6 +3732,8 @@ ${token}`;
3072
3732
  assets["/assets/workflow-workspace.css"] = "text/css";
3073
3733
  assets["/assets/kanban-workspace.js"] = "application/javascript";
3074
3734
  assets["/assets/kanban-workspace.css"] = "text/css";
3735
+ assets["/assets/resource-preview.js"] = "application/javascript";
3736
+ assets["/assets/resource-preview.css"] = "text/css";
3075
3737
  if (!assets[url.pathname]) {
3076
3738
  json(response, 404, { message: "Not found." });
3077
3739
  return;
@@ -3112,9 +3774,21 @@ ${token}`;
3112
3774
  controller,
3113
3775
  workflowController,
3114
3776
  kanbanController,
3777
+ setHostRelatedProjects: (value) => {
3778
+ relatedProjects = value;
3779
+ resources.clear();
3780
+ },
3781
+ setHostResourceProjects: (value) => {
3782
+ resourceProjects = value;
3783
+ resources.clear();
3784
+ },
3115
3785
  setHostConnection: async (value) => {
3786
+ if (hostManaged && JSON.stringify(value) === JSON.stringify(hostConnection)) {
3787
+ return;
3788
+ }
3116
3789
  authEpoch++;
3117
3790
  authCheck?.abort();
3791
+ resources.clear();
3118
3792
  hostConnection = value;
3119
3793
  hostManaged = true;
3120
3794
  signedOut = !value;
@@ -3150,6 +3824,32 @@ import { createInterface } from "readline";
3150
3824
 
3151
3825
  // src/web/host-config.ts
3152
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
+ }
3836
+ function parseHostRelatedProjects(value) {
3837
+ if (value === void 0) {
3838
+ return [];
3839
+ }
3840
+ if (!Array.isArray(value) || value.length > 100) {
3841
+ throw new Error("Invalid host related projects.");
3842
+ }
3843
+ const aliases = /* @__PURE__ */ new Set(), directories = /* @__PURE__ */ new Set();
3844
+ return value.map((project) => {
3845
+ if (!project || typeof project.alias !== "string" || !/^[a-z][a-z0-9_-]{0,63}$/.test(project.alias) || typeof project.projectDir !== "string" || !path7.isAbsolute(project.projectDir) || aliases.has(project.alias) || directories.has(project.projectDir)) {
3846
+ throw new Error("Invalid host related projects.");
3847
+ }
3848
+ aliases.add(project.alias);
3849
+ directories.add(project.projectDir);
3850
+ return { alias: project.alias, projectDir: project.projectDir };
3851
+ });
3852
+ }
3153
3853
  function parseHostConnection(value) {
3154
3854
  if (value === null || value === void 0) {
3155
3855
  return;
@@ -3183,7 +3883,14 @@ function parseHostConfiguration(value) {
3183
3883
  if (configuration.surface !== "assistant" && (assistant || assistantProjectDir !== void 0)) {
3184
3884
  throw new Error("Assistant configuration requires the assistant surface.");
3185
3885
  }
3186
- return { surface: configuration.surface, assistant, assistantProjectDir, connection: parseHostConnection(configuration.connection) };
3886
+ return {
3887
+ surface: configuration.surface,
3888
+ assistant,
3889
+ assistantProjectDir,
3890
+ connection: parseHostConnection(configuration.connection),
3891
+ relatedProjects: parseHostRelatedProjects(configuration.relatedProjects),
3892
+ ...configuration.resourceProjects === void 0 ? {} : { resourceProjects: parseHostResourceProjects(configuration.resourceProjects) }
3893
+ };
3187
3894
  }
3188
3895
 
3189
3896
  // src/web.ts
@@ -3277,7 +3984,7 @@ async function main(argv) {
3277
3984
  };
3278
3985
  process.once("SIGINT", close);
3279
3986
  process.once("SIGTERM", close);
3280
- console.log(options.json ? JSON.stringify({ type: "ready", protocolVersion: 1, hostProtocolVersion: 1, assistantProtocolVersion: host?.assistant ? ASSISTANT_PROTOCOL_VERSION : void 0, url: app.url, project: { id: app.project.id, name: app.project.name, projectType: app.project.projectType }, runtime: app.project.runtime }) : `Workflow Web: ${app.url}`);
3987
+ console.log(options.json ? JSON.stringify({ type: "ready", protocolVersion: 1, hostProtocolVersion: 1, accountProtocolVersion: 1, relatedProjectsProtocolVersion: 1, traceProtocolVersion: 1, assistantProtocolVersion: host?.assistant ? ASSISTANT_PROTOCOL_VERSION : void 0, url: app.url, project: { id: app.project.id, name: app.project.name, projectType: app.project.projectType }, runtime: app.project.runtime }) : `Workflow Web: ${app.url}`);
3281
3988
  if (lines) {
3282
3989
  void (async () => {
3283
3990
  try {
@@ -3292,6 +3999,11 @@ async function main(argv) {
3292
3999
  process.exitCode = 1;
3293
4000
  close();
3294
4001
  });
4002
+ } else if (message.type === "related-projects") {
4003
+ app.setHostRelatedProjects(parseHostRelatedProjects(message.relatedProjects));
4004
+ if (message.resourceProjects !== void 0) {
4005
+ app.setHostResourceProjects(parseHostResourceProjects(message.resourceProjects));
4006
+ }
3295
4007
  } else if (message.type === "close") {
3296
4008
  break;
3297
4009
  } else {