@workflow-code/cli 0.2.4-20261010002.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-KTQSEVQT.js → web-KUC6BL6E.js} +161 -28
- 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++;
|
|
@@ -1180,20 +1205,22 @@ ${token}`;
|
|
|
1180
1205
|
const states = parameterStates.get(stateKey) ?? {};
|
|
1181
1206
|
const trigger = message.trigger?.kind === "param" && parameters.fields.some((field) => field.name === message.trigger.paramName) ? { kind: "param", paramName: message.trigger.paramName } : { kind: message.trigger?.kind === "input" ? "input" : "init" };
|
|
1182
1207
|
const result = parameters.paramResolver?.enabled ? await runtime.resolveParams(project, { draftText: String(message.draftText ?? "").slice(0, 2e4), args: workflowParamArgs(parameters.fields, values, states), inputMode: "form", trigger }, entrypointId) : { params: {} };
|
|
1183
|
-
if (
|
|
1208
|
+
if (revision !== authRevision) {
|
|
1184
1209
|
break;
|
|
1185
1210
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1211
|
+
if (parameterEpochs.get(stateKey) === epoch) {
|
|
1212
|
+
for (const [name, patch] of Object.entries(result.params ?? {})) {
|
|
1213
|
+
states[name] = { ...states[name], ...patch, options: { ...states[name]?.options, ...patch.options } };
|
|
1214
|
+
if (patch.value === null) {
|
|
1215
|
+
delete values[name];
|
|
1216
|
+
} else if (patch.value !== void 0) {
|
|
1217
|
+
values[name] = patch.value;
|
|
1218
|
+
}
|
|
1192
1219
|
}
|
|
1220
|
+
parameterStates.set(stateKey, states);
|
|
1221
|
+
(current.parameterValues ??= {})[entrypointId ?? ""] = values;
|
|
1222
|
+
await controller.persist();
|
|
1193
1223
|
}
|
|
1194
|
-
parameterStates.set(stateKey, states);
|
|
1195
|
-
(current.parameterValues ??= {})[entrypointId ?? ""] = values;
|
|
1196
|
-
await controller.persist();
|
|
1197
1224
|
replies.push({ type: "params", projectUri: project.uri, sessionId: current.id, requestId: message.requestId, params: result.params });
|
|
1198
1225
|
} catch (error) {
|
|
1199
1226
|
replies.push({ type: "params", projectUri: project.uri, sessionId: current.id, requestId: message.requestId, error: String(error.message) });
|
|
@@ -1339,7 +1366,7 @@ ${token}`;
|
|
|
1339
1366
|
break;
|
|
1340
1367
|
}
|
|
1341
1368
|
case "login": {
|
|
1342
|
-
if (process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
|
|
1369
|
+
if (!options.host && process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
|
|
1343
1370
|
throw new Error("This session is supplied by the host. Sign in through the host.");
|
|
1344
1371
|
}
|
|
1345
1372
|
if (login || passwordLogin) {
|
|
@@ -1359,6 +1386,8 @@ ${token}`;
|
|
|
1359
1386
|
throw new Error("Invalid login credentials.");
|
|
1360
1387
|
}
|
|
1361
1388
|
await loginWebAccount(url.toString().replace(/\/+$/, ""), message.email, message.password);
|
|
1389
|
+
hostManaged = false;
|
|
1390
|
+
hostConnection = void 0;
|
|
1362
1391
|
} catch (error) {
|
|
1363
1392
|
account = { ...account, status: "error", error: String(error.message) };
|
|
1364
1393
|
broadcast();
|
|
@@ -1389,7 +1418,7 @@ ${token}`;
|
|
|
1389
1418
|
case "logout":
|
|
1390
1419
|
signedOut = true;
|
|
1391
1420
|
login?.kill();
|
|
1392
|
-
if (process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
|
|
1421
|
+
if (!hostManaged && process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
|
|
1393
1422
|
await clearCliAuthConfig();
|
|
1394
1423
|
}
|
|
1395
1424
|
authIdentity = "signed-out";
|
|
@@ -1405,7 +1434,7 @@ ${token}`;
|
|
|
1405
1434
|
void (async () => {
|
|
1406
1435
|
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
1407
1436
|
response.setHeader("Referrer-Policy", "no-referrer");
|
|
1408
|
-
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) {
|
|
1409
1438
|
json(response, 403, { message: "Invalid request origin." });
|
|
1410
1439
|
return;
|
|
1411
1440
|
}
|
|
@@ -1420,8 +1449,14 @@ ${token}`;
|
|
|
1420
1449
|
return;
|
|
1421
1450
|
}
|
|
1422
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
|
+
}
|
|
1423
1456
|
const value = String(request.headers.cookie ?? "").split(";").map((value2) => value2.trim()).find((value2) => value2.startsWith(`${cookie}=`))?.slice(cookie.length + 1) ?? "";
|
|
1424
|
-
|
|
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)) {
|
|
1425
1460
|
json(response, 401, { message: "Open the URL printed by workflow-code web." });
|
|
1426
1461
|
return;
|
|
1427
1462
|
}
|
|
@@ -1453,7 +1488,7 @@ ${token}`;
|
|
|
1453
1488
|
mutations = task;
|
|
1454
1489
|
}
|
|
1455
1490
|
await task;
|
|
1456
|
-
json(response, 200, { messages: redactWorkflowData(messages, [
|
|
1491
|
+
json(response, 200, { messages: redactWorkflowData(messages, [...sensitiveTokens]) });
|
|
1457
1492
|
return;
|
|
1458
1493
|
}
|
|
1459
1494
|
if (url.pathname.startsWith("/api/file/") && request.method === "GET") {
|
|
@@ -1477,8 +1512,9 @@ ${token}`;
|
|
|
1477
1512
|
return;
|
|
1478
1513
|
}
|
|
1479
1514
|
if (url.pathname === "/") {
|
|
1480
|
-
|
|
1481
|
-
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>`);
|
|
1482
1518
|
return;
|
|
1483
1519
|
}
|
|
1484
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" };
|
|
@@ -1491,7 +1527,7 @@ ${token}`;
|
|
|
1491
1527
|
response.end(content);
|
|
1492
1528
|
})().catch((error) => {
|
|
1493
1529
|
if (!response.headersSent) {
|
|
1494
|
-
json(response, 400, { message: String(redactWorkflowData(error.message, [
|
|
1530
|
+
json(response, 400, { message: String(redactWorkflowData(error.message, [...sensitiveTokens])) });
|
|
1495
1531
|
} else {
|
|
1496
1532
|
response.end();
|
|
1497
1533
|
}
|
|
@@ -1518,6 +1554,12 @@ ${token}`;
|
|
|
1518
1554
|
url: `${origin}/#session=${capability}`,
|
|
1519
1555
|
project,
|
|
1520
1556
|
controller,
|
|
1557
|
+
setHostConnection: async (value) => {
|
|
1558
|
+
hostConnection = value;
|
|
1559
|
+
hostManaged = true;
|
|
1560
|
+
signedOut = !value;
|
|
1561
|
+
await refreshAuth(true);
|
|
1562
|
+
},
|
|
1521
1563
|
close: () => closing ??= (async () => {
|
|
1522
1564
|
clearInterval(authPoll);
|
|
1523
1565
|
if (broadcastTimer) {
|
|
@@ -1537,9 +1579,43 @@ ${token}`;
|
|
|
1537
1579
|
};
|
|
1538
1580
|
}
|
|
1539
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
|
+
|
|
1540
1616
|
// src/web.ts
|
|
1541
1617
|
function parseWebArgs(argv) {
|
|
1542
|
-
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 };
|
|
1543
1619
|
let projectSet = false;
|
|
1544
1620
|
for (let index = 0; index < argv.length; index++) {
|
|
1545
1621
|
const arg = argv[index];
|
|
@@ -1547,6 +1623,10 @@ function parseWebArgs(argv) {
|
|
|
1547
1623
|
options.open = false;
|
|
1548
1624
|
} else if (arg === "--json") {
|
|
1549
1625
|
options.json = true;
|
|
1626
|
+
} else if (arg === "--inspect") {
|
|
1627
|
+
options.inspect = true;
|
|
1628
|
+
} else if (arg === "--host-stdio") {
|
|
1629
|
+
options.hostStdio = true;
|
|
1550
1630
|
} else if (["--help", "-h"].includes(arg)) {
|
|
1551
1631
|
options.help = true;
|
|
1552
1632
|
} else if (["--port", "--data-dir", "--theme"].includes(arg)) {
|
|
@@ -1565,12 +1645,12 @@ function parseWebArgs(argv) {
|
|
|
1565
1645
|
}
|
|
1566
1646
|
options.theme = value;
|
|
1567
1647
|
} else {
|
|
1568
|
-
options.dataDir =
|
|
1648
|
+
options.dataDir = path4.resolve(value);
|
|
1569
1649
|
}
|
|
1570
1650
|
} else if (arg.startsWith("-") || projectSet) {
|
|
1571
1651
|
throw new Error(`Unknown web argument: ${arg}`);
|
|
1572
1652
|
} else {
|
|
1573
|
-
options.projectDir =
|
|
1653
|
+
options.projectDir = path4.resolve(arg);
|
|
1574
1654
|
projectSet = true;
|
|
1575
1655
|
}
|
|
1576
1656
|
}
|
|
@@ -1582,10 +1662,39 @@ async function main(argv) {
|
|
|
1582
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");
|
|
1583
1663
|
return 0;
|
|
1584
1664
|
}
|
|
1585
|
-
|
|
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
|
+
});
|
|
1586
1693
|
const close = () => {
|
|
1587
1694
|
process.off("SIGINT", close);
|
|
1588
1695
|
process.off("SIGTERM", close);
|
|
1696
|
+
input?.close();
|
|
1697
|
+
process.stdin.pause();
|
|
1589
1698
|
void app.close().catch((error) => {
|
|
1590
1699
|
console.error(error);
|
|
1591
1700
|
process.exitCode = 1;
|
|
@@ -1593,7 +1702,31 @@ async function main(argv) {
|
|
|
1593
1702
|
};
|
|
1594
1703
|
process.once("SIGINT", close);
|
|
1595
1704
|
process.once("SIGTERM", close);
|
|
1596
|
-
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
|
+
}
|
|
1597
1730
|
if (options.open) {
|
|
1598
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] };
|
|
1599
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",
|