@workflow-code/cli 0.2.4-20261011006.codex-cli-web → 0.2.4-20261011008.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 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-KEM7Y2ZR.js");
27
+ const webCli = await import("./web-7M4WBBL7.js");
28
28
  return webCli.main(argv.slice(1));
29
29
  }
30
30
  if (argv[0] === "workspace") {
@@ -2443,8 +2443,7 @@ ${state2.user?.avatarUrl ?? ""}`;
2443
2443
  void this.connect();
2444
2444
  });
2445
2445
  addEventListener("offline", () => {
2446
- this.source?.close();
2447
- clearTimeout(this.retry);
2446
+ this.disconnect();
2448
2447
  this.update("offline");
2449
2448
  });
2450
2449
  addEventListener("online", () => {
@@ -2452,15 +2451,22 @@ ${state2.user?.avatarUrl ?? ""}`;
2452
2451
  });
2453
2452
  addEventListener("pagehide", () => {
2454
2453
  this.closed = true;
2455
- clearTimeout(this.retry);
2456
- this.source?.close();
2457
- }, { once: true });
2454
+ this.disconnect();
2455
+ });
2456
+ addEventListener("pageshow", (event) => {
2457
+ if (event.persisted) {
2458
+ this.closed = false;
2459
+ void this.connect();
2460
+ }
2461
+ });
2458
2462
  }
2459
2463
  emit;
2460
2464
  clientId = crypto.randomUUID();
2461
2465
  source;
2462
2466
  banner;
2463
2467
  connecting;
2468
+ handshake;
2469
+ epoch = 0;
2464
2470
  retry;
2465
2471
  status = "connecting";
2466
2472
  attempts = 0;
@@ -2480,9 +2486,17 @@ ${state2.user?.avatarUrl ?? ""}`;
2480
2486
  this.banner.querySelector(".connection-copy > span").textContent = status === "reconnecting" ? "\u6B63\u5728\u91CD\u65B0\u8FDE\u63A5..." : status === "offline" ? "\u7F51\u7EDC\u6062\u590D\u540E\u5C06\u81EA\u52A8\u91CD\u8FDE\u3002" : status === "expired" ? "\u8BF7\u4F7F\u7528\u9879\u76EE\u8FD0\u884C\u5730\u5740\u91CD\u65B0\u6253\u5F00\u3002" : "";
2481
2487
  this.banner.querySelector("button").disabled = status === "offline" || !!this.connecting;
2482
2488
  }
2483
- interrupted() {
2489
+ disconnect() {
2490
+ this.epoch++;
2491
+ this.handshake?.abort();
2492
+ this.handshake = void 0;
2493
+ this.connecting = void 0;
2484
2494
  this.source?.close();
2495
+ this.source = void 0;
2485
2496
  clearTimeout(this.retry);
2497
+ }
2498
+ interrupted() {
2499
+ this.disconnect();
2486
2500
  this.update(navigator.onLine ? "reconnecting" : "offline");
2487
2501
  if (!this.closed && navigator.onLine) {
2488
2502
  this.retry = setTimeout(() => {
@@ -2502,12 +2516,18 @@ ${state2.user?.avatarUrl ?? ""}`;
2502
2516
  this.update("offline");
2503
2517
  return Promise.resolve();
2504
2518
  }
2505
- this.source?.close();
2519
+ this.disconnect();
2520
+ const epoch = this.epoch, handshake = this.handshake = new AbortController();
2521
+ const active = () => epoch === this.epoch && !this.closed && navigator.onLine;
2522
+ const signal = AbortSignal.any([handshake.signal, AbortSignal.timeout(1e4)]);
2506
2523
  this.update(this.attempts ? "reconnecting" : "connecting");
2507
2524
  this.connecting = (async () => {
2508
2525
  try {
2509
2526
  if (this.token) {
2510
- const session2 = await fetch("/api/session", { method: "POST", headers: { Authorization: `Bearer ${this.token}` }, signal: AbortSignal.timeout(1e4) });
2527
+ const session2 = await fetch("/api/session", { method: "POST", headers: { Authorization: `Bearer ${this.token}` }, signal });
2528
+ if (!active()) {
2529
+ return;
2530
+ }
2511
2531
  if (session2.status === 401) {
2512
2532
  this.update("expired");
2513
2533
  return;
@@ -2516,7 +2536,10 @@ ${state2.user?.avatarUrl ?? ""}`;
2516
2536
  throw new Error("Cannot connect to the project.");
2517
2537
  }
2518
2538
  }
2519
- const session = await fetch("/api/session", { headers: this.headers(), signal: AbortSignal.timeout(1e4) });
2539
+ const session = await fetch("/api/session", { headers: this.headers(), signal });
2540
+ if (!active()) {
2541
+ return;
2542
+ }
2520
2543
  if (session.status === 401) {
2521
2544
  this.update("expired");
2522
2545
  return;
@@ -2528,24 +2551,32 @@ ${state2.user?.avatarUrl ?? ""}`;
2528
2551
  eventsUrl.searchParams.set("clientId", this.clientId);
2529
2552
  const source = this.source = new EventSource(eventsUrl.href);
2530
2553
  source.onopen = () => {
2531
- this.attempts = 0;
2532
- this.update("connected");
2554
+ if (active()) {
2555
+ this.attempts = 0;
2556
+ this.update("connected");
2557
+ }
2533
2558
  };
2534
2559
  source.onmessage = (event) => {
2535
- this.emit(this.publicFiles(JSON.parse(event.data)));
2560
+ if (active()) {
2561
+ this.emit(this.publicFiles(JSON.parse(event.data)));
2562
+ }
2536
2563
  };
2537
2564
  source.onerror = () => {
2538
- if (this.source === source && !this.closed) {
2565
+ if (active()) {
2539
2566
  this.interrupted();
2540
2567
  }
2541
2568
  };
2542
2569
  } catch {
2543
- if (!this.closed) {
2570
+ if (active()) {
2544
2571
  this.interrupted();
2545
2572
  }
2546
2573
  }
2547
2574
  })().finally(() => {
2575
+ if (epoch !== this.epoch) {
2576
+ return;
2577
+ }
2548
2578
  this.connecting = void 0;
2579
+ this.handshake = void 0;
2549
2580
  if (this.banner) {
2550
2581
  this.banner.querySelector("button").disabled = this.status === "offline";
2551
2582
  }
@@ -2557,16 +2588,22 @@ ${state2.user?.avatarUrl ?? ""}`;
2557
2588
  if (this.status !== "connected") {
2558
2589
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
2559
2590
  }
2591
+ const epoch = this.epoch;
2560
2592
  let response;
2561
2593
  try {
2562
2594
  const body = ["kanban-method-state", "kanban-method-reply"].includes(message.type) ? { ...message, clientId: this.clientId } : message;
2563
2595
  response = await fetch("/api/message", { method: "POST", headers: { ...this.headers(), "Content-Type": "application/json" }, body: JSON.stringify(body) });
2564
2596
  } catch {
2565
- this.interrupted();
2597
+ if (epoch === this.epoch && !this.closed) {
2598
+ this.interrupted();
2599
+ }
2566
2600
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
2567
2601
  }
2602
+ if (epoch !== this.epoch || this.closed) {
2603
+ throw new ConnectionError("\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002");
2604
+ }
2568
2605
  if (response.status === 401) {
2569
- this.source?.close();
2606
+ this.disconnect();
2570
2607
  this.update("expired");
2571
2608
  throw new ConnectionError("\u9879\u76EE\u8FDE\u63A5\u5DF2\u5931\u6548\u3002");
2572
2609
  }
@@ -2574,9 +2611,14 @@ ${state2.user?.avatarUrl ?? ""}`;
2574
2611
  try {
2575
2612
  reply = await response.json();
2576
2613
  } catch {
2577
- this.interrupted();
2614
+ if (epoch === this.epoch && !this.closed) {
2615
+ this.interrupted();
2616
+ }
2578
2617
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
2579
2618
  }
2619
+ if (epoch !== this.epoch || this.closed) {
2620
+ throw new ConnectionError("\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002");
2621
+ }
2580
2622
  if (!response.ok) {
2581
2623
  throw new Error(reply.message || "Request failed.");
2582
2624
  }
@@ -4399,7 +4441,7 @@ ${state2.user?.avatarUrl ?? ""}`;
4399
4441
  controls();
4400
4442
  }
4401
4443
  };
4402
- element("usage").onclick = () => host.postMessage({ type: "usage" });
4444
+ element("usage").onclick = () => dialogs.usage(() => host.postMessage({ type: "usage" }));
4403
4445
  window.addEventListener("message", (event) => {
4404
4446
  const message = event.data;
4405
4447
  if (event.source === frame.contentWindow) {
@@ -4500,7 +4542,11 @@ ${state2.user?.avatarUrl ?? ""}`;
4500
4542
  update(message.kanban);
4501
4543
  }
4502
4544
  if (message.type === "usage") {
4503
- dialogs.usage(message.usage);
4545
+ dialogs.usageResult(message.usage);
4546
+ }
4547
+ if (message.type === "error" && message.requestType === "usage") {
4548
+ dialogs.usageResult(void 0, message.message);
4549
+ return;
4504
4550
  }
4505
4551
  if (message.type === "error" && !message.silent && !message.requestId) {
4506
4552
  showError(message.message);
@@ -4518,6 +4564,14 @@ ${state2.user?.avatarUrl ?? ""}`;
4518
4564
  clearTimeout(readyTimer);
4519
4565
  clearInterval(methodHeartbeat);
4520
4566
  });
4567
+ addEventListener("pageshow", (event) => {
4568
+ if (!event.persisted) {
4569
+ return;
4570
+ }
4571
+ mountPreview();
4572
+ clearInterval(methodHeartbeat);
4573
+ methodHeartbeat = setInterval(publishMethods, 5e3);
4574
+ });
4521
4575
  })();
4522
4576
  /*! Bundled license information:
4523
4577
 
@@ -2846,7 +2846,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
2846
2846
  if (field.type === "file" || field.control === "audio") {
2847
2847
  let names = "";
2848
2848
  try {
2849
- names = JSON.parse(String(value || "[]")).map((file) => file.name).join(", ");
2849
+ const files = JSON.parse(String(value || "[]"));
2850
+ names = (Array.isArray(files) ? files : [files]).map((file) => file?.name ?? "").join(", ");
2850
2851
  } catch {
2851
2852
  }
2852
2853
  const selected = document.createElement("span");
@@ -4133,8 +4134,7 @@ ${state.user?.avatarUrl ?? ""}`;
4133
4134
  void this.connect();
4134
4135
  });
4135
4136
  addEventListener("offline", () => {
4136
- this.source?.close();
4137
- clearTimeout(this.retry);
4137
+ this.disconnect();
4138
4138
  this.update("offline");
4139
4139
  });
4140
4140
  addEventListener("online", () => {
@@ -4142,15 +4142,22 @@ ${state.user?.avatarUrl ?? ""}`;
4142
4142
  });
4143
4143
  addEventListener("pagehide", () => {
4144
4144
  this.closed = true;
4145
- clearTimeout(this.retry);
4146
- this.source?.close();
4147
- }, { once: true });
4145
+ this.disconnect();
4146
+ });
4147
+ addEventListener("pageshow", (event) => {
4148
+ if (event.persisted) {
4149
+ this.closed = false;
4150
+ void this.connect();
4151
+ }
4152
+ });
4148
4153
  }
4149
4154
  emit;
4150
4155
  clientId = crypto.randomUUID();
4151
4156
  source;
4152
4157
  banner;
4153
4158
  connecting;
4159
+ handshake;
4160
+ epoch = 0;
4154
4161
  retry;
4155
4162
  status = "connecting";
4156
4163
  attempts = 0;
@@ -4170,9 +4177,17 @@ ${state.user?.avatarUrl ?? ""}`;
4170
4177
  this.banner.querySelector(".connection-copy > span").textContent = status === "reconnecting" ? "\u6B63\u5728\u91CD\u65B0\u8FDE\u63A5..." : status === "offline" ? "\u7F51\u7EDC\u6062\u590D\u540E\u5C06\u81EA\u52A8\u91CD\u8FDE\u3002" : status === "expired" ? "\u8BF7\u4F7F\u7528\u9879\u76EE\u8FD0\u884C\u5730\u5740\u91CD\u65B0\u6253\u5F00\u3002" : "";
4171
4178
  this.banner.querySelector("button").disabled = status === "offline" || !!this.connecting;
4172
4179
  }
4173
- interrupted() {
4180
+ disconnect() {
4181
+ this.epoch++;
4182
+ this.handshake?.abort();
4183
+ this.handshake = void 0;
4184
+ this.connecting = void 0;
4174
4185
  this.source?.close();
4186
+ this.source = void 0;
4175
4187
  clearTimeout(this.retry);
4188
+ }
4189
+ interrupted() {
4190
+ this.disconnect();
4176
4191
  this.update(navigator.onLine ? "reconnecting" : "offline");
4177
4192
  if (!this.closed && navigator.onLine) {
4178
4193
  this.retry = setTimeout(() => {
@@ -4192,12 +4207,18 @@ ${state.user?.avatarUrl ?? ""}`;
4192
4207
  this.update("offline");
4193
4208
  return Promise.resolve();
4194
4209
  }
4195
- this.source?.close();
4210
+ this.disconnect();
4211
+ const epoch = this.epoch, handshake = this.handshake = new AbortController();
4212
+ const active = () => epoch === this.epoch && !this.closed && navigator.onLine;
4213
+ const signal = AbortSignal.any([handshake.signal, AbortSignal.timeout(1e4)]);
4196
4214
  this.update(this.attempts ? "reconnecting" : "connecting");
4197
4215
  this.connecting = (async () => {
4198
4216
  try {
4199
4217
  if (this.token) {
4200
- const session2 = await fetch("/api/session", { method: "POST", headers: { Authorization: `Bearer ${this.token}` }, signal: AbortSignal.timeout(1e4) });
4218
+ const session2 = await fetch("/api/session", { method: "POST", headers: { Authorization: `Bearer ${this.token}` }, signal });
4219
+ if (!active()) {
4220
+ return;
4221
+ }
4201
4222
  if (session2.status === 401) {
4202
4223
  this.update("expired");
4203
4224
  return;
@@ -4206,7 +4227,10 @@ ${state.user?.avatarUrl ?? ""}`;
4206
4227
  throw new Error("Cannot connect to the project.");
4207
4228
  }
4208
4229
  }
4209
- const session = await fetch("/api/session", { headers: this.headers(), signal: AbortSignal.timeout(1e4) });
4230
+ const session = await fetch("/api/session", { headers: this.headers(), signal });
4231
+ if (!active()) {
4232
+ return;
4233
+ }
4210
4234
  if (session.status === 401) {
4211
4235
  this.update("expired");
4212
4236
  return;
@@ -4218,24 +4242,32 @@ ${state.user?.avatarUrl ?? ""}`;
4218
4242
  eventsUrl.searchParams.set("clientId", this.clientId);
4219
4243
  const source = this.source = new EventSource(eventsUrl.href);
4220
4244
  source.onopen = () => {
4221
- this.attempts = 0;
4222
- this.update("connected");
4245
+ if (active()) {
4246
+ this.attempts = 0;
4247
+ this.update("connected");
4248
+ }
4223
4249
  };
4224
4250
  source.onmessage = (event) => {
4225
- this.emit(this.publicFiles(JSON.parse(event.data)));
4251
+ if (active()) {
4252
+ this.emit(this.publicFiles(JSON.parse(event.data)));
4253
+ }
4226
4254
  };
4227
4255
  source.onerror = () => {
4228
- if (this.source === source && !this.closed) {
4256
+ if (active()) {
4229
4257
  this.interrupted();
4230
4258
  }
4231
4259
  };
4232
4260
  } catch {
4233
- if (!this.closed) {
4261
+ if (active()) {
4234
4262
  this.interrupted();
4235
4263
  }
4236
4264
  }
4237
4265
  })().finally(() => {
4266
+ if (epoch !== this.epoch) {
4267
+ return;
4268
+ }
4238
4269
  this.connecting = void 0;
4270
+ this.handshake = void 0;
4239
4271
  if (this.banner) {
4240
4272
  this.banner.querySelector("button").disabled = this.status === "offline";
4241
4273
  }
@@ -4247,16 +4279,22 @@ ${state.user?.avatarUrl ?? ""}`;
4247
4279
  if (this.status !== "connected") {
4248
4280
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
4249
4281
  }
4282
+ const epoch = this.epoch;
4250
4283
  let response;
4251
4284
  try {
4252
4285
  const body = ["kanban-method-state", "kanban-method-reply"].includes(message.type) ? { ...message, clientId: this.clientId } : message;
4253
4286
  response = await fetch("/api/message", { method: "POST", headers: { ...this.headers(), "Content-Type": "application/json" }, body: JSON.stringify(body) });
4254
4287
  } catch {
4255
- this.interrupted();
4288
+ if (epoch === this.epoch && !this.closed) {
4289
+ this.interrupted();
4290
+ }
4256
4291
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
4257
4292
  }
4293
+ if (epoch !== this.epoch || this.closed) {
4294
+ throw new ConnectionError("\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002");
4295
+ }
4258
4296
  if (response.status === 401) {
4259
- this.source?.close();
4297
+ this.disconnect();
4260
4298
  this.update("expired");
4261
4299
  throw new ConnectionError("\u9879\u76EE\u8FDE\u63A5\u5DF2\u5931\u6548\u3002");
4262
4300
  }
@@ -4264,9 +4302,14 @@ ${state.user?.avatarUrl ?? ""}`;
4264
4302
  try {
4265
4303
  reply = await response.json();
4266
4304
  } catch {
4267
- this.interrupted();
4305
+ if (epoch === this.epoch && !this.closed) {
4306
+ this.interrupted();
4307
+ }
4268
4308
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
4269
4309
  }
4310
+ if (epoch !== this.epoch || this.closed) {
4311
+ throw new ConnectionError("\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002");
4312
+ }
4270
4313
  if (!response.ok) {
4271
4314
  throw new Error(reply.message || "Request failed.");
4272
4315
  }
@@ -5987,8 +5987,7 @@ ${state2.user?.avatarUrl ?? ""}`;
5987
5987
  void this.connect();
5988
5988
  });
5989
5989
  addEventListener("offline", () => {
5990
- this.source?.close();
5991
- clearTimeout(this.retry);
5990
+ this.disconnect();
5992
5991
  this.update("offline");
5993
5992
  });
5994
5993
  addEventListener("online", () => {
@@ -5996,15 +5995,22 @@ ${state2.user?.avatarUrl ?? ""}`;
5996
5995
  });
5997
5996
  addEventListener("pagehide", () => {
5998
5997
  this.closed = true;
5999
- clearTimeout(this.retry);
6000
- this.source?.close();
6001
- }, { once: true });
5998
+ this.disconnect();
5999
+ });
6000
+ addEventListener("pageshow", (event) => {
6001
+ if (event.persisted) {
6002
+ this.closed = false;
6003
+ void this.connect();
6004
+ }
6005
+ });
6002
6006
  }
6003
6007
  emit;
6004
6008
  clientId = crypto.randomUUID();
6005
6009
  source;
6006
6010
  banner;
6007
6011
  connecting;
6012
+ handshake;
6013
+ epoch = 0;
6008
6014
  retry;
6009
6015
  status = "connecting";
6010
6016
  attempts = 0;
@@ -6024,9 +6030,17 @@ ${state2.user?.avatarUrl ?? ""}`;
6024
6030
  this.banner.querySelector(".connection-copy > span").textContent = status === "reconnecting" ? "\u6B63\u5728\u91CD\u65B0\u8FDE\u63A5..." : status === "offline" ? "\u7F51\u7EDC\u6062\u590D\u540E\u5C06\u81EA\u52A8\u91CD\u8FDE\u3002" : status === "expired" ? "\u8BF7\u4F7F\u7528\u9879\u76EE\u8FD0\u884C\u5730\u5740\u91CD\u65B0\u6253\u5F00\u3002" : "";
6025
6031
  this.banner.querySelector("button").disabled = status === "offline" || !!this.connecting;
6026
6032
  }
6027
- interrupted() {
6033
+ disconnect() {
6034
+ this.epoch++;
6035
+ this.handshake?.abort();
6036
+ this.handshake = void 0;
6037
+ this.connecting = void 0;
6028
6038
  this.source?.close();
6039
+ this.source = void 0;
6029
6040
  clearTimeout(this.retry);
6041
+ }
6042
+ interrupted() {
6043
+ this.disconnect();
6030
6044
  this.update(navigator.onLine ? "reconnecting" : "offline");
6031
6045
  if (!this.closed && navigator.onLine) {
6032
6046
  this.retry = setTimeout(() => {
@@ -6046,12 +6060,18 @@ ${state2.user?.avatarUrl ?? ""}`;
6046
6060
  this.update("offline");
6047
6061
  return Promise.resolve();
6048
6062
  }
6049
- this.source?.close();
6063
+ this.disconnect();
6064
+ const epoch = this.epoch, handshake = this.handshake = new AbortController();
6065
+ const active = () => epoch === this.epoch && !this.closed && navigator.onLine;
6066
+ const signal = AbortSignal.any([handshake.signal, AbortSignal.timeout(1e4)]);
6050
6067
  this.update(this.attempts ? "reconnecting" : "connecting");
6051
6068
  this.connecting = (async () => {
6052
6069
  try {
6053
6070
  if (this.token) {
6054
- const session2 = await fetch("/api/session", { method: "POST", headers: { Authorization: `Bearer ${this.token}` }, signal: AbortSignal.timeout(1e4) });
6071
+ const session2 = await fetch("/api/session", { method: "POST", headers: { Authorization: `Bearer ${this.token}` }, signal });
6072
+ if (!active()) {
6073
+ return;
6074
+ }
6055
6075
  if (session2.status === 401) {
6056
6076
  this.update("expired");
6057
6077
  return;
@@ -6060,7 +6080,10 @@ ${state2.user?.avatarUrl ?? ""}`;
6060
6080
  throw new Error("Cannot connect to the project.");
6061
6081
  }
6062
6082
  }
6063
- const session = await fetch("/api/session", { headers: this.headers(), signal: AbortSignal.timeout(1e4) });
6083
+ const session = await fetch("/api/session", { headers: this.headers(), signal });
6084
+ if (!active()) {
6085
+ return;
6086
+ }
6064
6087
  if (session.status === 401) {
6065
6088
  this.update("expired");
6066
6089
  return;
@@ -6072,24 +6095,32 @@ ${state2.user?.avatarUrl ?? ""}`;
6072
6095
  eventsUrl.searchParams.set("clientId", this.clientId);
6073
6096
  const source = this.source = new EventSource(eventsUrl.href);
6074
6097
  source.onopen = () => {
6075
- this.attempts = 0;
6076
- this.update("connected");
6098
+ if (active()) {
6099
+ this.attempts = 0;
6100
+ this.update("connected");
6101
+ }
6077
6102
  };
6078
6103
  source.onmessage = (event) => {
6079
- this.emit(this.publicFiles(JSON.parse(event.data)));
6104
+ if (active()) {
6105
+ this.emit(this.publicFiles(JSON.parse(event.data)));
6106
+ }
6080
6107
  };
6081
6108
  source.onerror = () => {
6082
- if (this.source === source && !this.closed) {
6109
+ if (active()) {
6083
6110
  this.interrupted();
6084
6111
  }
6085
6112
  };
6086
6113
  } catch {
6087
- if (!this.closed) {
6114
+ if (active()) {
6088
6115
  this.interrupted();
6089
6116
  }
6090
6117
  }
6091
6118
  })().finally(() => {
6119
+ if (epoch !== this.epoch) {
6120
+ return;
6121
+ }
6092
6122
  this.connecting = void 0;
6123
+ this.handshake = void 0;
6093
6124
  if (this.banner) {
6094
6125
  this.banner.querySelector("button").disabled = this.status === "offline";
6095
6126
  }
@@ -6101,16 +6132,22 @@ ${state2.user?.avatarUrl ?? ""}`;
6101
6132
  if (this.status !== "connected") {
6102
6133
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
6103
6134
  }
6135
+ const epoch = this.epoch;
6104
6136
  let response;
6105
6137
  try {
6106
6138
  const body = ["kanban-method-state", "kanban-method-reply"].includes(message.type) ? { ...message, clientId: this.clientId } : message;
6107
6139
  response = await fetch("/api/message", { method: "POST", headers: { ...this.headers(), "Content-Type": "application/json" }, body: JSON.stringify(body) });
6108
6140
  } catch {
6109
- this.interrupted();
6141
+ if (epoch === this.epoch && !this.closed) {
6142
+ this.interrupted();
6143
+ }
6110
6144
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
6111
6145
  }
6146
+ if (epoch !== this.epoch || this.closed) {
6147
+ throw new ConnectionError("\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002");
6148
+ }
6112
6149
  if (response.status === 401) {
6113
- this.source?.close();
6150
+ this.disconnect();
6114
6151
  this.update("expired");
6115
6152
  throw new ConnectionError("\u9879\u76EE\u8FDE\u63A5\u5DF2\u5931\u6548\u3002");
6116
6153
  }
@@ -6118,9 +6155,14 @@ ${state2.user?.avatarUrl ?? ""}`;
6118
6155
  try {
6119
6156
  reply = await response.json();
6120
6157
  } catch {
6121
- this.interrupted();
6158
+ if (epoch === this.epoch && !this.closed) {
6159
+ this.interrupted();
6160
+ }
6122
6161
  throw new ConnectionError("\u8FDE\u63A5\u5DF2\u4E2D\u65AD\u3002");
6123
6162
  }
6163
+ if (epoch !== this.epoch || this.closed) {
6164
+ throw new ConnectionError("\u8FDE\u63A5\u5DF2\u53D8\u66F4\u3002");
6165
+ }
6124
6166
  if (!response.ok) {
6125
6167
  throw new Error(reply.message || "Request failed.");
6126
6168
  }
@@ -7833,7 +7875,8 @@ ${JSON.stringify(output2.details, null, 2)}` : "");
7833
7875
  if (field.type === "file" || field.control === "audio") {
7834
7876
  let names = "";
7835
7877
  try {
7836
- names = JSON.parse(String(value || "[]")).map((file) => file.name).join(", ");
7878
+ const files = JSON.parse(String(value || "[]"));
7879
+ names = (Array.isArray(files) ? files : [files]).map((file) => file?.name ?? "").join(", ");
7837
7880
  } catch {
7838
7881
  }
7839
7882
  const selected = document.createElement("span");
@@ -443,7 +443,7 @@ var ConversationController = class {
443
443
  throw new Error("\u5DF2\u505C\u6B62");
444
444
  }
445
445
  const report = await this.runtime.run(request, (event) => {
446
- if (this.disposed) {
446
+ if (task.cancelled || this.disposed || this.tasks.get(answer.runId) !== task) {
447
447
  return;
448
448
  }
449
449
  (answer.events ??= []).push(event);
@@ -475,11 +475,14 @@ var ConversationController = class {
475
475
  }
476
476
  this.changed();
477
477
  });
478
+ if (task.cancelled || this.disposed) {
479
+ throw new Error("\u5DF2\u505C\u6B62");
480
+ }
478
481
  answer.report = report;
479
482
  answer.status = report.status;
480
483
  answer.durationMs = report.durationMs;
481
- if (report.output?.items?.length) {
482
- answer.items = report.output.items;
484
+ if (report.output) {
485
+ answer.items = report.output.items ?? [];
483
486
  answer.content = outputText(answer.items);
484
487
  }
485
488
  answer.pendingInput = report.status === "waiting_for_input" ? report.pendingUserInput : void 0;
@@ -995,6 +998,9 @@ var WorkflowController = class {
995
998
  this.changed();
996
999
  this.scheduleSave();
997
1000
  });
1001
+ if (task.cancelled || this.disposed) {
1002
+ throw new Error("Stopped.");
1003
+ }
998
1004
  record2.report = report;
999
1005
  record2.durationMs = report.durationMs;
1000
1006
  record2.status = report.status;
@@ -1116,10 +1122,16 @@ var WorkflowController = class {
1116
1122
  }
1117
1123
  const other = workflowStorageReferences([this.runs.filter((item) => item !== record2), this.workspace.drafts, this.options.legacy ?? []]);
1118
1124
  const own = workflowStorageReferences(record2);
1125
+ for (const session of this.options.legacy ?? []) {
1126
+ other.scopes.add(session.id);
1127
+ }
1128
+ let unresolvedOwner = false;
1119
1129
  for (const id2 of other.files) {
1120
1130
  const file = await this.runtime.readFile(this.project, id2).catch(() => void 0);
1121
1131
  if (file?.reference.conversationId) {
1122
1132
  other.scopes.add(file.reference.conversationId);
1133
+ } else {
1134
+ unresolvedOwner = true;
1123
1135
  }
1124
1136
  }
1125
1137
  const owners = /* @__PURE__ */ new Map();
@@ -1137,7 +1149,7 @@ var WorkflowController = class {
1137
1149
  for (const [scope, ids] of owners) {
1138
1150
  await this.runtime.deleteFiles(this.project, scope, ids);
1139
1151
  }
1140
- if (!other.scopes.has(record2.scopeId)) {
1152
+ if (!unresolvedOwner && !other.scopes.has(record2.scopeId)) {
1141
1153
  await this.runtime.deleteConversation(this.project, record2.scopeId);
1142
1154
  }
1143
1155
  this.runs.splice(this.runs.indexOf(record2), 1);
@@ -1430,6 +1442,7 @@ var KanbanController = class {
1430
1442
  missingRelatedProjectAliases = [];
1431
1443
  relatedProjectWarning = "";
1432
1444
  pendingSave = Promise.resolve();
1445
+ updates = Promise.resolve();
1433
1446
  poll;
1434
1447
  feed;
1435
1448
  retry;
@@ -1495,34 +1508,49 @@ var KanbanController = class {
1495
1508
  }
1496
1509
  refresh() {
1497
1510
  clearTimeout(this.refreshTimer);
1498
- return this.refreshing ??= this.load().finally(() => {
1511
+ return this.refreshing ??= this.update(() => this.load()).finally(() => {
1499
1512
  this.refreshing = void 0;
1500
1513
  });
1501
1514
  }
1515
+ update(operation) {
1516
+ const task = this.updates.then(() => {
1517
+ if (this.disposed) {
1518
+ throw new Error("Kanban has closed.");
1519
+ }
1520
+ return operation();
1521
+ });
1522
+ this.updates = task.then(() => void 0, () => void 0);
1523
+ return task;
1524
+ }
1502
1525
  async load() {
1503
1526
  const pkg = JSON.parse(await readFile3(path3.join(this.project.folderPath, "package.json"), "utf8"));
1504
1527
  const artifact = readKanbanArtifactConfig(pkg);
1505
- this.project.kanban = artifact;
1506
- this.project.projectInfo = readKanbanProjectInfo(pkg);
1528
+ const projectInfo = readKanbanProjectInfo(pkg);
1529
+ let params;
1507
1530
  try {
1508
- this.params = parseKanbanManifestText(await readFile3(path3.join(this.project.folderPath, "kanban.json"), "utf8")).params;
1531
+ params = parseKanbanManifestText(await readFile3(path3.join(this.project.folderPath, "kanban.json"), "utf8")).params;
1509
1532
  } catch (error) {
1510
1533
  if (error.code !== "ENOENT") {
1511
1534
  throw error;
1512
1535
  }
1513
- this.params = [];
1536
+ params = [];
1514
1537
  }
1515
- this.project.fields = this.params;
1516
- for (const item of this.workspace.configurations) {
1517
- item.values = sanitizeKanbanValues(this.params, item.values);
1538
+ const workspace = structuredClone(this.workspace);
1539
+ for (const item of workspace.configurations) {
1540
+ item.values = sanitizeKanbanValues(params, item.values);
1518
1541
  }
1519
- if (!this.workspace.configurations.length && this.params.length) {
1520
- this.create();
1542
+ if (!workspace.configurations.length && params.length) {
1543
+ this.create(workspace, params);
1521
1544
  }
1522
- this.workspace.activeConfigurationId = this.current?.id ?? this.workspace.configurations[0]?.id;
1523
- this.workspace.revision++;
1545
+ workspace.activeConfigurationId = workspace.configurations.find((item) => item.id === workspace.activeConfigurationId)?.id ?? workspace.configurations[0]?.id;
1546
+ workspace.revision++;
1547
+ await this.persist(workspace);
1548
+ this.workspace = workspace;
1549
+ this.params = params;
1550
+ this.project.kanban = artifact;
1551
+ this.project.projectInfo = projectInfo;
1552
+ this.project.fields = params;
1524
1553
  this.newPreview();
1525
- await this.persist();
1526
1554
  this.authenticationChanged();
1527
1555
  await this.updateLocalRevisions();
1528
1556
  this.hooks.changed();
@@ -1532,21 +1560,21 @@ var KanbanController = class {
1532
1560
  this.methods.clear();
1533
1561
  this.preview = new KanbanPreview(this.project.folderPath, this.project.kanban, () => this.configuration());
1534
1562
  }
1535
- name() {
1536
- let index = 1;
1537
- while (this.workspace.configurations.some((item) => item.name === `\u914D\u7F6E ${index}`)) {
1538
- index++;
1563
+ create(workspace, params, values = createDefaultKanbanValues(params), name) {
1564
+ if (!name) {
1565
+ let index = 1;
1566
+ while (workspace.configurations.some((item2) => item2.name === `\u914D\u7F6E ${index}`)) {
1567
+ index++;
1568
+ }
1569
+ name = `\u914D\u7F6E ${index}`;
1539
1570
  }
1540
- return `\u914D\u7F6E ${index}`;
1541
- }
1542
- create(values = createDefaultKanbanValues(this.params), name = this.name()) {
1543
1571
  const date = (/* @__PURE__ */ new Date()).toISOString(), item = { id: randomUUID2(), name, values: { ...values }, createdAt: date, updatedAt: date };
1544
- this.workspace.configurations.push(item);
1545
- this.workspace.activeConfigurationId = item.id;
1572
+ workspace.configurations.push(item);
1573
+ workspace.activeConfigurationId = item.id;
1546
1574
  return item;
1547
1575
  }
1548
- async persist() {
1549
- const content = JSON.stringify(this.workspace);
1576
+ async persist(workspace = this.workspace) {
1577
+ const content = JSON.stringify(workspace);
1550
1578
  this.pendingSave = this.pendingSave.catch(() => void 0).then(async () => {
1551
1579
  await mkdir(path3.dirname(this.historyPath), { recursive: true });
1552
1580
  const temp = `${this.historyPath}.${process.pid}.tmp`;
@@ -1590,10 +1618,15 @@ var KanbanController = class {
1590
1618
  if (message.type !== "kanban-configuration") {
1591
1619
  throw new Error(`Unsupported Kanban action: ${message.type}`);
1592
1620
  }
1621
+ return this.update(() => this.configure(message));
1622
+ }
1623
+ async configure(message) {
1593
1624
  if (message.revision !== this.workspace.revision) {
1594
1625
  throw new Error("Kanban configuration changed. Try again.");
1595
1626
  }
1596
- const action = String(message.action), item = this.workspace.configurations.find((item2) => item2.id === message.id);
1627
+ const workspace = structuredClone(this.workspace);
1628
+ const action = String(message.action), item = workspace.configurations.find((item2) => item2.id === message.id);
1629
+ let previewChanged = false;
1597
1630
  if (!["create", "duplicate", "rename", "save", "select", "delete"].includes(action)) {
1598
1631
  throw new Error("Unknown Kanban configuration action.");
1599
1632
  }
@@ -1605,12 +1638,12 @@ var KanbanController = class {
1605
1638
  if (!this.params.length) {
1606
1639
  throw new Error("The project does not define parameters.");
1607
1640
  }
1608
- this.create();
1609
- this.newPreview();
1641
+ this.create(workspace, this.params);
1642
+ previewChanged = true;
1610
1643
  break;
1611
1644
  case "duplicate":
1612
- this.create(sanitizeKanbanValues(this.params, item.values), `${item.name} (copy)`);
1613
- this.newPreview();
1645
+ this.create(workspace, this.params, sanitizeKanbanValues(this.params, item.values), `${item.name} (copy)`);
1646
+ previewChanged = true;
1614
1647
  break;
1615
1648
  case "rename": {
1616
1649
  if (typeof message.name !== "string" || !message.name.trim() || message.name.length > 200) {
@@ -1629,23 +1662,27 @@ var KanbanController = class {
1629
1662
  break;
1630
1663
  }
1631
1664
  case "select":
1632
- this.workspace.activeConfigurationId = item.id;
1633
- this.newPreview();
1665
+ workspace.activeConfigurationId = item.id;
1666
+ previewChanged = true;
1634
1667
  break;
1635
1668
  case "delete": {
1636
- if (this.workspace.configurations.length <= 1) {
1669
+ if (workspace.configurations.length <= 1) {
1637
1670
  throw new Error("The last configuration cannot be deleted.");
1638
1671
  }
1639
- this.workspace.configurations = this.workspace.configurations.filter((value) => value !== item);
1640
- if (item.id === this.workspace.activeConfigurationId) {
1641
- this.workspace.activeConfigurationId = this.workspace.configurations[0].id;
1642
- this.newPreview();
1672
+ workspace.configurations = workspace.configurations.filter((value) => value !== item);
1673
+ if (item.id === workspace.activeConfigurationId) {
1674
+ workspace.activeConfigurationId = workspace.configurations[0].id;
1675
+ previewChanged = true;
1643
1676
  }
1644
1677
  break;
1645
1678
  }
1646
1679
  }
1647
- this.workspace.revision++;
1648
- await this.persist();
1680
+ workspace.revision++;
1681
+ await this.persist(workspace);
1682
+ this.workspace = workspace;
1683
+ if (previewChanged) {
1684
+ this.newPreview();
1685
+ }
1649
1686
  this.hooks.changed();
1650
1687
  return [{ type: "kanban-updated", requestId: message.requestId, kanban: this.state() }];
1651
1688
  }
@@ -1782,7 +1819,7 @@ var KanbanController = class {
1782
1819
  for (const request of this.requests) {
1783
1820
  request.abort();
1784
1821
  }
1785
- await this.refreshing?.catch(() => void 0);
1822
+ await this.updates;
1786
1823
  await this.pendingSave;
1787
1824
  }
1788
1825
  };
@@ -2042,7 +2079,7 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
2042
2079
  if (!record2) {
2043
2080
  throw new Error("Run no longer exists.");
2044
2081
  }
2045
- replies.push({ type: "download", name: `workflow-${record2.id}.json`, content: JSON.stringify(redactWorkflowData(record2), null, 2) });
2082
+ replies.push({ type: "download", name: `workflow-${record2.id}.json`, content: JSON.stringify(redactWorkflowData(record2, [], false), null, 2) });
2046
2083
  break;
2047
2084
  }
2048
2085
  case "tool-policy": {
@@ -2426,9 +2463,9 @@ async function startWebServer(options) {
2426
2463
  type: "state",
2427
2464
  project,
2428
2465
  ...isKanban ? { kanban: kanbanController.state() } : isWorkflow ? workflowActions.state() : { sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {} },
2429
- account,
2466
+ account: { ...account, user: account.user && { userId: account.user.userId, email: account.user.email, displayName: account.user.displayName, avatarUrl: account.user.avatarUrl } },
2430
2467
  authenticationRevision: authRevision
2431
- }, [...sensitiveTokens]));
2468
+ }, [...sensitiveTokens], false));
2432
2469
  const broadcast = () => {
2433
2470
  if (broadcastTimer) {
2434
2471
  return;
@@ -2555,6 +2592,9 @@ ${token}`;
2555
2592
  if (authenticationInitialized) {
2556
2593
  await stopForAuthentication();
2557
2594
  }
2595
+ if (epoch !== authEpoch) {
2596
+ return;
2597
+ }
2558
2598
  }
2559
2599
  authenticationInitialized = true;
2560
2600
  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 };
@@ -2592,6 +2632,9 @@ ${token}`;
2592
2632
  workflowActions?.invalidate();
2593
2633
  await stopForAuthentication();
2594
2634
  }
2635
+ if (epoch !== authEpoch) {
2636
+ return;
2637
+ }
2595
2638
  account = { ...account, status: unauthorized ? "expired" : "error", error: String(error.message) };
2596
2639
  }
2597
2640
  } else {
@@ -2788,7 +2831,7 @@ ${token}`;
2788
2831
  } else if (message.action === "delete") {
2789
2832
  await controller.remove(session.id);
2790
2833
  } else if (message.action === "export") {
2791
- replies.push({ type: "download", name: `conversation-${session.id}.json`, content: JSON.stringify(redactWorkflowData(session), null, 2) });
2834
+ replies.push({ type: "download", name: `conversation-${session.id}.json`, content: JSON.stringify(redactWorkflowData(session, [...sensitiveTokens], false), null, 2) });
2792
2835
  }
2793
2836
  break;
2794
2837
  }
@@ -2808,7 +2851,7 @@ ${token}`;
2808
2851
  throw new Error("Message no longer exists.");
2809
2852
  }
2810
2853
  if (message.type === "export-message") {
2811
- replies.push({ type: "download", name: `run-${answer.id}.json`, content: JSON.stringify(redactWorkflowData(answer), null, 2) }, { type: "exported", sessionId: current.id, messageId: answer.id });
2854
+ replies.push({ type: "download", name: `run-${answer.id}.json`, content: JSON.stringify(redactWorkflowData(answer, [...sensitiveTokens], false), null, 2) }, { type: "exported", sessionId: current.id, messageId: answer.id });
2812
2855
  } else {
2813
2856
  replies.push({ type: "preview", title: message.type === "logs" ? "\u8FD0\u884C\u65E5\u5FD7" : "\u8FD0\u884C\u8DDF\u8E2A", content: message.type === "logs" ? answer.report?.logs ?? "" : answer.report });
2814
2857
  }
@@ -2992,7 +3035,7 @@ ${token}`;
2992
3035
  mutations = task;
2993
3036
  }
2994
3037
  await task;
2995
- json(response, 200, { messages: redactWorkflowData(messages, [...sensitiveTokens]) });
3038
+ json(response, 200, { messages: redactWorkflowData(messages, [...sensitiveTokens], false) });
2996
3039
  return;
2997
3040
  }
2998
3041
  if (url.pathname.startsWith("/api/file/") && request.method === "GET") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow-code/cli",
3
- "version": "0.2.4-20261011006.codex-cli-web",
3
+ "version": "0.2.4-20261011008.codex-cli-web",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "workflow-code": "./dist/index.js"
@@ -42,11 +42,11 @@
42
42
  "@workflow-code/kanban-project": "0.1.0",
43
43
  "@workflow-code/openai-responses": "0.1.0",
44
44
  "@workflow-code/project-dependencies": "0.1.0",
45
- "@workflow-code/project-upload": "0.1.0",
46
- "@workflow-code/runtime-compatibility": "0.1.0",
47
45
  "@workflow-code/project-info": "0.1.0",
48
- "@workflow-code/workflow-ids": "0.1.0",
49
- "@workflow-code/workflow-mcp": "0.1.0"
46
+ "@workflow-code/runtime-compatibility": "0.1.0",
47
+ "@workflow-code/project-upload": "0.1.0",
48
+ "@workflow-code/workflow-mcp": "0.1.0",
49
+ "@workflow-code/workflow-ids": "0.1.0"
50
50
  },
51
51
  "scripts": {
52
52
  "bootstrap": "node scripts/bootstrap.mjs",