@workflow-code/cli 0.2.4-20261012004.codex-studio-integration → 0.2.4-20261012006.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.
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  createCliRelatedProjectRuntime,
7
7
  parseCliRelatedProjectSpec
8
- } from "./chunk-MNY562W5.js";
8
+ } from "./chunk-5FPIZZE4.js";
9
9
  import {
10
10
  readWorkflowProjectInfo
11
11
  } from "./chunk-P6K3NB5S.js";
@@ -12,7 +12,7 @@ import os from "os";
12
12
  import path from "path";
13
13
  import { readFileSync, realpathSync, statSync } from "fs";
14
14
  import * as workflowCode from "workflow-code";
15
- var targetStorageApi = workflowCode;
15
+ var defaultStorageApi = workflowCode;
16
16
  function parseCliRelatedProjectSpec(value) {
17
17
  const separatorIndex = value.indexOf("=");
18
18
  if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
@@ -30,6 +30,7 @@ function parseCliRelatedProjectSpec(value) {
30
30
  }
31
31
  function createCliRelatedProjectRuntime(options) {
32
32
  if (options.relatedProjects.length === 0) return {};
33
+ const targetStorageApi = options.storageApi ?? defaultStorageApi;
33
34
  const consumer = readProjectSnapshot(options.consumerProjectDir, "Current project");
34
35
  const configuredByAlias = /* @__PURE__ */ new Map();
35
36
  const configuredProjectIds = /* @__PURE__ */ new Set();
@@ -76,7 +77,7 @@ function createCliRelatedProjectRuntime(options) {
76
77
  configured.projectDir,
77
78
  `Related project "${alias}"`
78
79
  );
79
- if (currentTarget.projectId.toLowerCase() !== configured.projectId.toLowerCase()) {
80
+ if (currentConsumer.projectId.toLowerCase() !== consumer.projectId.toLowerCase() || currentTarget.projectId.toLowerCase() !== configured.projectId.toLowerCase()) {
80
81
  return void 0;
81
82
  }
82
83
  const access = resolveWorkflowRelatedProjectAccess({
@@ -93,7 +94,8 @@ function createCliRelatedProjectRuntime(options) {
93
94
  if (fixedStore === void 0 && typeof createRemoteStore !== "function") {
94
95
  throw new Error("Installed workflow-code does not support related project storage.");
95
96
  }
96
- const store = fixedStore ?? createRemoteStore({
97
+ const roots = options.localStorageForProject?.(currentTarget.projectDir) ?? { kvStoreDir: localKvStoreDir, persistentValueStoreDir: localPersistentValueStoreDir };
98
+ const store = location === "local" && options.localStorageForProject ? targetStorageApi.createLocalWorkflowKVStore({ rootDir: roots.kvStoreDir, storageKind: "kv" }) : fixedStore ?? createRemoteStore({
97
99
  consumerProjectId: currentConsumer.projectId,
98
100
  targetProjectId: currentTarget.projectId,
99
101
  alias,
@@ -105,8 +107,9 @@ function createCliRelatedProjectRuntime(options) {
105
107
  runId: options.runId,
106
108
  conversationId: options.conversationId
107
109
  };
108
- const persistentValue = fixedPersistentValueStore === void 0 ? void 0 : targetStorageApi.createWorkflowPersistentValueContext?.({
109
- store: fixedPersistentValueStore,
110
+ const persistentStore = location === "server" ? store : options.localStorageForProject && roots.persistentValueStoreDir !== void 0 ? targetStorageApi.createLocalWorkflowKVStore({ rootDir: roots.persistentValueStoreDir, storageKind: "persistent_value" }) : fixedPersistentValueStore;
111
+ const persistentValue = persistentStore === void 0 ? void 0 : targetStorageApi.createWorkflowPersistentValueContext?.({
112
+ store: persistentStore,
110
113
  projectId: currentTarget.projectId,
111
114
  source
112
115
  });
@@ -119,8 +122,9 @@ function createCliRelatedProjectRuntime(options) {
119
122
  // The roots settled above, not the raw options: the relation and the
120
123
  // target's own run have to land on the same store, defaults
121
124
  // included, or one side writes what the other cannot see.
122
- kvStoreDir: localKvStoreDir,
123
- persistentValueStoreDir: localPersistentValueStoreDir
125
+ kvStoreDir: roots.kvStoreDir,
126
+ persistentValueStoreDir: roots.persistentValueStoreDir,
127
+ storageApi: targetStorageApi
124
128
  });
125
129
  return {
126
130
  projectId: currentTarget.projectId,
@@ -139,6 +143,7 @@ function createCliRelatedProjectRuntime(options) {
139
143
  }
140
144
  function createLocalProjectRunner(options) {
141
145
  const projectDir = options.projectDir;
146
+ const targetStorageApi = options.storageApi;
142
147
  const inspect = targetStorageApi.inspectLocalWorkflowStructure;
143
148
  const run = targetStorageApi.runLocalWorkflowWithReport;
144
149
  const unwrap = targetStorageApi.readEntrypointOutput;
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ async function main(argv = process.argv.slice(2)) {
24
24
  const prepared = prepareCliInvocation(argv);
25
25
  argv = prepared.argv;
26
26
  if (argv[0] === "web") {
27
- const webCli = await import("./web-OMVO2EGT.js");
27
+ const webCli = await import("./web-FDREEISF.js");
28
28
  return webCli.main(argv.slice(1));
29
29
  }
30
30
  if (argv[0] === "workspace") {
@@ -33,7 +33,7 @@ async function main(argv = process.argv.slice(2)) {
33
33
  return typeof process.exitCode === "number" ? process.exitCode : 0;
34
34
  }
35
35
  if (argv[0] === "mcp") {
36
- const mcpCli = await import("./mcp-UQTMW3LK.js");
36
+ const mcpCli = await import("./mcp-RPT64F7T.js");
37
37
  return mcpCli.main(argv.slice(1));
38
38
  }
39
39
  if (argv.length === 0 || argv[0] === "help" || argv[0] === "--help" || argv[0] === "-h") {
@@ -43,7 +43,7 @@ async function main(argv = process.argv.slice(2)) {
43
43
  }
44
44
  const { register } = await import("tsx/esm/api");
45
45
  register();
46
- const localCli = await import("./local-QLYFH4WT.js");
46
+ const localCli = await import("./local-XHR2O2FP.js");
47
47
  return localCli.main(argv, { localeInitialized: true });
48
48
  }
49
49
  function formatHelp() {
@@ -9,9 +9,9 @@ import {
9
9
  main,
10
10
  parseWorkflowArgs,
11
11
  readInstalledWorkflowCoreVersion
12
- } from "./chunk-WN7VAGK6.js";
12
+ } from "./chunk-5DWX3LL6.js";
13
13
  import "./chunk-J2JMC4MZ.js";
14
- import "./chunk-MNY562W5.js";
14
+ import "./chunk-5FPIZZE4.js";
15
15
  import "./chunk-P6K3NB5S.js";
16
16
  import "./chunk-7WEXRL5A.js";
17
17
  import "./chunk-NSPRIPOP.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  parseCliRelatedProjectSpec
4
- } from "./chunk-MNY562W5.js";
4
+ } from "./chunk-5FPIZZE4.js";
5
5
  import {
6
6
  isWorkflowAuthConfigLoggedIn,
7
7
  normalizeWorkflowAuthConfig,
@@ -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",
@@ -15693,6 +15695,69 @@ ${state2.user?.avatarUrl ?? ""}`;
15693
15695
  }
15694
15696
  };
15695
15697
 
15698
+ // src/web/ui/clipboard-bridge.ts
15699
+ var ClipboardBridge = class {
15700
+ constructor(send, browserWrite, requiresHost = false) {
15701
+ this.send = send;
15702
+ this.browserWrite = browserWrite;
15703
+ this.requiresHost = requiresHost;
15704
+ }
15705
+ send;
15706
+ browserWrite;
15707
+ requiresHost;
15708
+ enabled = false;
15709
+ pending = /* @__PURE__ */ new Map();
15710
+ enable(version) {
15711
+ this.enabled = version === 1;
15712
+ }
15713
+ async writeText(text2) {
15714
+ if (!this.enabled) {
15715
+ if (this.requiresHost) {
15716
+ throw new Error(t2("Host clipboard is unavailable."));
15717
+ }
15718
+ return this.browserWrite(text2);
15719
+ }
15720
+ const requestId = crypto.randomUUID();
15721
+ return new Promise((resolve, reject) => {
15722
+ const timer = setTimeout(() => {
15723
+ this.pending.delete(requestId);
15724
+ reject(new Error(t2("Copy timed out.")));
15725
+ }, 1e4);
15726
+ this.pending.set(requestId, { resolve, reject, timer });
15727
+ try {
15728
+ this.send({ type: "workflow-clipboard", version: 1, command: "writeText", requestId, text: text2 });
15729
+ } catch (error) {
15730
+ clearTimeout(timer);
15731
+ this.pending.delete(requestId);
15732
+ reject(error);
15733
+ }
15734
+ });
15735
+ }
15736
+ receive(reply) {
15737
+ if (reply?.version !== 1 || typeof reply.requestId !== "string" || typeof reply.ok !== "boolean" || reply.error !== void 0 && typeof reply.error !== "string") {
15738
+ return;
15739
+ }
15740
+ const pending2 = this.pending.get(reply.requestId);
15741
+ if (!pending2) {
15742
+ return;
15743
+ }
15744
+ this.pending.delete(reply.requestId);
15745
+ clearTimeout(pending2.timer);
15746
+ if (reply.ok) {
15747
+ pending2.resolve();
15748
+ } else {
15749
+ pending2.reject(new Error(reply.error || t2("Host clipboard is unavailable.")));
15750
+ }
15751
+ }
15752
+ dispose() {
15753
+ for (const pending2 of this.pending.values()) {
15754
+ clearTimeout(pending2.timer);
15755
+ pending2.reject(new Error(t2("Connection changed.")));
15756
+ }
15757
+ this.pending.clear();
15758
+ }
15759
+ };
15760
+
15696
15761
  // src/web/ui/host.ts
15697
15762
  function acquireWorkflowHost() {
15698
15763
  const embedded = globalThis.acquireVsCodeApi;
@@ -15714,6 +15779,12 @@ ${state2.user?.avatarUrl ?? ""}`;
15714
15779
  window.parent.postMessage(message, "*");
15715
15780
  }
15716
15781
  });
15782
+ const clipboard = new ClipboardBridge(
15783
+ (message) => window.parent.postMessage(message, "*"),
15784
+ (text2) => navigator.clipboard.writeText(text2),
15785
+ window.parent !== window && document.body.dataset.accountHost === "true"
15786
+ );
15787
+ addEventListener("pagehide", () => clipboard.dispose());
15717
15788
  const connection = new BrowserConnection((message) => {
15718
15789
  if (message.type === "state" && message.account) {
15719
15790
  accountPanel.update(message.account);
@@ -15736,6 +15807,10 @@ ${state2.user?.avatarUrl ?? ""}`;
15736
15807
  if (event.data.traceVersion !== void 0) {
15737
15808
  trace.enable(event.data.traceVersion);
15738
15809
  }
15810
+ if (event.data.clipboardVersion !== void 0) {
15811
+ clipboard.enable(event.data.clipboardVersion);
15812
+ }
15813
+ clipboard.receive(event.data.clipboard);
15739
15814
  const traceCommand = trace.command(event.data.traceCommand);
15740
15815
  if (traceCommand) {
15741
15816
  void request2(traceCommand).catch((error) => trace.receive({ type: "trace-run", runId: traceCommand.id, error: error.message }));
@@ -15829,8 +15904,13 @@ ${state2.user?.avatarUrl ?? ""}`;
15829
15904
  }
15830
15905
  }
15831
15906
  if (message.type === "copy") {
15832
- void navigator.clipboard.writeText(message.text).catch((error) => emit({ type: "error", message: error.message }));
15833
- return;
15907
+ try {
15908
+ await clipboard.writeText(message.text);
15909
+ } catch (error) {
15910
+ emit({ type: "error", message: error.message });
15911
+ return false;
15912
+ }
15913
+ return true;
15834
15914
  }
15835
15915
  if (message.type === "attach") {
15836
15916
  chooseFiles(message);
@@ -0,0 +1,528 @@
1
+ // src/web/related-projects.ts
2
+ import path3 from "node:path";
3
+
4
+ // src/related-projects.ts
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { readFileSync, realpathSync, statSync } from "node:fs";
8
+ import * as workflowCode from "workflow-code";
9
+
10
+ // .shared/project-info/index.ts
11
+ var WORKFLOW_PROJECT_DATA_STORAGE_MODES = ["local", "server", "both"];
12
+ var WORKFLOW_RELATED_PROJECT_ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
13
+ var WORKFLOW_RELATED_PROJECT_LIMIT = 32;
14
+ var WORKFLOW_RELATED_PROJECT_PREFIX_LIMIT = 32;
15
+ var WORKFLOW_RELATED_PROJECT_KEY_LIMIT = 1024;
16
+ var WORKFLOW_PROJECT_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
17
+ var PROJECT_INFO_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
18
+ function createUndeclaredWorkflowProjectInfo() {
19
+ return {
20
+ localizedNames: {},
21
+ declarations: [],
22
+ platformSupport: { mode: "agnostic" },
23
+ dataStorage: null,
24
+ relatedProjects: []
25
+ };
26
+ }
27
+ function normalizeWorkflowProjectInfo(value) {
28
+ if (!isRecord(value)) {
29
+ throw new Error("workflowCode.projectInfo must be an object.");
30
+ }
31
+ const localizedNames = normalizeLocalizedNames(value["localizedNames"]);
32
+ const declarations = normalizeIds(value["declarations"], "workflowCode.projectInfo.declarations");
33
+ const platformSupport = normalizePlatformSupport(value["platformSupport"]);
34
+ const dataStorage = normalizeDataStorage(value["dataStorage"]);
35
+ const relatedProjects = normalizeRelatedProjects(value["relatedProjects"]);
36
+ return { localizedNames, declarations, platformSupport, dataStorage, relatedProjects };
37
+ }
38
+ function readWorkflowProjectInfo(packageJson) {
39
+ if (!isRecord(packageJson)) return createUndeclaredWorkflowProjectInfo();
40
+ const workflowCode2 = packageJson["workflowCode"];
41
+ if (!isRecord(workflowCode2) || workflowCode2["projectInfo"] === void 0) {
42
+ return createUndeclaredWorkflowProjectInfo();
43
+ }
44
+ const projectInfo = normalizeWorkflowProjectInfo(workflowCode2["projectInfo"]);
45
+ const projectId = typeof packageJson["id"] === "string" ? packageJson["id"].trim() : void 0;
46
+ if (projectId !== void 0 && projectId !== "") {
47
+ assertWorkflowRelatedProjects(projectInfo, projectId);
48
+ }
49
+ return projectInfo;
50
+ }
51
+ function assertWorkflowRelatedProjects(projectInfo, currentProjectId) {
52
+ if (projectInfo.relatedProjects.length === 0) return;
53
+ const normalizedProjectId = currentProjectId.trim();
54
+ if (!WORKFLOW_PROJECT_UUID_PATTERN.test(normalizedProjectId)) {
55
+ throw new Error("package.json.id must be a UUID before related projects can be declared.");
56
+ }
57
+ if (projectInfo.relatedProjects.some(
58
+ (relation) => relation.projectId.toLowerCase() === normalizedProjectId.toLowerCase()
59
+ )) {
60
+ throw new Error("workflowCode.projectInfo.relatedProjects cannot reference the current project.");
61
+ }
62
+ }
63
+ function findWorkflowRelatedProject(projectInfo, alias) {
64
+ const normalizedAlias = alias.trim();
65
+ return projectInfo.relatedProjects.find((relation) => relation.alias === normalizedAlias);
66
+ }
67
+ function resolveWorkflowRelatedProjectAccess(input) {
68
+ const consumer = findWorkflowRelatedProject(input.consumerProjectInfo, input.alias);
69
+ if (consumer?.projectId.toLowerCase() !== input.targetProjectId.trim().toLowerCase()) {
70
+ return void 0;
71
+ }
72
+ const target = input.targetProjectInfo.relatedProjects.find(
73
+ (relation) => relation.projectId.toLowerCase() === input.consumerProjectId.trim().toLowerCase()
74
+ );
75
+ if (target === void 0) return void 0;
76
+ return { consumer, target, grant: target.grantToRelatedProject };
77
+ }
78
+ function isWorkflowProjectDataStorageLocationAllowed(projectInfo, location) {
79
+ const mode = projectInfo.dataStorage?.mode;
80
+ return mode === "both" || mode === location;
81
+ }
82
+ function normalizeLocalizedNames(value) {
83
+ if (value === void 0) return {};
84
+ if (!isRecord(value)) {
85
+ throw new Error("workflowCode.projectInfo.localizedNames must be an object.");
86
+ }
87
+ const entries = [];
88
+ for (const [rawLocale, rawName] of Object.entries(value)) {
89
+ const locale = rawLocale.trim();
90
+ if (locale === "") {
91
+ throw new Error("workflowCode.projectInfo.localizedNames contains an empty locale.");
92
+ }
93
+ if (typeof rawName !== "string") {
94
+ throw new Error(`workflowCode.projectInfo.localizedNames.${locale} must be a string.`);
95
+ }
96
+ const name = rawName.trim();
97
+ if (name !== "") entries.push([locale, name]);
98
+ }
99
+ return Object.fromEntries(entries);
100
+ }
101
+ function normalizePlatformSupport(value) {
102
+ if (value === void 0) return { mode: "agnostic" };
103
+ if (!isRecord(value)) {
104
+ throw new Error("workflowCode.projectInfo.platformSupport must be an object.");
105
+ }
106
+ if (value["mode"] === "agnostic") {
107
+ if (value["platforms"] !== void 0) {
108
+ throw new Error("workflowCode.projectInfo.platformSupport.platforms is not allowed in agnostic mode.");
109
+ }
110
+ return { mode: "agnostic" };
111
+ }
112
+ if (value["mode"] !== "specific") {
113
+ throw new Error('workflowCode.projectInfo.platformSupport.mode must be "agnostic" or "specific".');
114
+ }
115
+ const platforms = normalizeIds(
116
+ value["platforms"],
117
+ "workflowCode.projectInfo.platformSupport.platforms"
118
+ );
119
+ if (platforms.length === 0) {
120
+ throw new Error("workflowCode.projectInfo.platformSupport.platforms must contain at least one platform.");
121
+ }
122
+ return { mode: "specific", platforms };
123
+ }
124
+ function normalizeDataStorage(value) {
125
+ if (value === void 0 || value === null) return null;
126
+ if (!isRecord(value)) {
127
+ throw new Error("workflowCode.projectInfo.dataStorage must be an object.");
128
+ }
129
+ const mode = value["mode"];
130
+ if (typeof mode !== "string" || !WORKFLOW_PROJECT_DATA_STORAGE_MODES.includes(
131
+ mode
132
+ )) {
133
+ throw new Error(
134
+ 'workflowCode.projectInfo.dataStorage.mode must be "local", "server", or "both".'
135
+ );
136
+ }
137
+ return { mode };
138
+ }
139
+ function normalizeRelatedProjects(value) {
140
+ if (value === void 0) return [];
141
+ if (!Array.isArray(value)) {
142
+ throw new Error("workflowCode.projectInfo.relatedProjects must be an array.");
143
+ }
144
+ if (value.length > WORKFLOW_RELATED_PROJECT_LIMIT) {
145
+ throw new Error(
146
+ `workflowCode.projectInfo.relatedProjects supports at most ${WORKFLOW_RELATED_PROJECT_LIMIT} items.`
147
+ );
148
+ }
149
+ const aliases = /* @__PURE__ */ new Set();
150
+ const projectIds = /* @__PURE__ */ new Set();
151
+ return value.map((item, index) => {
152
+ const field = `workflowCode.projectInfo.relatedProjects[${index}]`;
153
+ if (!isRecord(item)) throw new Error(`${field} must be an object.`);
154
+ const supportedFields = /* @__PURE__ */ new Set(["alias", "projectId", "grantToRelatedProject"]);
155
+ if (Object.keys(item).some((key) => !supportedFields.has(key))) {
156
+ throw new Error(`${field} contains unsupported fields.`);
157
+ }
158
+ const alias = readRelatedProjectString(item["alias"], `${field}.alias`);
159
+ if (!WORKFLOW_RELATED_PROJECT_ALIAS_PATTERN.test(alias)) {
160
+ throw new Error(`${field}.alias is invalid.`);
161
+ }
162
+ if (aliases.has(alias)) {
163
+ throw new Error(`workflowCode.projectInfo.relatedProjects contains duplicate alias "${alias}".`);
164
+ }
165
+ aliases.add(alias);
166
+ const projectId = readRelatedProjectString(item["projectId"], `${field}.projectId`);
167
+ if (!WORKFLOW_PROJECT_UUID_PATTERN.test(projectId)) {
168
+ throw new Error(`${field}.projectId must be a UUID.`);
169
+ }
170
+ const normalizedProjectId = projectId.toLowerCase();
171
+ if (projectIds.has(normalizedProjectId)) {
172
+ throw new Error(`workflowCode.projectInfo.relatedProjects contains duplicate projectId "${projectId}".`);
173
+ }
174
+ projectIds.add(normalizedProjectId);
175
+ return {
176
+ alias,
177
+ projectId,
178
+ grantToRelatedProject: normalizeRelatedProjectGrant(
179
+ item["grantToRelatedProject"],
180
+ `${field}.grantToRelatedProject`
181
+ )
182
+ };
183
+ });
184
+ }
185
+ function normalizeRelatedProjectGrant(value, field) {
186
+ if (!isRecord(value)) throw new Error(`${field} must be an object.`);
187
+ const supported = /* @__PURE__ */ new Set(["read", "write", "knowledge", "entrypoints"]);
188
+ if (Object.keys(value).some((key) => !supported.has(key))) {
189
+ throw new Error(`${field} contains unsupported fields.`);
190
+ }
191
+ const grant = {
192
+ read: normalizeProjectAccessRule(value["read"], `${field}.read`),
193
+ write: normalizeProjectAccessRule(value["write"], `${field}.write`)
194
+ };
195
+ const knowledge = value["knowledge"];
196
+ if (knowledge !== void 0) {
197
+ if (!isRecord(knowledge)) throw new Error(`${field}.knowledge must be an object.`);
198
+ if (Object.keys(knowledge).some((key) => key !== "read" && key !== "write")) {
199
+ throw new Error(`${field}.knowledge contains unsupported fields.`);
200
+ }
201
+ grant.knowledge = {
202
+ read: normalizeProjectAccessRule(knowledge["read"], `${field}.knowledge.read`),
203
+ write: normalizeProjectAccessRule(knowledge["write"], `${field}.knowledge.write`)
204
+ };
205
+ }
206
+ const entrypoints = value["entrypoints"];
207
+ if (entrypoints !== void 0) {
208
+ grant.entrypoints = normalizeProjectAccessRule(entrypoints, `${field}.entrypoints`);
209
+ }
210
+ return grant;
211
+ }
212
+ function normalizeProjectAccessRule(value, field) {
213
+ if (!isRecord(value)) throw new Error(`${field} must be an object.`);
214
+ const mode = value["mode"];
215
+ if (mode === "none" || mode === "all") {
216
+ if (Object.keys(value).some((key) => key !== "mode")) {
217
+ throw new Error(`${field} contains unsupported fields.`);
218
+ }
219
+ return { mode };
220
+ }
221
+ if (mode !== "prefixes") {
222
+ throw new Error(`${field}.mode must be "none", "all", or "prefixes".`);
223
+ }
224
+ if (Object.keys(value).some((key) => key !== "mode" && key !== "prefixes")) {
225
+ throw new Error(`${field} contains unsupported fields.`);
226
+ }
227
+ const rawPrefixes = value["prefixes"];
228
+ if (!Array.isArray(rawPrefixes) || rawPrefixes.length === 0 || rawPrefixes.length > WORKFLOW_RELATED_PROJECT_PREFIX_LIMIT) {
229
+ throw new Error(
230
+ `${field}.prefixes must contain 1 to ${WORKFLOW_RELATED_PROJECT_PREFIX_LIMIT} items.`
231
+ );
232
+ }
233
+ const prefixes = rawPrefixes.map((prefix, index) => {
234
+ const normalized = readRelatedProjectString(prefix, `${field}.prefixes[${index}]`);
235
+ if (normalized.length > WORKFLOW_RELATED_PROJECT_KEY_LIMIT || normalized.includes("\0")) {
236
+ throw new Error(`${field}.prefixes[${index}] is invalid.`);
237
+ }
238
+ return normalized;
239
+ });
240
+ if (new Set(prefixes).size !== prefixes.length) {
241
+ throw new Error(`${field}.prefixes contains duplicates.`);
242
+ }
243
+ return { mode: "prefixes", prefixes };
244
+ }
245
+ function readRelatedProjectString(value, field) {
246
+ if (typeof value !== "string" || value.trim() === "") {
247
+ throw new Error(`${field} must be a non-empty string.`);
248
+ }
249
+ return value.trim();
250
+ }
251
+ function normalizeIds(value, field) {
252
+ if (value === void 0) return [];
253
+ if (!Array.isArray(value)) throw new Error(`${field} must be an array.`);
254
+ const values = value.map((item, index) => {
255
+ if (typeof item !== "string" || !PROJECT_INFO_ID_PATTERN.test(item.trim())) {
256
+ throw new Error(`${field}[${index}] must be a lowercase identifier.`);
257
+ }
258
+ return item.trim();
259
+ });
260
+ return uniqueStrings(values, true);
261
+ }
262
+ function uniqueStrings(values, caseSensitive) {
263
+ const seen = /* @__PURE__ */ new Set();
264
+ const result = [];
265
+ for (const rawValue of values) {
266
+ const value = rawValue.trim();
267
+ if (value === "") continue;
268
+ const key = caseSensitive ? value : value.toLocaleLowerCase();
269
+ if (seen.has(key)) continue;
270
+ seen.add(key);
271
+ result.push(value);
272
+ }
273
+ return result;
274
+ }
275
+ function isRecord(value) {
276
+ return typeof value === "object" && value !== null && !Array.isArray(value);
277
+ }
278
+
279
+ // .shared/workflow-ids/index.ts
280
+ var WORKFLOW_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
281
+ function isWorkflowUuid(value) {
282
+ return WORKFLOW_UUID_PATTERN.test(value.trim());
283
+ }
284
+ function assertWorkflowUuid(value, label = "workflow id") {
285
+ const normalized = value.trim();
286
+ if (!isWorkflowUuid(normalized)) {
287
+ throw new Error(`Invalid ${label} "${value}".`);
288
+ }
289
+ return normalized;
290
+ }
291
+
292
+ // src/related-projects.ts
293
+ var defaultStorageApi = workflowCode;
294
+ function createCliRelatedProjectRuntime(options) {
295
+ if (options.relatedProjects.length === 0) return {};
296
+ const targetStorageApi = options.storageApi ?? defaultStorageApi;
297
+ const consumer = readProjectSnapshot(options.consumerProjectDir, "Current project");
298
+ const configuredByAlias = /* @__PURE__ */ new Map();
299
+ const configuredProjectIds = /* @__PURE__ */ new Set();
300
+ const configuredPaths = /* @__PURE__ */ new Set();
301
+ for (const spec of options.relatedProjects) {
302
+ if (configuredByAlias.has(spec.alias)) {
303
+ throw new Error(`Related project alias "${spec.alias}" was specified more than once.`);
304
+ }
305
+ const target = readProjectSnapshot(spec.projectDir, `Related project "${spec.alias}"`);
306
+ const targetProjectId = target.projectId.toLowerCase();
307
+ if (configuredProjectIds.has(targetProjectId)) {
308
+ throw new Error(`Related project UUID "${target.projectId}" was specified more than once.`);
309
+ }
310
+ if (configuredPaths.has(target.projectDir)) {
311
+ throw new Error(`Related project directory "${target.projectDir}" was specified more than once.`);
312
+ }
313
+ assertConfirmedRelationship(consumer, target, spec.alias);
314
+ configuredByAlias.set(spec.alias, target);
315
+ configuredProjectIds.add(targetProjectId);
316
+ configuredPaths.add(target.projectDir);
317
+ }
318
+ const localKvStoreDir = options.localKvStoreDir ?? path.join(os.tmpdir(), "workflow-code-kv");
319
+ const localPersistentValueStoreDir = options.localPersistentValueStoreDir;
320
+ const localKvStore = targetStorageApi.createLocalWorkflowKVStore({
321
+ rootDir: localKvStoreDir,
322
+ storageKind: "kv"
323
+ });
324
+ const localPersistentValueStore = typeof targetStorageApi.createWorkflowPersistentValueContext === "function" && localPersistentValueStoreDir !== void 0 ? targetStorageApi.createLocalWorkflowKVStore({
325
+ rootDir: localPersistentValueStoreDir,
326
+ storageKind: "persistent_value"
327
+ }) : void 0;
328
+ return {
329
+ localKvStore,
330
+ localResolver: createResolver("local", localKvStore, localPersistentValueStore),
331
+ serverResolver: createResolver("server")
332
+ };
333
+ function createResolver(location, fixedStore, fixedPersistentValueStore) {
334
+ return {
335
+ resolve(alias) {
336
+ const configured = configuredByAlias.get(alias);
337
+ if (configured === void 0) return void 0;
338
+ const currentConsumer = readProjectSnapshot(consumer.projectDir, "Current project");
339
+ const currentTarget = readProjectSnapshot(
340
+ configured.projectDir,
341
+ `Related project "${alias}"`
342
+ );
343
+ if (currentConsumer.projectId.toLowerCase() !== consumer.projectId.toLowerCase() || currentTarget.projectId.toLowerCase() !== configured.projectId.toLowerCase()) {
344
+ return void 0;
345
+ }
346
+ const access = resolveWorkflowRelatedProjectAccess({
347
+ consumerProjectId: currentConsumer.projectId,
348
+ consumerProjectInfo: currentConsumer.projectInfo,
349
+ targetProjectId: currentTarget.projectId,
350
+ targetProjectInfo: currentTarget.projectInfo,
351
+ alias
352
+ });
353
+ if (access === void 0 || !isWorkflowProjectDataStorageLocationAllowed(currentTarget.projectInfo, location)) {
354
+ return void 0;
355
+ }
356
+ const createRemoteStore = targetStorageApi.createRemoteWorkflowRelatedProjectKVStoreFromEnv;
357
+ if (fixedStore === void 0 && typeof createRemoteStore !== "function") {
358
+ throw new Error("Installed workflow-code does not support related project storage.");
359
+ }
360
+ const roots = options.localStorageForProject?.(currentTarget.projectDir) ?? { kvStoreDir: localKvStoreDir, persistentValueStoreDir: localPersistentValueStoreDir };
361
+ const store = location === "local" && options.localStorageForProject ? targetStorageApi.createLocalWorkflowKVStore({ rootDir: roots.kvStoreDir, storageKind: "kv" }) : fixedStore ?? createRemoteStore({
362
+ consumerProjectId: currentConsumer.projectId,
363
+ targetProjectId: currentTarget.projectId,
364
+ alias,
365
+ env: options.env
366
+ });
367
+ if (store === void 0) return void 0;
368
+ const source = {
369
+ projectId: currentConsumer.projectId,
370
+ runId: options.runId,
371
+ conversationId: options.conversationId
372
+ };
373
+ const persistentStore = location === "server" ? store : options.localStorageForProject && roots.persistentValueStoreDir !== void 0 ? targetStorageApi.createLocalWorkflowKVStore({ rootDir: roots.persistentValueStoreDir, storageKind: "persistent_value" }) : fixedPersistentValueStore;
374
+ const persistentValue = persistentStore === void 0 ? void 0 : targetStorageApi.createWorkflowPersistentValueContext?.({
375
+ store: persistentStore,
376
+ projectId: currentTarget.projectId,
377
+ source
378
+ });
379
+ const runner = createLocalProjectRunner({
380
+ projectDir: currentTarget.projectDir,
381
+ // Where the target keeps its data decides which store its own run
382
+ // uses: a project that lives on the server runs against the server,
383
+ // exactly as it would if its author started it here.
384
+ location,
385
+ // The roots settled above, not the raw options: the relation and the
386
+ // target's own run have to land on the same store, defaults
387
+ // included, or one side writes what the other cannot see.
388
+ kvStoreDir: roots.kvStoreDir,
389
+ persistentValueStoreDir: roots.persistentValueStoreDir,
390
+ storageApi: targetStorageApi
391
+ });
392
+ return {
393
+ projectId: currentTarget.projectId,
394
+ grant: access.grant,
395
+ kv: targetStorageApi.createWorkflowKVContext({
396
+ store,
397
+ projectId: currentTarget.projectId,
398
+ source
399
+ }),
400
+ ...persistentValue === void 0 ? {} : { persistentValue },
401
+ ...runner === void 0 ? {} : { runner }
402
+ };
403
+ }
404
+ };
405
+ }
406
+ }
407
+ function createLocalProjectRunner(options) {
408
+ const projectDir = options.projectDir;
409
+ const targetStorageApi = options.storageApi;
410
+ const inspect = targetStorageApi.inspectLocalWorkflowStructure;
411
+ const run = targetStorageApi.runLocalWorkflowWithReport;
412
+ const unwrap = targetStorageApi.readEntrypointOutput;
413
+ if (typeof inspect !== "function" || typeof run !== "function" || typeof unwrap !== "function") {
414
+ return void 0;
415
+ }
416
+ return {
417
+ async listEntrypoints() {
418
+ const structure = inspect({ workflowDir: projectDir });
419
+ return (structure.entrypoints ?? []).map((entrypoint) => ({
420
+ id: entrypoint.id,
421
+ ...entrypoint.title === void 0 ? {} : { title: entrypoint.title },
422
+ ...entrypoint.description === void 0 ? {} : { description: entrypoint.description }
423
+ }));
424
+ },
425
+ async run(entrypointId, args) {
426
+ const report = await run({
427
+ workflowDir: projectDir,
428
+ entrypointId,
429
+ args: [...args],
430
+ storage: options.location === "server" ? { host: "server" } : {
431
+ host: "local",
432
+ local: {
433
+ kvStoreDir: options.kvStoreDir,
434
+ persistentValueStoreDir: options.persistentValueStoreDir
435
+ }
436
+ }
437
+ });
438
+ if (report.result === void 0) {
439
+ throw new Error(`Related project entrypoint "${entrypointId}" returned no result.`);
440
+ }
441
+ return unwrap(report.result.data);
442
+ }
443
+ };
444
+ }
445
+ function assertConfirmedRelationship(consumer, target, alias) {
446
+ const consumerRelation = consumer.projectInfo.relatedProjects.find(
447
+ (relation) => relation.alias === alias
448
+ );
449
+ if (consumerRelation?.projectId.toLowerCase() !== target.projectId.toLowerCase()) {
450
+ throw new Error(
451
+ `Current project does not declare related project "${alias}" with UUID "${target.projectId}".`
452
+ );
453
+ }
454
+ if (resolveWorkflowRelatedProjectAccess({
455
+ consumerProjectId: consumer.projectId,
456
+ consumerProjectInfo: consumer.projectInfo,
457
+ targetProjectId: target.projectId,
458
+ targetProjectInfo: target.projectInfo,
459
+ alias
460
+ }) === void 0) {
461
+ throw new Error(`Related project "${alias}" is not confirmed by both project manifests.`);
462
+ }
463
+ }
464
+ function readProjectSnapshot(projectDir, label) {
465
+ let canonicalDir;
466
+ try {
467
+ canonicalDir = realpathSync(projectDir);
468
+ if (!statSync(canonicalDir).isDirectory()) {
469
+ throw new Error("not a directory");
470
+ }
471
+ } catch (error) {
472
+ throw new Error(`${label} directory is unavailable: ${projectDir}`, { cause: error });
473
+ }
474
+ const packagePath = path.join(canonicalDir, "package.json");
475
+ let packageJson;
476
+ try {
477
+ packageJson = JSON.parse(readFileSync(packagePath, "utf8"));
478
+ } catch (error) {
479
+ throw new Error(`${label} must contain a readable package.json: ${canonicalDir}`, {
480
+ cause: error
481
+ });
482
+ }
483
+ if (!isRecord2(packageJson) || typeof packageJson["id"] !== "string") {
484
+ throw new Error(`${label} package.json.id must be a UUID.`);
485
+ }
486
+ const projectId = assertWorkflowUuid(packageJson["id"], `${label} package.json.id`);
487
+ const projectInfo = readWorkflowProjectInfo(packageJson);
488
+ if (projectInfo.dataStorage === null) {
489
+ throw new Error(`${label} must declare workflowCode.projectInfo.dataStorage.mode.`);
490
+ }
491
+ return { projectDir: canonicalDir, projectId, projectInfo };
492
+ }
493
+ function isRecord2(value) {
494
+ return typeof value === "object" && value !== null && !Array.isArray(value);
495
+ }
496
+
497
+ // src/web/kanban-data.ts
498
+ import { createHash } from "node:crypto";
499
+ import path2 from "node:path";
500
+
501
+ // .shared/kanban-project/index.ts
502
+ var KANBAN_FILE_MAX_BYTES = 50 * 1024 * 1024;
503
+
504
+ // src/web/kanban-data.ts
505
+ var webProjectDataDirectory = (storageDir, projectDir) => path2.join(storageDir, createHash("sha256").update(projectDir).digest("hex").slice(0, 24));
506
+
507
+ // src/web/related-projects.ts
508
+ function createWebRelatedProjectRuntime(core, options) {
509
+ const roots = (projectDir) => {
510
+ const directory = webProjectDataDirectory(options.storageDir, projectDir);
511
+ return { kvStoreDir: path3.join(directory, "kv"), persistentValueStoreDir: path3.join(directory, "values") };
512
+ };
513
+ const local = roots(options.projectDir);
514
+ return createCliRelatedProjectRuntime({
515
+ consumerProjectDir: options.projectDir,
516
+ relatedProjects: options.relatedProjects,
517
+ localKvStoreDir: local.kvStoreDir,
518
+ localPersistentValueStoreDir: local.persistentValueStoreDir,
519
+ localStorageForProject: roots,
520
+ storageApi: core,
521
+ runId: options.runId,
522
+ conversationId: options.conversationId,
523
+ env: process.env
524
+ });
525
+ }
526
+ export {
527
+ createWebRelatedProjectRuntime
528
+ };
@@ -123,6 +123,13 @@ try {
123
123
  process.env.WORKFLOW_DEVELOPER_ASSISTANT_WORKSPACE_MANIFEST = manifestPath;
124
124
  }
125
125
  const storage = { host: 'local', local: { kvStoreDir: path.join(dataDir, 'kv'), persistentValueStoreDir: path.join(dataDir, 'values') } };
126
+ if (request.method === 'run' || request.method === 'resolve') {
127
+ const { createWebRelatedProjectRuntime } = await import(new URL(import.meta.url.includes('/src/web/') ? './related-projects.ts' : './related-projects.mjs', import.meta.url));
128
+ const related = createWebRelatedProjectRuntime(core, { projectDir, storageDir: request.storageDir,
129
+ relatedProjects: request.relatedProjects ?? [], runId: request.runId, conversationId: request.conversationId });
130
+ storage.local.relatedProjectResolver = related.localResolver;
131
+ storage.server = { relatedProjectResolver: related.serverResolver };
132
+ }
126
133
  const filesDir = path.join(dataDir, 'files');
127
134
  const createdFiles = [];
128
135
  const managed = createServerWorkflowFileStore({ rootDir: filesDir, workflowName: request.project.id, conversationId: request.conversationId });
@@ -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",
@@ -17386,6 +17388,69 @@ ${state.user?.avatarUrl ?? ""}`;
17386
17388
  }
17387
17389
  };
17388
17390
 
17391
+ // src/web/ui/clipboard-bridge.ts
17392
+ var ClipboardBridge = class {
17393
+ constructor(send, browserWrite, requiresHost = false) {
17394
+ this.send = send;
17395
+ this.browserWrite = browserWrite;
17396
+ this.requiresHost = requiresHost;
17397
+ }
17398
+ send;
17399
+ browserWrite;
17400
+ requiresHost;
17401
+ enabled = false;
17402
+ pending = /* @__PURE__ */ new Map();
17403
+ enable(version) {
17404
+ this.enabled = version === 1;
17405
+ }
17406
+ async writeText(text3) {
17407
+ if (!this.enabled) {
17408
+ if (this.requiresHost) {
17409
+ throw new Error(t2("Host clipboard is unavailable."));
17410
+ }
17411
+ return this.browserWrite(text3);
17412
+ }
17413
+ const requestId = crypto.randomUUID();
17414
+ return new Promise((resolve, reject) => {
17415
+ const timer = setTimeout(() => {
17416
+ this.pending.delete(requestId);
17417
+ reject(new Error(t2("Copy timed out.")));
17418
+ }, 1e4);
17419
+ this.pending.set(requestId, { resolve, reject, timer });
17420
+ try {
17421
+ this.send({ type: "workflow-clipboard", version: 1, command: "writeText", requestId, text: text3 });
17422
+ } catch (error) {
17423
+ clearTimeout(timer);
17424
+ this.pending.delete(requestId);
17425
+ reject(error);
17426
+ }
17427
+ });
17428
+ }
17429
+ receive(reply) {
17430
+ if (reply?.version !== 1 || typeof reply.requestId !== "string" || typeof reply.ok !== "boolean" || reply.error !== void 0 && typeof reply.error !== "string") {
17431
+ return;
17432
+ }
17433
+ const pending = this.pending.get(reply.requestId);
17434
+ if (!pending) {
17435
+ return;
17436
+ }
17437
+ this.pending.delete(reply.requestId);
17438
+ clearTimeout(pending.timer);
17439
+ if (reply.ok) {
17440
+ pending.resolve();
17441
+ } else {
17442
+ pending.reject(new Error(reply.error || t2("Host clipboard is unavailable.")));
17443
+ }
17444
+ }
17445
+ dispose() {
17446
+ for (const pending of this.pending.values()) {
17447
+ clearTimeout(pending.timer);
17448
+ pending.reject(new Error(t2("Connection changed.")));
17449
+ }
17450
+ this.pending.clear();
17451
+ }
17452
+ };
17453
+
17389
17454
  // src/web/ui/host.ts
17390
17455
  function acquireWorkflowHost() {
17391
17456
  const embedded = globalThis.acquireVsCodeApi;
@@ -17407,6 +17472,12 @@ ${state.user?.avatarUrl ?? ""}`;
17407
17472
  window.parent.postMessage(message, "*");
17408
17473
  }
17409
17474
  });
17475
+ const clipboard = new ClipboardBridge(
17476
+ (message) => window.parent.postMessage(message, "*"),
17477
+ (text3) => navigator.clipboard.writeText(text3),
17478
+ window.parent !== window && document.body.dataset.accountHost === "true"
17479
+ );
17480
+ addEventListener("pagehide", () => clipboard.dispose());
17410
17481
  const connection = new BrowserConnection((message) => {
17411
17482
  if (message.type === "state" && message.account) {
17412
17483
  accountPanel.update(message.account);
@@ -17429,6 +17500,10 @@ ${state.user?.avatarUrl ?? ""}`;
17429
17500
  if (event.data.traceVersion !== void 0) {
17430
17501
  trace.enable(event.data.traceVersion);
17431
17502
  }
17503
+ if (event.data.clipboardVersion !== void 0) {
17504
+ clipboard.enable(event.data.clipboardVersion);
17505
+ }
17506
+ clipboard.receive(event.data.clipboard);
17432
17507
  const traceCommand = trace.command(event.data.traceCommand);
17433
17508
  if (traceCommand) {
17434
17509
  void request(traceCommand).catch((error) => trace.receive({ type: "trace-run", runId: traceCommand.id, error: error.message }));
@@ -17522,8 +17597,13 @@ ${state.user?.avatarUrl ?? ""}`;
17522
17597
  }
17523
17598
  }
17524
17599
  if (message.type === "copy") {
17525
- void navigator.clipboard.writeText(message.text).catch((error) => emit({ type: "error", message: error.message }));
17526
- return;
17600
+ try {
17601
+ await clipboard.writeText(message.text);
17602
+ } catch (error) {
17603
+ emit({ type: "error", message: error.message });
17604
+ return false;
17605
+ }
17606
+ return true;
17527
17607
  }
17528
17608
  if (message.type === "attach") {
17529
17609
  chooseFiles(message);
@@ -18249,8 +18329,10 @@ ${state.user?.avatarUrl ?? ""}`;
18249
18329
  error.textContent = message;
18250
18330
  error.hidden = false;
18251
18331
  }
18252
- function copy2(text3, button) {
18253
- vscode.postMessage({ type: "copy", text: text3 });
18332
+ async function copy2(text3, button) {
18333
+ if (await vscode.postMessage({ type: "copy", text: text3 }) === false) {
18334
+ return;
18335
+ }
18254
18336
  button.innerHTML = iconHtml("check");
18255
18337
  button.title = t2("Copied");
18256
18338
  setTimeout(() => {
@@ -16172,6 +16172,8 @@
16172
16172
  "Configuration name": "\u914D\u7F6E\u540D\u79F0",
16173
16173
  "Connecting to project": "\u6B63\u5728\u8FDE\u63A5\u9879\u76EE",
16174
16174
  "Connection changed.": "\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002",
16175
+ "Copy timed out.": "\u590D\u5236\u8D85\u65F6\u3002",
16176
+ "Host clipboard is unavailable.": "\u5BBF\u4E3B\u526A\u8D34\u677F\u4E0D\u53EF\u7528\u3002",
16175
16177
  "Connection interrupted": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD",
16176
16178
  "Connection interrupted.": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002",
16177
16179
  "Connection not verified": "\u8FDE\u63A5\u672A\u9A8C\u8BC1",
@@ -19237,6 +19239,69 @@ ${state2.user?.avatarUrl ?? ""}`;
19237
19239
  }
19238
19240
  };
19239
19241
 
19242
+ // src/web/ui/clipboard-bridge.ts
19243
+ var ClipboardBridge = class {
19244
+ constructor(send, browserWrite, requiresHost = false) {
19245
+ this.send = send;
19246
+ this.browserWrite = browserWrite;
19247
+ this.requiresHost = requiresHost;
19248
+ }
19249
+ send;
19250
+ browserWrite;
19251
+ requiresHost;
19252
+ enabled = false;
19253
+ pending = /* @__PURE__ */ new Map();
19254
+ enable(version) {
19255
+ this.enabled = version === 1;
19256
+ }
19257
+ async writeText(text3) {
19258
+ if (!this.enabled) {
19259
+ if (this.requiresHost) {
19260
+ throw new Error(t2("Host clipboard is unavailable."));
19261
+ }
19262
+ return this.browserWrite(text3);
19263
+ }
19264
+ const requestId2 = crypto.randomUUID();
19265
+ return new Promise((resolve2, reject) => {
19266
+ const timer = setTimeout(() => {
19267
+ this.pending.delete(requestId2);
19268
+ reject(new Error(t2("Copy timed out.")));
19269
+ }, 1e4);
19270
+ this.pending.set(requestId2, { resolve: resolve2, reject, timer });
19271
+ try {
19272
+ this.send({ type: "workflow-clipboard", version: 1, command: "writeText", requestId: requestId2, text: text3 });
19273
+ } catch (error) {
19274
+ clearTimeout(timer);
19275
+ this.pending.delete(requestId2);
19276
+ reject(error);
19277
+ }
19278
+ });
19279
+ }
19280
+ receive(reply) {
19281
+ if (reply?.version !== 1 || typeof reply.requestId !== "string" || typeof reply.ok !== "boolean" || reply.error !== void 0 && typeof reply.error !== "string") {
19282
+ return;
19283
+ }
19284
+ const pending = this.pending.get(reply.requestId);
19285
+ if (!pending) {
19286
+ return;
19287
+ }
19288
+ this.pending.delete(reply.requestId);
19289
+ clearTimeout(pending.timer);
19290
+ if (reply.ok) {
19291
+ pending.resolve();
19292
+ } else {
19293
+ pending.reject(new Error(reply.error || t2("Host clipboard is unavailable.")));
19294
+ }
19295
+ }
19296
+ dispose() {
19297
+ for (const pending of this.pending.values()) {
19298
+ clearTimeout(pending.timer);
19299
+ pending.reject(new Error(t2("Connection changed.")));
19300
+ }
19301
+ this.pending.clear();
19302
+ }
19303
+ };
19304
+
19240
19305
  // src/web/ui/host.ts
19241
19306
  function acquireWorkflowHost() {
19242
19307
  const embedded = globalThis.acquireVsCodeApi;
@@ -19258,6 +19323,12 @@ ${state2.user?.avatarUrl ?? ""}`;
19258
19323
  window.parent.postMessage(message, "*");
19259
19324
  }
19260
19325
  });
19326
+ const clipboard = new ClipboardBridge(
19327
+ (message) => window.parent.postMessage(message, "*"),
19328
+ (text3) => navigator.clipboard.writeText(text3),
19329
+ window.parent !== window && document.body.dataset.accountHost === "true"
19330
+ );
19331
+ addEventListener("pagehide", () => clipboard.dispose());
19261
19332
  const connection = new BrowserConnection((message) => {
19262
19333
  if (message.type === "state" && message.account) {
19263
19334
  accountPanel.update(message.account);
@@ -19280,6 +19351,10 @@ ${state2.user?.avatarUrl ?? ""}`;
19280
19351
  if (event.data.traceVersion !== void 0) {
19281
19352
  trace.enable(event.data.traceVersion);
19282
19353
  }
19354
+ if (event.data.clipboardVersion !== void 0) {
19355
+ clipboard.enable(event.data.clipboardVersion);
19356
+ }
19357
+ clipboard.receive(event.data.clipboard);
19283
19358
  const traceCommand = trace.command(event.data.traceCommand);
19284
19359
  if (traceCommand) {
19285
19360
  void request(traceCommand).catch((error) => trace.receive({ type: "trace-run", runId: traceCommand.id, error: error.message }));
@@ -19373,8 +19448,13 @@ ${state2.user?.avatarUrl ?? ""}`;
19373
19448
  }
19374
19449
  }
19375
19450
  if (message.type === "copy") {
19376
- void navigator.clipboard.writeText(message.text).catch((error) => emit({ type: "error", message: error.message }));
19377
- return;
19451
+ try {
19452
+ await clipboard.writeText(message.text);
19453
+ } catch (error) {
19454
+ emit({ type: "error", message: error.message });
19455
+ return false;
19456
+ }
19457
+ return true;
19378
19458
  }
19379
19459
  if (message.type === "attach") {
19380
19460
  chooseFiles(message);
@@ -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,7 +21,7 @@ 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
27
  createEmptyWorkflowAuthConfig,
@@ -100,6 +100,8 @@ var messages_zh_cn_default = {
100
100
  "Configuration name": "\u914D\u7F6E\u540D\u79F0",
101
101
  "Connecting to project": "\u6B63\u5728\u8FDE\u63A5\u9879\u76EE",
102
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",
103
105
  "Connection interrupted": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD",
104
106
  "Connection interrupted.": "\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002",
105
107
  "Connection not verified": "\u8FDE\u63A5\u672A\u9A8C\u8BC1",
@@ -2485,7 +2487,7 @@ function cliRoot() {
2485
2487
  }
2486
2488
  throw new Error("Cannot locate the CLI installation.");
2487
2489
  }
2488
- function createWebRuntime(projectDir, storageDir, connection) {
2490
+ function createWebRuntime(projectDir, storageDir, connection, relatedProjects = () => []) {
2489
2491
  const root = cliRoot();
2490
2492
  const builtWorker = path5.join(root, "dist/web/worker.mjs");
2491
2493
  const worker = existsSync(builtWorker) ? builtWorker : path5.join(root, "src/web/worker.mjs");
@@ -2597,7 +2599,7 @@ function createWebRuntime(projectDir, storageDir, connection) {
2597
2599
  }
2598
2600
  });
2599
2601
  child.stdin.on("error", () => void 0);
2600
- child.stdin.end(JSON.stringify({ ...request, storageDir, runtimeProjectDir: projectDir }));
2602
+ child.stdin.end(JSON.stringify({ ...request, storageDir, runtimeProjectDir: projectDir, relatedProjects: relatedProjects() }));
2601
2603
  });
2602
2604
  return {
2603
2605
  available: true,
@@ -2735,7 +2737,8 @@ async function startWebServer(options) {
2735
2737
  let authenticationInitialized = false;
2736
2738
  let authCheck;
2737
2739
  const sensitiveTokens = /* @__PURE__ */ new Set();
2738
- const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection);
2740
+ let relatedProjects = options.host?.relatedProjects ?? [];
2741
+ const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection, () => relatedProjects);
2739
2742
  let inspected;
2740
2743
  try {
2741
2744
  inspected = await inspectWebProject(runtime, projectDir, options.host);
@@ -3448,6 +3451,9 @@ ${token}`;
3448
3451
  controller,
3449
3452
  workflowController,
3450
3453
  kanbanController,
3454
+ setHostRelatedProjects: (value) => {
3455
+ relatedProjects = value;
3456
+ },
3451
3457
  setHostConnection: async (value) => {
3452
3458
  authEpoch++;
3453
3459
  authCheck?.abort();
@@ -3486,6 +3492,23 @@ import { createInterface } from "readline";
3486
3492
 
3487
3493
  // src/web/host-config.ts
3488
3494
  import path7 from "path";
3495
+ function parseHostRelatedProjects(value) {
3496
+ if (value === void 0) {
3497
+ return [];
3498
+ }
3499
+ if (!Array.isArray(value) || value.length > 100) {
3500
+ throw new Error("Invalid host related projects.");
3501
+ }
3502
+ const aliases = /* @__PURE__ */ new Set(), directories = /* @__PURE__ */ new Set();
3503
+ return value.map((project) => {
3504
+ 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)) {
3505
+ throw new Error("Invalid host related projects.");
3506
+ }
3507
+ aliases.add(project.alias);
3508
+ directories.add(project.projectDir);
3509
+ return { alias: project.alias, projectDir: project.projectDir };
3510
+ });
3511
+ }
3489
3512
  function parseHostConnection(value) {
3490
3513
  if (value === null || value === void 0) {
3491
3514
  return;
@@ -3519,7 +3542,13 @@ function parseHostConfiguration(value) {
3519
3542
  if (configuration.surface !== "assistant" && (assistant || assistantProjectDir !== void 0)) {
3520
3543
  throw new Error("Assistant configuration requires the assistant surface.");
3521
3544
  }
3522
- return { surface: configuration.surface, assistant, assistantProjectDir, connection: parseHostConnection(configuration.connection) };
3545
+ return {
3546
+ surface: configuration.surface,
3547
+ assistant,
3548
+ assistantProjectDir,
3549
+ connection: parseHostConnection(configuration.connection),
3550
+ relatedProjects: parseHostRelatedProjects(configuration.relatedProjects)
3551
+ };
3523
3552
  }
3524
3553
 
3525
3554
  // src/web.ts
@@ -3613,7 +3642,7 @@ async function main(argv) {
3613
3642
  };
3614
3643
  process.once("SIGINT", close);
3615
3644
  process.once("SIGTERM", close);
3616
- console.log(options.json ? JSON.stringify({ type: "ready", protocolVersion: 1, hostProtocolVersion: 1, accountProtocolVersion: 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}`);
3645
+ 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}`);
3617
3646
  if (lines) {
3618
3647
  void (async () => {
3619
3648
  try {
@@ -3628,6 +3657,8 @@ async function main(argv) {
3628
3657
  process.exitCode = 1;
3629
3658
  close();
3630
3659
  });
3660
+ } else if (message.type === "related-projects") {
3661
+ app.setHostRelatedProjects(parseHostRelatedProjects(message.relatedProjects));
3631
3662
  } else if (message.type === "close") {
3632
3663
  break;
3633
3664
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow-code/cli",
3
- "version": "0.2.4-20261012004.codex-studio-integration",
3
+ "version": "0.2.4-20261012006.codex-studio-integration",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "workflow-code": "./dist/index.js"
@@ -36,17 +36,17 @@
36
36
  "typescript": "^6.0.3",
37
37
  "vitest": "^4.1.7",
38
38
  "workflow-code": "0.2.4-20260929001",
39
+ "@workflow-code/auth-config": "0.1.0",
39
40
  "@workflow-code/execution-targets": "0.1.0",
40
41
  "@workflow-code/i18n": "0.1.0",
41
42
  "@workflow-code/kanban-project": "0.1.0",
42
43
  "@workflow-code/openai-responses": "0.1.0",
43
44
  "@workflow-code/project-dependencies": "0.1.0",
44
45
  "@workflow-code/project-info": "0.1.0",
45
- "@workflow-code/runtime-compatibility": "0.1.0",
46
46
  "@workflow-code/project-upload": "0.1.0",
47
- "@workflow-code/workflow-mcp": "0.1.0",
47
+ "@workflow-code/runtime-compatibility": "0.1.0",
48
48
  "@workflow-code/workflow-ids": "0.1.0",
49
- "@workflow-code/auth-config": "0.1.0"
49
+ "@workflow-code/workflow-mcp": "0.1.0"
50
50
  },
51
51
  "scripts": {
52
52
  "bootstrap": "node scripts/bootstrap.mjs",