@workflow-code/cli 0.2.4-20261010003.codex-cli-web → 0.2.4-20261010004.codex-cli-web
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.
- package/dist/index.js +1 -1
- package/dist/web/worker.mjs +18 -2
- package/dist/web/workflow-ui.js +37 -8
- package/dist/{web-7QLJGSAQ.js → web-KUC6BL6E.js} +149 -18
- package/package.json +2 -2
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-
|
|
27
|
+
const webCli = await import("./web-KUC6BL6E.js");
|
|
28
28
|
return webCli.main(argv.slice(1));
|
|
29
29
|
}
|
|
30
30
|
if (argv[0] === "workspace") {
|
package/dist/web/worker.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { readFile, writeFile, readdir, rm, stat, mkdir, link } from 'node:fs/promises';
|
|
2
|
+
import { readFile, writeFile, readdir, rm, stat, mkdir, link, mkdtemp } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
5
|
import { register } from 'tsx/esm/api';
|
|
6
6
|
import { resolve } from 'import-meta-resolve';
|
|
7
7
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
8
8
|
let ts;
|
|
9
|
+
let assistantScopeDirectory;
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
const write = process.stdout.write.bind(process.stdout);
|
|
@@ -93,8 +94,21 @@ try {
|
|
|
93
94
|
}
|
|
94
95
|
send('result', { projects, errors });
|
|
95
96
|
} else if (request.project?.folderPath) {
|
|
96
|
-
const projectKey = createHash('sha256').update(request.project.folderPath).digest('hex').slice(0, 24);
|
|
97
|
+
const projectKey = createHash('sha256').update(request.project.assistant ? `assistant:${request.project.assistant.workdir}` : request.project.folderPath).digest('hex').slice(0, 24);
|
|
97
98
|
const dataDir = path.join(request.storageDir, projectKey);
|
|
99
|
+
if (request.project.assistant) {
|
|
100
|
+
const assistant = request.project.assistant;
|
|
101
|
+
const primary = assistant.roots.find(root => root.folderPath === assistant.workdir);
|
|
102
|
+
if (!primary) { throw new Error('Assistant working directory is not an opened workspace folder.'); }
|
|
103
|
+
await mkdir(dataDir, { recursive: true });
|
|
104
|
+
assistantScopeDirectory = await mkdtemp(path.join(dataDir, 'scope-'));
|
|
105
|
+
const manifestPath = path.join(assistantScopeDirectory, 'workspace.json');
|
|
106
|
+
await writeFile(manifestPath, JSON.stringify({ schemaVersion: 2, primaryTarget: { kind: 'folder', folderId: primary.uri }, warnings: [],
|
|
107
|
+
roots: assistant.roots.map(root => ({ target: { kind: 'folder', folderId: root.uri }, name: root.name, rootPath: root.folderPath, role: root === primary ? 'primary' : 'linked', depth: root === primary ? 0 : 1 })) }));
|
|
108
|
+
process.env.WORKFLOW_DEVELOPER_ASSISTANT_HOST = 'vscode';
|
|
109
|
+
process.env.WORKFLOW_DEVELOPER_ASSISTANT_WORKDIR = assistant.workdir;
|
|
110
|
+
process.env.WORKFLOW_DEVELOPER_ASSISTANT_WORKSPACE_MANIFEST = manifestPath;
|
|
111
|
+
}
|
|
98
112
|
const storage = { host: 'local', local: { kvStoreDir: path.join(dataDir, 'kv'), persistentValueStoreDir: path.join(dataDir, 'values') } };
|
|
99
113
|
const filesDir = path.join(dataDir, 'files');
|
|
100
114
|
const createdFiles = [];
|
|
@@ -205,4 +219,6 @@ try {
|
|
|
205
219
|
} catch (error) {
|
|
206
220
|
write(`__WORKFLOW_WEB__${JSON.stringify({ kind: 'error', error: error.message, status: error.metadata?.status })}\n`);
|
|
207
221
|
process.exitCode = 1;
|
|
222
|
+
} finally {
|
|
223
|
+
if (assistantScopeDirectory) { await rm(assistantScopeDirectory, { recursive: true, force: true }); }
|
|
208
224
|
}
|
package/dist/web/workflow-ui.js
CHANGED
|
@@ -4102,6 +4102,7 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4102
4102
|
constructor(emit) {
|
|
4103
4103
|
this.emit = emit;
|
|
4104
4104
|
if (this.token) {
|
|
4105
|
+
sessionStorage.setItem("workflow-web-session", this.token);
|
|
4105
4106
|
history.replaceState(null, "", `${location.pathname}${location.search}`);
|
|
4106
4107
|
}
|
|
4107
4108
|
queueMicrotask(() => {
|
|
@@ -4139,7 +4140,7 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4139
4140
|
status = "connecting";
|
|
4140
4141
|
attempts = 0;
|
|
4141
4142
|
closed = false;
|
|
4142
|
-
token = new URLSearchParams(location.hash.slice(1)).get("session");
|
|
4143
|
+
token = new URLSearchParams(location.hash.slice(1)).get("session") ?? sessionStorage.getItem("workflow-web-session");
|
|
4143
4144
|
update(status) {
|
|
4144
4145
|
this.status = status;
|
|
4145
4146
|
this.emit({ type: "connection", status });
|
|
@@ -4186,9 +4187,8 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4186
4187
|
this.update("expired");
|
|
4187
4188
|
return;
|
|
4188
4189
|
}
|
|
4189
|
-
this.token = null;
|
|
4190
4190
|
}
|
|
4191
|
-
const session = await fetch("/api/session", { signal: AbortSignal.timeout(1e4) });
|
|
4191
|
+
const session = await fetch("/api/session", { headers: this.headers(), signal: AbortSignal.timeout(1e4) });
|
|
4192
4192
|
if (session.status === 401) {
|
|
4193
4193
|
this.update("expired");
|
|
4194
4194
|
return;
|
|
@@ -4196,13 +4196,13 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4196
4196
|
if (!session.ok) {
|
|
4197
4197
|
throw new Error("Cannot connect to the project.");
|
|
4198
4198
|
}
|
|
4199
|
-
const source = this.source = new EventSource("/api/events");
|
|
4199
|
+
const source = this.source = new EventSource(this.fileUrl("/api/events"));
|
|
4200
4200
|
source.onopen = () => {
|
|
4201
4201
|
this.attempts = 0;
|
|
4202
4202
|
this.update("connected");
|
|
4203
4203
|
};
|
|
4204
4204
|
source.onmessage = (event) => {
|
|
4205
|
-
this.emit(JSON.parse(event.data));
|
|
4205
|
+
this.emit(this.publicFiles(JSON.parse(event.data)));
|
|
4206
4206
|
};
|
|
4207
4207
|
source.onerror = () => {
|
|
4208
4208
|
if (this.source === source && !this.closed) {
|
|
@@ -4229,7 +4229,7 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4229
4229
|
}
|
|
4230
4230
|
let response;
|
|
4231
4231
|
try {
|
|
4232
|
-
response = await fetch("/api/message", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(message) });
|
|
4232
|
+
response = await fetch("/api/message", { method: "POST", headers: { ...this.headers(), "Content-Type": "application/json" }, body: JSON.stringify(message) });
|
|
4233
4233
|
} catch {
|
|
4234
4234
|
this.interrupted();
|
|
4235
4235
|
throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
|
|
@@ -4249,7 +4249,25 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4249
4249
|
if (!response.ok) {
|
|
4250
4250
|
throw new Error(reply.message || "Request failed.");
|
|
4251
4251
|
}
|
|
4252
|
-
return reply.messages ?? [];
|
|
4252
|
+
return (reply.messages ?? []).map((message2) => this.publicFiles(message2));
|
|
4253
|
+
}
|
|
4254
|
+
headers() {
|
|
4255
|
+
return this.token ? { Authorization: `Bearer ${this.token}` } : {};
|
|
4256
|
+
}
|
|
4257
|
+
fileUrl(path) {
|
|
4258
|
+
return this.token ? `${path}?session=${encodeURIComponent(this.token)}` : path;
|
|
4259
|
+
}
|
|
4260
|
+
publicFiles(value) {
|
|
4261
|
+
if (Array.isArray(value)) {
|
|
4262
|
+
return value.map((entry) => this.publicFiles(entry));
|
|
4263
|
+
}
|
|
4264
|
+
if (!value || typeof value !== "object") {
|
|
4265
|
+
return value;
|
|
4266
|
+
}
|
|
4267
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
4268
|
+
key,
|
|
4269
|
+
key === "previewUrl" && typeof entry === "string" && entry.startsWith("/api/file/") ? this.fileUrl(entry) : this.publicFiles(entry)
|
|
4270
|
+
]));
|
|
4253
4271
|
}
|
|
4254
4272
|
};
|
|
4255
4273
|
|
|
@@ -4280,6 +4298,17 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4280
4298
|
handleReply(entry);
|
|
4281
4299
|
}
|
|
4282
4300
|
};
|
|
4301
|
+
addEventListener("message", (event) => {
|
|
4302
|
+
if (window.parent === window || event.source !== window.parent || event.data?.type !== "workflow-host") {
|
|
4303
|
+
return;
|
|
4304
|
+
}
|
|
4305
|
+
if (event.data.theme === "dark" || event.data.theme === "light") {
|
|
4306
|
+
document.documentElement.dataset.theme = event.data.theme;
|
|
4307
|
+
}
|
|
4308
|
+
if (event.data.command === "new" || event.data.command === "history") {
|
|
4309
|
+
emit({ type: "assistant-command", command: event.data.command });
|
|
4310
|
+
}
|
|
4311
|
+
});
|
|
4283
4312
|
const handleReply = (reply) => {
|
|
4284
4313
|
if (reply.type === "download") {
|
|
4285
4314
|
const url = URL.createObjectURL(new Blob([reply.content], { type: reply.mimeType ?? "application/json" }));
|
|
@@ -4364,7 +4393,7 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4364
4393
|
return;
|
|
4365
4394
|
}
|
|
4366
4395
|
if (message.type === "open-file") {
|
|
4367
|
-
window.open(`/api/file/${encodeURIComponent(message.id)}
|
|
4396
|
+
window.open(connection.fileUrl(`/api/file/${encodeURIComponent(message.id)}`), "_blank", "noopener");
|
|
4368
4397
|
return;
|
|
4369
4398
|
}
|
|
4370
4399
|
if (message.type === "login") {
|
|
@@ -17,7 +17,7 @@ import "./chunk-NSPRIPOP.js";
|
|
|
17
17
|
|
|
18
18
|
// src/web.ts
|
|
19
19
|
import { spawn as spawn3 } from "child_process";
|
|
20
|
-
import
|
|
20
|
+
import path4 from "path";
|
|
21
21
|
|
|
22
22
|
// src/web/server.ts
|
|
23
23
|
import { createServer } from "http";
|
|
@@ -965,7 +965,7 @@ async function loginWebAccount(serverUrl, email, password) {
|
|
|
965
965
|
// src/web/server.ts
|
|
966
966
|
async function startWebServer(options) {
|
|
967
967
|
const projectDir = await realpath(path2.resolve(options.projectDir));
|
|
968
|
-
const key = createHash("sha256").update(projectDir).digest("hex").slice(0, 24);
|
|
968
|
+
const key = createHash("sha256").update(options.host?.assistant ? `assistant:${options.host.assistant.workdir}` : projectDir).digest("hex").slice(0, 24);
|
|
969
969
|
const storageDir = path2.resolve(options.dataDir ?? path2.join(os.homedir(), ".workflow-code", "web"));
|
|
970
970
|
const historyPath = path2.join(storageDir, key, "conversations.json");
|
|
971
971
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -975,6 +975,10 @@ async function startWebServer(options) {
|
|
|
975
975
|
let authIdentity = "";
|
|
976
976
|
let login;
|
|
977
977
|
let passwordLogin = false;
|
|
978
|
+
let hostConnection = options.host?.connection;
|
|
979
|
+
let hostManaged = !!hostConnection;
|
|
980
|
+
let authEpoch = 0;
|
|
981
|
+
const sensitiveTokens = /* @__PURE__ */ new Set();
|
|
978
982
|
const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection);
|
|
979
983
|
let inspected;
|
|
980
984
|
try {
|
|
@@ -996,6 +1000,14 @@ async function startWebServer(options) {
|
|
|
996
1000
|
throw new Error("workflow-code web currently supports Conversation and Workflow projects.");
|
|
997
1001
|
}
|
|
998
1002
|
const project = { ...descriptor, uri: pathToFileURL(projectDir).href };
|
|
1003
|
+
if (options.host?.assistant) {
|
|
1004
|
+
project.assistant = options.host.assistant;
|
|
1005
|
+
project.uri = `studio-assistant:${encodeURIComponent(project.assistant.workdir)}`;
|
|
1006
|
+
project.requiresLogin = true;
|
|
1007
|
+
project.fields = project.fields.map((field) => field.name === "permissionPolicy" ? { ...field, defaultValue: "workspace" } : field);
|
|
1008
|
+
const supported = /* @__PURE__ */ new Set(["read", "write", "edit", "bash", "grep", "find", "ls", "skill", "workflow-memory_global_memory", "workflow-ui_present_card"]);
|
|
1009
|
+
project.tools = project.tools?.filter((tool) => supported.has(tool.name));
|
|
1010
|
+
}
|
|
999
1011
|
let saved = {};
|
|
1000
1012
|
try {
|
|
1001
1013
|
saved = JSON.parse(await readFile(historyPath, "utf8"));
|
|
@@ -1024,7 +1036,7 @@ async function startWebServer(options) {
|
|
|
1024
1036
|
}
|
|
1025
1037
|
return value;
|
|
1026
1038
|
};
|
|
1027
|
-
const state = () => publicValue(redactWorkflowData({ type: "state", project, sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {}, account, authenticationRevision: authRevision }, [
|
|
1039
|
+
const state = () => publicValue(redactWorkflowData({ type: "state", project, sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {}, account, authenticationRevision: authRevision }, [...sensitiveTokens]));
|
|
1028
1040
|
const broadcast = () => {
|
|
1029
1041
|
if (broadcastTimer) {
|
|
1030
1042
|
return;
|
|
@@ -1058,9 +1070,16 @@ async function startWebServer(options) {
|
|
|
1058
1070
|
if (passwordLogin || login) {
|
|
1059
1071
|
return;
|
|
1060
1072
|
}
|
|
1073
|
+
const epoch = ++authEpoch;
|
|
1061
1074
|
const config = await readCliAuthConfig();
|
|
1062
|
-
|
|
1063
|
-
|
|
1075
|
+
if (epoch !== authEpoch) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const serverUrl = hostManaged ? hostConnection?.serverUrl : process.env.WORKFLOW_SERVER_URL ?? config.serverUrl;
|
|
1079
|
+
const token = hostManaged ? hostConnection?.accessToken : process.env.WORKFLOW_SERVER_ADMIN_KEY ?? config.apiKey;
|
|
1080
|
+
if (token) {
|
|
1081
|
+
sensitiveTokens.add(token);
|
|
1082
|
+
}
|
|
1064
1083
|
const identity = signedOut ? "" : `${serverUrl}
|
|
1065
1084
|
${token}`;
|
|
1066
1085
|
if (!force && identity === authIdentity && account.status !== "checking") {
|
|
@@ -1081,11 +1100,17 @@ ${token}`;
|
|
|
1081
1100
|
try {
|
|
1082
1101
|
const response = await fetch(`${connection.serverUrl}/api/auth/me`, { headers: { Authorization: `Bearer ${connection.accessToken}` }, signal: AbortSignal.timeout(1e4), redirect: "error" });
|
|
1083
1102
|
const result = await response.json();
|
|
1103
|
+
if (epoch !== authEpoch) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1084
1106
|
if (!response.ok || result.errCode !== 0 || !result.data?.profile) {
|
|
1085
1107
|
throw new Error("Workflow login is invalid or expired.");
|
|
1086
1108
|
}
|
|
1087
1109
|
account = { ...account, status: "authenticated", user: result.data.profile, expiresAt: result.data.principal?.expiresAt ?? config.expiresAt };
|
|
1088
1110
|
} catch (error) {
|
|
1111
|
+
if (epoch !== authEpoch) {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1089
1114
|
connection = void 0;
|
|
1090
1115
|
if (!changed) {
|
|
1091
1116
|
authRevision++;
|
|
@@ -1341,7 +1366,7 @@ ${token}`;
|
|
|
1341
1366
|
break;
|
|
1342
1367
|
}
|
|
1343
1368
|
case "login": {
|
|
1344
|
-
if (process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
|
|
1369
|
+
if (!options.host && process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
|
|
1345
1370
|
throw new Error("This session is supplied by the host. Sign in through the host.");
|
|
1346
1371
|
}
|
|
1347
1372
|
if (login || passwordLogin) {
|
|
@@ -1361,6 +1386,8 @@ ${token}`;
|
|
|
1361
1386
|
throw new Error("Invalid login credentials.");
|
|
1362
1387
|
}
|
|
1363
1388
|
await loginWebAccount(url.toString().replace(/\/+$/, ""), message.email, message.password);
|
|
1389
|
+
hostManaged = false;
|
|
1390
|
+
hostConnection = void 0;
|
|
1364
1391
|
} catch (error) {
|
|
1365
1392
|
account = { ...account, status: "error", error: String(error.message) };
|
|
1366
1393
|
broadcast();
|
|
@@ -1391,7 +1418,7 @@ ${token}`;
|
|
|
1391
1418
|
case "logout":
|
|
1392
1419
|
signedOut = true;
|
|
1393
1420
|
login?.kill();
|
|
1394
|
-
if (process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
|
|
1421
|
+
if (!hostManaged && process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
|
|
1395
1422
|
await clearCliAuthConfig();
|
|
1396
1423
|
}
|
|
1397
1424
|
authIdentity = "signed-out";
|
|
@@ -1407,7 +1434,7 @@ ${token}`;
|
|
|
1407
1434
|
void (async () => {
|
|
1408
1435
|
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
1409
1436
|
response.setHeader("Referrer-Policy", "no-referrer");
|
|
1410
|
-
if (request.headers.host !== new URL(origin).host || request.headers.origin && request.headers.origin !== origin
|
|
1437
|
+
if (request.headers.host !== new URL(origin).host || request.headers.origin && request.headers.origin !== origin) {
|
|
1411
1438
|
json(response, 403, { message: "Invalid request origin." });
|
|
1412
1439
|
return;
|
|
1413
1440
|
}
|
|
@@ -1422,8 +1449,14 @@ ${token}`;
|
|
|
1422
1449
|
return;
|
|
1423
1450
|
}
|
|
1424
1451
|
if (url.pathname.startsWith("/api/")) {
|
|
1452
|
+
if (request.headers["sec-fetch-site"] === "cross-site") {
|
|
1453
|
+
json(response, 403, { message: "Invalid request origin." });
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1425
1456
|
const value = String(request.headers.cookie ?? "").split(";").map((value2) => value2.trim()).find((value2) => value2.startsWith(`${cookie}=`))?.slice(cookie.length + 1) ?? "";
|
|
1426
|
-
|
|
1457
|
+
const bearer = String(request.headers.authorization ?? "").replace(/^Bearer /, "");
|
|
1458
|
+
const readSession = request.method === "GET" && (url.pathname === "/api/events" || url.pathname.startsWith("/api/file/")) ? url.searchParams.get("session") ?? "" : "";
|
|
1459
|
+
if (!authMatches(value) && !authMatches(bearer) && !authMatches(readSession)) {
|
|
1427
1460
|
json(response, 401, { message: "Open the URL printed by workflow-code web." });
|
|
1428
1461
|
return;
|
|
1429
1462
|
}
|
|
@@ -1455,7 +1488,7 @@ ${token}`;
|
|
|
1455
1488
|
mutations = task;
|
|
1456
1489
|
}
|
|
1457
1490
|
await task;
|
|
1458
|
-
json(response, 200, { messages: redactWorkflowData(messages, [
|
|
1491
|
+
json(response, 200, { messages: redactWorkflowData(messages, [...sensitiveTokens]) });
|
|
1459
1492
|
return;
|
|
1460
1493
|
}
|
|
1461
1494
|
if (url.pathname.startsWith("/api/file/") && request.method === "GET") {
|
|
@@ -1479,8 +1512,9 @@ ${token}`;
|
|
|
1479
1512
|
return;
|
|
1480
1513
|
}
|
|
1481
1514
|
if (url.pathname === "/") {
|
|
1482
|
-
|
|
1483
|
-
response.
|
|
1515
|
+
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:";
|
|
1516
|
+
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:; object-src 'none'; base-uri 'none'; frame-ancestors ${ancestors}; form-action 'self'` });
|
|
1517
|
+
response.end(`<!doctype html><html lang="zh-CN" data-theme="${options.theme ?? "auto"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="workflow-project" content="${key}"><title>Workflow</title><link rel="stylesheet" href="/assets/codicons/codicon.css"><link rel="stylesheet" href="/assets/workflow-ui.css"><link rel="stylesheet" href="/assets/theme.css"></head><body data-surface="${options.host?.surface ?? "editor"}"><div id="app"></div><script src="/assets/workflow-ui.js" defer></script></body></html>`);
|
|
1484
1518
|
return;
|
|
1485
1519
|
}
|
|
1486
1520
|
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" };
|
|
@@ -1493,7 +1527,7 @@ ${token}`;
|
|
|
1493
1527
|
response.end(content);
|
|
1494
1528
|
})().catch((error) => {
|
|
1495
1529
|
if (!response.headersSent) {
|
|
1496
|
-
json(response, 400, { message: String(redactWorkflowData(error.message, [
|
|
1530
|
+
json(response, 400, { message: String(redactWorkflowData(error.message, [...sensitiveTokens])) });
|
|
1497
1531
|
} else {
|
|
1498
1532
|
response.end();
|
|
1499
1533
|
}
|
|
@@ -1520,6 +1554,12 @@ ${token}`;
|
|
|
1520
1554
|
url: `${origin}/#session=${capability}`,
|
|
1521
1555
|
project,
|
|
1522
1556
|
controller,
|
|
1557
|
+
setHostConnection: async (value) => {
|
|
1558
|
+
hostConnection = value;
|
|
1559
|
+
hostManaged = true;
|
|
1560
|
+
signedOut = !value;
|
|
1561
|
+
await refreshAuth(true);
|
|
1562
|
+
},
|
|
1523
1563
|
close: () => closing ??= (async () => {
|
|
1524
1564
|
clearInterval(authPoll);
|
|
1525
1565
|
if (broadcastTimer) {
|
|
@@ -1539,9 +1579,43 @@ ${token}`;
|
|
|
1539
1579
|
};
|
|
1540
1580
|
}
|
|
1541
1581
|
|
|
1582
|
+
// src/web.ts
|
|
1583
|
+
import { createInterface } from "readline";
|
|
1584
|
+
|
|
1585
|
+
// src/web/host-config.ts
|
|
1586
|
+
import path3 from "path";
|
|
1587
|
+
function parseHostConnection(value) {
|
|
1588
|
+
if (value === null || value === void 0) {
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
const connection = value;
|
|
1592
|
+
if (typeof connection.serverUrl !== "string" || typeof connection.accessToken !== "string" || !connection.accessToken || connection.accessToken.length > 16384) {
|
|
1593
|
+
throw new Error("Invalid host login.");
|
|
1594
|
+
}
|
|
1595
|
+
const url = new URL(connection.serverUrl);
|
|
1596
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
1597
|
+
throw new Error("Invalid host server URL.");
|
|
1598
|
+
}
|
|
1599
|
+
return { serverUrl: url.toString().replace(/\/+$/, ""), accessToken: connection.accessToken };
|
|
1600
|
+
}
|
|
1601
|
+
function parseHostConfiguration(value) {
|
|
1602
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1603
|
+
throw new Error("Invalid web host configuration.");
|
|
1604
|
+
}
|
|
1605
|
+
const configuration = value;
|
|
1606
|
+
if (configuration.type !== "configure" || !["editor", "assistant"].includes(configuration.surface)) {
|
|
1607
|
+
throw new Error("Invalid web host configuration.");
|
|
1608
|
+
}
|
|
1609
|
+
const assistant = configuration.assistant;
|
|
1610
|
+
if (assistant && (typeof assistant.workdir !== "string" || !path3.isAbsolute(assistant.workdir) || !Array.isArray(assistant.roots) || !assistant.roots.length || assistant.roots.length > 100 || assistant.roots.some((root) => typeof root.uri !== "string" || typeof root.name !== "string" || typeof root.folderPath !== "string" || !path3.isAbsolute(root.folderPath)) || !assistant.roots.some((root) => root.folderPath === assistant.workdir))) {
|
|
1611
|
+
throw new Error("Invalid assistant workspace.");
|
|
1612
|
+
}
|
|
1613
|
+
return { surface: configuration.surface, assistant, connection: parseHostConnection(configuration.connection) };
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1542
1616
|
// src/web.ts
|
|
1543
1617
|
function parseWebArgs(argv) {
|
|
1544
|
-
const options = { projectDir: process.cwd(), port: 0, theme: "auto", open: true, json: false, help: false };
|
|
1618
|
+
const options = { projectDir: process.cwd(), port: 0, theme: "auto", dataDir: void 0, open: true, json: false, help: false, inspect: false, hostStdio: false };
|
|
1545
1619
|
let projectSet = false;
|
|
1546
1620
|
for (let index = 0; index < argv.length; index++) {
|
|
1547
1621
|
const arg = argv[index];
|
|
@@ -1549,6 +1623,10 @@ function parseWebArgs(argv) {
|
|
|
1549
1623
|
options.open = false;
|
|
1550
1624
|
} else if (arg === "--json") {
|
|
1551
1625
|
options.json = true;
|
|
1626
|
+
} else if (arg === "--inspect") {
|
|
1627
|
+
options.inspect = true;
|
|
1628
|
+
} else if (arg === "--host-stdio") {
|
|
1629
|
+
options.hostStdio = true;
|
|
1552
1630
|
} else if (["--help", "-h"].includes(arg)) {
|
|
1553
1631
|
options.help = true;
|
|
1554
1632
|
} else if (["--port", "--data-dir", "--theme"].includes(arg)) {
|
|
@@ -1567,12 +1645,12 @@ function parseWebArgs(argv) {
|
|
|
1567
1645
|
}
|
|
1568
1646
|
options.theme = value;
|
|
1569
1647
|
} else {
|
|
1570
|
-
options.dataDir =
|
|
1648
|
+
options.dataDir = path4.resolve(value);
|
|
1571
1649
|
}
|
|
1572
1650
|
} else if (arg.startsWith("-") || projectSet) {
|
|
1573
1651
|
throw new Error(`Unknown web argument: ${arg}`);
|
|
1574
1652
|
} else {
|
|
1575
|
-
options.projectDir =
|
|
1653
|
+
options.projectDir = path4.resolve(arg);
|
|
1576
1654
|
projectSet = true;
|
|
1577
1655
|
}
|
|
1578
1656
|
}
|
|
@@ -1584,10 +1662,39 @@ async function main(argv) {
|
|
|
1584
1662
|
console.log(i18n.language.startsWith("zh") ? "workflow-code web [\u9879\u76EE\u76EE\u5F55]\n --port <0-65535> \u76D1\u542C\u7AEF\u53E3\uFF0C0 \u81EA\u52A8\u5206\u914D\n --no-open \u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\n --theme <auto|light|dark>\n --data-dir <\u76EE\u5F55> \u5BF9\u8BDD\u548C\u6587\u4EF6\u5B58\u50A8\u4F4D\u7F6E\n --json \u8F93\u51FA\u7ED3\u6784\u5316\u542F\u52A8\u4FE1\u606F" : "workflow-code web [project-directory]\n --port <0-65535> Listen port; 0 selects an available port\n --no-open Do not open a browser\n --theme <auto|light|dark>\n --data-dir <path> Conversation and file storage directory\n --json Print structured startup information");
|
|
1585
1663
|
return 0;
|
|
1586
1664
|
}
|
|
1587
|
-
|
|
1665
|
+
if (options.inspect) {
|
|
1666
|
+
const runtime = createWebRuntime(options.projectDir, options.dataDir ?? "", () => void 0);
|
|
1667
|
+
try {
|
|
1668
|
+
console.log(JSON.stringify(await runtime.inspect([options.projectDir])));
|
|
1669
|
+
} finally {
|
|
1670
|
+
runtime.dispose();
|
|
1671
|
+
}
|
|
1672
|
+
return 0;
|
|
1673
|
+
}
|
|
1674
|
+
const input = options.hostStdio ? createInterface({ input: process.stdin, crlfDelay: Infinity }) : void 0;
|
|
1675
|
+
const lines = input?.[Symbol.asyncIterator]();
|
|
1676
|
+
let host;
|
|
1677
|
+
try {
|
|
1678
|
+
if (lines) {
|
|
1679
|
+
const initial = await lines.next();
|
|
1680
|
+
if (initial.done || initial.value.length > 256e3) {
|
|
1681
|
+
throw new Error("Web host configuration is missing or too large.");
|
|
1682
|
+
}
|
|
1683
|
+
host = parseHostConfiguration(JSON.parse(initial.value));
|
|
1684
|
+
}
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
input?.close();
|
|
1687
|
+
throw error;
|
|
1688
|
+
}
|
|
1689
|
+
const app = await startWebServer({ ...options, host }).catch((error) => {
|
|
1690
|
+
input?.close();
|
|
1691
|
+
throw error;
|
|
1692
|
+
});
|
|
1588
1693
|
const close = () => {
|
|
1589
1694
|
process.off("SIGINT", close);
|
|
1590
1695
|
process.off("SIGTERM", close);
|
|
1696
|
+
input?.close();
|
|
1697
|
+
process.stdin.pause();
|
|
1591
1698
|
void app.close().catch((error) => {
|
|
1592
1699
|
console.error(error);
|
|
1593
1700
|
process.exitCode = 1;
|
|
@@ -1595,7 +1702,31 @@ async function main(argv) {
|
|
|
1595
1702
|
};
|
|
1596
1703
|
process.once("SIGINT", close);
|
|
1597
1704
|
process.once("SIGTERM", close);
|
|
1598
|
-
console.log(options.json ? JSON.stringify({ type: "ready", protocolVersion: 1, url: app.url, project: { id: app.project.id, name: app.project.name, projectType: app.project.projectType }, runtime: app.project.runtime }) : `Workflow Web: ${app.url}`);
|
|
1705
|
+
console.log(options.json ? JSON.stringify({ type: "ready", protocolVersion: 1, hostProtocolVersion: 1, url: app.url, project: { id: app.project.id, name: app.project.name, projectType: app.project.projectType }, runtime: app.project.runtime }) : `Workflow Web: ${app.url}`);
|
|
1706
|
+
if (lines) {
|
|
1707
|
+
void (async () => {
|
|
1708
|
+
try {
|
|
1709
|
+
for await (const line of { [Symbol.asyncIterator]: () => lines }) {
|
|
1710
|
+
if (line.length > 256e3) {
|
|
1711
|
+
throw new Error("Web host message is too large.");
|
|
1712
|
+
}
|
|
1713
|
+
const message = JSON.parse(line);
|
|
1714
|
+
if (message.type === "auth") {
|
|
1715
|
+
await app.setHostConnection(parseHostConnection(message.connection));
|
|
1716
|
+
} else if (message.type === "close") {
|
|
1717
|
+
break;
|
|
1718
|
+
} else {
|
|
1719
|
+
throw new Error("Unknown web host message.");
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
} catch {
|
|
1723
|
+
console.error("Workflow web host connection failed.");
|
|
1724
|
+
process.exitCode = 1;
|
|
1725
|
+
} finally {
|
|
1726
|
+
close();
|
|
1727
|
+
}
|
|
1728
|
+
})();
|
|
1729
|
+
}
|
|
1599
1730
|
if (options.open) {
|
|
1600
1731
|
const invocation = process.platform === "darwin" ? { command: "open", args: [app.url] } : process.platform === "win32" ? { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", app.url] } : { command: "xdg-open", args: [app.url] };
|
|
1601
1732
|
const browser = spawn3(invocation.command, invocation.args, { detached: true, shell: false, stdio: "ignore" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workflow-code/cli",
|
|
3
|
-
"version": "0.2.4-
|
|
3
|
+
"version": "0.2.4-20261010004.codex-cli-web",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"workflow-code": "./dist/index.js"
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"workflow-code": "0.2.4-20260929001",
|
|
38
38
|
"@workflow-code/auth-config": "0.1.0",
|
|
39
39
|
"@workflow-code/execution-targets": "0.1.0",
|
|
40
|
-
"@workflow-code/i18n": "0.1.0",
|
|
41
40
|
"@workflow-code/openai-responses": "0.1.0",
|
|
41
|
+
"@workflow-code/i18n": "0.1.0",
|
|
42
42
|
"@workflow-code/project-dependencies": "0.1.0",
|
|
43
43
|
"@workflow-code/project-info": "0.1.0",
|
|
44
44
|
"@workflow-code/project-upload": "0.1.0",
|