@workflow-code/cli 0.2.4-20261011007.codex-cli-web → 0.2.4-20261011009.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-RDWHQ2ST.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
  }
@@ -4522,6 +4564,14 @@ ${state2.user?.avatarUrl ?? ""}`;
4522
4564
  clearTimeout(readyTimer);
4523
4565
  clearInterval(methodHeartbeat);
4524
4566
  });
4567
+ addEventListener("pageshow", (event) => {
4568
+ if (!event.persisted) {
4569
+ return;
4570
+ }
4571
+ mountPreview();
4572
+ clearInterval(methodHeartbeat);
4573
+ methodHeartbeat = setInterval(publishMethods, 5e3);
4574
+ });
4525
4575
  })();
4526
4576
  /*! Bundled license information:
4527
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,15 @@ 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
+ const items = report.output?.items ?? [];
485
+ if (report.output && (report.status === "success" || !answer.items?.length || report.status !== "waiting_for_input" && items.length)) {
486
+ answer.items = items;
483
487
  answer.content = outputText(answer.items);
484
488
  }
485
489
  answer.pendingInput = report.status === "waiting_for_input" ? report.pendingUserInput : void 0;
@@ -995,11 +999,15 @@ var WorkflowController = class {
995
999
  this.changed();
996
1000
  this.scheduleSave();
997
1001
  });
1002
+ if (task.cancelled || this.disposed) {
1003
+ throw new Error("Stopped.");
1004
+ }
998
1005
  record2.report = report;
999
1006
  record2.durationMs = report.durationMs;
1000
1007
  record2.status = report.status;
1001
- if (report.output) {
1002
- record2.items = report.output.items ?? [];
1008
+ const items = report.output?.items ?? [];
1009
+ if (report.output && (report.status === "success" || !record2.items.length || report.status !== "waiting_for_input" && items.length)) {
1010
+ record2.items = items;
1003
1011
  }
1004
1012
  record2.nodes = report.nodes ?? record2.nodes;
1005
1013
  record2.logs = report.logs ?? record2.logs;
@@ -1116,10 +1124,16 @@ var WorkflowController = class {
1116
1124
  }
1117
1125
  const other = workflowStorageReferences([this.runs.filter((item) => item !== record2), this.workspace.drafts, this.options.legacy ?? []]);
1118
1126
  const own = workflowStorageReferences(record2);
1127
+ for (const session of this.options.legacy ?? []) {
1128
+ other.scopes.add(session.id);
1129
+ }
1130
+ let unresolvedOwner = false;
1119
1131
  for (const id2 of other.files) {
1120
1132
  const file = await this.runtime.readFile(this.project, id2).catch(() => void 0);
1121
1133
  if (file?.reference.conversationId) {
1122
1134
  other.scopes.add(file.reference.conversationId);
1135
+ } else {
1136
+ unresolvedOwner = true;
1123
1137
  }
1124
1138
  }
1125
1139
  const owners = /* @__PURE__ */ new Map();
@@ -1137,7 +1151,7 @@ var WorkflowController = class {
1137
1151
  for (const [scope, ids] of owners) {
1138
1152
  await this.runtime.deleteFiles(this.project, scope, ids);
1139
1153
  }
1140
- if (!other.scopes.has(record2.scopeId)) {
1154
+ if (!unresolvedOwner && !other.scopes.has(record2.scopeId)) {
1141
1155
  await this.runtime.deleteConversation(this.project, record2.scopeId);
1142
1156
  }
1143
1157
  this.runs.splice(this.runs.indexOf(record2), 1);
@@ -1430,6 +1444,7 @@ var KanbanController = class {
1430
1444
  missingRelatedProjectAliases = [];
1431
1445
  relatedProjectWarning = "";
1432
1446
  pendingSave = Promise.resolve();
1447
+ updates = Promise.resolve();
1433
1448
  poll;
1434
1449
  feed;
1435
1450
  retry;
@@ -1495,34 +1510,49 @@ var KanbanController = class {
1495
1510
  }
1496
1511
  refresh() {
1497
1512
  clearTimeout(this.refreshTimer);
1498
- return this.refreshing ??= this.load().finally(() => {
1513
+ return this.refreshing ??= this.update(() => this.load()).finally(() => {
1499
1514
  this.refreshing = void 0;
1500
1515
  });
1501
1516
  }
1517
+ update(operation) {
1518
+ const task = this.updates.then(() => {
1519
+ if (this.disposed) {
1520
+ throw new Error("Kanban has closed.");
1521
+ }
1522
+ return operation();
1523
+ });
1524
+ this.updates = task.then(() => void 0, () => void 0);
1525
+ return task;
1526
+ }
1502
1527
  async load() {
1503
1528
  const pkg = JSON.parse(await readFile3(path3.join(this.project.folderPath, "package.json"), "utf8"));
1504
1529
  const artifact = readKanbanArtifactConfig(pkg);
1505
- this.project.kanban = artifact;
1506
- this.project.projectInfo = readKanbanProjectInfo(pkg);
1530
+ const projectInfo = readKanbanProjectInfo(pkg);
1531
+ let params;
1507
1532
  try {
1508
- this.params = parseKanbanManifestText(await readFile3(path3.join(this.project.folderPath, "kanban.json"), "utf8")).params;
1533
+ params = parseKanbanManifestText(await readFile3(path3.join(this.project.folderPath, "kanban.json"), "utf8")).params;
1509
1534
  } catch (error) {
1510
1535
  if (error.code !== "ENOENT") {
1511
1536
  throw error;
1512
1537
  }
1513
- this.params = [];
1538
+ params = [];
1514
1539
  }
1515
- this.project.fields = this.params;
1516
- for (const item of this.workspace.configurations) {
1517
- item.values = sanitizeKanbanValues(this.params, item.values);
1540
+ const workspace = structuredClone(this.workspace);
1541
+ for (const item of workspace.configurations) {
1542
+ item.values = sanitizeKanbanValues(params, item.values);
1518
1543
  }
1519
- if (!this.workspace.configurations.length && this.params.length) {
1520
- this.create();
1544
+ if (!workspace.configurations.length && params.length) {
1545
+ this.create(workspace, params);
1521
1546
  }
1522
- this.workspace.activeConfigurationId = this.current?.id ?? this.workspace.configurations[0]?.id;
1523
- this.workspace.revision++;
1547
+ workspace.activeConfigurationId = workspace.configurations.find((item) => item.id === workspace.activeConfigurationId)?.id ?? workspace.configurations[0]?.id;
1548
+ workspace.revision++;
1549
+ await this.persist(workspace);
1550
+ this.workspace = workspace;
1551
+ this.params = params;
1552
+ this.project.kanban = artifact;
1553
+ this.project.projectInfo = projectInfo;
1554
+ this.project.fields = params;
1524
1555
  this.newPreview();
1525
- await this.persist();
1526
1556
  this.authenticationChanged();
1527
1557
  await this.updateLocalRevisions();
1528
1558
  this.hooks.changed();
@@ -1532,21 +1562,21 @@ var KanbanController = class {
1532
1562
  this.methods.clear();
1533
1563
  this.preview = new KanbanPreview(this.project.folderPath, this.project.kanban, () => this.configuration());
1534
1564
  }
1535
- name() {
1536
- let index = 1;
1537
- while (this.workspace.configurations.some((item) => item.name === `\u914D\u7F6E ${index}`)) {
1538
- index++;
1565
+ create(workspace, params, values = createDefaultKanbanValues(params), name) {
1566
+ if (!name) {
1567
+ let index = 1;
1568
+ while (workspace.configurations.some((item2) => item2.name === `\u914D\u7F6E ${index}`)) {
1569
+ index++;
1570
+ }
1571
+ name = `\u914D\u7F6E ${index}`;
1539
1572
  }
1540
- return `\u914D\u7F6E ${index}`;
1541
- }
1542
- create(values = createDefaultKanbanValues(this.params), name = this.name()) {
1543
1573
  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;
1574
+ workspace.configurations.push(item);
1575
+ workspace.activeConfigurationId = item.id;
1546
1576
  return item;
1547
1577
  }
1548
- async persist() {
1549
- const content = JSON.stringify(this.workspace);
1578
+ async persist(workspace = this.workspace) {
1579
+ const content = JSON.stringify(workspace);
1550
1580
  this.pendingSave = this.pendingSave.catch(() => void 0).then(async () => {
1551
1581
  await mkdir(path3.dirname(this.historyPath), { recursive: true });
1552
1582
  const temp = `${this.historyPath}.${process.pid}.tmp`;
@@ -1590,10 +1620,15 @@ var KanbanController = class {
1590
1620
  if (message.type !== "kanban-configuration") {
1591
1621
  throw new Error(`Unsupported Kanban action: ${message.type}`);
1592
1622
  }
1623
+ return this.update(() => this.configure(message));
1624
+ }
1625
+ async configure(message) {
1593
1626
  if (message.revision !== this.workspace.revision) {
1594
1627
  throw new Error("Kanban configuration changed. Try again.");
1595
1628
  }
1596
- const action = String(message.action), item = this.workspace.configurations.find((item2) => item2.id === message.id);
1629
+ const workspace = structuredClone(this.workspace);
1630
+ const action = String(message.action), item = workspace.configurations.find((item2) => item2.id === message.id);
1631
+ let previewChanged = false;
1597
1632
  if (!["create", "duplicate", "rename", "save", "select", "delete"].includes(action)) {
1598
1633
  throw new Error("Unknown Kanban configuration action.");
1599
1634
  }
@@ -1605,12 +1640,12 @@ var KanbanController = class {
1605
1640
  if (!this.params.length) {
1606
1641
  throw new Error("The project does not define parameters.");
1607
1642
  }
1608
- this.create();
1609
- this.newPreview();
1643
+ this.create(workspace, this.params);
1644
+ previewChanged = true;
1610
1645
  break;
1611
1646
  case "duplicate":
1612
- this.create(sanitizeKanbanValues(this.params, item.values), `${item.name} (copy)`);
1613
- this.newPreview();
1647
+ this.create(workspace, this.params, sanitizeKanbanValues(this.params, item.values), `${item.name} (copy)`);
1648
+ previewChanged = true;
1614
1649
  break;
1615
1650
  case "rename": {
1616
1651
  if (typeof message.name !== "string" || !message.name.trim() || message.name.length > 200) {
@@ -1629,23 +1664,27 @@ var KanbanController = class {
1629
1664
  break;
1630
1665
  }
1631
1666
  case "select":
1632
- this.workspace.activeConfigurationId = item.id;
1633
- this.newPreview();
1667
+ workspace.activeConfigurationId = item.id;
1668
+ previewChanged = true;
1634
1669
  break;
1635
1670
  case "delete": {
1636
- if (this.workspace.configurations.length <= 1) {
1671
+ if (workspace.configurations.length <= 1) {
1637
1672
  throw new Error("The last configuration cannot be deleted.");
1638
1673
  }
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();
1674
+ workspace.configurations = workspace.configurations.filter((value) => value !== item);
1675
+ if (item.id === workspace.activeConfigurationId) {
1676
+ workspace.activeConfigurationId = workspace.configurations[0].id;
1677
+ previewChanged = true;
1643
1678
  }
1644
1679
  break;
1645
1680
  }
1646
1681
  }
1647
- this.workspace.revision++;
1648
- await this.persist();
1682
+ workspace.revision++;
1683
+ await this.persist(workspace);
1684
+ this.workspace = workspace;
1685
+ if (previewChanged) {
1686
+ this.newPreview();
1687
+ }
1649
1688
  this.hooks.changed();
1650
1689
  return [{ type: "kanban-updated", requestId: message.requestId, kanban: this.state() }];
1651
1690
  }
@@ -1782,7 +1821,7 @@ var KanbanController = class {
1782
1821
  for (const request of this.requests) {
1783
1822
  request.abort();
1784
1823
  }
1785
- await this.refreshing?.catch(() => void 0);
1824
+ await this.updates;
1786
1825
  await this.pendingSave;
1787
1826
  }
1788
1827
  };
@@ -2042,7 +2081,7 @@ function createWorkflowActions(controller, runtime, project, revision, changed)
2042
2081
  if (!record2) {
2043
2082
  throw new Error("Run no longer exists.");
2044
2083
  }
2045
- replies.push({ type: "download", name: `workflow-${record2.id}.json`, content: JSON.stringify(redactWorkflowData(record2), null, 2) });
2084
+ replies.push({ type: "download", name: `workflow-${record2.id}.json`, content: JSON.stringify(redactWorkflowData(record2, [], false), null, 2) });
2046
2085
  break;
2047
2086
  }
2048
2087
  case "tool-policy": {
@@ -2426,9 +2465,9 @@ async function startWebServer(options) {
2426
2465
  type: "state",
2427
2466
  project,
2428
2467
  ...isKanban ? { kanban: kanbanController.state() } : isWorkflow ? workflowActions.state() : { sessions: controller.sessions, activeId: controller.activeId, running: controller.running, parameterValues: controller.current.parameterValues ?? {} },
2429
- account,
2468
+ account: { ...account, user: account.user && { userId: account.user.userId, email: account.user.email, displayName: account.user.displayName, avatarUrl: account.user.avatarUrl } },
2430
2469
  authenticationRevision: authRevision
2431
- }, [...sensitiveTokens]));
2470
+ }, [...sensitiveTokens], false));
2432
2471
  const broadcast = () => {
2433
2472
  if (broadcastTimer) {
2434
2473
  return;
@@ -2555,6 +2594,9 @@ ${token}`;
2555
2594
  if (authenticationInitialized) {
2556
2595
  await stopForAuthentication();
2557
2596
  }
2597
+ if (epoch !== authEpoch) {
2598
+ return;
2599
+ }
2558
2600
  }
2559
2601
  authenticationInitialized = true;
2560
2602
  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 +2634,9 @@ ${token}`;
2592
2634
  workflowActions?.invalidate();
2593
2635
  await stopForAuthentication();
2594
2636
  }
2637
+ if (epoch !== authEpoch) {
2638
+ return;
2639
+ }
2595
2640
  account = { ...account, status: unauthorized ? "expired" : "error", error: String(error.message) };
2596
2641
  }
2597
2642
  } else {
@@ -2788,7 +2833,7 @@ ${token}`;
2788
2833
  } else if (message.action === "delete") {
2789
2834
  await controller.remove(session.id);
2790
2835
  } else if (message.action === "export") {
2791
- replies.push({ type: "download", name: `conversation-${session.id}.json`, content: JSON.stringify(redactWorkflowData(session), null, 2) });
2836
+ replies.push({ type: "download", name: `conversation-${session.id}.json`, content: JSON.stringify(redactWorkflowData(session, [...sensitiveTokens], false), null, 2) });
2792
2837
  }
2793
2838
  break;
2794
2839
  }
@@ -2808,7 +2853,7 @@ ${token}`;
2808
2853
  throw new Error("Message no longer exists.");
2809
2854
  }
2810
2855
  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 });
2856
+ 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
2857
  } else {
2813
2858
  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
2859
  }
@@ -2992,7 +3037,7 @@ ${token}`;
2992
3037
  mutations = task;
2993
3038
  }
2994
3039
  await task;
2995
- json(response, 200, { messages: redactWorkflowData(messages, [...sensitiveTokens]) });
3040
+ json(response, 200, { messages: redactWorkflowData(messages, [...sensitiveTokens], false) });
2996
3041
  return;
2997
3042
  }
2998
3043
  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-20261011007.codex-cli-web",
3
+ "version": "0.2.4-20261011009.codex-cli-web",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "workflow-code": "./dist/index.js"
@@ -37,9 +37,9 @@
37
37
  "vitest": "^4.1.7",
38
38
  "workflow-code": "0.2.4-20260929001",
39
39
  "@workflow-code/auth-config": "0.1.0",
40
+ "@workflow-code/i18n": "0.1.0",
40
41
  "@workflow-code/execution-targets": "0.1.0",
41
42
  "@workflow-code/kanban-project": "0.1.0",
42
- "@workflow-code/i18n": "0.1.0",
43
43
  "@workflow-code/openai-responses": "0.1.0",
44
44
  "@workflow-code/project-dependencies": "0.1.0",
45
45
  "@workflow-code/project-info": "0.1.0",