@workflow-code/cli 0.2.4-20261010003.codex-cli-web → 0.2.4-20261011001.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 +41 -9
- package/dist/{web-7QLJGSAQ.js → web-TJ2SME4G.js} +215 -31
- package/package.json +4 -4
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-TJ2SME4G.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 });
|
|
@@ -4182,13 +4183,15 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4182
4183
|
try {
|
|
4183
4184
|
if (this.token) {
|
|
4184
4185
|
const session2 = await fetch("/api/session", { method: "POST", headers: { Authorization: `Bearer ${this.token}` }, signal: AbortSignal.timeout(1e4) });
|
|
4185
|
-
if (
|
|
4186
|
+
if (session2.status === 401) {
|
|
4186
4187
|
this.update("expired");
|
|
4187
4188
|
return;
|
|
4188
4189
|
}
|
|
4189
|
-
|
|
4190
|
+
if (!session2.ok) {
|
|
4191
|
+
throw new Error("Cannot connect to the project.");
|
|
4192
|
+
}
|
|
4190
4193
|
}
|
|
4191
|
-
const session = await fetch("/api/session", { signal: AbortSignal.timeout(1e4) });
|
|
4194
|
+
const session = await fetch("/api/session", { headers: this.headers(), signal: AbortSignal.timeout(1e4) });
|
|
4192
4195
|
if (session.status === 401) {
|
|
4193
4196
|
this.update("expired");
|
|
4194
4197
|
return;
|
|
@@ -4196,13 +4199,13 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4196
4199
|
if (!session.ok) {
|
|
4197
4200
|
throw new Error("Cannot connect to the project.");
|
|
4198
4201
|
}
|
|
4199
|
-
const source = this.source = new EventSource("/api/events");
|
|
4202
|
+
const source = this.source = new EventSource(this.fileUrl("/api/events"));
|
|
4200
4203
|
source.onopen = () => {
|
|
4201
4204
|
this.attempts = 0;
|
|
4202
4205
|
this.update("connected");
|
|
4203
4206
|
};
|
|
4204
4207
|
source.onmessage = (event) => {
|
|
4205
|
-
this.emit(JSON.parse(event.data));
|
|
4208
|
+
this.emit(this.publicFiles(JSON.parse(event.data)));
|
|
4206
4209
|
};
|
|
4207
4210
|
source.onerror = () => {
|
|
4208
4211
|
if (this.source === source && !this.closed) {
|
|
@@ -4229,7 +4232,7 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4229
4232
|
}
|
|
4230
4233
|
let response;
|
|
4231
4234
|
try {
|
|
4232
|
-
response = await fetch("/api/message", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(message) });
|
|
4235
|
+
response = await fetch("/api/message", { method: "POST", headers: { ...this.headers(), "Content-Type": "application/json" }, body: JSON.stringify(message) });
|
|
4233
4236
|
} catch {
|
|
4234
4237
|
this.interrupted();
|
|
4235
4238
|
throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
|
|
@@ -4249,7 +4252,25 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4249
4252
|
if (!response.ok) {
|
|
4250
4253
|
throw new Error(reply.message || "Request failed.");
|
|
4251
4254
|
}
|
|
4252
|
-
return reply.messages ?? [];
|
|
4255
|
+
return (reply.messages ?? []).map((message2) => this.publicFiles(message2));
|
|
4256
|
+
}
|
|
4257
|
+
headers() {
|
|
4258
|
+
return this.token ? { Authorization: `Bearer ${this.token}` } : {};
|
|
4259
|
+
}
|
|
4260
|
+
fileUrl(path) {
|
|
4261
|
+
return this.token ? `${path}?session=${encodeURIComponent(this.token)}` : path;
|
|
4262
|
+
}
|
|
4263
|
+
publicFiles(value) {
|
|
4264
|
+
if (Array.isArray(value)) {
|
|
4265
|
+
return value.map((entry) => this.publicFiles(entry));
|
|
4266
|
+
}
|
|
4267
|
+
if (!value || typeof value !== "object") {
|
|
4268
|
+
return value;
|
|
4269
|
+
}
|
|
4270
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
4271
|
+
key,
|
|
4272
|
+
key === "previewUrl" && typeof entry === "string" && entry.startsWith("/api/file/") ? this.fileUrl(entry) : this.publicFiles(entry)
|
|
4273
|
+
]));
|
|
4253
4274
|
}
|
|
4254
4275
|
};
|
|
4255
4276
|
|
|
@@ -4280,6 +4301,17 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4280
4301
|
handleReply(entry);
|
|
4281
4302
|
}
|
|
4282
4303
|
};
|
|
4304
|
+
addEventListener("message", (event) => {
|
|
4305
|
+
if (window.parent === window || event.source !== window.parent || event.data?.type !== "workflow-host") {
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
if (event.data.theme === "dark" || event.data.theme === "light") {
|
|
4309
|
+
document.documentElement.dataset.theme = event.data.theme;
|
|
4310
|
+
}
|
|
4311
|
+
if (event.data.command === "new" || event.data.command === "history") {
|
|
4312
|
+
emit({ type: "assistant-command", command: event.data.command });
|
|
4313
|
+
}
|
|
4314
|
+
});
|
|
4283
4315
|
const handleReply = (reply) => {
|
|
4284
4316
|
if (reply.type === "download") {
|
|
4285
4317
|
const url = URL.createObjectURL(new Blob([reply.content], { type: reply.mimeType ?? "application/json" }));
|
|
@@ -4364,7 +4396,7 @@ ${state.user?.avatarUrl ?? ""}`;
|
|
|
4364
4396
|
return;
|
|
4365
4397
|
}
|
|
4366
4398
|
if (message.type === "open-file") {
|
|
4367
|
-
window.open(`/api/file/${encodeURIComponent(message.id)}
|
|
4399
|
+
window.open(connection.fileUrl(`/api/file/${encodeURIComponent(message.id)}`), "_blank", "noopener");
|
|
4368
4400
|
return;
|
|
4369
4401
|
}
|
|
4370
4402
|
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";
|
|
@@ -699,8 +699,29 @@ function createWebRuntime(projectDir, storageDir, connection) {
|
|
|
699
699
|
return;
|
|
700
700
|
}
|
|
701
701
|
const auth = connection();
|
|
702
|
-
|
|
703
|
-
|
|
702
|
+
if ((request.method === "run" || request.method === "resolve") && request.project?.requiresLogin && !auth) {
|
|
703
|
+
reject(new Error("Workflow Server login is not configured."));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const env = {
|
|
707
|
+
...process.env,
|
|
708
|
+
WORKFLOW_RUNTIME_PLATFORM: "cli",
|
|
709
|
+
WORKFLOW_SERVER_URL: auth?.serverUrl ?? "",
|
|
710
|
+
WORKFLOW_SERVER_ADMIN_KEY: auth?.accessToken ?? "",
|
|
711
|
+
WORKFLOW_SERVER_TOKEN: "",
|
|
712
|
+
WORKFLOW_LLM_SERVER_URL: auth?.serverUrl ?? "",
|
|
713
|
+
WORKFLOW_LLM_SERVER_TOKEN: auth?.accessToken ?? "",
|
|
714
|
+
WORKFLOW_CLI_AUTH_DIR: path.join(storageDir, "runtime-auth")
|
|
715
|
+
};
|
|
716
|
+
for (const key of [
|
|
717
|
+
"TSX_TSCONFIG_PATH",
|
|
718
|
+
"WORKFLOW_LOCAL_CORE_ROOT",
|
|
719
|
+
"WORKFLOW_LOCAL_SHARED_ROOT",
|
|
720
|
+
"WORKFLOW_LOCAL_SOURCE_MODE",
|
|
721
|
+
"WORKFLOW_DEVELOPER_ASSISTANT_HOST",
|
|
722
|
+
"WORKFLOW_DEVELOPER_ASSISTANT_WORKDIR",
|
|
723
|
+
"WORKFLOW_DEVELOPER_ASSISTANT_WORKSPACE_MANIFEST"
|
|
724
|
+
]) {
|
|
704
725
|
delete env[key];
|
|
705
726
|
}
|
|
706
727
|
if (env.NODE_OPTIONS) {
|
|
@@ -965,7 +986,7 @@ async function loginWebAccount(serverUrl, email, password) {
|
|
|
965
986
|
// src/web/server.ts
|
|
966
987
|
async function startWebServer(options) {
|
|
967
988
|
const projectDir = await realpath(path2.resolve(options.projectDir));
|
|
968
|
-
const key = createHash("sha256").update(projectDir).digest("hex").slice(0, 24);
|
|
989
|
+
const key = createHash("sha256").update(options.host?.assistant ? `assistant:${options.host.assistant.workdir}` : projectDir).digest("hex").slice(0, 24);
|
|
969
990
|
const storageDir = path2.resolve(options.dataDir ?? path2.join(os.homedir(), ".workflow-code", "web"));
|
|
970
991
|
const historyPath = path2.join(storageDir, key, "conversations.json");
|
|
971
992
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -975,6 +996,11 @@ async function startWebServer(options) {
|
|
|
975
996
|
let authIdentity = "";
|
|
976
997
|
let login;
|
|
977
998
|
let passwordLogin = false;
|
|
999
|
+
let hostConnection = options.host?.connection;
|
|
1000
|
+
let hostManaged = !!hostConnection;
|
|
1001
|
+
let authEpoch = 0;
|
|
1002
|
+
let authCheck;
|
|
1003
|
+
const sensitiveTokens = /* @__PURE__ */ new Set();
|
|
978
1004
|
const runtime = options.runtime ?? createWebRuntime(projectDir, storageDir, () => connection);
|
|
979
1005
|
let inspected;
|
|
980
1006
|
try {
|
|
@@ -996,6 +1022,14 @@ async function startWebServer(options) {
|
|
|
996
1022
|
throw new Error("workflow-code web currently supports Conversation and Workflow projects.");
|
|
997
1023
|
}
|
|
998
1024
|
const project = { ...descriptor, uri: pathToFileURL(projectDir).href };
|
|
1025
|
+
if (options.host?.assistant) {
|
|
1026
|
+
project.assistant = options.host.assistant;
|
|
1027
|
+
project.uri = `studio-assistant:${encodeURIComponent(project.assistant.workdir)}`;
|
|
1028
|
+
project.requiresLogin = true;
|
|
1029
|
+
project.fields = project.fields.map((field) => field.name === "permissionPolicy" ? { ...field, defaultValue: "workspace" } : field);
|
|
1030
|
+
const supported = /* @__PURE__ */ new Set(["read", "write", "edit", "bash", "grep", "find", "ls", "skill", "workflow-memory_global_memory", "workflow-ui_present_card"]);
|
|
1031
|
+
project.tools = project.tools?.filter((tool) => supported.has(tool.name));
|
|
1032
|
+
}
|
|
999
1033
|
let saved = {};
|
|
1000
1034
|
try {
|
|
1001
1035
|
saved = JSON.parse(await readFile(historyPath, "utf8"));
|
|
@@ -1024,7 +1058,7 @@ async function startWebServer(options) {
|
|
|
1024
1058
|
}
|
|
1025
1059
|
return value;
|
|
1026
1060
|
};
|
|
1027
|
-
const state = () => publicValue(redactWorkflowData({ type: "state", project, sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {}, account, authenticationRevision: authRevision }, [
|
|
1061
|
+
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
1062
|
const broadcast = () => {
|
|
1029
1063
|
if (broadcastTimer) {
|
|
1030
1064
|
return;
|
|
@@ -1055,37 +1089,63 @@ async function startWebServer(options) {
|
|
|
1055
1089
|
}
|
|
1056
1090
|
});
|
|
1057
1091
|
const refreshAuth = async (force = false) => {
|
|
1058
|
-
if (passwordLogin || login) {
|
|
1092
|
+
if ((passwordLogin || login) && !force) {
|
|
1059
1093
|
return;
|
|
1060
1094
|
}
|
|
1095
|
+
const readingEpoch = authEpoch;
|
|
1061
1096
|
const config = await readCliAuthConfig();
|
|
1062
|
-
|
|
1063
|
-
|
|
1097
|
+
if (readingEpoch !== authEpoch) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const serverUrl = hostManaged ? hostConnection?.serverUrl : process.env.WORKFLOW_SERVER_URL ?? config.serverUrl;
|
|
1101
|
+
const token = hostManaged ? hostConnection?.accessToken : process.env.WORKFLOW_SERVER_ADMIN_KEY ?? config.apiKey;
|
|
1102
|
+
if (token) {
|
|
1103
|
+
sensitiveTokens.add(token);
|
|
1104
|
+
}
|
|
1064
1105
|
const identity = signedOut ? "" : `${serverUrl}
|
|
1065
1106
|
${token}`;
|
|
1066
|
-
|
|
1107
|
+
const expired = account.expiresAt && Date.parse(account.expiresAt) <= Date.now();
|
|
1108
|
+
if (!force && identity === authIdentity && account.status !== "error" && !expired) {
|
|
1067
1109
|
return;
|
|
1068
1110
|
}
|
|
1111
|
+
const epoch = ++authEpoch;
|
|
1112
|
+
authCheck?.abort();
|
|
1113
|
+
const check = authCheck = new AbortController();
|
|
1069
1114
|
const changed = identity !== authIdentity;
|
|
1070
1115
|
authIdentity = identity;
|
|
1071
|
-
|
|
1116
|
+
const candidate = !signedOut && serverUrl && token ? { serverUrl: serverUrl.replace(/\/+$/, ""), accessToken: token } : void 0;
|
|
1072
1117
|
if (changed) {
|
|
1118
|
+
connection = void 0;
|
|
1073
1119
|
authRevision++;
|
|
1074
1120
|
parameterStates.clear();
|
|
1075
1121
|
for (const session of controller.sessions) {
|
|
1076
1122
|
controller.stop(session);
|
|
1077
1123
|
}
|
|
1078
1124
|
}
|
|
1079
|
-
account = { status:
|
|
1080
|
-
if (
|
|
1125
|
+
account = { status: candidate ? "checking" : "signed-out", serverUrl: serverUrl || "http://localhost:7126", user: candidate ? changed ? hostManaged ? void 0 : config.user : account.user : void 0, revision: account.revision + 1 };
|
|
1126
|
+
if (candidate) {
|
|
1127
|
+
let unauthorized = false;
|
|
1081
1128
|
try {
|
|
1082
|
-
const response = await fetch(`${
|
|
1129
|
+
const response = await fetch(`${candidate.serverUrl}/api/auth/me`, { headers: { Authorization: `Bearer ${candidate.accessToken}` }, signal: AbortSignal.any([check.signal, AbortSignal.timeout(1e4)]), redirect: "error" });
|
|
1130
|
+
unauthorized = response.status === 401;
|
|
1083
1131
|
const result = await response.json();
|
|
1084
|
-
if (
|
|
1132
|
+
if (epoch !== authEpoch) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
const profile = result.data?.profile;
|
|
1136
|
+
if (!response.ok || result.errCode !== 0 || typeof profile?.userId !== "string" || !profile.userId || typeof profile.email !== "string" || typeof profile.displayName !== "string") {
|
|
1085
1137
|
throw new Error("Workflow login is invalid or expired.");
|
|
1086
1138
|
}
|
|
1087
|
-
|
|
1139
|
+
if (!connection) {
|
|
1140
|
+
authRevision++;
|
|
1141
|
+
parameterStates.clear();
|
|
1142
|
+
}
|
|
1143
|
+
connection = candidate;
|
|
1144
|
+
account = { ...account, status: "authenticated", user: profile, expiresAt: result.data.principal?.expiresAt ?? config.expiresAt };
|
|
1088
1145
|
} catch (error) {
|
|
1146
|
+
if (epoch !== authEpoch) {
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1089
1149
|
connection = void 0;
|
|
1090
1150
|
if (!changed) {
|
|
1091
1151
|
authRevision++;
|
|
@@ -1094,8 +1154,10 @@ ${token}`;
|
|
|
1094
1154
|
controller.stop(session);
|
|
1095
1155
|
}
|
|
1096
1156
|
}
|
|
1097
|
-
account = { ...account, status: "error", error: String(error.message) };
|
|
1157
|
+
account = { ...account, status: unauthorized ? "expired" : "error", error: String(error.message) };
|
|
1098
1158
|
}
|
|
1159
|
+
} else {
|
|
1160
|
+
connection = void 0;
|
|
1099
1161
|
}
|
|
1100
1162
|
broadcast();
|
|
1101
1163
|
};
|
|
@@ -1341,7 +1403,7 @@ ${token}`;
|
|
|
1341
1403
|
break;
|
|
1342
1404
|
}
|
|
1343
1405
|
case "login": {
|
|
1344
|
-
if (process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
|
|
1406
|
+
if (!options.host && process.env.WORKFLOW_SERVER_ADMIN_KEY !== void 0) {
|
|
1345
1407
|
throw new Error("This session is supplied by the host. Sign in through the host.");
|
|
1346
1408
|
}
|
|
1347
1409
|
if (login || passwordLogin) {
|
|
@@ -1355,15 +1417,23 @@ ${token}`;
|
|
|
1355
1417
|
account = { ...account, status: "checking" };
|
|
1356
1418
|
broadcast();
|
|
1357
1419
|
if (message.email !== void 0 || message.password !== void 0) {
|
|
1420
|
+
const epoch = ++authEpoch;
|
|
1358
1421
|
passwordLogin = true;
|
|
1359
1422
|
try {
|
|
1360
1423
|
if (typeof message.email !== "string" || typeof message.password !== "string") {
|
|
1361
1424
|
throw new Error("Invalid login credentials.");
|
|
1362
1425
|
}
|
|
1363
1426
|
await loginWebAccount(url.toString().replace(/\/+$/, ""), message.email, message.password);
|
|
1427
|
+
if (epoch !== authEpoch) {
|
|
1428
|
+
break;
|
|
1429
|
+
}
|
|
1430
|
+
hostManaged = false;
|
|
1431
|
+
hostConnection = void 0;
|
|
1364
1432
|
} catch (error) {
|
|
1365
|
-
|
|
1366
|
-
|
|
1433
|
+
if (epoch === authEpoch) {
|
|
1434
|
+
account = { ...account, status: "error", error: String(error.message) };
|
|
1435
|
+
broadcast();
|
|
1436
|
+
}
|
|
1367
1437
|
throw error;
|
|
1368
1438
|
} finally {
|
|
1369
1439
|
passwordLogin = false;
|
|
@@ -1391,7 +1461,7 @@ ${token}`;
|
|
|
1391
1461
|
case "logout":
|
|
1392
1462
|
signedOut = true;
|
|
1393
1463
|
login?.kill();
|
|
1394
|
-
if (process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
|
|
1464
|
+
if (!hostManaged && process.env.WORKFLOW_SERVER_ADMIN_KEY === void 0) {
|
|
1395
1465
|
await clearCliAuthConfig();
|
|
1396
1466
|
}
|
|
1397
1467
|
authIdentity = "signed-out";
|
|
@@ -1407,7 +1477,9 @@ ${token}`;
|
|
|
1407
1477
|
void (async () => {
|
|
1408
1478
|
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
1409
1479
|
response.setHeader("Referrer-Policy", "no-referrer");
|
|
1410
|
-
|
|
1480
|
+
const host = new URL(`http://${request.headers.host ?? ""}`);
|
|
1481
|
+
const forwarded = !!options.host && ["127.0.0.1", "localhost", "[::1]"].includes(host.hostname) && !host.username && !host.password && host.pathname === "/" && !host.search && !host.hash;
|
|
1482
|
+
if (request.headers.host !== new URL(origin).host && !forwarded || request.headers.origin && request.headers.origin !== (forwarded ? host.origin : origin)) {
|
|
1411
1483
|
json(response, 403, { message: "Invalid request origin." });
|
|
1412
1484
|
return;
|
|
1413
1485
|
}
|
|
@@ -1422,8 +1494,14 @@ ${token}`;
|
|
|
1422
1494
|
return;
|
|
1423
1495
|
}
|
|
1424
1496
|
if (url.pathname.startsWith("/api/")) {
|
|
1497
|
+
if (request.headers["sec-fetch-site"] === "cross-site") {
|
|
1498
|
+
json(response, 403, { message: "Invalid request origin." });
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1425
1501
|
const value = String(request.headers.cookie ?? "").split(";").map((value2) => value2.trim()).find((value2) => value2.startsWith(`${cookie}=`))?.slice(cookie.length + 1) ?? "";
|
|
1426
|
-
|
|
1502
|
+
const bearer = String(request.headers.authorization ?? "").replace(/^Bearer /, "");
|
|
1503
|
+
const readSession = request.method === "GET" && (url.pathname === "/api/events" || url.pathname.startsWith("/api/file/")) ? url.searchParams.get("session") ?? "" : "";
|
|
1504
|
+
if (!authMatches(value) && !authMatches(bearer) && !authMatches(readSession)) {
|
|
1427
1505
|
json(response, 401, { message: "Open the URL printed by workflow-code web." });
|
|
1428
1506
|
return;
|
|
1429
1507
|
}
|
|
@@ -1455,7 +1533,7 @@ ${token}`;
|
|
|
1455
1533
|
mutations = task;
|
|
1456
1534
|
}
|
|
1457
1535
|
await task;
|
|
1458
|
-
json(response, 200, { messages: redactWorkflowData(messages, [
|
|
1536
|
+
json(response, 200, { messages: redactWorkflowData(messages, [...sensitiveTokens]) });
|
|
1459
1537
|
return;
|
|
1460
1538
|
}
|
|
1461
1539
|
if (url.pathname.startsWith("/api/file/") && request.method === "GET") {
|
|
@@ -1479,8 +1557,9 @@ ${token}`;
|
|
|
1479
1557
|
return;
|
|
1480
1558
|
}
|
|
1481
1559
|
if (url.pathname === "/") {
|
|
1482
|
-
|
|
1483
|
-
response.
|
|
1560
|
+
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:";
|
|
1561
|
+
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'` });
|
|
1562
|
+
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
1563
|
return;
|
|
1485
1564
|
}
|
|
1486
1565
|
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 +1572,7 @@ ${token}`;
|
|
|
1493
1572
|
response.end(content);
|
|
1494
1573
|
})().catch((error) => {
|
|
1495
1574
|
if (!response.headersSent) {
|
|
1496
|
-
json(response, 400, { message: String(redactWorkflowData(error.message, [
|
|
1575
|
+
json(response, 400, { message: String(redactWorkflowData(error.message, [...sensitiveTokens])) });
|
|
1497
1576
|
} else {
|
|
1498
1577
|
response.end();
|
|
1499
1578
|
}
|
|
@@ -1520,7 +1599,17 @@ ${token}`;
|
|
|
1520
1599
|
url: `${origin}/#session=${capability}`,
|
|
1521
1600
|
project,
|
|
1522
1601
|
controller,
|
|
1602
|
+
setHostConnection: async (value) => {
|
|
1603
|
+
authEpoch++;
|
|
1604
|
+
authCheck?.abort();
|
|
1605
|
+
hostConnection = value;
|
|
1606
|
+
hostManaged = true;
|
|
1607
|
+
signedOut = !value;
|
|
1608
|
+
await refreshAuth(true);
|
|
1609
|
+
},
|
|
1523
1610
|
close: () => closing ??= (async () => {
|
|
1611
|
+
authEpoch++;
|
|
1612
|
+
authCheck?.abort();
|
|
1524
1613
|
clearInterval(authPoll);
|
|
1525
1614
|
if (broadcastTimer) {
|
|
1526
1615
|
clearTimeout(broadcastTimer);
|
|
@@ -1539,9 +1628,43 @@ ${token}`;
|
|
|
1539
1628
|
};
|
|
1540
1629
|
}
|
|
1541
1630
|
|
|
1631
|
+
// src/web.ts
|
|
1632
|
+
import { createInterface } from "readline";
|
|
1633
|
+
|
|
1634
|
+
// src/web/host-config.ts
|
|
1635
|
+
import path3 from "path";
|
|
1636
|
+
function parseHostConnection(value) {
|
|
1637
|
+
if (value === null || value === void 0) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const connection = value;
|
|
1641
|
+
if (typeof connection.serverUrl !== "string" || typeof connection.accessToken !== "string" || !connection.accessToken || connection.accessToken.length > 16384) {
|
|
1642
|
+
throw new Error("Invalid host login.");
|
|
1643
|
+
}
|
|
1644
|
+
const url = new URL(connection.serverUrl);
|
|
1645
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
1646
|
+
throw new Error("Invalid host server URL.");
|
|
1647
|
+
}
|
|
1648
|
+
return { serverUrl: url.toString().replace(/\/+$/, ""), accessToken: connection.accessToken };
|
|
1649
|
+
}
|
|
1650
|
+
function parseHostConfiguration(value) {
|
|
1651
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1652
|
+
throw new Error("Invalid web host configuration.");
|
|
1653
|
+
}
|
|
1654
|
+
const configuration = value;
|
|
1655
|
+
if (configuration.type !== "configure" || !["editor", "assistant"].includes(configuration.surface)) {
|
|
1656
|
+
throw new Error("Invalid web host configuration.");
|
|
1657
|
+
}
|
|
1658
|
+
const assistant = configuration.assistant;
|
|
1659
|
+
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))) {
|
|
1660
|
+
throw new Error("Invalid assistant workspace.");
|
|
1661
|
+
}
|
|
1662
|
+
return { surface: configuration.surface, assistant, connection: parseHostConnection(configuration.connection) };
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1542
1665
|
// src/web.ts
|
|
1543
1666
|
function parseWebArgs(argv) {
|
|
1544
|
-
const options = { projectDir: process.cwd(), port: 0, theme: "auto", open: true, json: false, help: false };
|
|
1667
|
+
const options = { projectDir: process.cwd(), port: 0, theme: "auto", dataDir: void 0, open: true, json: false, help: false, inspect: false, hostStdio: false };
|
|
1545
1668
|
let projectSet = false;
|
|
1546
1669
|
for (let index = 0; index < argv.length; index++) {
|
|
1547
1670
|
const arg = argv[index];
|
|
@@ -1549,6 +1672,10 @@ function parseWebArgs(argv) {
|
|
|
1549
1672
|
options.open = false;
|
|
1550
1673
|
} else if (arg === "--json") {
|
|
1551
1674
|
options.json = true;
|
|
1675
|
+
} else if (arg === "--inspect") {
|
|
1676
|
+
options.inspect = true;
|
|
1677
|
+
} else if (arg === "--host-stdio") {
|
|
1678
|
+
options.hostStdio = true;
|
|
1552
1679
|
} else if (["--help", "-h"].includes(arg)) {
|
|
1553
1680
|
options.help = true;
|
|
1554
1681
|
} else if (["--port", "--data-dir", "--theme"].includes(arg)) {
|
|
@@ -1567,12 +1694,12 @@ function parseWebArgs(argv) {
|
|
|
1567
1694
|
}
|
|
1568
1695
|
options.theme = value;
|
|
1569
1696
|
} else {
|
|
1570
|
-
options.dataDir =
|
|
1697
|
+
options.dataDir = path4.resolve(value);
|
|
1571
1698
|
}
|
|
1572
1699
|
} else if (arg.startsWith("-") || projectSet) {
|
|
1573
1700
|
throw new Error(`Unknown web argument: ${arg}`);
|
|
1574
1701
|
} else {
|
|
1575
|
-
options.projectDir =
|
|
1702
|
+
options.projectDir = path4.resolve(arg);
|
|
1576
1703
|
projectSet = true;
|
|
1577
1704
|
}
|
|
1578
1705
|
}
|
|
@@ -1584,10 +1711,39 @@ async function main(argv) {
|
|
|
1584
1711
|
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
1712
|
return 0;
|
|
1586
1713
|
}
|
|
1587
|
-
|
|
1714
|
+
if (options.inspect) {
|
|
1715
|
+
const runtime = createWebRuntime(options.projectDir, options.dataDir ?? "", () => void 0);
|
|
1716
|
+
try {
|
|
1717
|
+
console.log(JSON.stringify(await runtime.inspect([options.projectDir])));
|
|
1718
|
+
} finally {
|
|
1719
|
+
runtime.dispose();
|
|
1720
|
+
}
|
|
1721
|
+
return 0;
|
|
1722
|
+
}
|
|
1723
|
+
const input = options.hostStdio ? createInterface({ input: process.stdin, crlfDelay: Infinity }) : void 0;
|
|
1724
|
+
const lines = input?.[Symbol.asyncIterator]();
|
|
1725
|
+
let host;
|
|
1726
|
+
try {
|
|
1727
|
+
if (lines) {
|
|
1728
|
+
const initial = await lines.next();
|
|
1729
|
+
if (initial.done || initial.value.length > 256e3) {
|
|
1730
|
+
throw new Error("Web host configuration is missing or too large.");
|
|
1731
|
+
}
|
|
1732
|
+
host = parseHostConfiguration(JSON.parse(initial.value));
|
|
1733
|
+
}
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
input?.close();
|
|
1736
|
+
throw error;
|
|
1737
|
+
}
|
|
1738
|
+
const app = await startWebServer({ ...options, host }).catch((error) => {
|
|
1739
|
+
input?.close();
|
|
1740
|
+
throw error;
|
|
1741
|
+
});
|
|
1588
1742
|
const close = () => {
|
|
1589
1743
|
process.off("SIGINT", close);
|
|
1590
1744
|
process.off("SIGTERM", close);
|
|
1745
|
+
input?.close();
|
|
1746
|
+
process.stdin.pause();
|
|
1591
1747
|
void app.close().catch((error) => {
|
|
1592
1748
|
console.error(error);
|
|
1593
1749
|
process.exitCode = 1;
|
|
@@ -1595,7 +1751,35 @@ async function main(argv) {
|
|
|
1595
1751
|
};
|
|
1596
1752
|
process.once("SIGINT", close);
|
|
1597
1753
|
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}`);
|
|
1754
|
+
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}`);
|
|
1755
|
+
if (lines) {
|
|
1756
|
+
void (async () => {
|
|
1757
|
+
try {
|
|
1758
|
+
for await (const line of { [Symbol.asyncIterator]: () => lines }) {
|
|
1759
|
+
if (line.length > 256e3) {
|
|
1760
|
+
throw new Error("Web host message is too large.");
|
|
1761
|
+
}
|
|
1762
|
+
const message = JSON.parse(line);
|
|
1763
|
+
if (message.type === "auth") {
|
|
1764
|
+
void app.setHostConnection(parseHostConnection(message.connection)).catch(() => {
|
|
1765
|
+
console.error("Workflow web host connection failed.");
|
|
1766
|
+
process.exitCode = 1;
|
|
1767
|
+
close();
|
|
1768
|
+
});
|
|
1769
|
+
} else if (message.type === "close") {
|
|
1770
|
+
break;
|
|
1771
|
+
} else {
|
|
1772
|
+
throw new Error("Unknown web host message.");
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
} catch {
|
|
1776
|
+
console.error("Workflow web host connection failed.");
|
|
1777
|
+
process.exitCode = 1;
|
|
1778
|
+
} finally {
|
|
1779
|
+
close();
|
|
1780
|
+
}
|
|
1781
|
+
})();
|
|
1782
|
+
}
|
|
1599
1783
|
if (options.open) {
|
|
1600
1784
|
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
1785
|
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-20261011001.codex-cli-web",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"workflow-code": "./dist/index.js"
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"@workflow-code/execution-targets": "0.1.0",
|
|
40
40
|
"@workflow-code/i18n": "0.1.0",
|
|
41
41
|
"@workflow-code/openai-responses": "0.1.0",
|
|
42
|
-
"@workflow-code/project-dependencies": "0.1.0",
|
|
43
42
|
"@workflow-code/project-info": "0.1.0",
|
|
43
|
+
"@workflow-code/project-dependencies": "0.1.0",
|
|
44
44
|
"@workflow-code/project-upload": "0.1.0",
|
|
45
|
-
"@workflow-code/runtime-compatibility": "0.1.0",
|
|
46
45
|
"@workflow-code/workflow-ids": "0.1.0",
|
|
47
|
-
"@workflow-code/workflow-mcp": "0.1.0"
|
|
46
|
+
"@workflow-code/workflow-mcp": "0.1.0",
|
|
47
|
+
"@workflow-code/runtime-compatibility": "0.1.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"bootstrap": "node scripts/bootstrap.mjs",
|