@workflow-code/cli 0.2.4-20261012003.codex-cli-web → 0.2.4-20261012005.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;
@@ -163,6 +163,7 @@ async function clearCliAuthConfig(env = process.env) {
163
163
  }
164
164
 
165
165
  export {
166
+ createEmptyWorkflowAuthConfig,
166
167
  normalizeWorkflowAuthConfig,
167
168
  isWorkflowAuthConfigLoggedIn,
168
169
  isWorkflowAuthExpiringSoon,
package/dist/index.js CHANGED
@@ -24,16 +24,16 @@ 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-YUMRIE22.js");
27
+ const webCli = await import("./web-6SWHMU2A.js");
28
28
  return webCli.main(argv.slice(1));
29
29
  }
30
30
  if (argv[0] === "workspace") {
31
- const workspaceCli = await import("./workspace-V3UXVFK6.js");
31
+ const workspaceCli = await import("./workspace-OKFR57T6.js");
32
32
  await workspaceCli.main(argv.slice(1), { localeInitialized: true });
33
33
  return typeof process.exitCode === "number" ? process.exitCode : 0;
34
34
  }
35
35
  if (argv[0] === "mcp") {
36
- const mcpCli = await import("./mcp-LQ2IV6VA.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,13 +1,13 @@
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,
8
8
  readCliAuthConfigSync,
9
9
  resolveWorkflowDesktopConfigFilePath
10
- } from "./chunk-PGM3IPWA.js";
10
+ } from "./chunk-SGVXPYCT.js";
11
11
  import {
12
12
  assertWorkflowUuid
13
13
  } from "./chunk-P6K3NB5S.js";
@@ -12720,6 +12720,7 @@
12720
12720
  Loop: "\u5FAA\u73AF",
12721
12721
  "Loop end": "\u5FAA\u73AF\u7ED3\u675F",
12722
12722
  "Managed by server": "\u7531\u670D\u52A1\u5668\u7BA1\u7406",
12723
+ "Manage this account through Studio.": "\u8BF7\u5728 Studio \u4E2D\u7BA1\u7406\u6B64\u8D26\u53F7\u3002",
12723
12724
  Merge: "\u6C47\u5408",
12724
12725
  Message: "\u6D88\u606F",
12725
12726
  Monthly: "\u6BCF\u6708",
@@ -15144,8 +15145,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
15144
15145
 
15145
15146
  // src/web/ui/account.ts
15146
15147
  var AccountPanel = class {
15147
- constructor(menu2, request2) {
15148
+ constructor(menu2, request2, openHostAccount) {
15148
15149
  this.request = request2;
15150
+ this.openHostAccount = openHostAccount;
15149
15151
  this.button.id = "web-account";
15150
15152
  this.button.className = "web-account";
15151
15153
  this.button.type = "button";
@@ -15217,6 +15219,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
15217
15219
  });
15218
15220
  }
15219
15221
  request;
15222
+ openHostAccount;
15220
15223
  button = document.createElement("button");
15221
15224
  dialog = document.createElement("dialog");
15222
15225
  state;
@@ -15226,6 +15229,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
15226
15229
  return this.dialog.querySelector(selector);
15227
15230
  }
15228
15231
  open() {
15232
+ if (this.openHostAccount) {
15233
+ this.openHostAccount();
15234
+ return;
15235
+ }
15229
15236
  this.render();
15230
15237
  if (!this.dialog.open) {
15231
15238
  this.dialog.showModal();
@@ -15805,7 +15812,8 @@ ${state2.user?.avatarUrl ?? ""}`;
15805
15812
  };
15806
15813
  let accountPanel;
15807
15814
  queueMicrotask(() => {
15808
- accountPanel = new AccountPanel(document.querySelector("#project-menu"), request2);
15815
+ const openHostAccount = document.body.dataset.accountHost === "true" ? () => window.parent.postMessage({ type: "workflow-account", version: 1 }, "*") : void 0;
15816
+ accountPanel = new AccountPanel(document.querySelector("#project-menu"), request2, openHostAccount);
15809
15817
  });
15810
15818
  return {
15811
15819
  postMessage: async (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 });
@@ -12720,6 +12720,7 @@
12720
12720
  Loop: "\u5FAA\u73AF",
12721
12721
  "Loop end": "\u5FAA\u73AF\u7ED3\u675F",
12722
12722
  "Managed by server": "\u7531\u670D\u52A1\u5668\u7BA1\u7406",
12723
+ "Manage this account through Studio.": "\u8BF7\u5728 Studio \u4E2D\u7BA1\u7406\u6B64\u8D26\u53F7\u3002",
12723
12724
  Merge: "\u6C47\u5408",
12724
12725
  Message: "\u6D88\u606F",
12725
12726
  Monthly: "\u6BCF\u6708",
@@ -16837,8 +16838,9 @@ ${JSON.stringify(output.details, null, 2)}` : "");
16837
16838
 
16838
16839
  // src/web/ui/account.ts
16839
16840
  var AccountPanel = class {
16840
- constructor(menu, request) {
16841
+ constructor(menu, request, openHostAccount) {
16841
16842
  this.request = request;
16843
+ this.openHostAccount = openHostAccount;
16842
16844
  this.button.id = "web-account";
16843
16845
  this.button.className = "web-account";
16844
16846
  this.button.type = "button";
@@ -16910,6 +16912,7 @@ ${JSON.stringify(output.details, null, 2)}` : "");
16910
16912
  });
16911
16913
  }
16912
16914
  request;
16915
+ openHostAccount;
16913
16916
  button = document.createElement("button");
16914
16917
  dialog = document.createElement("dialog");
16915
16918
  state;
@@ -16919,6 +16922,10 @@ ${JSON.stringify(output.details, null, 2)}` : "");
16919
16922
  return this.dialog.querySelector(selector);
16920
16923
  }
16921
16924
  open() {
16925
+ if (this.openHostAccount) {
16926
+ this.openHostAccount();
16927
+ return;
16928
+ }
16922
16929
  this.render();
16923
16930
  if (!this.dialog.open) {
16924
16931
  this.dialog.showModal();
@@ -17498,7 +17505,8 @@ ${state.user?.avatarUrl ?? ""}`;
17498
17505
  };
17499
17506
  let accountPanel;
17500
17507
  queueMicrotask(() => {
17501
- accountPanel = new AccountPanel(document.querySelector("#project-menu"), request);
17508
+ const openHostAccount = document.body.dataset.accountHost === "true" ? () => window.parent.postMessage({ type: "workflow-account", version: 1 }, "*") : void 0;
17509
+ accountPanel = new AccountPanel(document.querySelector("#project-menu"), request, openHostAccount);
17502
17510
  });
17503
17511
  return {
17504
17512
  postMessage: async (message) => {
@@ -16249,6 +16249,7 @@
16249
16249
  Loop: "\u5FAA\u73AF",
16250
16250
  "Loop end": "\u5FAA\u73AF\u7ED3\u675F",
16251
16251
  "Managed by server": "\u7531\u670D\u52A1\u5668\u7BA1\u7406",
16252
+ "Manage this account through Studio.": "\u8BF7\u5728 Studio \u4E2D\u7BA1\u7406\u6B64\u8D26\u53F7\u3002",
16252
16253
  Merge: "\u6C47\u5408",
16253
16254
  Message: "\u6D88\u606F",
16254
16255
  Monthly: "\u6BCF\u6708",
@@ -18688,8 +18689,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
18688
18689
 
18689
18690
  // src/web/ui/account.ts
18690
18691
  var AccountPanel = class {
18691
- constructor(menu2, request) {
18692
+ constructor(menu2, request, openHostAccount) {
18692
18693
  this.request = request;
18694
+ this.openHostAccount = openHostAccount;
18693
18695
  this.button.id = "web-account";
18694
18696
  this.button.className = "web-account";
18695
18697
  this.button.type = "button";
@@ -18761,6 +18763,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
18761
18763
  });
18762
18764
  }
18763
18765
  request;
18766
+ openHostAccount;
18764
18767
  button = document.createElement("button");
18765
18768
  dialog = document.createElement("dialog");
18766
18769
  state;
@@ -18770,6 +18773,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
18770
18773
  return this.dialog.querySelector(selector);
18771
18774
  }
18772
18775
  open() {
18776
+ if (this.openHostAccount) {
18777
+ this.openHostAccount();
18778
+ return;
18779
+ }
18773
18780
  this.render();
18774
18781
  if (!this.dialog.open) {
18775
18782
  this.dialog.showModal();
@@ -19349,7 +19356,8 @@ ${state2.user?.avatarUrl ?? ""}`;
19349
19356
  };
19350
19357
  let accountPanel;
19351
19358
  queueMicrotask(() => {
19352
- accountPanel = new AccountPanel(document.querySelector("#project-menu"), request);
19359
+ const openHostAccount = document.body.dataset.accountHost === "true" ? () => window.parent.postMessage({ type: "workflow-account", version: 1 }, "*") : void 0;
19360
+ accountPanel = new AccountPanel(document.querySelector("#project-menu"), request, openHostAccount);
19353
19361
  });
19354
19362
  return {
19355
19363
  postMessage: async (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,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,
@@ -176,6 +177,7 @@ var messages_zh_cn_default = {
176
177
  Loop: "\u5FAA\u73AF",
177
178
  "Loop end": "\u5FAA\u73AF\u7ED3\u675F",
178
179
  "Managed by server": "\u7531\u670D\u52A1\u5668\u7BA1\u7406",
180
+ "Manage this account through Studio.": "\u8BF7\u5728 Studio \u4E2D\u7BA1\u7406\u6B64\u8D26\u53F7\u3002",
179
181
  Merge: "\u6C47\u5408",
180
182
  Message: "\u6D88\u606F",
181
183
  Monthly: "\u6BCF\u6708",
@@ -2483,7 +2485,7 @@ function cliRoot() {
2483
2485
  }
2484
2486
  throw new Error("Cannot locate the CLI installation.");
2485
2487
  }
2486
- function createWebRuntime(projectDir, storageDir, connection) {
2488
+ function createWebRuntime(projectDir, storageDir, connection, relatedProjects = () => []) {
2487
2489
  const root = cliRoot();
2488
2490
  const builtWorker = path5.join(root, "dist/web/worker.mjs");
2489
2491
  const worker = existsSync(builtWorker) ? builtWorker : path5.join(root, "src/web/worker.mjs");
@@ -2595,7 +2597,7 @@ function createWebRuntime(projectDir, storageDir, connection) {
2595
2597
  }
2596
2598
  });
2597
2599
  child.stdin.on("error", () => void 0);
2598
- child.stdin.end(JSON.stringify({ ...request, storageDir, runtimeProjectDir: projectDir }));
2600
+ child.stdin.end(JSON.stringify({ ...request, storageDir, runtimeProjectDir: projectDir, relatedProjects: relatedProjects() }));
2599
2601
  });
2600
2602
  return {
2601
2603
  available: true,
@@ -2728,12 +2730,13 @@ async function startWebServer(options) {
2728
2730
  let login;
2729
2731
  let passwordLogin = false;
2730
2732
  let hostConnection = options.host?.connection;
2731
- let hostManaged = !!hostConnection;
2733
+ let hostManaged = options.host !== void 0;
2732
2734
  let authEpoch = 0;
2733
2735
  let authenticationInitialized = false;
2734
2736
  let authCheck;
2735
2737
  const sensitiveTokens = /* @__PURE__ */ new Set();
2736
- const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection);
2738
+ let relatedProjects = options.host?.relatedProjects ?? [];
2739
+ const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection, () => relatedProjects);
2737
2740
  let inspected;
2738
2741
  try {
2739
2742
  inspected = await inspectWebProject(runtime, projectDir, options.host);
@@ -2891,7 +2894,7 @@ async function startWebServer(options) {
2891
2894
  return;
2892
2895
  }
2893
2896
  const readingEpoch = authEpoch;
2894
- const config = await readCliAuthConfig();
2897
+ const config = hostManaged ? createEmptyWorkflowAuthConfig() : await readCliAuthConfig();
2895
2898
  if (readingEpoch !== authEpoch) {
2896
2899
  return;
2897
2900
  }
@@ -3220,6 +3223,9 @@ ${token}`;
3220
3223
  break;
3221
3224
  }
3222
3225
  case "login": {
3226
+ if (hostManaged) {
3227
+ throw new Error(t("Manage this account through Studio."));
3228
+ }
3223
3229
  if (!options.host && process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
3224
3230
  throw new Error("This session is supplied by the host. Sign in through the host.");
3225
3231
  }
@@ -3275,15 +3281,19 @@ ${token}`;
3275
3281
  case "verify":
3276
3282
  await refreshAuth(true);
3277
3283
  break;
3278
- case "logout":
3284
+ case "logout": {
3285
+ if (hostManaged) {
3286
+ throw new Error(t("Manage this account through Studio."));
3287
+ }
3279
3288
  signedOut = true;
3280
3289
  login?.kill();
3281
- if (!hostManaged && process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
3290
+ if (process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
3282
3291
  await clearCliAuthConfig();
3283
3292
  }
3284
3293
  authIdentity = "signed-out";
3285
3294
  await refreshAuth();
3286
3295
  break;
3296
+ }
3287
3297
  default:
3288
3298
  throw new Error(`Unsupported action: ${message.type}`);
3289
3299
  }
@@ -3391,7 +3401,7 @@ ${token}`;
3391
3401
  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:";
3392
3402
  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'` });
3393
3403
  const ui = isKanban ? "kanban-workspace" : isWorkflow ? "workflow-workspace" : "workflow-ui";
3394
- 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"}"><div id="app"></div><script src="/assets/${ui}.js" defer></script></body></html>`);
3404
+ 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>`);
3395
3405
  return;
3396
3406
  }
3397
3407
  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" };
@@ -3439,6 +3449,9 @@ ${token}`;
3439
3449
  controller,
3440
3450
  workflowController,
3441
3451
  kanbanController,
3452
+ setHostRelatedProjects: (value) => {
3453
+ relatedProjects = value;
3454
+ },
3442
3455
  setHostConnection: async (value) => {
3443
3456
  authEpoch++;
3444
3457
  authCheck?.abort();
@@ -3477,6 +3490,23 @@ import { createInterface } from "readline";
3477
3490
 
3478
3491
  // src/web/host-config.ts
3479
3492
  import path7 from "path";
3493
+ function parseHostRelatedProjects(value) {
3494
+ if (value === void 0) {
3495
+ return [];
3496
+ }
3497
+ if (!Array.isArray(value) || value.length > 100) {
3498
+ throw new Error("Invalid host related projects.");
3499
+ }
3500
+ const aliases = /* @__PURE__ */ new Set(), directories = /* @__PURE__ */ new Set();
3501
+ return value.map((project) => {
3502
+ 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)) {
3503
+ throw new Error("Invalid host related projects.");
3504
+ }
3505
+ aliases.add(project.alias);
3506
+ directories.add(project.projectDir);
3507
+ return { alias: project.alias, projectDir: project.projectDir };
3508
+ });
3509
+ }
3480
3510
  function parseHostConnection(value) {
3481
3511
  if (value === null || value === void 0) {
3482
3512
  return;
@@ -3510,7 +3540,13 @@ function parseHostConfiguration(value) {
3510
3540
  if (configuration.surface !== "assistant" && (assistant || assistantProjectDir !== void 0)) {
3511
3541
  throw new Error("Assistant configuration requires the assistant surface.");
3512
3542
  }
3513
- return { surface: configuration.surface, assistant, assistantProjectDir, connection: parseHostConnection(configuration.connection) };
3543
+ return {
3544
+ surface: configuration.surface,
3545
+ assistant,
3546
+ assistantProjectDir,
3547
+ connection: parseHostConnection(configuration.connection),
3548
+ relatedProjects: parseHostRelatedProjects(configuration.relatedProjects)
3549
+ };
3514
3550
  }
3515
3551
 
3516
3552
  // src/web.ts
@@ -3604,7 +3640,7 @@ async function main(argv) {
3604
3640
  };
3605
3641
  process.once("SIGINT", close);
3606
3642
  process.once("SIGTERM", close);
3607
- console.log(options.json ? JSON.stringify({ type: "ready", protocolVersion: 1, hostProtocolVersion: 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}`);
3643
+ 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}`);
3608
3644
  if (lines) {
3609
3645
  void (async () => {
3610
3646
  try {
@@ -3619,6 +3655,8 @@ async function main(argv) {
3619
3655
  process.exitCode = 1;
3620
3656
  close();
3621
3657
  });
3658
+ } else if (message.type === "related-projects") {
3659
+ app.setHostRelatedProjects(parseHostRelatedProjects(message.relatedProjects));
3622
3660
  } else if (message.type === "close") {
3623
3661
  break;
3624
3662
  } else {
@@ -14,7 +14,7 @@ import {
14
14
  readCliAuthConfig,
15
15
  readCliAuthConfigSync,
16
16
  writeCliAuthConfig
17
- } from "./chunk-PGM3IPWA.js";
17
+ } from "./chunk-SGVXPYCT.js";
18
18
  import {
19
19
  isWorkflowUuid,
20
20
  readWorkflowProjectInfo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow-code/cli",
3
- "version": "0.2.4-20261012003.codex-cli-web",
3
+ "version": "0.2.4-20261012005.codex-studio-integration",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "workflow-code": "./dist/index.js"
@@ -42,8 +42,8 @@
42
42
  "@workflow-code/kanban-project": "0.1.0",
43
43
  "@workflow-code/openai-responses": "0.1.0",
44
44
  "@workflow-code/project-dependencies": "0.1.0",
45
- "@workflow-code/project-upload": "0.1.0",
46
45
  "@workflow-code/project-info": "0.1.0",
46
+ "@workflow-code/project-upload": "0.1.0",
47
47
  "@workflow-code/runtime-compatibility": "0.1.0",
48
48
  "@workflow-code/workflow-ids": "0.1.0",
49
49
  "@workflow-code/workflow-mcp": "0.1.0"