@ljwei-stak/dsh-model-router 0.22.1 → 0.23.0

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,51 @@ 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
+ function savedJob() {
4034
+ try {
4035
+ return typeof sessionStorage === "undefined" ? "" : text2(sessionStorage.getItem(ACTIVE_JOB_KEY));
4036
+ } catch {
4037
+ return "";
4038
+ }
4039
+ }
4040
+ function rememberJob(jobId) {
4041
+ try {
4042
+ if (typeof sessionStorage !== "undefined") sessionStorage.setItem(ACTIVE_JOB_KEY, jobId);
4043
+ } catch {
4044
+ }
4045
+ }
4046
+ function forgetJob(jobId) {
4047
+ try {
4048
+ if (typeof sessionStorage !== "undefined" && savedJob() === jobId) sessionStorage.removeItem(ACTIVE_JOB_KEY);
4049
+ } catch {
4050
+ }
4051
+ }
4052
+ var unknownJob = (error) => /unknown run job id\.?/i.test(text2(error?.message));
4053
+ var pendingLaunch = null;
4054
+ var launchListeners = /* @__PURE__ */ new Set();
4055
+ function notifyLaunch(event) {
4056
+ for (const listener of launchListeners) listener(event);
4057
+ }
4058
+ function settleLaunch(identity, value, error) {
4059
+ if (pendingLaunch !== identity) return;
4060
+ pendingLaunch = null;
4061
+ if (isJobSnapshot(value) && LIVE_STATUSES.has(value.status)) rememberJob(value.jobId);
4062
+ notifyLaunch({ kind: "settled", identity, value, error });
4063
+ }
4064
+ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger, previewRun, startRun, runStatus, cancelRun, onStarted, disabledReason, planningRevision, assignmentMode = "algorithm", packageAssignments = {}, packagePlanKey: packagePlanKey2 }) {
4021
4065
  const [preset, setPreset] = import_react3.default.useState(defaultPreset || "balanced");
4022
4066
  const [executor, setExecutor] = import_react3.default.useState("routed");
4023
4067
  const [executionMode, setExecutionMode] = import_react3.default.useState("read-only");
@@ -4030,17 +4074,75 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4030
4074
  const lastWorkspace = [...ledger?.runs ?? []].map((run) => run.workspace).find(Boolean) ?? "";
4031
4075
  const [workspace, setWorkspace] = import_react3.default.useState("");
4032
4076
  const [phase, setPhase] = import_react3.default.useState({ kind: "idle" });
4077
+ const currentPhase = import_react3.default.useRef(phase);
4078
+ currentPhase.current = phase;
4079
+ const [now, setNow] = import_react3.default.useState(Date.now);
4033
4080
  const mounted = import_react3.default.useRef(true);
4034
4081
  const confirmRef = import_react3.default.useRef(null);
4035
4082
  const pendingRequest = import_react3.default.useRef(0);
4083
+ const activeRun = import_react3.default.useRef(null);
4084
+ const launchSequence = import_react3.default.useRef(0);
4085
+ const launching = import_react3.default.useRef(false);
4086
+ const awaitingLaunch = import_react3.default.useRef(null);
4087
+ const onFinished = import_react3.default.useRef(onStarted);
4088
+ onFinished.current = onStarted;
4036
4089
  const inputSignature = JSON.stringify([task, mode, directRoute?.provider, directRoute?.model, budgetUsd, runPreset, workspace, lastWorkspace, planningRevision, disabledReason, runExecutor, runMode, runEnvironment, assignmentMode, packagePlanKey2, packageAssignments]);
4037
4090
  const latestSignature = import_react3.default.useRef(inputSignature);
4038
4091
  latestSignature.current = inputSignature;
4039
4092
  import_react3.default.useEffect(() => {
4040
4093
  mounted.current = true;
4094
+ const resume = (jobId, snapshot) => {
4095
+ activeRun.current = {
4096
+ token: ++launchSequence.current,
4097
+ jobId,
4098
+ terminal: false,
4099
+ cancelRequested: false,
4100
+ cancelPending: false,
4101
+ requestSequence: 0,
4102
+ appliedSequence: 0,
4103
+ restoring: true
4104
+ };
4105
+ launching.current = true;
4106
+ setPhase({ kind: "running", job: snapshot ?? { jobId, status: "running" }, restoring: !snapshot });
4107
+ };
4108
+ const receiveLaunch = (event) => {
4109
+ if (!mounted.current) return;
4110
+ if (event.kind === "pending") {
4111
+ if (activeRun.current?.launchIdentity === event.identity || activeRun.current?.jobId && !activeRun.current.terminal) return;
4112
+ awaitingLaunch.current = event.identity;
4113
+ pendingRequest.current += 1;
4114
+ launching.current = true;
4115
+ setPhase({ kind: "starting" });
4116
+ return;
4117
+ }
4118
+ if (awaitingLaunch.current !== event.identity) return;
4119
+ awaitingLaunch.current = null;
4120
+ const value = event.value;
4121
+ if (isJobSnapshot(value) && LIVE_STATUSES.has(value.status)) {
4122
+ resume(value.jobId, value);
4123
+ return;
4124
+ }
4125
+ launching.current = false;
4126
+ if (event.error || value?.status === "needs-confirmation") {
4127
+ 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" });
4128
+ return;
4129
+ }
4130
+ const result = isJobSnapshot(value) ? value.result ?? { status: value.status } : value;
4131
+ if (isJobSnapshot(value)) forgetJob(value.jobId);
4132
+ setPhase({ kind: "done", value: result });
4133
+ onFinished.current?.(result.runId);
4134
+ };
4135
+ launchListeners.add(receiveLaunch);
4136
+ if (pendingLaunch) receiveLaunch({ kind: "pending", identity: pendingLaunch });
4137
+ else {
4138
+ const jobId = savedJob();
4139
+ if (jobId && !activeRun.current) resume(jobId);
4140
+ }
4041
4141
  return () => {
4042
4142
  mounted.current = false;
4143
+ launchListeners.delete(receiveLaunch);
4043
4144
  pendingRequest.current += 1;
4145
+ activeRun.current = null;
4044
4146
  };
4045
4147
  }, []);
4046
4148
  import_react3.default.useEffect(() => {
@@ -4060,6 +4162,93 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4060
4162
  import_react3.default.useEffect(() => {
4061
4163
  if (phase.kind === "preview") confirmRef.current?.focus();
4062
4164
  }, [phase.kind]);
4165
+ const acceptSnapshot = import_react3.default.useCallback((value, token, sequence, cancelAcknowledged = false) => {
4166
+ const active = activeRun.current;
4167
+ if (!mounted.current || !active || active.token !== token || active.terminal || value.jobId !== active.jobId) return false;
4168
+ if (!isJobSnapshot(value)) throw new Error("\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u6267\u884C\u8FDB\u5EA6\u3002");
4169
+ const terminal = !LIVE_STATUSES.has(value.status);
4170
+ if (!terminal && sequence < active.appliedSequence) {
4171
+ if (cancelAcknowledged) {
4172
+ active.cancelRequested = true;
4173
+ setPhase((previous) => previous.kind === "running" ? {
4174
+ ...previous,
4175
+ job: { ...previous.job, status: "cancelling", cancelRequested: true },
4176
+ cancelPending: false,
4177
+ cancelError: ""
4178
+ } : previous);
4179
+ return true;
4180
+ }
4181
+ return false;
4182
+ }
4183
+ active.appliedSequence = Math.max(active.appliedSequence, sequence);
4184
+ active.cancelRequested || (active.cancelRequested = cancelAcknowledged || value.cancelRequested || value.status === "cancelling");
4185
+ const job = {
4186
+ ...value,
4187
+ cancelRequested: Boolean(active.cancelRequested),
4188
+ status: !terminal && active.cancelRequested ? "cancelling" : value.status
4189
+ };
4190
+ if (terminal) {
4191
+ active.terminal = true;
4192
+ launching.current = false;
4193
+ forgetJob(value.jobId);
4194
+ const result = value.result ?? { status: value.status };
4195
+ setPhase({ kind: "done", value: result, job });
4196
+ onFinished.current?.(result.runId);
4197
+ } else {
4198
+ rememberJob(value.jobId);
4199
+ setPhase((previous) => previous.kind === "running" ? {
4200
+ ...previous,
4201
+ job,
4202
+ restoring: false,
4203
+ pollError: "",
4204
+ cancelError: cancelAcknowledged ? "" : previous.cancelError,
4205
+ cancelPending: cancelAcknowledged ? false : previous.cancelPending
4206
+ } : previous);
4207
+ }
4208
+ return true;
4209
+ }, []);
4210
+ const runningJobId = phase.kind === "running" ? phase.job?.jobId : null;
4211
+ import_react3.default.useEffect(() => {
4212
+ if (!runningJobId || typeof runStatus !== "function") return;
4213
+ const active = activeRun.current;
4214
+ if (!active || active.jobId !== runningJobId) return;
4215
+ let disposed = false, timer;
4216
+ const poll = async () => {
4217
+ if (disposed || activeRun.current !== active || active.terminal) return;
4218
+ const sequence = ++active.requestSequence;
4219
+ try {
4220
+ const value = unwrapRemote(await runStatus({ jobId: runningJobId }), "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u6267\u884C\u8FDB\u5EA6\u3002");
4221
+ if (disposed) return;
4222
+ if (!isJobSnapshot(value) || value.jobId !== runningJobId) throw new Error("\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u6267\u884C\u8FDB\u5EA6\u3002");
4223
+ acceptSnapshot(value, active.token, sequence);
4224
+ } catch (error) {
4225
+ if (!disposed && mounted.current && activeRun.current === active && !active.terminal) {
4226
+ if (unknownJob(error)) {
4227
+ active.terminal = true;
4228
+ launching.current = false;
4229
+ forgetJob(runningJobId);
4230
+ 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" });
4231
+ } else {
4232
+ 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);
4233
+ }
4234
+ }
4235
+ } finally {
4236
+ if (!disposed && activeRun.current === active && !active.terminal) timer = setTimeout(poll, 1e3);
4237
+ }
4238
+ };
4239
+ timer = setTimeout(poll, active.restoring ? 0 : 1e3);
4240
+ active.restoring = false;
4241
+ return () => {
4242
+ disposed = true;
4243
+ clearTimeout(timer);
4244
+ };
4245
+ }, [runningJobId, runStatus, acceptSnapshot]);
4246
+ import_react3.default.useEffect(() => {
4247
+ if (!runningJobId) return;
4248
+ setNow(Date.now());
4249
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
4250
+ return () => clearInterval(timer);
4251
+ }, [runningJobId]);
4063
4252
  const request = () => launchRequest({
4064
4253
  task,
4065
4254
  mode,
@@ -4076,11 +4265,11 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4076
4265
  });
4077
4266
  const unavailable = typeof previewRun !== "function" || typeof startRun !== "function";
4078
4267
  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";
4268
+ const busy = phase.kind === "previewing" || phase.kind === "running" || phase.kind === "starting";
4080
4269
  const recordedRun = phase.kind === "done" && phase.value.runId ? (ledger?.runs ?? []).find((run) => run.id === phase.value.runId) : null;
4081
4270
  const resultStatus = recordedRun?.status ?? phase.value?.status;
4082
4271
  const preview = async () => {
4083
- if (blocked || busy) return;
4272
+ if (blocked || busy || launching.current || pendingLaunch) return;
4084
4273
  const current = ++pendingRequest.current;
4085
4274
  let snapshot;
4086
4275
  try {
@@ -4099,14 +4288,38 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4099
4288
  }
4100
4289
  };
4101
4290
  const confirm2 = async () => {
4102
- if (blocked || phase.kind !== "preview" || phase.signature !== latestSignature.current || phase.value.status === "blocked") return;
4291
+ if (launching.current || pendingLaunch || currentPhase.current !== phase || blocked || phase.kind !== "preview" || phase.signature !== latestSignature.current || phase.value.status === "blocked") return;
4103
4292
  const shown = phase.value;
4104
4293
  const snapshot = phase.request;
4105
4294
  const signature2 = phase.signature;
4295
+ launching.current = true;
4296
+ const identity = /* @__PURE__ */ Symbol("launch");
4297
+ const active = {
4298
+ token: ++launchSequence.current,
4299
+ launchIdentity: identity,
4300
+ jobId: null,
4301
+ terminal: false,
4302
+ cancelRequested: false,
4303
+ cancelPending: false,
4304
+ requestSequence: 0,
4305
+ appliedSequence: 0
4306
+ };
4307
+ activeRun.current = active;
4308
+ pendingLaunch = identity;
4309
+ notifyLaunch({ kind: "pending", identity });
4106
4310
  setPhase({ kind: "running", value: shown, request: snapshot, signature: signature2 });
4107
4311
  try {
4108
4312
  const value = unwrapRemote(await startRun({ ...snapshot, confirmedReasons: (shown.reasons ?? []).map((item) => item.code) }), "\u6267\u884C\u5931\u8D25\u3002");
4313
+ settleLaunch(identity, value);
4109
4314
  if (!mounted.current) return;
4315
+ if (activeRun.current !== active) return;
4316
+ if (isJobSnapshot(value)) {
4317
+ active.jobId = value.jobId;
4318
+ acceptSnapshot(value, active.token, ++active.requestSequence);
4319
+ return;
4320
+ }
4321
+ active.terminal = true;
4322
+ launching.current = false;
4110
4323
  if (value.status === "needs-confirmation") {
4111
4324
  setPhase(signature2 === latestSignature.current ? { kind: "preview", value, request: snapshot, signature: signature2, changed: true } : { kind: "idle" });
4112
4325
  return;
@@ -4114,7 +4327,34 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4114
4327
  setPhase({ kind: "done", value });
4115
4328
  onStarted?.(value.runId);
4116
4329
  } catch (error) {
4117
- if (mounted.current) setPhase({ kind: "error", message: text2(error?.message) || "\u6267\u884C\u5931\u8D25\u3002" });
4330
+ settleLaunch(identity, null, text2(error?.message) || "\u6267\u884C\u5931\u8D25\u3002");
4331
+ if (mounted.current && activeRun.current === active) {
4332
+ active.terminal = true;
4333
+ launching.current = false;
4334
+ setPhase({ kind: "error", message: text2(error?.message) || "\u6267\u884C\u5931\u8D25\u3002" });
4335
+ }
4336
+ }
4337
+ };
4338
+ const cancel = async () => {
4339
+ const active = activeRun.current;
4340
+ if (!active?.jobId || active.terminal || active.cancelPending || active.cancelRequested || typeof cancelRun !== "function") return;
4341
+ active.cancelPending = true;
4342
+ const sequence = ++active.requestSequence;
4343
+ setPhase((previous) => previous.kind === "running" ? { ...previous, cancelPending: true, cancelError: "" } : previous);
4344
+ try {
4345
+ const value = unwrapRemote(await cancelRun({ jobId: active.jobId }), "\u53D6\u6D88\u8BF7\u6C42\u672A\u786E\u8BA4\u3002");
4346
+ if (!isJobSnapshot(value) || value.jobId !== active.jobId) throw new Error("\u53D6\u6D88\u8BF7\u6C42\u672A\u786E\u8BA4\u3002");
4347
+ acceptSnapshot(value, active.token, sequence, true);
4348
+ } catch {
4349
+ if (mounted.current && activeRun.current === active && !active.terminal) {
4350
+ setPhase((previous) => previous.kind === "running" ? {
4351
+ ...previous,
4352
+ cancelPending: false,
4353
+ cancelError: "\u53D6\u6D88\u8BF7\u6C42\u672A\u786E\u8BA4\uFF0C\u4EFB\u52A1\u53EF\u80FD\u4ECD\u5728\u6267\u884C\u3002\u8BF7\u91CD\u8BD5\u53D6\u6D88\u3002"
4354
+ } : previous);
4355
+ }
4356
+ } finally {
4357
+ active.cancelPending = false;
4118
4358
  }
4119
4359
  };
4120
4360
  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 +4368,7 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4128
4368
  setExecutionEnvironment("harness");
4129
4369
  } }, "\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
4370
  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(
4371
+ } }, 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
4372
  LaunchPreview,
4133
4373
  {
4134
4374
  value: phase.value,
@@ -4139,7 +4379,23 @@ function RunLauncher({ task, mode, directRoute, budgetUsd, defaultPreset, ledger
4139
4379
  onConfirm: () => {
4140
4380
  void confirm2();
4141
4381
  },
4142
- onCancel: () => setPhase({ kind: "idle" })
4382
+ onCancel: () => {
4383
+ if (!launching.current) setPhase({ kind: "idle" });
4384
+ }
4385
+ }
4386
+ ), phase.kind === "running" && phase.job && /* @__PURE__ */ import_react3.default.createElement(
4387
+ RunProgress,
4388
+ {
4389
+ job: phase.job,
4390
+ now,
4391
+ restoring: phase.restoring,
4392
+ cancelPending: phase.cancelPending,
4393
+ 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" : ""),
4394
+ cancelError: phase.cancelError,
4395
+ canCancel: typeof cancelRun === "function",
4396
+ onCancel: () => {
4397
+ void cancel();
4398
+ }
4143
4399
  }
4144
4400
  ), 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
4401
  }
@@ -4156,7 +4412,25 @@ function LaunchPreview({ value, request, changed, running, confirmRef, onConfirm
4156
4412
  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
4413
  const tool = item.manualAssignment?.kind === "tool";
4158
4414
  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")));
4415
+ })), 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")));
4416
+ }
4417
+ function RunProgress({ job, now, restoring, pollError, cancelError, cancelPending, canCancel, onCancel }) {
4418
+ const progress = job.progress ?? {};
4419
+ const packages = Array.isArray(progress.packages) ? progress.packages : [];
4420
+ const startedAt = typeof job.startedAt === "number" ? job.startedAt : Date.parse(job.startedAt);
4421
+ const seconds = Number.isFinite(startedAt) ? Math.max(0, Math.floor((now - startedAt) / 1e3)) : 0;
4422
+ const elapsed = Number.isFinite(startedAt) ? `${Math.floor(seconds / 60)} \u5206 ${String(seconds % 60).padStart(2, "0")} \u79D2` : "\u5F85\u66F4\u65B0";
4423
+ const cancelling = job.cancelRequested || job.status === "cancelling";
4424
+ 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(
4425
+ "button",
4426
+ {
4427
+ className: "mr-button mr-button-secondary",
4428
+ type: "button",
4429
+ disabled: !canCancel || cancelPending || cancelling,
4430
+ onClick: onCancel
4431
+ },
4432
+ cancelPending ? "\u6B63\u5728\u8BF7\u6C42\u53D6\u6D88\u2026" : cancelling ? "\u6B63\u5728\u53D6\u6D88\u2026" : "\u53D6\u6D88\u6267\u884C"
4433
+ ), 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
4434
  }
4161
4435
 
4162
4436
  // .dsh-plugin/client/cli-terminal.jsx
@@ -13589,7 +13863,7 @@ var xterm_default = `/**
13589
13863
  `;
13590
13864
 
13591
13865
  // .dsh-plugin/client/host-version.mjs
13592
- var ROUTER_CLIENT_VERSION = true ? "0.22.1" : "";
13866
+ var ROUTER_CLIENT_VERSION = true ? "0.23.0" : "";
13593
13867
  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
13868
  function isMissingRemoteMethod(message) {
13595
13869
  const value = String(message ?? "");
@@ -14315,12 +14589,21 @@ var router_main_default = `.mr-workspace {
14315
14589
  .mr-launch-workspace { flex: 1 1 320px; min-width: min(100%, 260px); }
14316
14590
  .mr-launch-preview { margin-top: 16px; padding: 14px; border: 1px solid var(--mr-line); border-radius: 12px; background: #f9fbfe; }
14317
14591
  .mr-launch-packages { margin: 12px 0 0; padding-left: 20px; font-size: 12px; line-height: 1.7; }
14592
+ .mr-run-progress-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
14593
+ .mr-run-meter { display: block; width: 100%; height: 8px; accent-color: var(--mr-accent); }
14594
+ .mr-run-packages { display: grid; gap: 7px; margin: 12px 0; padding-left: 20px; font-size: 12px; line-height: 1.6; }
14595
+ .mr-run-packages li > span:first-child { margin-right: 8px; }
14596
+ .mr-run-package-running { color: var(--mr-accent); }
14597
+ .mr-run-package-failed, .mr-run-package-blocked { color: #b42c39; }
14598
+ .mr-run-package-succeeded { color: #187042; }
14318
14599
  .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
14600
  .mr-confirm-box .mr-section-title { margin: 0 0 6px; }
14320
14601
  .mr-confirm-box ol { margin: 0; padding-left: 20px; }
14321
14602
  .mr-break { overflow-wrap: anywhere; }
14322
14603
  @media (prefers-color-scheme: dark) {
14323
14604
  .mr-launch-preview { background: rgba(255, 255, 255, .04); }
14605
+ .mr-run-package-failed, .mr-run-package-blocked { color: #ffd4da; }
14606
+ .mr-run-package-succeeded { color: #b4f1cc; }
14324
14607
  .mr-confirm-box { background: rgba(217, 140, 30, .12); color: #f2d39b; }
14325
14608
  }
14326
14609
 
@@ -15114,7 +15397,7 @@ ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
15114
15397
  }, []);
15115
15398
  return { health, ledger, boundaries, busy, refreshHealth, refreshLedger, refreshBoundaries, finishOnboarding, rate, rerun };
15116
15399
  }
15117
- function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
15400
+ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, runStatus, cancelRun, terminalApi }) {
15118
15401
  const workbench = useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries });
15119
15402
  const [catalogState, setCatalogState] = import_react5.default.useState({ status: "loading", catalog: null, error: "" });
15120
15403
  const [task, setTask] = import_react5.default.useState("");
@@ -15336,6 +15619,8 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOff
15336
15619
  ledger: workbench.ledger.value,
15337
15620
  previewRun,
15338
15621
  startRun,
15622
+ runStatus,
15623
+ cancelRun,
15339
15624
  planningRevision: `${routingSettings}/${dataRevision}/${feedbackRevision}/${evidenceRevision}`,
15340
15625
  assignmentMode: effectiveAssignmentMode,
15341
15626
  packageAssignments: effectiveAssignmentMode === "manual" ? packageAssignments : {},
@@ -15494,6 +15779,12 @@ var idText = (value, subject) => {
15494
15779
  if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,80}$/.test(value)) throw new TypeError(`${subject} must be a short id`);
15495
15780
  return value;
15496
15781
  };
15782
+ function parseRunJobRequest(value) {
15783
+ const request = plainObject2(value, "run job request");
15784
+ if (Object.keys(request).some((key) => key !== "jobId")) throw new TypeError("run job request only accepts jobId");
15785
+ return { jobId: idText(request.jobId, "jobId") };
15786
+ }
15787
+ var runJobRequestCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#RunJobRequest`, parseRunJobRequest);
15497
15788
  var optionalRouteText = (value, subject) => {
15498
15789
  if (value === void 0 || value === null || value === "") return void 0;
15499
15790
  if (typeof value !== "string" || value.length > 240 || value.includes("\0")) throw new TypeError(`${subject} is invalid`);
@@ -15619,6 +15910,8 @@ var OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
15619
15910
  // Workbench "start a run": plan preview with cost estimate and confirmation reasons, then execute.
15620
15911
  descriptor("previewRun", [jsonParameter("request", runRequestCodec)], anyObjectCodec("RunPreview")),
15621
15912
  descriptor("startRun", [jsonParameter("request", runRequestCodec)], anyObjectCodec("RunStarted")),
15913
+ descriptor("runStatus", [jsonParameter("request", runJobRequestCodec)], anyObjectCodec("RunJobStatus")),
15914
+ descriptor("cancelRun", [jsonParameter("request", runJobRequestCodec)], anyObjectCodec("RunJobStatus")),
15622
15915
  // Workbench "官方工具终端": interactive shell / fixed official CLI sessions.
15623
15916
  // Output streams through long-poll reads; input and resize are unary calls.
15624
15917
  descriptor("terminalInfo", [], anyObjectCodec("TerminalInfo")),
@@ -16072,6 +16365,8 @@ function registerUi(ctx) {
16072
16365
  loadBoundaries: () => officialToolsRemote.boundaries(),
16073
16366
  previewRun: (request) => officialToolsRemote.previewRun(request),
16074
16367
  startRun: (request) => officialToolsRemote.startRun(request),
16368
+ runStatus: (request) => officialToolsRemote.runStatus(request),
16369
+ cancelRun: (request) => officialToolsRemote.cancelRun(request),
16075
16370
  terminalApi: Object.freeze({
16076
16371
  terminalInfo: () => officialToolsRemote.terminalInfo(),
16077
16372
  terminalStart: (request) => officialToolsRemote.terminalStart(request),