@ljwei-stak/dsh-model-router 0.22.2 → 0.23.1

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.
@@ -4017,7 +4017,71 @@ var STATUS_TEXT = Object.freeze({
4017
4017
  "paused-budget": "\u5DF2\u56E0\u9884\u7B97\u6682\u505C\uFF0C\u672A\u542F\u52A8\u6A21\u578B",
4018
4018
  "paused-subscription-failure": "\u6709\u6B65\u9AA4\u56E0\u8BA2\u9605\u8C03\u7528\u5931\u8D25\u800C\u6682\u505C\uFF0C\u8BF7\u5728\u4E0B\u65B9\u6267\u884C\u8BB0\u5F55\u4E2D\u9009\u62E9\u5982\u4F55\u7EE7\u7EED"
4019
4019
  });
4020
- function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger, previewRun, startRun, onStarted, disabledReason, planningRevision, assignmentMode = "algorithm", packageAssignments = {}, packagePlanKey: packagePlanKey2 }) {
4020
+ var LIVE_STATUSES = /* @__PURE__ */ new Set(["running", "cancelling"]);
4021
+ var JOB_STATUSES = /* @__PURE__ */ new Set([...LIVE_STATUSES, "completed", "cancelled", "failed"]);
4022
+ var PACKAGE_STATUS_TEXT = Object.freeze({ pending: "\u7B49\u5F85\u6267\u884C", running: "\u6B63\u5728\u6267\u884C", succeeded: "\u5DF2\u5B8C\u6210", failed: "\u6267\u884C\u5931\u8D25", cancelled: "\u5DF2\u53D6\u6D88", blocked: "\u4F9D\u8D56\u672A\u5B8C\u6210", skipped: "\u5DF2\u8DF3\u8FC7" });
4023
+ var STAGE_TEXT = Object.freeze({
4024
+ starting: "\u6B63\u5728\u542F\u52A8\u4EFB\u52A1\u2026",
4025
+ preparing: "\u6B63\u5728\u51C6\u5907\u6267\u884C\u8BA1\u5212\u2026",
4026
+ executing: "\u6B63\u5728\u5B89\u6392\u5DE5\u4F5C\u5305\u2026",
4027
+ integrating: "\u6B63\u5728\u6574\u5408\u5DE5\u4F5C\u5305\u7ED3\u679C\u2026",
4028
+ reviewing: "\u6B63\u5728\u68C0\u67E5\u6267\u884C\u7ED3\u679C\u2026",
4029
+ saving: "\u6B63\u5728\u4FDD\u5B58\u6267\u884C\u8BB0\u5F55\u2026"
4030
+ });
4031
+ var ACTIVE_JOB_KEY = "dsh-model-router:active-run-job";
4032
+ var isJobSnapshot = (value) => Boolean(text2(value?.jobId) && JOB_STATUSES.has(value.status));
4033
+ var REMOTE_WAIT_MS = 15e3;
4034
+ async function waitForRemote(operation, controllers) {
4035
+ const controller = new AbortController();
4036
+ controllers.add(controller);
4037
+ let timer;
4038
+ try {
4039
+ return await new Promise((resolve, reject) => {
4040
+ controller.signal.onabort = () => {
4041
+ clearTimeout(timer);
4042
+ reject(new Error("\u5DE5\u4F5C\u53F0\u8BF7\u6C42\u7B49\u5F85\u8D85\u65F6\uFF0C\u8BF7\u5728\u8FDE\u63A5\u6062\u590D\u540E\u91CD\u8BD5\u3002"));
4043
+ };
4044
+ timer = setTimeout(() => controller.abort(), REMOTE_WAIT_MS);
4045
+ Promise.resolve(operation(controller.signal)).then(resolve, reject);
4046
+ });
4047
+ } finally {
4048
+ clearTimeout(timer);
4049
+ controller.signal.onabort = null;
4050
+ controllers.delete(controller);
4051
+ }
4052
+ }
4053
+ function savedJob() {
4054
+ try {
4055
+ return typeof sessionStorage === "undefined" ? "" : text2(sessionStorage.getItem(ACTIVE_JOB_KEY));
4056
+ } catch {
4057
+ return "";
4058
+ }
4059
+ }
4060
+ function rememberJob(jobId) {
4061
+ try {
4062
+ if (typeof sessionStorage !== "undefined") sessionStorage.setItem(ACTIVE_JOB_KEY, jobId);
4063
+ } catch {
4064
+ }
4065
+ }
4066
+ function forgetJob(jobId) {
4067
+ try {
4068
+ if (typeof sessionStorage !== "undefined" && savedJob() === jobId) sessionStorage.removeItem(ACTIVE_JOB_KEY);
4069
+ } catch {
4070
+ }
4071
+ }
4072
+ var unknownJob = (error) => /unknown run job id\.?/i.test(text2(error?.message));
4073
+ var pendingLaunch = null;
4074
+ var launchListeners = /* @__PURE__ */ new Set();
4075
+ function notifyLaunch(event) {
4076
+ for (const listener of launchListeners) listener(event);
4077
+ }
4078
+ function settleLaunch(identity, value, error) {
4079
+ if (pendingLaunch !== identity) return;
4080
+ pendingLaunch = null;
4081
+ if (isJobSnapshot(value) && LIVE_STATUSES.has(value.status)) rememberJob(value.jobId);
4082
+ notifyLaunch({ kind: "settled", identity, value, error });
4083
+ }
4084
+ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger, previewRun, startRun, runStatus, cancelRun, onStarted, disabledReason, planningRevision, assignmentMode = "algorithm", packageAssignments = {}, packagePlanKey: packagePlanKey2 }) {
4021
4085
  const [preset, setPreset] = import_react3.default.useState(defaultPreset || "balanced");
4022
4086
  const [executor, setExecutor] = import_react3.default.useState("routed");
4023
4087
  const [executionMode, setExecutionMode] = import_react3.default.useState("read-only");
@@ -4030,17 +4094,78 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4030
4094
  const lastWorkspace = [...ledger?.runs ?? []].map((run) => run.workspace).find(Boolean) ?? "";
4031
4095
  const [workspace, setWorkspace] = import_react3.default.useState("");
4032
4096
  const [phase, setPhase] = import_react3.default.useState({ kind: "idle" });
4097
+ const currentPhase = import_react3.default.useRef(phase);
4098
+ currentPhase.current = phase;
4099
+ const [now, setNow] = import_react3.default.useState(Date.now);
4033
4100
  const mounted = import_react3.default.useRef(true);
4034
4101
  const confirmRef = import_react3.default.useRef(null);
4035
4102
  const pendingRequest = import_react3.default.useRef(0);
4103
+ const activeRun = import_react3.default.useRef(null);
4104
+ const launchSequence = import_react3.default.useRef(0);
4105
+ const launching = import_react3.default.useRef(false);
4106
+ const awaitingLaunch = import_react3.default.useRef(null);
4107
+ const pendingRemotes = import_react3.default.useRef(/* @__PURE__ */ new Set());
4108
+ const onFinished = import_react3.default.useRef(onStarted);
4109
+ onFinished.current = onStarted;
4036
4110
  const inputSignature = JSON.stringify([task, mode, directRoute?.provider, directRoute?.model, budgetUsd, runPreset, workspace, lastWorkspace, planningRevision, disabledReason, runExecutor, runMode, runEnvironment, assignmentMode, packagePlanKey2, packageAssignments]);
4037
4111
  const latestSignature = import_react3.default.useRef(inputSignature);
4038
4112
  latestSignature.current = inputSignature;
4039
4113
  import_react3.default.useEffect(() => {
4040
4114
  mounted.current = true;
4115
+ const resume = (jobId, snapshot) => {
4116
+ activeRun.current = {
4117
+ token: ++launchSequence.current,
4118
+ jobId,
4119
+ terminal: false,
4120
+ cancelRequested: false,
4121
+ cancelPending: false,
4122
+ requestSequence: 0,
4123
+ appliedSequence: 0,
4124
+ restoring: true
4125
+ };
4126
+ launching.current = true;
4127
+ setPhase({ kind: "running", job: snapshot ?? { jobId, status: "running" }, restoring: !snapshot });
4128
+ };
4129
+ const receiveLaunch = (event) => {
4130
+ if (!mounted.current) return;
4131
+ if (event.kind === "pending") {
4132
+ if (activeRun.current?.launchIdentity === event.identity || activeRun.current?.jobId && !activeRun.current.terminal) return;
4133
+ awaitingLaunch.current = event.identity;
4134
+ pendingRequest.current += 1;
4135
+ launching.current = true;
4136
+ setPhase({ kind: "starting" });
4137
+ return;
4138
+ }
4139
+ if (awaitingLaunch.current !== event.identity) return;
4140
+ awaitingLaunch.current = null;
4141
+ const value = event.value;
4142
+ if (isJobSnapshot(value) && LIVE_STATUSES.has(value.status)) {
4143
+ resume(value.jobId, value);
4144
+ return;
4145
+ }
4146
+ launching.current = false;
4147
+ if (event.error || value?.status === "needs-confirmation") {
4148
+ setPhase({ kind: "error", message: event.error || "\u542F\u52A8\u524D\u7684\u786E\u8BA4\u9879\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u5E76\u786E\u8BA4\u6267\u884C\u3002" });
4149
+ return;
4150
+ }
4151
+ const result = isJobSnapshot(value) ? value.result ?? { status: value.status } : value;
4152
+ if (isJobSnapshot(value)) forgetJob(value.jobId);
4153
+ setPhase({ kind: "done", value: result });
4154
+ onFinished.current?.(result.runId);
4155
+ };
4156
+ launchListeners.add(receiveLaunch);
4157
+ if (pendingLaunch) receiveLaunch({ kind: "pending", identity: pendingLaunch });
4158
+ else {
4159
+ const jobId = savedJob();
4160
+ if (jobId && !activeRun.current) resume(jobId);
4161
+ }
4041
4162
  return () => {
4042
4163
  mounted.current = false;
4164
+ for (const controller of pendingRemotes.current) controller.abort();
4165
+ pendingRemotes.current.clear();
4166
+ launchListeners.delete(receiveLaunch);
4043
4167
  pendingRequest.current += 1;
4168
+ activeRun.current = null;
4044
4169
  };
4045
4170
  }, []);
4046
4171
  import_react3.default.useEffect(() => {
@@ -4060,6 +4185,93 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4060
4185
  import_react3.default.useEffect(() => {
4061
4186
  if (phase.kind === "preview") confirmRef.current?.focus();
4062
4187
  }, [phase.kind]);
4188
+ const acceptSnapshot = import_react3.default.useCallback((value, token, sequence, cancelAcknowledged = false) => {
4189
+ const active = activeRun.current;
4190
+ if (!mounted.current || !active || active.token !== token || active.terminal || value.jobId !== active.jobId) return false;
4191
+ if (!isJobSnapshot(value)) throw new Error("\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u6267\u884C\u8FDB\u5EA6\u3002");
4192
+ const terminal = !LIVE_STATUSES.has(value.status);
4193
+ if (!terminal && sequence < active.appliedSequence) {
4194
+ if (cancelAcknowledged) {
4195
+ active.cancelRequested = true;
4196
+ setPhase((previous) => previous.kind === "running" ? {
4197
+ ...previous,
4198
+ job: { ...previous.job, status: "cancelling", cancelRequested: true },
4199
+ cancelPending: false,
4200
+ cancelError: ""
4201
+ } : previous);
4202
+ return true;
4203
+ }
4204
+ return false;
4205
+ }
4206
+ active.appliedSequence = Math.max(active.appliedSequence, sequence);
4207
+ active.cancelRequested || (active.cancelRequested = cancelAcknowledged || value.cancelRequested || value.status === "cancelling");
4208
+ const job = {
4209
+ ...value,
4210
+ cancelRequested: Boolean(active.cancelRequested),
4211
+ status: !terminal && active.cancelRequested ? "cancelling" : value.status
4212
+ };
4213
+ if (terminal) {
4214
+ active.terminal = true;
4215
+ launching.current = false;
4216
+ forgetJob(value.jobId);
4217
+ const result = value.result ?? { status: value.status };
4218
+ setPhase({ kind: "done", value: result, job });
4219
+ onFinished.current?.(result.runId);
4220
+ } else {
4221
+ rememberJob(value.jobId);
4222
+ setPhase((previous) => previous.kind === "running" ? {
4223
+ ...previous,
4224
+ job,
4225
+ restoring: false,
4226
+ pollError: "",
4227
+ cancelError: cancelAcknowledged ? "" : previous.cancelError,
4228
+ cancelPending: cancelAcknowledged ? false : previous.cancelPending
4229
+ } : previous);
4230
+ }
4231
+ return true;
4232
+ }, []);
4233
+ const runningJobId = phase.kind === "running" ? phase.job?.jobId : null;
4234
+ import_react3.default.useEffect(() => {
4235
+ if (!runningJobId || typeof runStatus !== "function") return;
4236
+ const active = activeRun.current;
4237
+ if (!active || active.jobId !== runningJobId) return;
4238
+ let disposed = false, timer;
4239
+ const poll = async () => {
4240
+ if (disposed || activeRun.current !== active || active.terminal) return;
4241
+ const sequence = ++active.requestSequence;
4242
+ try {
4243
+ const value = unwrapRemote(await waitForRemote((signal) => runStatus({ jobId: runningJobId }, signal), pendingRemotes.current), "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u6267\u884C\u8FDB\u5EA6\u3002");
4244
+ if (disposed) return;
4245
+ if (!isJobSnapshot(value) || value.jobId !== runningJobId) throw new Error("\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u6267\u884C\u8FDB\u5EA6\u3002");
4246
+ acceptSnapshot(value, active.token, sequence);
4247
+ } catch (error) {
4248
+ if (!disposed && mounted.current && activeRun.current === active && !active.terminal) {
4249
+ if (unknownJob(error)) {
4250
+ active.terminal = true;
4251
+ launching.current = false;
4252
+ forgetJob(runningJobId);
4253
+ setPhase({ kind: "error", message: "\u65E0\u6CD5\u627E\u5230\u6B64\u524D\u7684\u6267\u884C\uFF0C\u53EF\u80FD\u5DF2\u8FC7\u671F\u6216\u5DE5\u4F5C\u53F0\u5DF2\u91CD\u65B0\u542F\u52A8\u3002\u8BF7\u5237\u65B0\u4E0B\u65B9\u6267\u884C\u8BB0\u5F55\u6838\u5BF9\u7ED3\u679C\u3002" });
4254
+ } else {
4255
+ setPhase((previous) => previous.kind === "running" && previous.job?.jobId === runningJobId ? { ...previous, restoring: false, pollError: "\u6682\u65F6\u65E0\u6CD5\u66F4\u65B0\u6267\u884C\u8FDB\u5EA6\uFF0C\u5C06\u81EA\u52A8\u91CD\u8BD5\uFF1B\u4EFB\u52A1\u53EF\u80FD\u4ECD\u5728\u6267\u884C\u3002" } : previous);
4256
+ }
4257
+ }
4258
+ } finally {
4259
+ if (!disposed && activeRun.current === active && !active.terminal) timer = setTimeout(poll, 1e3);
4260
+ }
4261
+ };
4262
+ timer = setTimeout(poll, active.restoring ? 0 : 1e3);
4263
+ active.restoring = false;
4264
+ return () => {
4265
+ disposed = true;
4266
+ clearTimeout(timer);
4267
+ };
4268
+ }, [runningJobId, runStatus, acceptSnapshot]);
4269
+ import_react3.default.useEffect(() => {
4270
+ if (!runningJobId) return;
4271
+ setNow(Date.now());
4272
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
4273
+ return () => clearInterval(timer);
4274
+ }, [runningJobId]);
4063
4275
  const request = () => launchRequest({
4064
4276
  task,
4065
4277
  mode,
@@ -4076,11 +4288,11 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4076
4288
  });
4077
4289
  const unavailable = typeof previewRun !== "function" || typeof startRun !== "function";
4078
4290
  const blocked = disabledReason || (unavailable ? "\u5DE5\u4F5C\u53F0\u6267\u884C\u670D\u52A1\u5C1A\u672A\u52A0\u8F7D\uFF0C\u8BF7\u66F4\u65B0\u63D2\u4EF6\u540E\u91CD\u8BD5\u3002" : !text2(task) ? "\u5148\u5728\u4E0A\u65B9\u201C\u4EFB\u52A1\u89C4\u5212\u201D\u4E2D\u63CF\u8FF0\u4EFB\u52A1\u3002" : manual && !packagePlanKey2 ? "\u8BF7\u91CD\u65B0\u751F\u6210\u5DE5\u4F5C\u5305\u540E\u518D\u624B\u52A8\u5206\u914D\u3002" : runExecutor === "official-team" && mode !== "team" ? "\u5B98\u65B9\u5DE5\u5177\u534F\u4F5C\u9700\u8981\u5148\u5728\u4E0A\u65B9\u9009\u62E9\u201C\u56E2\u961F\u5206\u5DE5\u201D\u3002" : "");
4079
- const busy = phase.kind === "previewing" || phase.kind === "running";
4291
+ const busy = phase.kind === "previewing" || phase.kind === "running" || phase.kind === "starting";
4080
4292
  const recordedRun = phase.kind === "done" && phase.value.runId ? (ledger?.runs ?? []).find((run) => run.id === phase.value.runId) : null;
4081
4293
  const resultStatus = recordedRun?.status ?? phase.value?.status;
4082
4294
  const preview = async () => {
4083
- if (blocked || busy) return;
4295
+ if (blocked || busy || launching.current || pendingLaunch) return;
4084
4296
  const current = ++pendingRequest.current;
4085
4297
  let snapshot;
4086
4298
  try {
@@ -4092,21 +4304,45 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4092
4304
  const signature2 = inputSignature;
4093
4305
  setPhase({ kind: "previewing" });
4094
4306
  try {
4095
- const value = unwrapRemote(await previewRun(snapshot), "\u6267\u884C\u9884\u89C8\u5931\u8D25\u3002");
4307
+ const value = unwrapRemote(await waitForRemote((signal) => previewRun(snapshot, signal), pendingRemotes.current), "\u6267\u884C\u9884\u89C8\u5931\u8D25\u3002");
4096
4308
  if (mounted.current && current === pendingRequest.current && signature2 === latestSignature.current) setPhase({ kind: "preview", value, request: snapshot, signature: signature2 });
4097
4309
  } catch (error) {
4098
4310
  if (mounted.current && current === pendingRequest.current && signature2 === latestSignature.current) setPhase({ kind: "error", message: text2(error?.message) || "\u6267\u884C\u9884\u89C8\u5931\u8D25\u3002" });
4099
4311
  }
4100
4312
  };
4101
4313
  const confirm2 = async () => {
4102
- if (blocked || phase.kind !== "preview" || phase.signature !== latestSignature.current || phase.value.status === "blocked") return;
4314
+ if (launching.current || pendingLaunch || currentPhase.current !== phase || blocked || phase.kind !== "preview" || phase.signature !== latestSignature.current || phase.value.status === "blocked") return;
4103
4315
  const shown = phase.value;
4104
4316
  const snapshot = phase.request;
4105
4317
  const signature2 = phase.signature;
4318
+ launching.current = true;
4319
+ const identity = /* @__PURE__ */ Symbol("launch");
4320
+ const active = {
4321
+ token: ++launchSequence.current,
4322
+ launchIdentity: identity,
4323
+ jobId: null,
4324
+ terminal: false,
4325
+ cancelRequested: false,
4326
+ cancelPending: false,
4327
+ requestSequence: 0,
4328
+ appliedSequence: 0
4329
+ };
4330
+ activeRun.current = active;
4331
+ pendingLaunch = identity;
4332
+ notifyLaunch({ kind: "pending", identity });
4106
4333
  setPhase({ kind: "running", value: shown, request: snapshot, signature: signature2 });
4107
4334
  try {
4108
4335
  const value = unwrapRemote(await startRun({ ...snapshot, confirmedReasons: (shown.reasons ?? []).map((item) => item.code) }), "\u6267\u884C\u5931\u8D25\u3002");
4336
+ settleLaunch(identity, value);
4109
4337
  if (!mounted.current) return;
4338
+ if (activeRun.current !== active) return;
4339
+ if (isJobSnapshot(value)) {
4340
+ active.jobId = value.jobId;
4341
+ acceptSnapshot(value, active.token, ++active.requestSequence);
4342
+ return;
4343
+ }
4344
+ active.terminal = true;
4345
+ launching.current = false;
4110
4346
  if (value.status === "needs-confirmation") {
4111
4347
  setPhase(signature2 === latestSignature.current ? { kind: "preview", value, request: snapshot, signature: signature2, changed: true } : { kind: "idle" });
4112
4348
  return;
@@ -4114,7 +4350,34 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4114
4350
  setPhase({ kind: "done", value });
4115
4351
  onStarted?.(value.runId);
4116
4352
  } catch (error) {
4117
- if (mounted.current) setPhase({ kind: "error", message: text2(error?.message) || "\u6267\u884C\u5931\u8D25\u3002" });
4353
+ settleLaunch(identity, null, text2(error?.message) || "\u6267\u884C\u5931\u8D25\u3002");
4354
+ if (mounted.current && activeRun.current === active) {
4355
+ active.terminal = true;
4356
+ launching.current = false;
4357
+ setPhase({ kind: "error", message: text2(error?.message) || "\u6267\u884C\u5931\u8D25\u3002" });
4358
+ }
4359
+ }
4360
+ };
4361
+ const cancel = async () => {
4362
+ const active = activeRun.current;
4363
+ if (!active?.jobId || active.terminal || active.cancelPending || active.cancelRequested || typeof cancelRun !== "function") return;
4364
+ active.cancelPending = true;
4365
+ const sequence = ++active.requestSequence;
4366
+ setPhase((previous) => previous.kind === "running" ? { ...previous, cancelPending: true, cancelError: "" } : previous);
4367
+ try {
4368
+ const value = unwrapRemote(await waitForRemote((signal) => cancelRun({ jobId: active.jobId }, signal), pendingRemotes.current), "\u53D6\u6D88\u8BF7\u6C42\u672A\u786E\u8BA4\u3002");
4369
+ if (!isJobSnapshot(value) || value.jobId !== active.jobId) throw new Error("\u53D6\u6D88\u8BF7\u6C42\u672A\u786E\u8BA4\u3002");
4370
+ acceptSnapshot(value, active.token, sequence, true);
4371
+ } catch {
4372
+ if (mounted.current && activeRun.current === active && !active.terminal) {
4373
+ setPhase((previous) => previous.kind === "running" ? {
4374
+ ...previous,
4375
+ cancelPending: false,
4376
+ cancelError: "\u53D6\u6D88\u8BF7\u6C42\u672A\u786E\u8BA4\uFF0C\u4EFB\u52A1\u53EF\u80FD\u4ECD\u5728\u6267\u884C\u3002\u8BF7\u91CD\u8BD5\u53D6\u6D88\u3002"
4377
+ } : previous);
4378
+ }
4379
+ } finally {
4380
+ active.cancelPending = false;
4118
4381
  }
4119
4382
  };
4120
4383
  return /* @__PURE__ */ import_react3.default.createElement("section", { className: "mr-card", "aria-label": "\u5728\u5DE5\u4F5C\u53F0\u6267\u884C" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react3.default.createElement("div", null, /* @__PURE__ */ import_react3.default.createElement("h2", { className: "mr-card-title" }, "\u5728\u5DE5\u4F5C\u53F0\u6267\u884C"), /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-card-copy" }, "\u4F7F\u7528\u4E0A\u65B9\u7684\u4EFB\u52A1\u63CF\u8FF0\u4E0E\u89C4\u5212\u6A21\u5F0F\uFF0C\u5148\u9884\u89C8\u6267\u884C\u8BA1\u5212\u3001\u6743\u9650\u4E0E\u9884\u4F30\u8D39\u7528\uFF0C\u786E\u8BA4\u540E\u624D\u4F1A\u542F\u52A8\u6267\u884C\u3002\u6A21\u578B\u8DEF\u7531\u6267\u884C\u53EA\u8BFB\u4EFB\u52A1\uFF1B\u5B98\u65B9\u5DE5\u5177\u534F\u4F5C\u53EF\u9009\u62E9\u53EA\u8BFB\u68C0\u67E5\uFF0C\u6216\u5728\u9694\u79BB\u5DE5\u4F5C\u6811\u4E2D\u4F7F\u7528 CLI \u7684\u6B63\u5E38\u5DE5\u5177\u7F16\u8F91\u6587\u4EF6\u3002"))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-card-body" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-controls" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-control-group" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-control-label", id: "mr-launch-executor" }, "\u6267\u884C\u65B9\u5F0F"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-segment", role: "group", "aria-labelledby": "mr-launch-executor" }, /* @__PURE__ */ import_react3.default.createElement("button", { type: "button", "aria-pressed": runExecutor === "routed", disabled: busy, onClick: () => {
@@ -4128,7 +4391,7 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4128
4391
  setExecutionEnvironment("harness");
4129
4392
  } }, "\u53EA\u8BFB\u68C0\u67E5"), /* @__PURE__ */ import_react3.default.createElement("button", { type: "button", "aria-pressed": executionMode === "workspace-write", disabled: busy, onClick: () => setExecutionMode("workspace-write") }, "\u7F16\u8F91\u6587\u4EF6"))), runExecutor === "official-team" && executionMode === "workspace-write" && /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-control-group" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-control-label", id: "mr-launch-environment" }, "\u6267\u884C\u73AF\u5883"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-segment", role: "group", "aria-labelledby": "mr-launch-environment" }, /* @__PURE__ */ import_react3.default.createElement("button", { type: "button", "aria-pressed": executionEnvironment === "harness", disabled: busy, onClick: () => setExecutionEnvironment("harness") }, "Harness \u53D7\u9650\u73AF\u5883"), /* @__PURE__ */ import_react3.default.createElement("button", { type: "button", "aria-pressed": executionEnvironment === "cli-native", disabled: busy, onClick: () => setExecutionEnvironment("cli-native") }, "\u5382\u5546 CLI \u6B63\u5E38\u73AF\u5883"))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-control-group" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-control-label", id: "mr-launch-preset" }, "\u8DEF\u7531\u65B9\u6848"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-segment", role: "group", "aria-labelledby": "mr-launch-preset" }, Object.values(ROUTING_PRESETS).map((item) => /* @__PURE__ */ import_react3.default.createElement("button", { key: item.id, type: "button", title: manual ? "\u624B\u52A8\u5206\u914D\u6CBF\u7528\u5F53\u524D\u5EFA\u8BAE\u7684\u65B9\u6848\uFF1B\u66F4\u6539\u65B9\u6848\u540E\u8BF7\u91CD\u65B0\u751F\u6210\u5EFA\u8BAE\u3002" : item.description, "aria-pressed": runPreset === item.id, disabled: busy || mode === "direct" || manual, onClick: () => setPreset(item.id) }, item.label)))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-control-group mr-launch-workspace" }, /* @__PURE__ */ import_react3.default.createElement("label", { className: "mr-control-label", htmlFor: "mr-launch-workspace" }, "\u5DE5\u4F5C\u533A\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"), /* @__PURE__ */ import_react3.default.createElement("input", { className: "mr-input", id: "mr-launch-workspace", value: workspace, disabled: busy, placeholder: lastWorkspace ? `\u7559\u7A7A\u6CBF\u7528\u4E0A\u6B21\u8FD0\u884C\uFF1A${lastWorkspace}` : "\u4F8B\u5982 D:\\projects\\demo \u6216 /home/me/demo", onChange: (event) => setWorkspace(event.target.value) }))), /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, manual ? "\u624B\u52A8\u5206\u914D\u6309\u5305\u6267\u884C\u53EA\u8BFB\u4EFB\u52A1\uFF0C\u53EF\u6DF7\u5408\u6A21\u578B API \u4E0E\u5B98\u65B9\u5DE5\u5177\uFF1B\u5DE5\u5177\u4F7F\u7528\u9ED8\u8BA4\u6A21\u578B\u3002\u6240\u9009\u76EE\u6807\u3001\u5C31\u7EEA\u72B6\u6001\u548C\u8D39\u7528\u4EE5\u6267\u884C\u9884\u89C8\u4E3A\u51C6\u3002" : executor === "official-team" ? `${executionMode === "workspace-write" ? "\u7F16\u8F91\u6587\u4EF6\uFF1A\u5404\u5DE5\u4F5C\u5305\u5728\u72EC\u7ACB Git \u5DE5\u4F5C\u6811\u4E2D\u4F7F\u7528 CLI \u6B63\u5E38\u5DE5\u5177\uFF0C\u5B8C\u6210\u540E\u6574\u5408\u56DE\u6240\u9009\u5DE5\u4F5C\u533A\u3002" : "\u53EA\u8BFB\u68C0\u67E5\uFF1A\u5404\u5DE5\u4F5C\u5305\u4F7F\u7528\u53D7\u9650\u5DE5\u5177\u96C6\uFF0C\u4E0D\u4FEE\u6539\u5DE5\u4F5C\u533A\u6587\u4EF6\uFF1BClaude \u7981\u7528 shell\u3001\u6280\u80FD\u53CA\u65E2\u6709 MCP\uFF0C\u5E76\u5FFD\u7565\u7528\u6237\u8BBE\u7F6E\u3002"}\u5931\u8D25\u65F6\u4E0D\u4F1A\u81EA\u52A8\u66FF\u6362\u4E3A\u6A21\u578B\u76EE\u5F55 API\u3002\u5DF2\u767B\u5F55\u4E0D\u4EE3\u8868\u5F53\u524D\u4EFB\u52A1\u6743\u9650\u5DF2\u5C31\u7EEA\uFF0C\u9884\u89C8\u4F1A\u68C0\u67E5\u53EF\u7528\u5DE5\u5177\u3002` : mode === "direct" ? `\u6307\u5B9A\u6A21\u578B\uFF1A${directRoute ? `${directRoute.provider}/${directRoute.model}` : "\u672A\u9009\u62E9"}\uFF08\u8DF3\u8FC7\u8DEF\u7531\uFF0C\u65B9\u6848\u4E0D\u751F\u6548\uFF09` : mode === "team" ? "\u56E2\u961F\u5206\u5DE5\uFF1A\u6309\u4F9D\u8D56\u6267\u884C\u53EA\u8BFB\u5DE5\u4F5C\u5305\uFF0C\u6BCF\u6B65\u4F7F\u7528\u53EF\u7528\u7684\u5B98\u65B9 CLI \u6216\u6A21\u578B\u76EE\u5F55 API\u3002" : "\u5355\u4EFB\u52A1\uFF1A\u6309\u65B9\u6848\u9009\u62E9\u4E00\u6761\u8DEF\u7EBF\u3002"), mode !== "team" && !manual && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, "\u4F7F\u7528\u5B98\u65B9\u5DE5\u5177\u534F\u4F5C\u524D\uFF0C\u5148\u5728\u4E0A\u65B9\u9009\u62E9\u201C\u56E2\u961F\u5206\u5DE5\u201D\u3002"), runEnvironment === "cli-native" && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, "\u5382\u5546 CLI \u4F7F\u7528\u539F\u767B\u5F55\u3001\u914D\u7F6E\u3001\u6280\u80FD\u53CA MCP\uFF0C\u4EE5\u5F53\u524D\u7528\u6237\u6743\u9650\u8FD0\u884C\uFF0C\u4E0D\u7ECF\u8FC7 Harness \u8FDB\u7A0B\u6C99\u7BB1\u3002\u72EC\u7ACB Git \u5DE5\u4F5C\u6811\u7528\u4E8E\u6536\u96C6\u6539\u52A8\uFF0C\u4E0D\u662F\u64CD\u4F5C\u7CFB\u7EDF\u8BFB\u5199\u8FB9\u754C\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u5207\u6362\u5230\u6B64\u73AF\u5883\u3002"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react3.default.createElement("button", { className: "mr-button", type: "button", disabled: Boolean(blocked) || busy, onClick: () => {
4130
4393
  void preview();
4131
- } }, phase.kind === "previewing" ? "\u6B63\u5728\u751F\u6210\u9884\u89C8\u2026" : "\u9884\u89C8\u6267\u884C\u8BA1\u5212"), blocked && /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-caption" }, blocked)), phase.kind === "previewing" && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u6309\u5F53\u524D\u6A21\u578B\u76EE\u5F55\u3001\u767B\u5F55\u72B6\u6001\u548C\u9884\u7B97\u751F\u6210\u6267\u884C\u8BA1\u5212\u2026"), phase.kind === "error" && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-error", role: "alert" }, phase.message), (phase.kind === "preview" && phase.signature === inputSignature || phase.kind === "running") && /* @__PURE__ */ import_react3.default.createElement(
4394
+ } }, phase.kind === "previewing" ? "\u6B63\u5728\u751F\u6210\u9884\u89C8\u2026" : "\u9884\u89C8\u6267\u884C\u8BA1\u5212"), blocked && /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-caption" }, blocked)), phase.kind === "previewing" && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u6309\u5F53\u524D\u6A21\u578B\u76EE\u5F55\u3001\u767B\u5F55\u72B6\u6001\u548C\u9884\u7B97\u751F\u6210\u6267\u884C\u8BA1\u5212\u2026"), phase.kind === "starting" && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u7B49\u5F85\u5DF2\u786E\u8BA4\u4EFB\u52A1\u7684\u542F\u52A8\u7ED3\u679C\uFF0C\u8BF7\u7A0D\u5019\u2026"), phase.kind === "error" && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-error", role: "alert" }, phase.message), (phase.kind === "preview" && phase.signature === inputSignature || phase.kind === "running" && !phase.job) && /* @__PURE__ */ import_react3.default.createElement(
4132
4395
  LaunchPreview,
4133
4396
  {
4134
4397
  value: phase.value,
@@ -4139,7 +4402,23 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4139
4402
  onConfirm: () => {
4140
4403
  void confirm2();
4141
4404
  },
4142
- onCancel: () => setPhase({ kind: "idle" })
4405
+ onCancel: () => {
4406
+ if (!launching.current) setPhase({ kind: "idle" });
4407
+ }
4408
+ }
4409
+ ), phase.kind === "running" && phase.job && /* @__PURE__ */ import_react3.default.createElement(
4410
+ RunProgress,
4411
+ {
4412
+ job: phase.job,
4413
+ now,
4414
+ restoring: phase.restoring,
4415
+ cancelPending: phase.cancelPending,
4416
+ pollError: phase.pollError || (typeof runStatus !== "function" ? "\u5F53\u524D\u65E0\u6CD5\u66F4\u65B0\u6267\u884C\u8FDB\u5EA6\uFF0C\u4EFB\u52A1\u53EF\u80FD\u4ECD\u5728\u6267\u884C\u3002\u8BF7\u66F4\u65B0\u63D2\u4EF6\u540E\u67E5\u770B\u6267\u884C\u8BB0\u5F55\u3002" : ""),
4417
+ cancelError: phase.cancelError,
4418
+ canCancel: typeof cancelRun === "function",
4419
+ onCancel: () => {
4420
+ void cancel();
4421
+ }
4143
4422
  }
4144
4423
  ), phase.kind === "done" && /* @__PURE__ */ import_react3.default.createElement("div", { className: ["failed", "partial", "blocked", "incomplete"].includes(resultStatus) || resultStatus?.startsWith("paused") ? "mr-error" : "mr-empty", role: "status" }, STATUS_TEXT[resultStatus] ?? `\u72B6\u6001\uFF1A${resultStatus}`, phase.value.runId ? `\uFF1B\u7ED3\u679C\u5DF2\u8BB0\u5F55\uFF08\u8FD0\u884C ${phase.value.runId.slice(0, 8)}\uFF09\uFF0C\u89C1\u4E0B\u65B9\u201C\u6267\u884C\u8BB0\u5F55\u4E0E\u5B50\u4EFB\u52A1\u201D\u3002` : "\u3002", phase.value.budget?.message ? ` ${phase.value.budget.exceeded && resultStatus !== "paused-budget" ? "\u5DF2\u6309\u4F60\u7684\u786E\u8BA4\u8D85\u9884\u7B97\u6267\u884C\uFF1A" : ""}${phase.value.budget.message}` : "")));
4145
4424
  }
@@ -4156,7 +4435,25 @@ function LaunchPreview({ value, request, changed, running, confirmRef, onConfirm
4156
4435
  return /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-launch-preview", role: "region", "aria-label": "\u6267\u884C\u9884\u89C8" }, changed && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-error", role: "alert" }, "\u786E\u8BA4\u671F\u95F4\u60C5\u51B5\u6709\u53D8\u5316\uFF08\u4F8B\u5982\u9884\u7B97\u6216\u767B\u5F55\u72B6\u6001\uFF09\uFF0C\u8BF7\u91CD\u65B0\u6838\u5BF9\u4E0B\u9762\u7684\u786E\u8BA4\u9879\u3002"), blocked && /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-error", role: "alert" }, /* @__PURE__ */ import_react3.default.createElement("strong", null, "\u5F53\u524D\u65E0\u6CD5\u542F\u52A8\u6267\u884C\u3002"), blocking.length ? /* @__PURE__ */ import_react3.default.createElement("ul", null, blocking.map((message) => /* @__PURE__ */ import_react3.default.createElement("li", { key: message }, message))) : /* @__PURE__ */ import_react3.default.createElement("p", null, "\u6267\u884C\u6761\u4EF6\u5C1A\u672A\u6EE1\u8DB3\uFF0C\u8BF7\u68C0\u67E5\u6240\u9009\u6743\u9650\u3001\u5DE5\u4F5C\u533A\u548C\u5B98\u65B9\u5DE5\u5177\u72B6\u6001\u540E\u91CD\u65B0\u9884\u89C8\u3002")), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-result-grid" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric-label" }, officialTeam || manual ? "\u6267\u884C\u65B9\u5F0F" : "\u8DEF\u7EBF"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric-value" }, manual ? "\u624B\u52A8\u5206\u914D \xB7 \u6309\u5305\u53EA\u8BFB\u6267\u884C" : officialTeam ? "\u5B98\u65B9\u5DE5\u5177\u534F\u4F5C" : value.selected ? `${value.selected.provider}/${value.selected.model}` : "\u6682\u65E0\u8DEF\u7EBF", value.routingBypassed ? "\uFF08\u6307\u5B9A\u6A21\u578B\uFF09" : "")), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric-label" }, "\u9884\u4F30\u8D39\u7528"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric-value" }, value.estimatedCost === null || value.estimatedCost === void 0 ? "\u4EF7\u683C\u5F85\u914D\u7F6E" : money(value.estimatedCost))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric-label" }, "\u65B9\u6848"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric-value" }, ROUTING_PRESETS[value.decision?.preset]?.label ?? "\u2014", value.budget?.downgraded ? "\uFF08\u8D85\u9884\u7B97\u81EA\u52A8\u964D\u7EA7\uFF09" : "")), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric-label" }, "\u5DE5\u4F5C\u533A"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-metric-value mr-break" }, /* @__PURE__ */ import_react3.default.createElement("code", null, value.workspace)))), officialTeam && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, "\u4EFB\u52A1\u6743\u9650\uFF1A", readOnly ? "\u53EA\u8BFB\u68C0\u67E5\uFF0C\u4E0D\u4FEE\u6539\u5DE5\u4F5C\u533A\u6587\u4EF6\u3002\u4F7F\u7528\u53D7\u9650\u5DE5\u5177\u96C6\uFF1BClaude \u7981\u7528 shell\u3001\u6280\u80FD\u53CA\u65E2\u6709 MCP\uFF0C\u5E76\u5FFD\u7565\u7528\u6237\u8BBE\u7F6E\u3002" : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, "\u7F16\u8F91\u6587\u4EF6\u3002\u7F16\u8F91\u8303\u56F4\uFF1A\u6240\u9009\u5DE5\u4F5C\u533A ", /* @__PURE__ */ import_react3.default.createElement("code", null, value.workspace), " \u5185\u7684\u4EFB\u52A1\u6587\u4EF6\u3002\u5B98\u65B9 CLI \u4F7F\u7528\u81EA\u5DF1\u7684\u6B63\u5E38\u5DE5\u5177\uFF0C\u5728\u72EC\u7ACB Git \u5DE5\u4F5C\u6811\u4E2D\u5B8C\u6210\u540E\u6574\u5408\u56DE\u8BE5\u5DE5\u4F5C\u533A\u3002"), "\u5931\u8D25\u65F6\u4E0D\u4F1A\u81EA\u52A8\u66FF\u6362\u4E3A\u6A21\u578B\u76EE\u5F55 API\u3002"), native && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, "\u6267\u884C\u73AF\u5883\uFF1A\u5382\u5546 CLI \u6B63\u5E38\u73AF\u5883\uFF0C\u4EE5\u5F53\u524D\u7528\u6237\u6743\u9650\u8FD0\u884C\uFF0C\u4E0D\u7ECF Harness \u8FDB\u7A0B\u6C99\u7BB1\u3002\u5DE5\u4F5C\u6811\u4E0D\u9650\u5236\u5DE5\u5177\u8BBF\u95EE\u5DE5\u4F5C\u6811\u5916\u8D44\u6E90\u3002"), !officialTeam && !readOnly && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, "\u7F16\u8F91\u8303\u56F4\uFF1A\u6240\u9009\u5DE5\u4F5C\u533A ", /* @__PURE__ */ import_react3.default.createElement("code", null, value.workspace), " \u5185\u7684\u4EFB\u52A1\u6587\u4EF6\uFF1B\u5728\u72EC\u7ACB Git \u5DE5\u4F5C\u6811\u4E2D\u5B8C\u6210\u540E\u6574\u5408\u56DE\u8BE5\u5DE5\u4F5C\u533A\u3002\u4E0D\u4F1A\u81EA\u52A8\u56DE\u9000\u6A21\u578B\u76EE\u5F55 API\u3002"), value.budget?.message && /* @__PURE__ */ import_react3.default.createElement("p", { className: value.budget.exceeded ? "mr-error" : "mr-caption" }, "\u9884\u7B97\u68C0\u67E5\uFF1A", value.budget.message), packages.length > 0 && /* @__PURE__ */ import_react3.default.createElement("ol", { className: "mr-launch-packages" }, packages.map((item) => {
4157
4436
  const tool = item.manualAssignment?.kind === "tool";
4158
4437
  return /* @__PURE__ */ import_react3.default.createElement("li", { key: item.id }, /* @__PURE__ */ import_react3.default.createElement("strong", null, item.name), (manual || item.selectionSource === "manual") && /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-pill" }, "\u624B\u52A8"), " \u2192 ", tool ? `${item.toolLabel || item.toolId || item.manualAssignment.toolId} \xB7 \u9ED8\u8BA4\u6A21\u578B` : item.route || item.toolLabel || item.toolId || item.tool || "\u5F85\u5206\u914D\u8DEF\u7EBF", " ", /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-caption" }, "\uFF08\u9884\u4F30 ", tool ? "\u8D39\u7528\u672A\u77E5" : item.estimatedCost === null || item.estimatedCost === void 0 ? "\u4EF7\u683C\u5F85\u914D\u7F6E" : money(item.estimatedCost), item.channel === "official-cli" ? " \xB7 \u5B98\u65B9 CLI" : item.channel ? " \xB7 \u6A21\u578B\u76EE\u5F55 API" : "", "\uFF09"), item.channelDetail && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, item.channelDetail));
4159
- })), reasons.length > 0 ? /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-confirm-box" }, /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-section-title" }, "\u6267\u884C\u524D\u9700\u8981\u786E\u8BA4\u4EE5\u4E0B ", reasons.length, " \u9879\uFF1A"), /* @__PURE__ */ import_react3.default.createElement("ol", null, reasons.map((item) => /* @__PURE__ */ import_react3.default.createElement("li", { key: item.code }, reasonText(item))))) : /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, "\u5F53\u524D\u6CA1\u6709\u989D\u5916\u786E\u8BA4\u9879\u3002", value.estimatedCost === null || value.estimatedCost === void 0 ? "\u8D39\u7528\u672A\u77E5\uFF0C\u65E0\u6CD5\u786E\u8BA4\u672C\u6B21\u8D39\u7528\u662F\u5426\u5728\u9884\u7B97\u5185\u3002" : value.budget?.limited && value.budget?.estimateKnown && !value.budget?.exceeded ? "\u6309\u5DF2\u63D0\u4F9B\u5355\u4EF7\u4F30\u7B97\uFF0C\u672C\u6B21\u672A\u8D85\u51FA\u9884\u7B97\uFF1B\u5B9E\u9645\u8D39\u7528\u4EE5\u6267\u884C\u8BB0\u5F55\u548C\u8D26\u5355\u4E3A\u51C6\u3002" : "\u5B9E\u9645\u8D39\u7528\u4EE5\u6267\u884C\u8BB0\u5F55\u548C\u8D26\u5355\u4E3A\u51C6\u3002"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react3.default.createElement("button", { ref: confirmRef, className: "mr-button", type: "button", disabled: running || blocked, onClick: onConfirm }, running ? "\u6B63\u5728\u6267\u884C\u2026" : reasons.length ? "\u5168\u90E8\u786E\u8BA4\u5E76\u6267\u884C" : "\u786E\u8BA4\u6267\u884C"), /* @__PURE__ */ import_react3.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: running, onClick: onCancel }, "\u53D6\u6D88"), running && /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-caption", role: "status", "aria-live": "polite" }, "\u6A21\u578B\u8C03\u7528\u53EF\u80FD\u9700\u8981\u51E0\u5206\u949F\uFF0C\u8BF7\u52FF\u5173\u95ED\u5DE5\u4F5C\u53F0\u3002")));
4438
+ })), reasons.length > 0 ? /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-confirm-box" }, /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-section-title" }, "\u6267\u884C\u524D\u9700\u8981\u786E\u8BA4\u4EE5\u4E0B ", reasons.length, " \u9879\uFF1A"), /* @__PURE__ */ import_react3.default.createElement("ol", null, reasons.map((item) => /* @__PURE__ */ import_react3.default.createElement("li", { key: item.code }, reasonText(item))))) : /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, "\u5F53\u524D\u6CA1\u6709\u989D\u5916\u786E\u8BA4\u9879\u3002", value.estimatedCost === null || value.estimatedCost === void 0 ? "\u8D39\u7528\u672A\u77E5\uFF0C\u65E0\u6CD5\u786E\u8BA4\u672C\u6B21\u8D39\u7528\u662F\u5426\u5728\u9884\u7B97\u5185\u3002" : value.budget?.limited && value.budget?.estimateKnown && !value.budget?.exceeded ? "\u6309\u5DF2\u63D0\u4F9B\u5355\u4EF7\u4F30\u7B97\uFF0C\u672C\u6B21\u672A\u8D85\u51FA\u9884\u7B97\uFF1B\u5B9E\u9645\u8D39\u7528\u4EE5\u6267\u884C\u8BB0\u5F55\u548C\u8D26\u5355\u4E3A\u51C6\u3002" : "\u5B9E\u9645\u8D39\u7528\u4EE5\u6267\u884C\u8BB0\u5F55\u548C\u8D26\u5355\u4E3A\u51C6\u3002"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react3.default.createElement("button", { ref: confirmRef, className: "mr-button", type: "button", disabled: running || blocked, onClick: onConfirm }, running ? "\u6B63\u5728\u6267\u884C\u2026" : reasons.length ? "\u5168\u90E8\u786E\u8BA4\u5E76\u6267\u884C" : "\u786E\u8BA4\u6267\u884C"), /* @__PURE__ */ import_react3.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: running, onClick: onCancel }, "\u53D6\u6D88"), running && /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-caption", role: "status", "aria-live": "polite" }, "\u6B63\u5728\u542F\u52A8\u6267\u884C\uFF0C\u8BF7\u7A0D\u5019\u2026")));
4439
+ }
4440
+ function RunProgress({ job, now, restoring, pollError, cancelError, cancelPending, canCancel, onCancel }) {
4441
+ const progress = job.progress ?? {};
4442
+ const packages = Array.isArray(progress.packages) ? progress.packages : [];
4443
+ const startedAt = typeof job.startedAt === "number" ? job.startedAt : Date.parse(job.startedAt);
4444
+ const seconds = Number.isFinite(startedAt) ? Math.max(0, Math.floor((now - startedAt) / 1e3)) : 0;
4445
+ const elapsed = Number.isFinite(startedAt) ? `${Math.floor(seconds / 60)} \u5206 ${String(seconds % 60).padStart(2, "0")} \u79D2` : "\u5F85\u66F4\u65B0";
4446
+ const cancelling = job.cancelRequested || job.status === "cancelling";
4447
+ return /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-launch-preview mr-run-progress", role: "region", "aria-label": "\u6267\u884C\u8FDB\u5EA6" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-run-progress-head" }, /* @__PURE__ */ import_react3.default.createElement("strong", { role: "status" }, cancelling ? "\u6B63\u5728\u53D6\u6D88\u2026" : restoring ? "\u6B63\u5728\u6062\u590D\u6267\u884C\u8FDB\u5EA6\u2026" : "\u6B63\u5728\u6267\u884C\u2026"), /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-caption" }, "\u5DF2\u8017\u65F6 ", elapsed)), job.progress && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption" }, "\u5DF2\u5904\u7406 ", progress.completed ?? 0, " / ", progress.total ?? packages.length, " \u4E2A\u5DE5\u4F5C\u5305"), /* @__PURE__ */ import_react3.default.createElement("progress", { className: "mr-run-meter", "aria-label": "\u5DE5\u4F5C\u5305\u5B8C\u6210\u8FDB\u5EA6", value: progress.completed ?? 0, max: Math.max(1, progress.total ?? packages.length) })), /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-caption", "aria-live": "polite" }, progress.current ? `\u5F53\u524D\u5DE5\u4F5C\u5305\uFF1A${progress.current.name || progress.current.id}` : cancelling ? "\u6B63\u5728\u7ED3\u675F\u5F53\u524D\u6267\u884C\u5E76\u4FDD\u7559\u5DF2\u5B8C\u6210\u7ED3\u679C\u3002" : STAGE_TEXT[progress.stage] ?? "\u6B63\u5728\u8BFB\u53D6\u6267\u884C\u8FDB\u5EA6\u2026"), packages.length > 0 && /* @__PURE__ */ import_react3.default.createElement("ol", { className: "mr-run-packages" }, packages.map((item) => /* @__PURE__ */ import_react3.default.createElement("li", { key: item.id }, /* @__PURE__ */ import_react3.default.createElement("span", null, item.name || item.id), /* @__PURE__ */ import_react3.default.createElement("span", { className: `mr-pill mr-run-package-${item.status}` }, PACKAGE_STATUS_TEXT[item.status] ?? "\u7B49\u5F85\u66F4\u65B0")))), pollError && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-error", role: "status" }, pollError), cancelError && /* @__PURE__ */ import_react3.default.createElement("p", { className: "mr-error", role: "alert" }, cancelError), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react3.default.createElement(
4448
+ "button",
4449
+ {
4450
+ className: "mr-button mr-button-secondary",
4451
+ type: "button",
4452
+ disabled: !canCancel || cancelPending || cancelling,
4453
+ onClick: onCancel
4454
+ },
4455
+ cancelPending ? "\u6B63\u5728\u8BF7\u6C42\u53D6\u6D88\u2026" : cancelling ? "\u6B63\u5728\u53D6\u6D88\u2026" : "\u53D6\u6D88\u6267\u884C"
4456
+ ), cancelling && /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-caption" }, "\u53D6\u6D88\u5DF2\u8BF7\u6C42\uFF0C\u7B49\u5F85\u6267\u884C\u7ED3\u675F\uFF1B\u5DF2\u5B8C\u6210\u7ED3\u679C\u4F1A\u4FDD\u7559\u5728\u6267\u884C\u8BB0\u5F55\u4E2D\u3002"), !canCancel && /* @__PURE__ */ import_react3.default.createElement("span", { className: "mr-caption" }, "\u5F53\u524D\u5DE5\u4F5C\u53F0\u6682\u4E0D\u652F\u6301\u53D6\u6D88\uFF0C\u8BF7\u66F4\u65B0\u63D2\u4EF6\u3002")));
4160
4457
  }
4161
4458
 
4162
4459
  // .dsh-plugin/client/cli-terminal.jsx
@@ -13589,7 +13886,7 @@ var xterm_default = `/**
13589
13886
  `;
13590
13887
 
13591
13888
  // .dsh-plugin/client/host-version.mjs
13592
- var ROUTER_CLIENT_VERSION = true ? "0.22.2" : "";
13889
+ var ROUTER_CLIENT_VERSION = true ? "0.23.1" : "";
13593
13890
  var STALE_HOST_MESSAGE = "\u63D2\u4EF6\u540E\u53F0\u7248\u672C\u8F83\u65E7\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F Harness\uFF08\u5305\u62EC\u6258\u76D8\u56FE\u6807\uFF09\u540E\u518D\u4F7F\u7528\u3002";
13594
13891
  function isMissingRemoteMethod(message) {
13595
13892
  const value = String(message ?? "");
@@ -14315,12 +14612,21 @@ var router_main_default = `.mr-workspace {
14315
14612
  .mr-launch-workspace { flex: 1 1 320px; min-width: min(100%, 260px); }
14316
14613
  .mr-launch-preview { margin-top: 16px; padding: 14px; border: 1px solid var(--mr-line); border-radius: 12px; background: #f9fbfe; }
14317
14614
  .mr-launch-packages { margin: 12px 0 0; padding-left: 20px; font-size: 12px; line-height: 1.7; }
14615
+ .mr-run-progress-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
14616
+ .mr-run-meter { display: block; width: 100%; height: 8px; accent-color: var(--mr-accent); }
14617
+ .mr-run-packages { display: grid; gap: 7px; margin: 12px 0; padding-left: 20px; font-size: 12px; line-height: 1.6; }
14618
+ .mr-run-packages li > span:first-child { margin-right: 8px; }
14619
+ .mr-run-package-running { color: var(--mr-accent); }
14620
+ .mr-run-package-failed, .mr-run-package-blocked { color: #b42c39; }
14621
+ .mr-run-package-succeeded { color: #187042; }
14318
14622
  .mr-confirm-box { margin-top: 12px; padding: 10px 14px; border: 1px solid rgba(217, 140, 30, .35); border-radius: 10px; background: #fff8ec; color: #6d4a12; font-size: 12px; line-height: 1.6; }
14319
14623
  .mr-confirm-box .mr-section-title { margin: 0 0 6px; }
14320
14624
  .mr-confirm-box ol { margin: 0; padding-left: 20px; }
14321
14625
  .mr-break { overflow-wrap: anywhere; }
14322
14626
  @media (prefers-color-scheme: dark) {
14323
14627
  .mr-launch-preview { background: rgba(255, 255, 255, .04); }
14628
+ .mr-run-package-failed, .mr-run-package-blocked { color: #ffd4da; }
14629
+ .mr-run-package-succeeded { color: #b4f1cc; }
14324
14630
  .mr-confirm-box { background: rgba(217, 140, 30, .12); color: #f2d39b; }
14325
14631
  }
14326
14632
 
@@ -15114,7 +15420,7 @@ ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
15114
15420
  }, []);
15115
15421
  return { health, ledger, boundaries, busy, refreshHealth, refreshLedger, refreshBoundaries, finishOnboarding, rate, rerun };
15116
15422
  }
15117
- function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
15423
+ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, runStatus, cancelRun, terminalApi }) {
15118
15424
  const workbench = useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries });
15119
15425
  const [catalogState, setCatalogState] = import_react5.default.useState({ status: "loading", catalog: null, error: "" });
15120
15426
  const [task, setTask] = import_react5.default.useState("");
@@ -15336,6 +15642,8 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOff
15336
15642
  ledger: workbench.ledger.value,
15337
15643
  previewRun,
15338
15644
  startRun,
15645
+ runStatus,
15646
+ cancelRun,
15339
15647
  planningRevision: `${routingSettings}/${dataRevision}/${feedbackRevision}/${evidenceRevision}`,
15340
15648
  assignmentMode: effectiveAssignmentMode,
15341
15649
  packageAssignments: effectiveAssignmentMode === "manual" ? packageAssignments : {},
@@ -15494,6 +15802,12 @@ var idText = (value, subject) => {
15494
15802
  if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,80}$/.test(value)) throw new TypeError(`${subject} must be a short id`);
15495
15803
  return value;
15496
15804
  };
15805
+ function parseRunJobRequest(value) {
15806
+ const request = plainObject2(value, "run job request");
15807
+ if (Object.keys(request).some((key) => key !== "jobId")) throw new TypeError("run job request only accepts jobId");
15808
+ return { jobId: idText(request.jobId, "jobId") };
15809
+ }
15810
+ var runJobRequestCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#RunJobRequest`, parseRunJobRequest);
15497
15811
  var optionalRouteText = (value, subject) => {
15498
15812
  if (value === void 0 || value === null || value === "") return void 0;
15499
15813
  if (typeof value !== "string" || value.length > 240 || value.includes("\0")) throw new TypeError(`${subject} is invalid`);
@@ -15583,7 +15897,7 @@ var terminalWriteCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#TerminalW
15583
15897
  var terminalResizeCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#TerminalResize`, parseTerminalResize);
15584
15898
  var terminalStopCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#TerminalStop`, parseTerminalStop);
15585
15899
  var runRequestCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#RunRequest`, parseRunRequest);
15586
- function descriptor(method, parameters, result) {
15900
+ function descriptor(method, parameters, result, cancellable = false) {
15587
15901
  return Object.freeze({
15588
15902
  id: `${OFFICIAL_TOOLS_REMOTE_PACKAGE}#${OFFICIAL_TOOLS_REMOTE_NAMESPACE}/${method}`,
15589
15903
  service: OFFICIAL_TOOLS_REMOTE_NAMESPACE,
@@ -15591,7 +15905,8 @@ function descriptor(method, parameters, result) {
15591
15905
  method,
15592
15906
  invocation: { kind: "direct" },
15593
15907
  parameters,
15594
- result
15908
+ result,
15909
+ ...cancellable ? { cancellation: { parameter: "signal" } } : {}
15595
15910
  });
15596
15911
  }
15597
15912
  var toolIdParameter = Object.freeze({
@@ -15617,8 +15932,10 @@ var OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
15617
15932
  descriptor("rerunStep", [jsonParameter("request", rerunRequestCodec)], anyObjectCodec("RerunResult")),
15618
15933
  descriptor("boundaries", [], anyObjectCodec("SecurityBoundaries")),
15619
15934
  // Workbench "start a run": plan preview with cost estimate and confirmation reasons, then execute.
15620
- descriptor("previewRun", [jsonParameter("request", runRequestCodec)], anyObjectCodec("RunPreview")),
15935
+ descriptor("previewRun", [jsonParameter("request", runRequestCodec)], anyObjectCodec("RunPreview"), true),
15621
15936
  descriptor("startRun", [jsonParameter("request", runRequestCodec)], anyObjectCodec("RunStarted")),
15937
+ descriptor("runStatus", [jsonParameter("request", runJobRequestCodec)], anyObjectCodec("RunJobStatus"), true),
15938
+ descriptor("cancelRun", [jsonParameter("request", runJobRequestCodec)], anyObjectCodec("RunJobStatus"), true),
15622
15939
  // Workbench "官方工具终端": interactive shell / fixed official CLI sessions.
15623
15940
  // Output streams through long-poll reads; input and resize are unary calls.
15624
15941
  descriptor("terminalInfo", [], anyObjectCodec("TerminalInfo")),
@@ -16070,8 +16387,10 @@ function registerUi(ctx) {
16070
16387
  rateResult: (request) => officialToolsRemote.rateResult(request),
16071
16388
  rerunStep: (request) => officialToolsRemote.rerunStep(request),
16072
16389
  loadBoundaries: () => officialToolsRemote.boundaries(),
16073
- previewRun: (request) => officialToolsRemote.previewRun(request),
16390
+ previewRun: (request, signal) => officialToolsRemote.previewRun(request, signal),
16074
16391
  startRun: (request) => officialToolsRemote.startRun(request),
16392
+ runStatus: (request, signal) => officialToolsRemote.runStatus(request, signal),
16393
+ cancelRun: (request, signal) => officialToolsRemote.cancelRun(request, signal),
16075
16394
  terminalApi: Object.freeze({
16076
16395
  terminalInfo: () => officialToolsRemote.terminalInfo(),
16077
16396
  terminalStart: (request) => officialToolsRemote.terminalStart(request),