@linxin666/dsh-client-ui-task-board 0.4.5 → 0.4.6

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.
Files changed (57) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +20 -12
  3. package/README.zh.md +20 -12
  4. package/lib/client.js +1327 -229
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +1909 -348
  7. package/lib/types/client/TaskBoardSettingsCard.d.ts +12 -0
  8. package/lib/types/client/index.d.ts +4 -5
  9. package/lib/types/client/locales.d.ts +65 -0
  10. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  11. package/lib/types/client/schedule-zone.d.ts +50 -0
  12. package/lib/types/core/controller.d.ts +10 -11
  13. package/lib/types/core/poll-cadence.d.ts +31 -0
  14. package/lib/types/core/tasks.d.ts +144 -14
  15. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  16. package/lib/types/core/verification-budget.d.ts +52 -0
  17. package/lib/types/core/verification.d.ts +376 -9
  18. package/lib/types/host/verification-gate.d.ts +24 -0
  19. package/lib/types/host/verification-runner.d.ts +38 -7
  20. package/lib/types/host-ledger.d.ts +82 -7
  21. package/lib/types/host-runner.d.ts +32 -9
  22. package/lib/types/host-service.d.ts +117 -9
  23. package/lib/types/index.d.ts +54 -0
  24. package/lib/types/protocol.d.ts +50 -12
  25. package/package.json +2 -6
  26. package/src/client/TaskBoardSettingsCard.tsx +74 -1
  27. package/src/client/board/NewTaskModal.tsx +151 -20
  28. package/src/client/board/TaskCard.tsx +3 -3
  29. package/src/client/board/TaskDetail.tsx +219 -18
  30. package/src/client/board/VerificationReport.tsx +132 -13
  31. package/src/client/board/status-key.ts +3 -0
  32. package/src/client/index.ts +43 -50
  33. package/src/client/locales.ts +137 -4
  34. package/src/client/plugin-card-seat.ts +7 -0
  35. package/src/client/schedule-zone.ts +100 -2
  36. package/src/core/controller.ts +12 -8
  37. package/src/core/poll-cadence.ts +41 -0
  38. package/src/core/store.ts +64 -15
  39. package/src/core/tasks.ts +193 -39
  40. package/src/core/use-cases/task-create.ts +30 -9
  41. package/src/core/use-cases/task-schedule.ts +190 -25
  42. package/src/core/use-cases/task-update.ts +3 -3
  43. package/src/core/verification-budget.ts +115 -0
  44. package/src/core/verification.ts +785 -17
  45. package/src/host/agent-tools.ts +106 -21
  46. package/src/host/verification-gate.ts +172 -16
  47. package/src/host/verification-runner.ts +207 -40
  48. package/src/host-ledger.ts +328 -42
  49. package/src/host-runner.ts +52 -19
  50. package/src/host-service.ts +299 -36
  51. package/src/index.ts +101 -5
  52. package/src/protocol.ts +132 -28
  53. package/lib/invariant.js +0 -6
  54. package/lib/types/client/board-card-seat.d.ts +0 -34
  55. package/lib/types/invariant.d.ts +0 -3
  56. package/src/client/board-card-seat.ts +0 -102
  57. package/src/invariant.ts +0 -4
package/lib/client.js CHANGED
@@ -78,6 +78,28 @@ window.__ModuleLoader__.load({
78
78
  }
79
79
  return Object.keys(result).length === 0 ? void 0 : result;
80
80
  }
81
+ //#endregion
82
+ //#region src/core/tasks.ts
83
+ /** Every stop reason this build understands; the repair and wire gates share it. */
84
+ const SCHEDULE_STOP_REASONS = [
85
+ "fired",
86
+ "limit",
87
+ "no-target",
88
+ "missed",
89
+ "busy",
90
+ "permission",
91
+ "launch-failed"
92
+ ];
93
+ /**
94
+ * The rule's total run budget: one for a one-shot, the stored cap for a
95
+ * recurring rule, and undefined when the rule is unlimited.
96
+ * @param rule - the schedule rule.
97
+ * @returns the budget, or undefined for an uncapped rule.
98
+ */
99
+ function scheduleRunBudget(rule) {
100
+ if (rule.mode === "once") return 1;
101
+ return rule.maxRuns;
102
+ }
81
103
  /**
82
104
  * Repair a persisted tag list: keep the well-formed entries, trim, drop
83
105
  * blanks and repeats, cap the count, and collapse a blank prompt line to
@@ -274,7 +296,7 @@ window.__ModuleLoader__.load({
274
296
  model: normalizeTargetId(input.model),
275
297
  reuseSession: input.reuseSession === true ? true : void 0,
276
298
  teamRun: input.teamRun === true ? true : void 0,
277
- goalRun: input.goalRun === false ? false : void 0,
299
+ goalRun: input.goalRun === true ? true : void 0,
278
300
  skipVerification: input.skipVerification === true ? true : void 0,
279
301
  ...input.freeze === void 0 ? {} : { freeze: freezeOf(input.freeze, now) },
280
302
  ...input.handover === void 0 ? {} : { handover: {
@@ -294,27 +316,47 @@ window.__ModuleLoader__.load({
294
316
  updatedAt: now
295
317
  };
296
318
  }
319
+ /** Every field a schedule patch may carry; the rest of the rule is carried over. */
320
+ const SCHEDULE_PATCH_KEYS = [
321
+ "enabled",
322
+ "mode",
323
+ "cron",
324
+ "at",
325
+ "timeZone",
326
+ "nextRunAt",
327
+ "lastTriggeredAt",
328
+ "runCount",
329
+ "maxRuns",
330
+ "endedAt",
331
+ "endedReason",
332
+ "skippedAt",
333
+ "skippedReason"
334
+ ];
297
335
  /**
298
336
  * Merge a schedule patch into a task's schedule rule (creating it when
299
337
  * absent), with a fresh updatedAt. Keys present in the patch overwrite the
300
338
  * current value — including explicit `undefined`, which clears a field (used
301
- * to disarm `nextRunAt`); absent keys keep their current value.
339
+ * to disarm `nextRunAt` or to drop an ended/skip record); absent keys keep
340
+ * their current value.
302
341
  */
303
342
  function withSchedule(task, patch, now) {
304
- const current = task.schedule;
305
- const schedule = {
306
- enabled: current?.enabled ?? false,
307
- cron: current?.cron ?? "",
308
- ...current?.timeZone === void 0 ? {} : { timeZone: current.timeZone },
309
- nextRunAt: current?.nextRunAt,
310
- lastTriggeredAt: current?.lastTriggeredAt
311
- };
312
- if ("enabled" in patch) schedule.enabled = patch.enabled ?? false;
313
- if ("cron" in patch) schedule.cron = patch.cron ?? "";
314
- if ("timeZone" in patch) if (patch.timeZone === void 0) delete schedule.timeZone;
315
- else schedule.timeZone = patch.timeZone;
316
- if ("nextRunAt" in patch) schedule.nextRunAt = patch.nextRunAt;
317
- if ("lastTriggeredAt" in patch) schedule.lastTriggeredAt = patch.lastTriggeredAt;
343
+ const schedule = task.schedule === void 0 ? {
344
+ enabled: false,
345
+ mode: "cron",
346
+ nextRunAt: void 0,
347
+ lastTriggeredAt: void 0,
348
+ runCount: 0
349
+ } : { ...task.schedule };
350
+ const fields = schedule;
351
+ const next = patch;
352
+ for (const key of SCHEDULE_PATCH_KEYS) {
353
+ if (!(key in patch)) continue;
354
+ if (next[key] === void 0) delete fields[key];
355
+ else fields[key] = next[key];
356
+ }
357
+ if (schedule.enabled !== true) schedule.enabled = false;
358
+ if (schedule.mode !== "once") schedule.mode = "cron";
359
+ if (!Number.isFinite(schedule.runCount) || schedule.runCount < 0) schedule.runCount = 0;
318
360
  return {
319
361
  ...task,
320
362
  updatedAt: now,
@@ -889,21 +931,49 @@ window.__ModuleLoader__.load({
889
931
  model: input.model ?? parent.model
890
932
  }, now, id);
891
933
  const requested = input.schedule;
892
- if (requested?.enabled === true && requested.cron.trim() !== "" && isValidCron(requested.cron)) {
893
- const cron = requested.cron.trim();
934
+ if (requested?.enabled === true) {
894
935
  const requestedZone = requested.timeZone;
895
936
  if (requestedZone !== void 0 && !isValidTimeZone(requestedZone)) return {
896
937
  task: void 0,
897
938
  tasks,
898
939
  error: "invalid schedule time zone"
899
940
  };
900
- const nextRunAt = nextRunAtMs(cron, now, requestedZone ?? hostTimeZone);
901
- if (nextRunAt !== void 0) task = withSchedule(task, {
902
- enabled: true,
903
- cron,
904
- timeZone: requestedZone,
905
- nextRunAt
906
- }, now);
941
+ if (requested.mode === "once") {
942
+ const at = requested.at;
943
+ if (at === void 0 || !Number.isInteger(at) || at <= now) return {
944
+ task: void 0,
945
+ tasks,
946
+ error: "invalid schedule instant"
947
+ };
948
+ task = withSchedule(task, {
949
+ enabled: true,
950
+ mode: "once",
951
+ at,
952
+ timeZone: requestedZone,
953
+ nextRunAt: at,
954
+ runCount: 0
955
+ }, now);
956
+ } else {
957
+ const cron = (requested.cron ?? "").trim();
958
+ if (cron !== "" && isValidCron(cron)) {
959
+ const maxRuns = requested.maxRuns;
960
+ if (maxRuns !== void 0 && (!Number.isInteger(maxRuns) || maxRuns < 1)) return {
961
+ task: void 0,
962
+ tasks,
963
+ error: "invalid schedule run cap"
964
+ };
965
+ const nextRunAt = nextRunAtMs(cron, now, requestedZone ?? hostTimeZone);
966
+ if (nextRunAt !== void 0) task = withSchedule(task, {
967
+ enabled: true,
968
+ mode: "cron",
969
+ cron,
970
+ maxRuns,
971
+ timeZone: requestedZone,
972
+ nextRunAt,
973
+ runCount: 0
974
+ }, now);
975
+ }
976
+ }
907
977
  }
908
978
  return {
909
979
  task,
@@ -934,16 +1004,30 @@ window.__ModuleLoader__.load({
934
1004
  //#endregion
935
1005
  //#region src/core/use-cases/task-schedule.ts
936
1006
  /**
937
- * Schedule use case: arm/disarm a task's cron rule and roll a rule forward.
938
- * Pure ledger transitions (no persistence or notify — the controller
939
- * orchestrates those). Validation and next-run computation live here, sharing
940
- * the core cron parser (schedule.ts) and the withSchedule transition.
941
- */
942
- /**
943
- * Set an on-board task's schedule rule. A blank or invalid cron, an unknown or
944
- * archived task, or an unusable zone is rejected (state untouched); an enabled
945
- * rule computes the next run instant immediately in the rule's own zone, a
946
- * disabled one carries no next-run instant.
1007
+ * Schedule use cases: arm/disarm a task's scheduling rule, and record what a
1008
+ * due occurrence did to it. Pure ledger transitions (no persistence or notify
1009
+ * — the controller orchestrates those). Validation and next-run computation
1010
+ * live here, sharing the core cron parser (schedule.ts) and the withSchedule
1011
+ * transition.
1012
+ *
1013
+ * A rule is either a recurring cron rule or a single planned instant, and
1014
+ * either kind may be capped by a run budget. The counter (`runCount`) moves
1015
+ * only when the Host actually opened an execution; a skipped occurrence
1016
+ * (busy, unconfirmed permission, missed instant, unreachable plan) moves the
1017
+ * rule without consuming its budget.
1018
+ */
1019
+ /**
1020
+ * Set an on-board task's scheduling rule. An unknown or archived task, an
1021
+ * unusable zone, a blank or invalid cron, a one-shot with no usable instant,
1022
+ * a one-shot armed in the past, and a run cap that is not a positive integer
1023
+ * are all rejected (state untouched). An enabled rule computes its next run
1024
+ * instant immediately — a one-shot's next instant IS its planned instant — and
1025
+ * a disabled rule carries none.
1026
+ *
1027
+ * Arming a rule that was not enabled, or switching its kind, starts a fresh
1028
+ * budget: the run counter and any previous stop/skip record are cleared. Edits
1029
+ * to a live rule (expression, zone, cap) keep the consumed count, so an
1030
+ * already-spent budget can never silently reset and overrun.
947
1031
  * @param tasks - current ledger.
948
1032
  * @param id - the task to schedule.
949
1033
  * @param patch - rule fields to change (absent fields keep their current value).
@@ -957,36 +1041,91 @@ window.__ModuleLoader__.load({
957
1041
  applied: false
958
1042
  };
959
1043
  const current = task.schedule;
1044
+ const mode = patch.mode ?? current?.mode ?? "cron";
1045
+ const timeZone = patch.timeZone === void 0 ? current?.timeZone : patch.timeZone ?? void 0;
1046
+ if (timeZone !== void 0 && !isValidTimeZone(timeZone)) return {
1047
+ tasks,
1048
+ applied: false
1049
+ };
1050
+ const enabled = patch.enabled ?? current?.enabled ?? false;
1051
+ const restart = enabled && current?.enabled !== true || (current?.mode ?? "cron") !== mode;
1052
+ const runCount = restart ? 0 : current?.runCount ?? 0;
1053
+ const effectiveZone = timeZone ?? hostTimeZone;
1054
+ const cleared = restart ? {
1055
+ endedAt: void 0,
1056
+ endedReason: void 0,
1057
+ skippedAt: void 0,
1058
+ skippedReason: void 0
1059
+ } : {
1060
+ endedAt: current?.endedAt,
1061
+ endedReason: current?.endedReason,
1062
+ skippedAt: current?.skippedAt,
1063
+ skippedReason: current?.skippedReason
1064
+ };
1065
+ if (mode === "once") {
1066
+ const at = patch.at ?? current?.at;
1067
+ if (at === void 0 || !Number.isInteger(at)) return {
1068
+ tasks,
1069
+ applied: false
1070
+ };
1071
+ if (enabled && at <= now) return {
1072
+ tasks,
1073
+ applied: false
1074
+ };
1075
+ return {
1076
+ tasks: tasks.map((candidate) => candidate.id !== id ? candidate : withSchedule(candidate, {
1077
+ enabled,
1078
+ mode: "once",
1079
+ at,
1080
+ cron: void 0,
1081
+ maxRuns: void 0,
1082
+ timeZone: timeZone ?? void 0,
1083
+ nextRunAt: enabled ? at : void 0,
1084
+ runCount,
1085
+ ...cleared
1086
+ }, now)),
1087
+ applied: true
1088
+ };
1089
+ }
960
1090
  const cron = (patch.cron ?? current?.cron ?? "").trim();
961
1091
  if (cron === "" || !isValidCron(cron)) return {
962
1092
  tasks,
963
1093
  applied: false
964
1094
  };
965
- const requestedZone = patch.timeZone === void 0 ? current?.timeZone : patch.timeZone ?? void 0;
966
- if (requestedZone !== void 0 && !isValidTimeZone(requestedZone)) return {
1095
+ const maxRuns = ("maxRuns" in patch ? patch.maxRuns : current?.maxRuns) ?? void 0;
1096
+ if (maxRuns !== void 0 && (!Number.isInteger(maxRuns) || maxRuns < 1)) return {
967
1097
  tasks,
968
1098
  applied: false
969
1099
  };
970
- const enabled = patch.enabled ?? current?.enabled ?? false;
971
- const nextRunAt = enabled ? nextRunAtMs(cron, now, requestedZone ?? hostTimeZone) : void 0;
972
- if (enabled && nextRunAt === void 0) return {
1100
+ const spent = maxRuns !== void 0 && runCount >= maxRuns;
1101
+ const armed = enabled && !spent;
1102
+ const nextRunAt = armed ? nextRunAtMs(cron, now, effectiveZone) : void 0;
1103
+ if (armed && nextRunAt === void 0) return {
973
1104
  tasks,
974
1105
  applied: false
975
1106
  };
1107
+ const exhausted = enabled && spent;
976
1108
  return {
977
- tasks: tasks.map((candidate) => candidate.id === id ? withSchedule(candidate, {
978
- enabled,
1109
+ tasks: tasks.map((candidate) => candidate.id !== id ? candidate : withSchedule(candidate, {
1110
+ enabled: armed,
1111
+ mode: "cron",
979
1112
  cron,
980
- timeZone: requestedZone ?? void 0,
981
- nextRunAt
982
- }, now) : candidate),
1113
+ at: void 0,
1114
+ maxRuns,
1115
+ timeZone: timeZone ?? void 0,
1116
+ nextRunAt,
1117
+ runCount,
1118
+ ...exhausted ? {
1119
+ endedAt: now,
1120
+ endedReason: "limit"
1121
+ } : cleared
1122
+ }, now)),
983
1123
  applied: true
984
1124
  };
985
1125
  }
986
1126
  /**
987
- * Roll a task's schedule rule forward (scheduler callback): persist the next
988
- * due instant and the trigger instant. No-op for tasks without a rule (deleted
989
- * mid-tick, for example).
1127
+ * Roll a task's schedule rule forward (legacy pure-controller seam): persist
1128
+ * the next due instant and the trigger instant. No-op for tasks without a rule.
990
1129
  * @param tasks - current ledger.
991
1130
  * @param id - the task to roll forward.
992
1131
  * @param nextRunAt - next due instant (may be undefined to clear).
@@ -1258,7 +1397,7 @@ window.__ModuleLoader__.load({
1258
1397
  if ("permission" in patch && patch.permission !== void 0 && patch.permission !== task.permission || "handover" in patch) next.permissionConfirmedAt = void 0;
1259
1398
  if ("reuseSession" in patch) next.reuseSession = patch.reuseSession === true ? true : void 0;
1260
1399
  if ("teamRun" in patch) next.teamRun = patch.teamRun === true ? true : void 0;
1261
- if ("goalRun" in patch) next.goalRun = patch.goalRun === false ? false : void 0;
1400
+ if ("goalRun" in patch) next.goalRun = patch.goalRun === true ? true : void 0;
1262
1401
  if ("skipVerification" in patch) next.skipVerification = patch.skipVerification === true ? true : void 0;
1263
1402
  if (workspaceId !== void 0 || "workspaceId" in patch) next.workspaceId = workspaceId;
1264
1403
  if (mode !== void 0 || "mode" in patch) next.mode = mode;
@@ -1673,13 +1812,15 @@ window.__ModuleLoader__.load({
1673
1812
  return true;
1674
1813
  }
1675
1814
  /**
1676
- * Update a task's schedule rule. A blank or invalid cron expression is
1677
- * rejected (returns false, state untouched). When the rule ends up enabled
1678
- * the next run instant is computed immediately; a disabled rule carries no
1679
- * next-run instant. Delegates the domain transition to the schedule use case.
1815
+ * Update a task's schedule rule. An unusable plan (blank or invalid cron, a
1816
+ * one-shot with no future instant, a non-positive run cap) is rejected
1817
+ * (returns false, state untouched). When the rule ends up enabled the next
1818
+ * run instant is computed immediately — a one-shot's next instant IS its
1819
+ * planned instant — and a disabled rule carries none. Delegates the domain
1820
+ * transition to the schedule use case.
1680
1821
  * @param id - the task to schedule.
1681
1822
  * @param patch - fields to change (absent fields keep their current value).
1682
- * @returns true when applied, false when rejected (invalid cron / unknown task).
1823
+ * @returns true when applied, false when rejected (unknown task / unusable plan).
1683
1824
  */
1684
1825
  setSchedule(id, patch) {
1685
1826
  const hostTimeZone = this.hostState?.scheduler.timeZone ?? resolveHostTimeZone();
@@ -1879,6 +2020,7 @@ window.__ModuleLoader__.load({
1879
2020
  const sessionDefaultPermission = snapshot.sessionDefaultPermission ?? this.hostState?.sessionDefaultPermission;
1880
2021
  const maxSubtaskDepth = snapshot.maxSubtaskDepth ?? this.hostState?.maxSubtaskDepth;
1881
2022
  const teamRunAvailable = snapshot.teamRunAvailable ?? this.hostState?.teamRunAvailable;
2023
+ const goalRunEnabled = snapshot.goalRunEnabled ?? this.hostState?.goalRunEnabled;
1882
2024
  const extensions = snapshot.extensions ?? this.hostState?.extensions;
1883
2025
  return {
1884
2026
  revision: snapshot.revision,
@@ -1887,6 +2029,7 @@ window.__ModuleLoader__.load({
1887
2029
  ...sessionDefaultPermission === void 0 ? {} : { sessionDefaultPermission },
1888
2030
  ...maxSubtaskDepth === void 0 ? {} : { maxSubtaskDepth },
1889
2031
  ...teamRunAvailable === void 0 ? {} : { teamRunAvailable },
2032
+ ...goalRunEnabled === void 0 ? {} : { goalRunEnabled },
1890
2033
  ...extensions === void 0 ? {} : { extensions }
1891
2034
  };
1892
2035
  }
@@ -2210,6 +2353,19 @@ window.__ModuleLoader__.load({
2210
2353
  function exceptionAttempts(verification) {
2211
2354
  return verification === void 0 ? [] : verification.attempts.filter((attempt) => attempt.stage === "exception");
2212
2355
  }
2356
+ /**
2357
+ * Invalid acceptances spent by this cycle: a veto whose required evidence was
2358
+ * missing, unlocatable, or aimed at the empty-work baseline. They are NOT
2359
+ * quality verdicts, so they never close the cycle and never fail the card; the
2360
+ * bound is what keeps an evidence-free judge from re-running forever.
2361
+ */
2362
+ function invalidAttempts(verification) {
2363
+ return verification === void 0 ? [] : verification.attempts.filter((attempt) => attempt.stage === "invalid");
2364
+ }
2365
+ /** Whether this cycle still has invalid-acceptance budget left. */
2366
+ function hasInvalidBudget(verification) {
2367
+ return invalidAttempts(verification).length < 2;
2368
+ }
2213
2369
  /** The quality attempt that passed, when one did. */
2214
2370
  function passedAttempt(verification) {
2215
2371
  return qualityAttempts(verification).find((attempt) => attempt.passed);
@@ -2225,7 +2381,7 @@ window.__ModuleLoader__.load({
2225
2381
  if (verification.failedReason !== void 0) return "failed";
2226
2382
  if (passedAttempt(verification) !== void 0) return "passed";
2227
2383
  if (verification.inFlight === true) return "verifying";
2228
- if (qualityAttempts(verification).length === 0) return "executing";
2384
+ if (qualityAttempts(verification).length === 0) return hasInvalidBudget(verification) ? "executing" : "invalid";
2229
2385
  return hasQualityBudget(verification) ? "repairing" : "failed";
2230
2386
  }
2231
2387
  /** Whether an unknown value carries every field a persisted attempt needs. */
@@ -2233,7 +2389,7 @@ window.__ModuleLoader__.load({
2233
2389
  if (typeof value !== "object" || value === null) return void 0;
2234
2390
  const row = value;
2235
2391
  if (typeof row.index !== "number" || typeof row.at !== "number") return void 0;
2236
- if (row.stage !== "quality" && row.stage !== "exception") return void 0;
2392
+ if (row.stage !== "quality" && row.stage !== "exception" && row.stage !== "budget" && row.stage !== "invalid") return void 0;
2237
2393
  if (typeof row.passed !== "boolean") return void 0;
2238
2394
  if (typeof row.score !== "number" || !Number.isFinite(row.score)) return void 0;
2239
2395
  if (typeof row.baseline !== "number" || !Number.isFinite(row.baseline)) return void 0;
@@ -2268,7 +2424,18 @@ window.__ModuleLoader__.load({
2268
2424
  const routeRow = route;
2269
2425
  if (typeof routeRow.provider !== "string" || typeof routeRow.model !== "string") return void 0;
2270
2426
  if (routeRow.reasoningEffort !== void 0 && typeof routeRow.reasoningEffort !== "string") return void 0;
2271
- const findings = Array.isArray(row.findings) ? row.findings.filter((item) => typeof item === "string") : [];
2427
+ const findings = (Array.isArray(row.findings) ? row.findings : []).filter((item) => typeof item === "string").slice(0, 12).map((item) => clipRead(item, 400));
2428
+ const criterionFindings = [];
2429
+ if (Array.isArray(row.criterionFindings)) {
2430
+ if (row.criterionFindings.length > 12) return void 0;
2431
+ for (const entry of row.criterionFindings) {
2432
+ const finding = readCriterionFinding(entry);
2433
+ if (finding === void 0) return void 0;
2434
+ criterionFindings.push(finding);
2435
+ }
2436
+ }
2437
+ const invalidReason = row.invalidReason;
2438
+ if (invalidReason !== void 0 && invalidReason !== "missing-finding" && invalidReason !== "unlocatable-quote" && invalidReason !== "baseline-finding" && invalidReason !== "vacuous-finding" && invalidReason !== "insufficient-evidence") return void 0;
2272
2439
  return {
2273
2440
  index: row.index,
2274
2441
  at: row.at,
@@ -2301,7 +2468,51 @@ window.__ModuleLoader__.load({
2301
2468
  },
2302
2469
  channel: "explicit-tag",
2303
2470
  rounds: typeof row.rounds === "number" ? row.rounds : 2,
2304
- ...typeof row.error === "string" ? { error: row.error } : {}
2471
+ ...typeof row.error === "string" ? { error: row.error } : {},
2472
+ ...criterionFindings.length === 0 ? {} : { criterionFindings },
2473
+ ...typeof invalidReason === "string" ? { invalidReason } : {}
2474
+ };
2475
+ }
2476
+ /**
2477
+ * Bound one persisted finding's text to what the acceptance itself would have
2478
+ * written.
2479
+ *
2480
+ * The reader is the only other door into the ledger: a hand-edited or
2481
+ * corrupted document could otherwise carry a megabyte of prose in a field the
2482
+ * writer caps at a few hundred characters, and every later render, audit
2483
+ * summary and cleanup pass would pay for it. Clipping on READ (never rejecting)
2484
+ * keeps the block usable and its capacity bounded: the acceptance-only material
2485
+ * the cleanup removes is bounded by construction on both sides.
2486
+ * @param value - the raw persisted text.
2487
+ * @param max - the cap the writer applies to that field.
2488
+ * @returns the bounded text.
2489
+ */
2490
+ function clipRead(value, max) {
2491
+ return value.length <= max ? value : value.slice(0, max);
2492
+ }
2493
+ /**
2494
+ * Repair one persisted structured finding, or undefined when it is unusable.
2495
+ * A malformed finding drops the WHOLE acceptance block (fail closed): a
2496
+ * persisted veto that cannot be read back could otherwise be counted as
2497
+ * evidence it no longer holds.
2498
+ */
2499
+ function readCriterionFinding(value) {
2500
+ if (typeof value !== "object" || value === null) return void 0;
2501
+ const row = value;
2502
+ if (typeof row.criterionId !== "string" || row.criterionId === "") return void 0;
2503
+ if (typeof row.requirement !== "string" || typeof row.observation !== "string" || typeof row.gap !== "string") return void 0;
2504
+ if (typeof row.quote !== "string") return void 0;
2505
+ if (row.criterionId.length > 64) return void 0;
2506
+ const location = row.location;
2507
+ if (location !== "task" && location !== "trajectory" && location !== "workspace" && location !== "baseline" && location !== "unknown") return void 0;
2508
+ return {
2509
+ criterionId: row.criterionId,
2510
+ requirement: clipRead(row.requirement, 400),
2511
+ observation: clipRead(row.observation, 400),
2512
+ gap: clipRead(row.gap, 400),
2513
+ quote: clipRead(row.quote, 600),
2514
+ location,
2515
+ ...typeof row.action === "string" ? { action: clipRead(row.action, 400) } : {}
2305
2516
  };
2306
2517
  }
2307
2518
  /**
@@ -2349,7 +2560,7 @@ window.__ModuleLoader__.load({
2349
2560
  };
2350
2561
  }
2351
2562
  const applicability = row.applicability;
2352
- if (applicability !== "enforced" && applicability !== "disabled" && applicability !== "skipped" && applicability !== "goal-unavailable" && applicability !== "team-member") return void 0;
2563
+ if (applicability !== "enforced" && applicability !== "disabled" && applicability !== "skipped" && applicability !== "goal-unavailable" && applicability !== "team-member" && applicability !== "goal-disabled") return void 0;
2353
2564
  const attempts = [];
2354
2565
  if (!Array.isArray(row.attempts)) return void 0;
2355
2566
  for (const entry of row.attempts) {
@@ -2358,6 +2569,8 @@ window.__ModuleLoader__.load({
2358
2569
  attempts.push(attempt);
2359
2570
  }
2360
2571
  if (typeof row.failedReason === "string" && row.failedReason === "") return void 0;
2572
+ const cleanup = readCleanupRecord(row.cleanup);
2573
+ if (row.cleanup !== void 0 && cleanup === void 0) return void 0;
2361
2574
  return {
2362
2575
  contract: {
2363
2576
  enabled: contractRow.enabled,
@@ -2372,7 +2585,98 @@ window.__ModuleLoader__.load({
2372
2585
  ...row.inFlight === true ? { inFlight: true } : {},
2373
2586
  applicability,
2374
2587
  ...typeof row.failedReason === "string" ? { failedReason: row.failedReason } : {},
2375
- ...typeof row.failedAt === "number" && Number.isFinite(row.failedAt) ? { failedAt: row.failedAt } : {}
2588
+ ...typeof row.failedAt === "number" && Number.isFinite(row.failedAt) ? { failedAt: row.failedAt } : {},
2589
+ ...cleanup === void 0 ? {} : { cleanup }
2590
+ };
2591
+ }
2592
+ /**
2593
+ * Repair the persisted cleanup record, or undefined when it is unusable. The
2594
+ * record is the audit trail the UI reads to tell "the judge gave its evidence
2595
+ * and it was cleaned up" apart from "the judge produced no evidence at all",
2596
+ * so a malformed one drops the whole block (fail closed).
2597
+ */
2598
+ function readCleanupRecord(value) {
2599
+ if (typeof value !== "object" || value === null) return void 0;
2600
+ const row = value;
2601
+ if (row.state !== "pending" && row.state !== "cleaned" && row.state !== "failed") return void 0;
2602
+ if (typeof row.attempts !== "number" || !Number.isInteger(row.attempts) || row.attempts < 0) return void 0;
2603
+ if (typeof row.startedAt !== "number" || !Number.isFinite(row.startedAt)) return void 0;
2604
+ if (row.cleanedAt !== void 0 && (typeof row.cleanedAt !== "number" || !Number.isFinite(row.cleanedAt))) return void 0;
2605
+ if (row.lastError !== void 0 && typeof row.lastError !== "string") return void 0;
2606
+ if (!Array.isArray(row.audit)) return void 0;
2607
+ const audit = [];
2608
+ for (const entry of row.audit) {
2609
+ const parsed = readAuditRecord(entry);
2610
+ if (parsed === void 0) return void 0;
2611
+ audit.push(parsed);
2612
+ }
2613
+ return {
2614
+ state: row.state,
2615
+ attempts: row.attempts,
2616
+ startedAt: row.startedAt,
2617
+ ...typeof row.cleanedAt === "number" ? { cleanedAt: row.cleanedAt } : {},
2618
+ ...typeof row.lastError === "string" ? { lastError: row.lastError } : {},
2619
+ audit
2620
+ };
2621
+ }
2622
+ /** Repair one persisted audit credential, or undefined when it is unusable. */
2623
+ function readAuditRecord(value) {
2624
+ if (typeof value !== "object" || value === null) return void 0;
2625
+ const row = value;
2626
+ if (row.stage !== "quality" && row.stage !== "exception" && row.stage !== "budget" && row.stage !== "invalid") return void 0;
2627
+ if (typeof row.passed !== "boolean") return void 0;
2628
+ if (typeof row.at !== "number" || !Number.isFinite(row.at)) return void 0;
2629
+ if (typeof row.score !== "number" || !Number.isFinite(row.score)) return void 0;
2630
+ if (typeof row.baseline !== "number" || !Number.isFinite(row.baseline)) return void 0;
2631
+ if (!Array.isArray(row.criteria)) return void 0;
2632
+ const criteria = [];
2633
+ for (const entry of row.criteria) {
2634
+ if (typeof entry !== "object" || entry === null) return void 0;
2635
+ const criterion = entry;
2636
+ if (typeof criterion.id !== "string" || typeof criterion.name !== "string") return void 0;
2637
+ if (typeof criterion.score !== "number" || !Number.isFinite(criterion.score)) return void 0;
2638
+ if (typeof criterion.baseline !== "number" || !Number.isFinite(criterion.baseline)) return void 0;
2639
+ if (typeof criterion.threshold !== "number" || !Number.isFinite(criterion.threshold)) return void 0;
2640
+ criteria.push({
2641
+ id: criterion.id,
2642
+ name: criterion.name,
2643
+ score: criterion.score,
2644
+ baseline: criterion.baseline,
2645
+ threshold: criterion.threshold,
2646
+ passed: criterion.passed === true
2647
+ });
2648
+ }
2649
+ if (typeof row.evidenceHash !== "string") return void 0;
2650
+ const route = row.route;
2651
+ if (typeof route !== "object" || route === null) return void 0;
2652
+ const routeRow = route;
2653
+ if (typeof routeRow.provider !== "string" || typeof routeRow.model !== "string") return void 0;
2654
+ const usage = row.usage;
2655
+ if (typeof usage !== "object" || usage === null) return void 0;
2656
+ const usageRow = usage;
2657
+ if (typeof usageRow.calls !== "number" || typeof usageRow.inputTokens !== "number" || typeof usageRow.outputTokens !== "number" || typeof usageRow.reasoningTokens !== "number") return void 0;
2658
+ return {
2659
+ stage: row.stage,
2660
+ passed: row.passed,
2661
+ at: row.at,
2662
+ score: row.score,
2663
+ baseline: row.baseline,
2664
+ criteria,
2665
+ evidenceHash: row.evidenceHash,
2666
+ route: {
2667
+ provider: routeRow.provider,
2668
+ model: routeRow.model,
2669
+ ...typeof routeRow.reasoningEffort === "string" ? { reasoningEffort: routeRow.reasoningEffort } : {}
2670
+ },
2671
+ ...typeof row.error === "string" ? { error: row.error } : {},
2672
+ ...typeof row.findingSummary === "string" ? { findingSummary: row.findingSummary } : {},
2673
+ usage: {
2674
+ calls: usageRow.calls,
2675
+ inputTokens: usageRow.inputTokens,
2676
+ outputTokens: usageRow.outputTokens,
2677
+ reasoningTokens: usageRow.reasoningTokens,
2678
+ ...usageRow.usageIncomplete === true ? { usageIncomplete: true } : {}
2679
+ }
2376
2680
  };
2377
2681
  }
2378
2682
  /** Sum the attempts of one execution. */
@@ -2380,6 +2684,7 @@ window.__ModuleLoader__.load({
2380
2684
  const totals = {
2381
2685
  quality: 0,
2382
2686
  exceptions: 0,
2687
+ invalid: 0,
2383
2688
  calls: 0,
2384
2689
  inputTokens: 0,
2385
2690
  outputTokens: 0,
@@ -2389,6 +2694,7 @@ window.__ModuleLoader__.load({
2389
2694
  if (verification === void 0) return totals;
2390
2695
  for (const attempt of verification.attempts) {
2391
2696
  if (attempt.stage === "quality") totals.quality += 1;
2697
+ else if (attempt.stage === "invalid") totals.invalid += 1;
2392
2698
  else totals.exceptions += 1;
2393
2699
  totals.calls += attempt.usage.calls;
2394
2700
  totals.inputTokens += attempt.usage.inputTokens;
@@ -2526,6 +2832,7 @@ window.__ModuleLoader__.load({
2526
2832
  if (entry.initiatedBy !== void 0 && typeof entry.initiatedBy !== "string") return false;
2527
2833
  if (entry.frozenBy !== void 0 && typeof entry.frozenBy !== "string") return false;
2528
2834
  if (entry.frozenAt !== void 0 && typeof entry.frozenAt !== "number") return false;
2835
+ if (entry.external !== void 0 && typeof entry.external !== "boolean") return false;
2529
2836
  if (entry.verification !== void 0 && normalizeVerification(entry.verification) === void 0) return false;
2530
2837
  }
2531
2838
  return true;
@@ -2534,23 +2841,62 @@ window.__ModuleLoader__.load({
2534
2841
  function normalizeStatus(status) {
2535
2842
  return isTaskStatus(status) ? status : "todo";
2536
2843
  }
2844
+ /** A persisted ms timestamp, kept only when it is a usable finite number. */
2845
+ function optionalInstant(value) {
2846
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2847
+ }
2848
+ /** A persisted positive-integer run cap, or undefined for unlimited/repair. */
2849
+ function optionalRunCap(value) {
2850
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : void 0;
2851
+ }
2852
+ /** A persisted stop reason, kept only when it names a reason this build knows. */
2853
+ function optionalStopReason(value) {
2854
+ return typeof value === "string" && SCHEDULE_STOP_REASONS.includes(value) ? value : void 0;
2855
+ }
2537
2856
  /**
2538
- * Repair a persisted schedule rule: drop rules without a usable cron string,
2539
- * coerce booleans/numbers, and leave `nextRunAt`/`lastTriggeredAt` undefined
2540
- * when missing (a fresh recompute or the next tick fixes them).
2857
+ * Repair a persisted schedule rule. Both modes are repaired field by field and
2858
+ * a rule that carries no usable plan is dropped (the task row survives):
2859
+ * a recurring rule without a well-formed 5-field cron, or a one-shot without a
2860
+ * finite planned instant. A rule written before `mode` existed is a cron rule.
2541
2861
  */
2542
2862
  function normalizeSchedule(schedule) {
2543
2863
  if (typeof schedule !== "object" || schedule === null) return void 0;
2544
2864
  const rule = schedule;
2865
+ const timeZone = typeof rule.timeZone === "string" && isValidTimeZone(rule.timeZone) ? rule.timeZone : void 0;
2866
+ const runCountValue = rule.runCount;
2867
+ const runCount = typeof runCountValue === "number" && Number.isInteger(runCountValue) && runCountValue >= 0 ? runCountValue : 0;
2868
+ const endedAt = optionalInstant(rule.endedAt);
2869
+ const endedReason = optionalStopReason(rule.endedReason);
2870
+ const skippedAt = optionalInstant(rule.skippedAt);
2871
+ const skippedReason = optionalStopReason(rule.skippedReason);
2872
+ const common = {
2873
+ enabled: rule.enabled === true,
2874
+ ...timeZone === void 0 ? {} : { timeZone },
2875
+ nextRunAt: optionalInstant(rule.nextRunAt),
2876
+ lastTriggeredAt: optionalInstant(rule.lastTriggeredAt),
2877
+ runCount,
2878
+ ...endedAt === void 0 ? {} : { endedAt },
2879
+ ...endedReason === void 0 ? {} : { endedReason },
2880
+ ...skippedAt === void 0 ? {} : { skippedAt },
2881
+ ...skippedReason === void 0 ? {} : { skippedReason }
2882
+ };
2883
+ if (rule.mode === "once") {
2884
+ const at = optionalInstant(rule.at);
2885
+ if (at === void 0) return void 0;
2886
+ return {
2887
+ ...common,
2888
+ mode: "once",
2889
+ at
2890
+ };
2891
+ }
2545
2892
  if (typeof rule.cron !== "string") return void 0;
2546
2893
  if (rule.cron.trim() === "" || !isValidCron(rule.cron)) return void 0;
2547
- const timeZone = typeof rule.timeZone === "string" && isValidTimeZone(rule.timeZone) ? rule.timeZone : void 0;
2894
+ const maxRuns = optionalRunCap(rule.maxRuns);
2548
2895
  return {
2549
- enabled: rule.enabled === true,
2896
+ ...common,
2897
+ mode: "cron",
2550
2898
  cron: rule.cron,
2551
- ...timeZone === void 0 ? {} : { timeZone },
2552
- nextRunAt: typeof rule.nextRunAt === "number" ? rule.nextRunAt : void 0,
2553
- lastTriggeredAt: typeof rule.lastTriggeredAt === "number" ? rule.lastTriggeredAt : void 0
2899
+ ...maxRuns === void 0 ? {} : { maxRuns }
2554
2900
  };
2555
2901
  }
2556
2902
  /**
@@ -2627,14 +2973,15 @@ window.__ModuleLoader__.load({
2627
2973
  runGroupId: normalizeTargetId(execution.runGroupId),
2628
2974
  ownResult: isExecutionOutcome(execution.ownResult) ? execution.ownResult : void 0,
2629
2975
  ownError: typeof execution.ownError === "string" ? execution.ownError : void 0,
2630
- verification: normalizeVerification(execution.verification)
2976
+ verification: normalizeVerification(execution.verification),
2977
+ external: execution.external === true ? true : void 0
2631
2978
  }));
2632
2979
  task.workspaceId = normalizeTargetId(row.workspaceId);
2633
2980
  task.mode = normalizeTargetId(row.mode);
2634
2981
  task.archivedAt = typeof row.archivedAt === "number" && Number.isFinite(row.archivedAt) ? row.archivedAt : void 0;
2635
2982
  task.permission = isTaskPermission(row.permission) ? row.permission : void 0;
2636
2983
  task.reuseSession = row.reuseSession === true ? true : void 0;
2637
- task.goalRun = row.goalRun === false ? false : void 0;
2984
+ task.goalRun = row.goalRun === true ? true : void 0;
2638
2985
  task.skipVerification = row.skipVerification === true ? true : void 0;
2639
2986
  task.freeze = normalizeFreeze(row.freeze);
2640
2987
  task.handover = normalizeHandover(row.handover);
@@ -2877,6 +3224,7 @@ window.__ModuleLoader__.load({
2877
3224
  "detail.noSession": "暂无会话",
2878
3225
  "detail.executionStarted": "已启动",
2879
3226
  "detail.execution.initiator": "发起会话 {session}",
3227
+ "detail.execution.external": "Host 之外完成:{agent}",
2880
3228
  "detail.executionEnded": "已结束",
2881
3229
  "detail.result.succeeded": "成功",
2882
3230
  "detail.result.failed": "失败",
@@ -2919,7 +3267,34 @@ window.__ModuleLoader__.load({
2919
3267
  "detail.schedule.duration.hours": "{count} 小时",
2920
3268
  "detail.schedule.duration.minutes": "{count} 分钟",
2921
3269
  "detail.schedule.duration.seconds": "{count} 秒",
3270
+ "detail.schedule.mode": "调度方式",
3271
+ "detail.schedule.mode.cron": "循环",
3272
+ "detail.schedule.mode.once": "单次",
3273
+ "detail.schedule.at": "执行时间",
3274
+ "detail.schedule.atHint": "按所选时区解读的计划时刻;必须是未来时间。",
3275
+ "detail.schedule.atInvalid": "请选择未来的有效时间",
3276
+ "detail.schedule.plannedAt": "计划时间",
3277
+ "detail.schedule.zoneOnceHint": "所选时刻按此时区解读。",
3278
+ "detail.schedule.maxRuns": "次数上限",
3279
+ "detail.schedule.maxRunsUnlimited": "不限次数",
3280
+ "detail.schedule.maxRunsValue": "最多 {count} 次",
3281
+ "detail.schedule.maxRunsCustom": "自定义",
3282
+ "detail.schedule.maxRunsInvalid": "次数上限必须是正整数",
3283
+ "detail.schedule.runs": "已执行 {done}/{total} 次",
3284
+ "detail.schedule.runsUnlimited": "已执行 {done} 次",
3285
+ "detail.schedule.remaining": "剩余 {count} 次",
3286
+ "detail.schedule.cancel": "关闭调度",
3287
+ "detail.schedule.ended": "调度已结束:{reason}",
3288
+ "detail.schedule.skipped": "上次跳过:{reason}",
3289
+ "detail.schedule.stop.fired": "单次计划已执行",
3290
+ "detail.schedule.stop.limit": "已达次数上限",
3291
+ "detail.schedule.stop.no-target": "计划已无可执行时刻",
3292
+ "detail.schedule.stop.missed": "错过执行时刻,已跳过",
3293
+ "detail.schedule.stop.busy": "触发时任务正在运行,已跳过",
3294
+ "detail.schedule.stop.permission": "权限待人工确认,已跳过",
3295
+ "detail.schedule.stop.launch-failed": "启动失败未创建执行,次数已退回",
2922
3296
  "card.scheduled": "定时",
3297
+ "card.scheduledOnce": "单次",
2923
3298
  "new.workspace": "工作区",
2924
3299
  "new.agentPreset": "Agent 预设",
2925
3300
  "new.permission": "权限",
@@ -2949,7 +3324,7 @@ window.__ModuleLoader__.load({
2949
3324
  "exec.teamRunUnavailable": "当前部署未提供 Agent Teams 服务,无法启用。",
2950
3325
  "exec.reuseSessionHint": "开启后,本任务的后续执行在上一次会话里继续(该会话空闲且仍存在时),不再每次新建对话;每次复用时都会重新应用上面钉住的权限与模型。",
2951
3326
  "exec.goalRun": "以 dsh 内置的 /goal 开始执行任务",
2952
- "exec.goalRunHint": "默认开启:执行时把任务目标作为持久目标交给 dsh 内置的 /goal,会话会自动续跑多轮,直到 agent 标记目标完成;看板在该目标真正结束后才结算本次执行。关闭则只执行一轮普通对话。",
3327
+ "exec.goalRunHint": "默认关闭:勾选后执行时把任务目标作为持久目标交给 dsh 内置的 /goal,会话会自动续跑多轮,直到 agent 标记目标完成;看板在该目标真正结束后才结算本次执行。不勾选则只执行一轮普通对话。",
2953
3328
  "exec.skipVerification": "跳过本任务的验收",
2954
3329
  "exec.skipVerificationHint": "默认关闭:勾选后本任务以 goal 形式执行时不再拦截 update_goal 标记完成,也不要求通过验收;执行记录会标注「该卡片跳过验收」。仅对之后新开的执行生效,验收总开关关闭时本项无意义。",
2955
3330
  "detail.executionSettings": "执行设置",
@@ -2982,9 +3357,16 @@ window.__ModuleLoader__.load({
2982
3357
  "settings.unsaved": "未保存",
2983
3358
  "settings.saveFailed": "部署未接受这些值,已保留供你修改。",
2984
3359
  "settings.invalidNumber": "请输入数字,留空则使用默认值。",
3360
+ "settings.sessionPollSeconds": "会话轮询间隔(秒)",
3361
+ "settings.sessionPollSecondsHint": "默认 5 秒。仅在看板有需要核对的事情——运行中的卡片或未结算的执行——时才读取会话名册;两者都没有时不再轮询,Host 不会持续扫描持久化会话目录。取值 1–300 秒:间隔越短结算越快、Host 负载越高。",
2985
3362
  "settings.maxSubtaskDepth": "子任务深度上限",
2986
3363
  "settings.maxSubtaskDepthHint": "默认 1:一个任务只允许一层子任务,子任务不能再创建或关联子任务。最大 3。执行父任务会并发执行它的整棵子任务树,层级越深,一次执行开启的会话越多。",
2987
3364
  "settings.maxSubtaskDepthOption": "{depth} 层",
3365
+ "settings.runBehaviorTitle": "运行行为",
3366
+ "settings.runBehaviorCardHint": "全局原生 /goal 执行开关(总开关,默认关闭)。",
3367
+ "settings.goalRunGlobal": "全局启用 DSH 原生 /goal 执行",
3368
+ "settings.goalRunGlobalHint": "默认关闭。关闭时所有任务都按普通单回合执行,不调用 dsh 内置的 /goal;只有这里开启、且任务级「以 /goal 开始执行」被勾选时,才执行原生 /goal。任务级勾选始终可操作、也不会被这里的开关改写,总开关只决定它是否生效。开关按执行启动时冻结,只影响之后新开的执行。",
3369
+ "settings.goalRunGlobalDisabledTaskOption": "全局「原生 /goal 执行」总开关已关闭:任务级选项仍可随时勾选或取消,但暂不生效,你保存的偏好也不会被改写;开启总开关后即按该偏好执行。本次执行按普通单回合进行,未执行 goal 验收。",
2988
3370
  "settings.goalVerificationTitle": "任务验收",
2989
3371
  "settings.goalVerification": "启用任务验收",
2990
3372
  "settings.goalVerificationCardHint": "验收开关、裁判模型与判据阈值。",
@@ -3014,6 +3396,7 @@ window.__ModuleLoader__.load({
3014
3396
  "running.repairing": "验收未通过修复中",
3015
3397
  "running.verificationPassed": "验收通过",
3016
3398
  "running.verificationFailed": "验收未通过",
3399
+ "running.verificationInvalid": "验收无效,等待处理",
3017
3400
  "verify.title": "验收报告",
3018
3401
  "verify.status.passed": "通过",
3019
3402
  "verify.status.failed": "未通过",
@@ -3021,6 +3404,34 @@ window.__ModuleLoader__.load({
3021
3404
  "verify.status.verifying": "验收中",
3022
3405
  "verify.status.pending": "待验收",
3023
3406
  "verify.status.off": "本次执行未启用验收",
3407
+ "verify.status.invalid": "验收无效",
3408
+ "verify.status.invalidHeld": "验收无效,等待人工处理",
3409
+ "verify.attemptInvalid": "第 {index} 次验收无效",
3410
+ "verify.invalidReason.missing-finding": "未达标的判据没有对应的结构化问题反馈",
3411
+ "verify.invalidReason.unlocatable-quote": "问题反馈引用的位置不存在于本次裁判所见的证据中",
3412
+ "verify.invalidReason.baseline-finding": "问题反馈指向空工作基线,而不是本次执行的工作",
3413
+ "verify.invalidReason.vacuous-finding": "问题反馈为空或空泛评价,没有说明实际观测与差异",
3414
+ "verify.invalidReason.insufficient-evidence": "裁判看到的证据不足(例如轨迹被截断),无法支撑否决",
3415
+ "verify.invalidReason.unknown": "未产生可用的质量判定",
3416
+ "verify.invalidCounts": "有效质量判定 {quality}/{max} · 无效验收 {invalid}/{maxInvalid} · 验收异常 {exceptions}/{max}",
3417
+ "verify.invalidDetail": "无效原因:{reason}",
3418
+ "verify.criterionFindings": "结构化问题反馈(判据 · 引用位置 · 实际引用)",
3419
+ "verify.criterionFinding": "{criterion} · {location}:“{quote}”",
3420
+ "verify.criterionFindingFull": "{criterion} · {location}:“{quote}” 要求:{requirement} 观测:{observation} 差异:{gap}",
3421
+ "verify.location.task": "任务要求",
3422
+ "verify.location.trajectory": "执行轨迹",
3423
+ "verify.location.workspace": "宿主记录的变更",
3424
+ "verify.location.baseline": "空工作基线(不构成否决证据)",
3425
+ "verify.location.unknown": "未标明位置",
3426
+ "verify.detailRemoved": "本条验收的详细材料已在通过并成功结算后自动清理(不是裁判未举证)。",
3427
+ "verify.cleanup.cleaned": "本次执行的验收专用详细材料已在通过并成功结算后自动清理;以下为保留的轻量审计凭据。",
3428
+ "verify.cleanup.pending": "验收专用详细材料待清理(将通过并结算后启动)。",
3429
+ "verify.cleanup.failed": "验收详细材料清理失败(已重试 {attempts} 次):{error};通过记录仍然有效,宿主重启后会补清。",
3430
+ "verify.cleanup.audit": "保留凭据:第 {index} 次 · {stage} · 分数 {score} · 证据哈希 {hash}",
3431
+ "verify.cleanup.stage.quality": "质量判定",
3432
+ "verify.cleanup.stage.invalid": "无效验收",
3433
+ "verify.cleanup.stage.exception": "验收异常",
3434
+ "verify.cleanup.stage.budget": "时间预算耗尽",
3024
3435
  "verify.summary": "总分 {score}(阈值 {threshold})· 空工作基线 {baseline}",
3025
3436
  "verify.criteria": "逐项判据",
3026
3437
  "verify.criterion": "{name}:{score}(阈值 {threshold})",
@@ -3039,9 +3450,10 @@ window.__ModuleLoader__.load({
3039
3450
  "verify.usageIncomplete": "(至少一次请求的用量未知,以上为下限)",
3040
3451
  "verify.finalFailure": "判定依据:{reason}",
3041
3452
  "verify.applicability.disabled": "本次执行启动时验收开关关闭,按原有回合判定结算。",
3042
- "verify.applicability.goalUnavailable": "本次执行未成为 goal 执行(/goal 被拒绝或不可用),验收未强制执行。",
3453
+ "verify.applicability.goalUnavailable": "本次执行未成为 goal 执行(卡片未勾选「以 /goal 开始执行任务」,或 /goal 被拒绝/不可用),验收未强制执行。",
3043
3454
  "verify.applicability.skipped": "该任务卡勾选了「跳过验收」,本次执行未经验收判定。",
3044
3455
  "verify.applicability.teamMember": "团队执行成员:由 Lead 的团队汇总证据统一验收。",
3456
+ "verify.applicability.goalDisabled": "本次执行启动时全局「原生 /goal 执行」总开关关闭,按普通单回合执行:本次未执行 goal 验收,既不算已验收通过,也不因此判失败。",
3045
3457
  "verify.effortFallback": "推理强度回退:{requested} → {resolved}",
3046
3458
  "verify.thresholdValue": "{value}%",
3047
3459
  "detail.parent": "父任务",
@@ -3236,6 +3648,7 @@ window.__ModuleLoader__.load({
3236
3648
  "detail.noSession": "No session",
3237
3649
  "detail.executionStarted": "Started",
3238
3650
  "detail.execution.initiator": "Initiated by session {session}",
3651
+ "detail.execution.external": "Completed outside this Host by {agent}",
3239
3652
  "detail.executionEnded": "Ended",
3240
3653
  "detail.result.succeeded": "Succeeded",
3241
3654
  "detail.result.failed": "Failed",
@@ -3278,7 +3691,34 @@ window.__ModuleLoader__.load({
3278
3691
  "detail.schedule.duration.hours": "{count}h",
3279
3692
  "detail.schedule.duration.minutes": "{count}m",
3280
3693
  "detail.schedule.duration.seconds": "{count}s",
3694
+ "detail.schedule.mode": "Mode",
3695
+ "detail.schedule.mode.cron": "Recurring",
3696
+ "detail.schedule.mode.once": "Once",
3697
+ "detail.schedule.at": "Run at",
3698
+ "detail.schedule.atHint": "Planned instant, read in the selected zone; it must be in the future.",
3699
+ "detail.schedule.atInvalid": "Choose a valid future time",
3700
+ "detail.schedule.plannedAt": "Planned time",
3701
+ "detail.schedule.zoneOnceHint": "The chosen instant is read in this zone.",
3702
+ "detail.schedule.maxRuns": "Run limit",
3703
+ "detail.schedule.maxRunsUnlimited": "Unlimited",
3704
+ "detail.schedule.maxRunsValue": "At most {count} runs",
3705
+ "detail.schedule.maxRunsCustom": "Custom",
3706
+ "detail.schedule.maxRunsInvalid": "The run limit must be a positive whole number",
3707
+ "detail.schedule.runs": "Ran {done}/{total}",
3708
+ "detail.schedule.runsUnlimited": "Ran {done}",
3709
+ "detail.schedule.remaining": "{count} left",
3710
+ "detail.schedule.cancel": "Cancel schedule",
3711
+ "detail.schedule.ended": "Schedule ended: {reason}",
3712
+ "detail.schedule.skipped": "Last skip: {reason}",
3713
+ "detail.schedule.stop.fired": "the one-shot ran",
3714
+ "detail.schedule.stop.limit": "the run limit was reached",
3715
+ "detail.schedule.stop.no-target": "the plan has no reachable occurrence",
3716
+ "detail.schedule.stop.missed": "the instant was missed and skipped",
3717
+ "detail.schedule.stop.busy": "the task was already running, so the occurrence was skipped",
3718
+ "detail.schedule.stop.permission": "a permission awaited human confirmation, so the occurrence was skipped",
3719
+ "detail.schedule.stop.launch-failed": "the launch failed before any execution existed, so the run was refunded",
3281
3720
  "card.scheduled": "scheduled",
3721
+ "card.scheduledOnce": "once",
3282
3722
  "new.workspace": "Workspace",
3283
3723
  "new.agentPreset": "Agent preset",
3284
3724
  "new.permission": "Permission",
@@ -3308,7 +3748,7 @@ window.__ModuleLoader__.load({
3308
3748
  "exec.teamRunUnavailable": "This deployment serves no Agent Teams service, so team runs cannot be enabled.",
3309
3749
  "exec.reuseSessionHint": "When on, later runs continue in the previous session (when that session is idle and still exists) instead of starting a new conversation each time; the pinned permission and model above are re-applied on every reuse.",
3310
3750
  "exec.goalRun": "Start the run with dsh's built-in /goal",
3311
- "exec.goalRunHint": "On by default: the run arms dsh built-in /goal with the task objective, so the session keeps working automatic continuation rounds until the agent marks the goal complete, and the board settles the execution only when that goal really ends. Off: one plain turn.",
3751
+ "exec.goalRunHint": "Off by default: when checked, the run arms dsh built-in /goal with the task objective, so the session keeps working automatic continuation rounds until the agent marks the goal complete, and the board settles the execution only when that goal really ends. Unchecked: one plain turn.",
3312
3752
  "exec.skipVerification": "Skip acceptance for this task",
3313
3753
  "exec.skipVerificationHint": "Off by default: when checked, a goal-form run of this task no longer gates update_goal on a passing acceptance, and the execution record is marked as skipped. It applies only to executions started afterwards, and means nothing while the board-wide acceptance switch is off.",
3314
3754
  "detail.executionSettings": "Execution Settings",
@@ -3341,9 +3781,16 @@ window.__ModuleLoader__.load({
3341
3781
  "settings.unsaved": "Unsaved",
3342
3782
  "settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
3343
3783
  "settings.invalidNumber": "Enter a number, or leave blank to use the default.",
3784
+ "settings.sessionPollSeconds": "Session poll interval (seconds)",
3785
+ "settings.sessionPollSecondsHint": "Default 5. The Host reads the DSH session roster only while the board has something to reconcile: a running card or an open execution. With neither it stops polling and never re-scans the persisted session directory. Range 1 to 300 seconds: a shorter interval settles runs sooner and costs the Host more.",
3344
3786
  "settings.maxSubtaskDepth": "Subtask depth limit",
3345
3787
  "settings.maxSubtaskDepthHint": "Default 1: a task may carry one level of subtasks, and a subtask cannot create or link further subtasks. Maximum 3. Running a task also runs its whole subtask tree concurrently, and every extra level opens more sessions per run.",
3346
3788
  "settings.maxSubtaskDepthOption": "{depth} levels",
3789
+ "settings.runBehaviorTitle": "Run behavior",
3790
+ "settings.runBehaviorCardHint": "The global native /goal switch (master control, off by default).",
3791
+ "settings.goalRunGlobal": "Global: run tasks with dsh native /goal",
3792
+ "settings.goalRunGlobalHint": "Off by default. While off, every task runs one plain turn and never calls dsh built-in /goal; a run uses native /goal only when this is on AND the task-level option is checked (see the task option below). The task-level checkbox always stays operable and this switch never rewrites it: the master switch only decides whether that choice takes effect. The switch is frozen when an execution starts, so it only affects runs opened afterwards.",
3793
+ "settings.goalRunGlobalDisabledTaskOption": "The global native /goal switch is off: the task-level option can still be checked or cleared, but it has no effect right now and the preference you save is never rewritten; turning the global switch back on applies it. This execution runs one plain turn and performs no goal acceptance.",
3347
3794
  "settings.goalVerificationTitle": "Task acceptance",
3348
3795
  "settings.goalVerification": "Enable task acceptance",
3349
3796
  "settings.goalVerificationCardHint": "The switch, the judge model and the criteria threshold.",
@@ -3373,6 +3820,7 @@ window.__ModuleLoader__.load({
3373
3820
  "running.repairing": "Fixing a failed acceptance",
3374
3821
  "running.verificationPassed": "Acceptance passed",
3375
3822
  "running.verificationFailed": "Acceptance failed",
3823
+ "running.verificationInvalid": "Invalid acceptance, waiting for a human",
3376
3824
  "verify.title": "Acceptance report",
3377
3825
  "verify.status.passed": "Passed",
3378
3826
  "verify.status.failed": "Failed",
@@ -3380,6 +3828,34 @@ window.__ModuleLoader__.load({
3380
3828
  "verify.status.verifying": "Verifying",
3381
3829
  "verify.status.pending": "Not yet verified",
3382
3830
  "verify.status.off": "Acceptance was off for this execution",
3831
+ "verify.status.invalid": "Invalid acceptance",
3832
+ "verify.status.invalidHeld": "Invalid acceptance, waiting for a human",
3833
+ "verify.attemptInvalid": "Invalid acceptance {index}",
3834
+ "verify.invalidReason.missing-finding": "A failing criterion carries no structured finding of its own",
3835
+ "verify.invalidReason.unlocatable-quote": "A finding cites a passage that is not in the evidence the judge saw",
3836
+ "verify.invalidReason.baseline-finding": "A finding points at the empty-work baseline instead of this execution's work",
3837
+ "verify.invalidReason.vacuous-finding": "A finding is empty or a generic evaluation with no observation or difference",
3838
+ "verify.invalidReason.insufficient-evidence": "The judge saw too little evidence (for example a truncated trace) to support a veto",
3839
+ "verify.invalidReason.unknown": "No usable quality verdict was produced",
3840
+ "verify.invalidCounts": "Valid quality verdicts {quality}/{max} · invalid acceptances {invalid}/{maxInvalid} · anomalies {exceptions}/{max}",
3841
+ "verify.invalidDetail": "Invalid because: {reason}",
3842
+ "verify.criterionFindings": "Structured findings (criterion · cited location · verbatim citation)",
3843
+ "verify.criterionFinding": "{criterion} · {location}: \"{quote}\"",
3844
+ "verify.criterionFindingFull": "{criterion} · {location}: \"{quote}\" requirement: {requirement} observed: {observation} gap: {gap}",
3845
+ "verify.location.task": "task statement",
3846
+ "verify.location.trajectory": "execution trajectory",
3847
+ "verify.location.workspace": "host-recorded changes",
3848
+ "verify.location.baseline": "empty-work baseline (not evidence against the work)",
3849
+ "verify.location.unknown": "location not named",
3850
+ "verify.detailRemoved": "This attempt detail was cleaned up automatically after the execution passed and settled (the judge did report evidence).",
3851
+ "verify.cleanup.cleaned": "The acceptance-only detail of this execution was cleaned up automatically after it passed and settled; what remains is the lightweight audit credential.",
3852
+ "verify.cleanup.pending": "The acceptance-only detail is pending cleanup (started once the execution passes and settles).",
3853
+ "verify.cleanup.failed": "Cleaning the acceptance detail failed after {attempts} attempts: {error}. The pass record is still valid and the host retries on its next start.",
3854
+ "verify.cleanup.audit": "Kept credential: attempt {index} · {stage} · score {score} · evidence hash {hash}",
3855
+ "verify.cleanup.stage.quality": "quality verdict",
3856
+ "verify.cleanup.stage.invalid": "invalid acceptance",
3857
+ "verify.cleanup.stage.exception": "acceptance anomaly",
3858
+ "verify.cleanup.stage.budget": "time budget exhausted",
3383
3859
  "verify.summary": "Total {score} (threshold {threshold}) · empty-work baseline {baseline}",
3384
3860
  "verify.criteria": "Per-criterion scores",
3385
3861
  "verify.criterion": "{name}: {score} (threshold {threshold})",
@@ -3398,9 +3874,10 @@ window.__ModuleLoader__.load({
3398
3874
  "verify.usageIncomplete": " (at least one request usage is unknown; these counts are a floor)",
3399
3875
  "verify.finalFailure": "Verdict basis: {reason}",
3400
3876
  "verify.applicability.disabled": "Acceptance was off when this execution started; it settles on the historical verdict.",
3401
- "verify.applicability.goalUnavailable": "This execution never became a goal run (/goal was refused or unavailable), so acceptance was not enforced.",
3877
+ "verify.applicability.goalUnavailable": "This execution never became a goal run (the card did not check Start with /goal, or /goal was refused or unavailable), so acceptance was not enforced.",
3402
3878
  "verify.applicability.skipped": "This card checked Skip acceptance, so the execution was never judged.",
3403
3879
  "verify.applicability.teamMember": "Team member: the Lead team-summary evidence is accepted as one execution.",
3880
+ "verify.applicability.goalDisabled": "The global native /goal switch was off when this execution started, so it ran one plain turn: no goal acceptance was performed, which neither counts as a pass nor fails the run.",
3404
3881
  "verify.effortFallback": "Reasoning effort fallback: {requested} → {resolved}",
3405
3882
  "verify.thresholdValue": "{value}%",
3406
3883
  "detail.parent": "Parent task",
@@ -3812,6 +4289,95 @@ window.__ModuleLoader__.load({
3812
4289
  if (delta < 0) return t("detail.schedule.nextRunOverdue", { duration: durationParts(-delta) });
3813
4290
  return `${durationParts(delta)}`;
3814
4291
  }
4292
+ /** Zero-pad a calendar/clock field for a `datetime-local` value. */
4293
+ function pad(value) {
4294
+ return String(value).padStart(2, "0");
4295
+ }
4296
+ /**
4297
+ * The `datetime-local` input value for an instant, read in an explicit zone:
4298
+ * `YYYY-MM-DDTHH:mm`. Uses the shared engine's zone reader, so the editor
4299
+ * shows the same wall clock the Host schedules by.
4300
+ * @param epochMs - the instant to render.
4301
+ * @param timeZone - zone to read it in; absent uses the Host zone.
4302
+ * @returns the input value.
4303
+ */
4304
+ function zonedInputValue(epochMs, timeZone) {
4305
+ const parts = partsInZone(usableZone(timeZone), epochMs);
4306
+ return `${parts.year}-${pad(parts.month)}-${pad(parts.day)}T${pad(parts.hour)}:${pad(parts.minute)}`;
4307
+ }
4308
+ /**
4309
+ * Parse a `datetime-local` input value back to an instant, read in an explicit
4310
+ * zone. Returns undefined when the text is not a whole calendar wall clock or
4311
+ * names one the zone skips (a spring-forward gap), so the editor can refuse an
4312
+ * unusable instant instead of silently moving it.
4313
+ * @param value - the input text.
4314
+ * @param timeZone - zone to interpret it in; absent uses the Host zone.
4315
+ * @returns the instant, or undefined when it is not a usable wall clock.
4316
+ */
4317
+ function parseZonedInput(value, timeZone) {
4318
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(value.trim());
4319
+ if (match === null) return void 0;
4320
+ const [year, month, day, hour, minute] = match.slice(1).map(Number);
4321
+ if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59) return void 0;
4322
+ return zonedEpoch(usableZone(timeZone), year, month, day, hour, minute);
4323
+ }
4324
+ /**
4325
+ * The default instant a new one-shot editor opens on: the next whole hour,
4326
+ * rendered in the zone the rule will be read in.
4327
+ * @param now - reference instant.
4328
+ * @param timeZone - zone the rule is read in.
4329
+ * @returns the input value for a near-future default.
4330
+ */
4331
+ function defaultZonedInputValue(now, timeZone) {
4332
+ return zonedInputValue(Math.ceil(now / 36e5) * 36e5, timeZone);
4333
+ }
4334
+ /** The zone the input helpers resolve against: an explicit resolved zone, or the Host zone. */
4335
+ function usableZone(timeZone) {
4336
+ return timeZone !== void 0 && isValidTimeZone(timeZone) ? timeZone : resolveHostTimeZone();
4337
+ }
4338
+ /**
4339
+ * The editor's run-cap option for a stored cap: the two named presets, the
4340
+ * unlimited entry, or a custom value the editor shows in its own field.
4341
+ * @param maxRuns - the stored cap, when any.
4342
+ * @returns the select option value.
4343
+ */
4344
+ function runCapOption(maxRuns) {
4345
+ if (maxRuns === void 0) return "unlimited";
4346
+ if (maxRuns === 1) return "1";
4347
+ if (maxRuns === 2) return "2";
4348
+ return "custom";
4349
+ }
4350
+ /** The locale key describing why a rule stopped or skipped an occurrence. */
4351
+ function stopReasonKey(reason) {
4352
+ return `detail.schedule.stop.${reason}`;
4353
+ }
4354
+ /**
4355
+ * The run-budget line: how many scheduled executions the Host opened and how
4356
+ * many remain. Derived from the counter and the cap rather than stored, so the
4357
+ * display cannot drift from the ledger.
4358
+ * @param rule - the schedule rule.
4359
+ * @returns localized wording.
4360
+ */
4361
+ function budgetLabel(rule) {
4362
+ const budget = scheduleRunBudget(rule);
4363
+ const done = String(rule.runCount);
4364
+ if (budget === void 0) return t("detail.schedule.runsUnlimited", { done });
4365
+ const remaining = Math.max(budget - rule.runCount, 0);
4366
+ return `${t("detail.schedule.runs", {
4367
+ done,
4368
+ total: String(budget)
4369
+ })} · ${t("detail.schedule.remaining", { count: String(remaining) })}`;
4370
+ }
4371
+ /** The line describing a rule that stopped itself, or undefined while it may still fire. */
4372
+ function endedLabel(rule) {
4373
+ if (rule.endedReason === void 0) return void 0;
4374
+ return t("detail.schedule.ended", { reason: t(stopReasonKey(rule.endedReason)) });
4375
+ }
4376
+ /** The line describing the last skipped occurrence, or undefined when none. */
4377
+ function skippedLabel(rule) {
4378
+ if (rule.skippedReason === void 0) return void 0;
4379
+ return t("detail.schedule.skipped", { reason: t(stopReasonKey(rule.skippedReason)) });
4380
+ }
3815
4381
  /**
3816
4382
  * The full next-run label: the absolute wall clock in the schedule's own zone,
3817
4383
  * plus the relative distance in parentheses. Showing both matters because a
@@ -4474,7 +5040,8 @@ window.__ModuleLoader__.load({
4474
5040
  verifying: "running.verifying",
4475
5041
  repairing: "running.repairing",
4476
5042
  passed: "running.verificationPassed",
4477
- failed: "running.verificationFailed"
5043
+ failed: "running.verificationFailed",
5044
+ invalid: "running.verificationInvalid"
4478
5045
  };
4479
5046
  /**
4480
5047
  * The acceptance label for one open execution, or undefined when acceptance
@@ -6649,8 +7216,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6649
7216
  }),
6650
7217
  !archived && task.schedule?.enabled === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6651
7218
  className: board_module_css_default.cardSchedule,
6652
- title: task.schedule.nextRunAt !== void 0 ? `${t("card.scheduled")} · ${formatHostTimestamp(task.schedule.nextRunAt, timeZone)}` : t("card.scheduled"),
6653
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconClock, { size: 12 }), t("card.scheduled")]
7219
+ title: task.schedule.nextRunAt !== void 0 ? `${t(task.schedule.mode === "once" ? "card.scheduledOnce" : "card.scheduled")} · ${formatHostTimestamp(task.schedule.nextRunAt, timeZone)}` : t(task.schedule.mode === "once" ? "card.scheduledOnce" : "card.scheduled"),
7220
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconClock, { size: 12 }), t(task.schedule.mode === "once" ? "card.scheduledOnce" : "card.scheduled")]
6654
7221
  }),
6655
7222
  latest !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6656
7223
  className: board_module_css_default.cardRun,
@@ -6765,10 +7332,14 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6765
7332
  const [model, setModel] = (0, react.useState)(initialTask?.model ?? parentTask?.model ?? "");
6766
7333
  const inheritedPermission = parentTask === void 0 ? void 0 : effectiveTaskPermission(parentTask);
6767
7334
  const [reuseSession, setReuseSession] = (0, react.useState)(initialTask?.reuseSession ?? false);
6768
- const [goalRun, setGoalRun] = (0, react.useState)(initialTask?.goalRun ?? true);
7335
+ const [goalRun, setGoalRun] = (0, react.useState)(initialTask?.goalRun ?? false);
6769
7336
  const [skipVerification, setSkipVerification] = (0, react.useState)(initialTask?.skipVerification ?? false);
6770
7337
  const [scheduleEnabled, setScheduleEnabled] = (0, react.useState)(initialTask?.schedule?.enabled ?? false);
7338
+ const [scheduleMode, setScheduleMode] = (0, react.useState)(initialTask?.schedule?.mode ?? "cron");
6771
7339
  const [scheduleCron, setScheduleCron] = (0, react.useState)(initialTask?.schedule?.cron ?? "");
7340
+ const [scheduleAt, setScheduleAt] = (0, react.useState)(() => initialTask?.schedule?.mode === "once" && initialTask.schedule.at !== void 0 ? zonedInputValue(initialTask.schedule.at, initialTask.schedule.timeZone) : defaultZonedInputValue(Date.now(), initialTask?.schedule?.timeZone));
7341
+ const [scheduleMaxRunsChoice, setScheduleMaxRunsChoice] = (0, react.useState)(() => runCapOption(initialTask?.schedule?.maxRuns));
7342
+ const [scheduleMaxRunsText, setScheduleMaxRunsText] = (0, react.useState)(initialTask?.schedule?.maxRuns === void 0 ? "" : String(initialTask.schedule.maxRuns));
6772
7343
  const [scheduleZone, setScheduleZone] = (0, react.useState)(initialTask?.schedule?.timeZone ?? "");
6773
7344
  const [scheduleError, setScheduleError] = (0, react.useState)(void 0);
6774
7345
  const [freezeText, setFreezeText] = (0, react.useState)("");
@@ -6779,6 +7350,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6779
7350
  const [error, setError] = (0, react.useState)(void 0);
6780
7351
  const [pending, setPending] = (0, react.useState)(false);
6781
7352
  const [options, setOptions] = (0, react.useState)(controller.getSnapshot().executionOptions);
7353
+ const [goalRunEnabled, setGoalRunEnabled] = (0, react.useState)(controller.getSnapshot().host?.goalRunEnabled === true);
6782
7354
  const [canParse] = (0, react.useState)(controller.getSnapshot().canParseTask === true);
6783
7355
  const [parseText, setParseText] = (0, react.useState)("");
6784
7356
  const [parseModel, setParseModel] = (0, react.useState)(() => readParseModelPreference());
@@ -6789,7 +7361,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6789
7361
  const workspaceKnown = workspaceId === "" || options.workspaces.some((item) => item.workspaceId === workspaceId);
6790
7362
  const modeKnown = mode === "" || options.presets.some((item) => item.id === mode);
6791
7363
  const modelKnown = model === "" || parseModels.some((item) => item.id === model);
6792
- (0, react.useEffect)(() => controller.subscribe(() => setOptions(controller.getSnapshot().executionOptions)), [controller]);
7364
+ (0, react.useEffect)(() => controller.subscribe(() => {
7365
+ const snapshot = controller.getSnapshot();
7366
+ setOptions(snapshot.executionOptions);
7367
+ setGoalRunEnabled(snapshot.host?.goalRunEnabled === true);
7368
+ }), [controller]);
6793
7369
  (0, react.useEffect)(() => {
6794
7370
  if (parseModel === "" || parseModels.length === 0) return;
6795
7371
  if (parseModels.some((option) => option.id === parseModel)) return;
@@ -6828,12 +7404,25 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6828
7404
  * reporting the creation as failed.
6829
7405
  */
6830
7406
  const submit = async (runAfterCreate) => {
6831
- if (scheduleEnabled) {
7407
+ if (scheduleEnabled) if (scheduleMode === "once") {
7408
+ const at = parseZonedInput(scheduleAt, scheduleZone === "" ? hostTimeZone : scheduleZone);
7409
+ if (at === void 0 || at <= Date.now()) {
7410
+ setScheduleError(t("detail.schedule.atInvalid"));
7411
+ return;
7412
+ }
7413
+ } else {
6832
7414
  const cron = scheduleCron.trim();
6833
7415
  if (cron === "" || !isValidCron(cron)) {
6834
7416
  setScheduleError(t("detail.schedule.invalid"));
6835
7417
  return;
6836
7418
  }
7419
+ if (scheduleMaxRunsChoice === "custom") {
7420
+ const cap = Number(scheduleMaxRunsText.trim());
7421
+ if (scheduleMaxRunsText.trim() === "" || !Number.isInteger(cap) || cap < 1) {
7422
+ setScheduleError(t("detail.schedule.maxRunsInvalid"));
7423
+ return;
7424
+ }
7425
+ }
6837
7426
  }
6838
7427
  let freeze = void 0;
6839
7428
  if (freezeText.trim() !== "") {
@@ -6868,12 +7457,19 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6868
7457
  permission: permission === "" ? void 0 : permission,
6869
7458
  model: model === "" ? void 0 : model,
6870
7459
  ...reuseSession ? { reuseSession: true } : {},
6871
- ...goalRun ? {} : { goalRun: false },
7460
+ ...goalRun ? { goalRun: true } : {},
6872
7461
  ...skipVerification ? { skipVerification: true } : {},
6873
7462
  ...tagList.length > 0 ? { tags: tagList } : {},
6874
- schedule: scheduleEnabled ? {
7463
+ schedule: scheduleEnabled ? scheduleMode === "once" ? {
7464
+ enabled: true,
7465
+ mode: "once",
7466
+ at: parseZonedInput(scheduleAt, scheduleZone === "" ? hostTimeZone : scheduleZone),
7467
+ ...scheduleZone === "" ? {} : { timeZone: scheduleZone }
7468
+ } : {
6875
7469
  enabled: true,
7470
+ mode: "cron",
6876
7471
  cron: scheduleCron.trim(),
7472
+ ...scheduleMaxRunsChoice === "unlimited" ? {} : { maxRuns: scheduleMaxRunsChoice === "custom" ? Number(scheduleMaxRunsText.trim()) : Number(scheduleMaxRunsChoice) },
6877
7473
  ...scheduleZone === "" ? {} : { timeZone: scheduleZone }
6878
7474
  } : void 0
6879
7475
  });
@@ -6891,7 +7487,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6891
7487
  };
6892
7488
  const hostTimeZone = controller.getSnapshot().host?.scheduler.timeZone;
6893
7489
  const scheduleTimeZone = scheduleZone === "" ? hostTimeZone : scheduleZone;
6894
- const scheduleNextRun = scheduleEnabled && scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now(), scheduleTimeZone) : void 0;
7490
+ const scheduleNextRun = !scheduleEnabled ? void 0 : scheduleMode === "once" ? parseZonedInput(scheduleAt, scheduleTimeZone) : scheduleCron.trim() !== "" && isValidCron(scheduleCron) ? nextRunAtMs(scheduleCron, Date.now(), scheduleTimeZone) : void 0;
6895
7491
  const modalTitle = parentTask !== void 0 ? t("new.subtaskTitle") : isDuplicate ? t("new.duplicateTitle") : t("board.new");
6896
7492
  const builtinPresets = options.presets.filter((preset) => isBuiltinPreset(preset.id));
6897
7493
  const customPresets = options.presets.filter((preset) => !isBuiltinPreset(preset.id));
@@ -6909,9 +7505,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6909
7505
  ].join(" · ");
6910
7506
  const tagCount = cleanTags(tags).length;
6911
7507
  const labelsSummary = tagCount === 0 ? t("new.summary.none") : t("new.summary.labelCount", { count: String(tagCount) });
6912
- const runSummary = [goalRun ? t("new.summary.multiRound") : t("new.summary.singleRound"), ...reuseSession ? [t("exec.reuseSession")] : []].join(" · ");
7508
+ const runSummary = [goalRun && goalRunEnabled ? t("new.summary.multiRound") : t("new.summary.singleRound"), ...reuseSession ? [t("exec.reuseSession")] : []].join(" · ");
6913
7509
  const handoverSummary = freezeText.trim() !== "" || handoverText.trim() !== "" ? t("new.summary.filled") : t("new.summary.none");
6914
- const scheduleSummary = !scheduleEnabled ? t("new.summary.scheduleOff") : scheduleCron.trim() === "" ? t("new.summary.none") : scheduleCron.trim();
7510
+ const scheduleSummary = !scheduleEnabled ? t("new.summary.scheduleOff") : scheduleMode === "once" ? `${t("detail.schedule.mode.once")} · ${scheduleAt.replace("T", " ")}` : scheduleCron.trim() === "" ? t("new.summary.none") : [scheduleCron.trim(), ...scheduleMaxRunsChoice === "unlimited" ? [] : [t("detail.schedule.maxRunsValue", { count: scheduleMaxRunsChoice === "custom" ? scheduleMaxRunsText : scheduleMaxRunsChoice })]].join(" · ");
6915
7511
  const parseSummary = parseText.trim() === "" ? t("new.summary.none") : t("new.summary.filled");
6916
7512
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ModalShell, {
6917
7513
  ariaLabel: modalTitle,
@@ -7202,6 +7798,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7202
7798
  className: board_module_css_default.detailText,
7203
7799
  children: t("exec.goalRunHint")
7204
7800
  }),
7801
+ !goalRunEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7802
+ className: board_module_css_default.detailText,
7803
+ children: t("settings.goalRunGlobalDisabledTaskOption")
7804
+ }),
7205
7805
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7206
7806
  className: board_module_css_default.scheduleToggle,
7207
7807
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -7277,36 +7877,116 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7277
7877
  }
7278
7878
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
7279
7879
  }), scheduleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7280
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7281
- className: board_module_css_default.scheduleRow,
7282
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7283
- className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
7284
- value: scheduleCron,
7285
- placeholder: "0 9 * * *",
7286
- spellCheck: false,
7287
- "aria-label": t("detail.schedule.cron"),
7288
- onChange: (event) => {
7289
- setScheduleCron(event.target.value);
7290
- setScheduleError(void 0);
7291
- }
7292
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7880
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7881
+ className: board_module_css_default.scheduleZone,
7882
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.mode") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7293
7883
  className: board_module_css_default.schedulePreset,
7294
- value: "",
7295
- "aria-label": t("detail.schedule.presets"),
7884
+ value: scheduleMode,
7885
+ "aria-label": t("detail.schedule.mode"),
7296
7886
  onChange: (event) => {
7297
- if (event.target.value === "") return;
7298
- setScheduleCron(event.target.value);
7887
+ setScheduleMode(event.target.value);
7299
7888
  setScheduleError(void 0);
7300
7889
  },
7301
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
7302
- value: "",
7303
- children: [t("detail.schedule.presets"), "…"]
7304
- }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7305
- value: preset.cron,
7306
- children: t(preset.label)
7307
- }, preset.cron))]
7890
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7891
+ value: "cron",
7892
+ children: t("detail.schedule.mode.cron")
7893
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7894
+ value: "once",
7895
+ children: t("detail.schedule.mode.once")
7896
+ })]
7308
7897
  })]
7309
7898
  }),
7899
+ scheduleMode === "once" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7900
+ className: board_module_css_default.scheduleZone,
7901
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.at") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7902
+ className: board_module_css_default.input,
7903
+ type: "datetime-local",
7904
+ value: scheduleAt,
7905
+ "aria-label": t("detail.schedule.at"),
7906
+ title: t("detail.schedule.atHint"),
7907
+ onChange: (event) => {
7908
+ setScheduleAt(event.target.value);
7909
+ setScheduleError(void 0);
7910
+ }
7911
+ })]
7912
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7914
+ className: board_module_css_default.scheduleRow,
7915
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7916
+ className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${scheduleError !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
7917
+ value: scheduleCron,
7918
+ placeholder: "0 9 * * *",
7919
+ spellCheck: false,
7920
+ "aria-label": t("detail.schedule.cron"),
7921
+ onChange: (event) => {
7922
+ setScheduleCron(event.target.value);
7923
+ setScheduleError(void 0);
7924
+ }
7925
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7926
+ className: board_module_css_default.schedulePreset,
7927
+ value: "",
7928
+ "aria-label": t("detail.schedule.presets"),
7929
+ onChange: (event) => {
7930
+ if (event.target.value === "") return;
7931
+ setScheduleCron(event.target.value);
7932
+ setScheduleError(void 0);
7933
+ },
7934
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
7935
+ value: "",
7936
+ children: [t("detail.schedule.presets"), "…"]
7937
+ }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7938
+ value: preset.cron,
7939
+ children: t(preset.label)
7940
+ }, preset.cron))]
7941
+ })]
7942
+ }),
7943
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7944
+ className: board_module_css_default.scheduleZone,
7945
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.maxRuns") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7946
+ className: board_module_css_default.schedulePreset,
7947
+ value: scheduleMaxRunsChoice,
7948
+ "aria-label": t("detail.schedule.maxRuns"),
7949
+ onChange: (event) => {
7950
+ setScheduleMaxRunsChoice(event.target.value);
7951
+ setScheduleError(void 0);
7952
+ if (event.target.value === "custom") setScheduleMaxRunsText(initialTask?.schedule?.maxRuns === void 0 ? "" : String(initialTask.schedule.maxRuns));
7953
+ },
7954
+ children: [
7955
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7956
+ value: "unlimited",
7957
+ children: t("detail.schedule.maxRunsUnlimited")
7958
+ }),
7959
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7960
+ value: "1",
7961
+ children: t("detail.schedule.maxRunsValue", { count: "1" })
7962
+ }),
7963
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7964
+ value: "2",
7965
+ children: t("detail.schedule.maxRunsValue", { count: "2" })
7966
+ }),
7967
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7968
+ value: "custom",
7969
+ children: t("detail.schedule.maxRunsCustom")
7970
+ })
7971
+ ]
7972
+ })]
7973
+ }),
7974
+ scheduleMaxRunsChoice === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
7975
+ className: board_module_css_default.scheduleZone,
7976
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.maxRuns") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7977
+ className: board_module_css_default.input,
7978
+ type: "number",
7979
+ min: 1,
7980
+ step: 1,
7981
+ value: scheduleMaxRunsText,
7982
+ "aria-label": t("detail.schedule.maxRuns"),
7983
+ onChange: (event) => {
7984
+ setScheduleMaxRunsText(event.target.value);
7985
+ setScheduleError(void 0);
7986
+ }
7987
+ })]
7988
+ })
7989
+ ] }),
7310
7990
  scheduleError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7311
7991
  className: board_module_css_default.formError,
7312
7992
  children: scheduleError
@@ -7317,7 +7997,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7317
7997
  className: board_module_css_default.schedulePreset,
7318
7998
  value: scheduleZone,
7319
7999
  "aria-label": t("detail.schedule.timeZone"),
7320
- title: t("detail.schedule.timeZoneHint"),
8000
+ title: scheduleMode === "once" ? t("detail.schedule.zoneOnceHint") : t("detail.schedule.timeZoneHint"),
7321
8001
  onChange: (event) => {
7322
8002
  setScheduleZone(event.target.value);
7323
8003
  setScheduleError(void 0);
@@ -7847,11 +8527,39 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7847
8527
  verifying: "verify.status.verifying",
7848
8528
  repairing: "verify.status.failed",
7849
8529
  passed: "verify.status.passed",
7850
- failed: "verify.status.failed"
8530
+ failed: "verify.status.failed",
8531
+ invalid: "verify.status.invalidHeld"
7851
8532
  };
7852
- /** One recorded attempt: a quality verdict or an acceptance anomaly. */
7853
- function AttemptRow({ attempt }) {
7854
- const exception = attempt.stage === "exception";
8533
+ /** Evidence location → locale key, for one structured finding's citation. */
8534
+ const LOCATION_KEY = {
8535
+ task: "verify.location.task",
8536
+ trajectory: "verify.location.trajectory",
8537
+ workspace: "verify.location.workspace",
8538
+ baseline: "verify.location.baseline",
8539
+ unknown: "verify.location.unknown"
8540
+ };
8541
+ /** Invalid reason → locale key. */
8542
+ function invalidReasonKey(attempt) {
8543
+ switch (attempt.invalidReason) {
8544
+ case "missing-finding": return "verify.invalidReason.missing-finding";
8545
+ case "unlocatable-quote": return "verify.invalidReason.unlocatable-quote";
8546
+ case "baseline-finding": return "verify.invalidReason.baseline-finding";
8547
+ case "vacuous-finding": return "verify.invalidReason.vacuous-finding";
8548
+ case "insufficient-evidence": return "verify.invalidReason.insufficient-evidence";
8549
+ default: return "verify.invalidReason.unknown";
8550
+ }
8551
+ }
8552
+ /** Audit stage → locale key (the lightweight credential after cleanup). */
8553
+ const AUDIT_STAGE_KEY = {
8554
+ quality: "verify.cleanup.stage.quality",
8555
+ invalid: "verify.cleanup.stage.invalid",
8556
+ exception: "verify.cleanup.stage.exception",
8557
+ budget: "verify.cleanup.stage.budget"
8558
+ };
8559
+ /** One recorded attempt: a quality verdict, an invalid veto, an anomaly, or a budget stop. */
8560
+ function AttemptRow({ attempt, detailRemoved }) {
8561
+ const invalid = attempt.stage === "invalid";
8562
+ const exception = attempt.stage !== "quality" && !invalid;
7855
8563
  const result = exception ? "failed" : attempt.passed ? "succeeded" : "failed";
7856
8564
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
7857
8565
  className: board_module_css_default.executionRow,
@@ -7860,17 +8568,21 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7860
8568
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7861
8569
  className: board_module_css_default.executionBadge,
7862
8570
  "data-result": result,
7863
- children: exception ? t("verify.status.exception") : attempt.passed ? t("verify.status.passed") : t("verify.status.failed")
8571
+ children: invalid ? t("verify.status.invalid") : exception ? t("verify.status.exception") : attempt.passed ? t("verify.status.passed") : t("verify.status.failed")
7864
8572
  }),
7865
8573
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7866
8574
  className: board_module_css_default.executionTimes,
7867
8575
  children: [
7868
- exception ? t("verify.attemptException", { index: String(attempt.index) }) : t("verify.attempt", { index: String(attempt.index) }),
8576
+ invalid ? t("verify.attemptInvalid", { index: String(attempt.index) }) : exception ? t("verify.attemptException", { index: String(attempt.index) }) : t("verify.attempt", { index: String(attempt.index) }),
7869
8577
  " · ",
7870
8578
  routeLabel(attempt),
7871
- !exception && " · " + t("verify.rounds", { rounds: String(attempt.rounds) })
8579
+ !exception && !invalid && " · " + t("verify.rounds", { rounds: String(attempt.rounds) })
7872
8580
  ]
7873
8581
  }),
8582
+ invalid && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8583
+ className: board_module_css_default.executionTimes,
8584
+ children: t("verify.invalidDetail", { reason: t(invalidReasonKey(attempt)) })
8585
+ }),
7874
8586
  attempt.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7875
8587
  className: board_module_css_default.executionTimes,
7876
8588
  children: attempt.error
@@ -7900,6 +8612,23 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7900
8612
  }, criterion.id))
7901
8613
  })
7902
8614
  ] }),
8615
+ (attempt.criterionFindings ?? []).length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8616
+ className: board_module_css_default.executionTimes,
8617
+ children: t("verify.criterionFindings")
8618
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8619
+ className: board_module_css_default.executionList,
8620
+ children: (attempt.criterionFindings ?? []).map((finding, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
8621
+ className: board_module_css_default.executionTimes,
8622
+ children: t("verify.criterionFindingFull", {
8623
+ criterion: finding.criterionId,
8624
+ location: t(LOCATION_KEY[finding.location]),
8625
+ quote: finding.quote,
8626
+ requirement: finding.requirement,
8627
+ observation: finding.observation,
8628
+ gap: finding.gap
8629
+ })
8630
+ }, findingKey(attempt, index)))
8631
+ })] }),
7903
8632
  attempt.findings.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7904
8633
  className: board_module_css_default.executionTimes,
7905
8634
  children: t("verify.findings")
@@ -7910,9 +8639,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7910
8639
  children: finding
7911
8640
  }, findingKey(attempt, index)))
7912
8641
  })] }),
7913
- !exception && attempt.findings.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8642
+ !exception && !invalid && attempt.findings.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7914
8643
  className: board_module_css_default.executionTimes,
7915
- children: t("verify.noFindings")
8644
+ children: t(detailRemoved ? "verify.detailRemoved" : "verify.noFindings")
7916
8645
  }),
7917
8646
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7918
8647
  className: board_module_css_default.executionTimes,
@@ -7948,7 +8677,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7948
8677
  if (phase === "off" && verification.contract.enabled === false && verification.applicability !== "skipped") return null;
7949
8678
  const totals = verificationTotals(verification);
7950
8679
  const route = verification.contract.route;
7951
- const applicabilityKey = verification.applicability === "disabled" ? "verify.applicability.disabled" : verification.applicability === "skipped" ? "verify.applicability.skipped" : verification.applicability === "goal-unavailable" ? "verify.applicability.goalUnavailable" : verification.applicability === "team-member" ? "verify.applicability.teamMember" : void 0;
8680
+ const applicabilityKey = verification.applicability === "disabled" ? "verify.applicability.disabled" : verification.applicability === "skipped" ? "verify.applicability.skipped" : verification.applicability === "goal-unavailable" ? "verify.applicability.goalUnavailable" : verification.applicability === "goal-disabled" ? "verify.applicability.goalDisabled" : verification.applicability === "team-member" ? "verify.applicability.teamMember" : void 0;
7952
8681
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7953
8682
  className: board_module_css_default.executionTimes,
7954
8683
  children: [
@@ -7963,9 +8692,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
7963
8692
  }),
7964
8693
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7965
8694
  className: board_module_css_default.executionTimes,
7966
- children: t("verify.counts", {
8695
+ children: t("verify.invalidCounts", {
7967
8696
  quality: String(qualityAttempts(verification).length),
7968
8697
  max: String(2),
8698
+ invalid: String(invalidAttempts(verification).length),
8699
+ maxInvalid: String(2),
7969
8700
  exceptions: String(exceptionAttempts(verification).length)
7970
8701
  })
7971
8702
  }),
@@ -8002,11 +8733,45 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8002
8733
  }),
8003
8734
  verification.attempts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8004
8735
  className: board_module_css_default.executionList,
8005
- children: [...verification.attempts].reverse().map((attempt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttemptRow, { attempt }, attempt.at.toString(36) + "-" + attempt.stage + "-" + String(attempt.index)))
8006
- })
8736
+ children: [...verification.attempts].reverse().map((attempt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttemptRow, {
8737
+ attempt,
8738
+ detailRemoved: verification.cleanup?.state === "cleaned"
8739
+ }, attempt.at.toString(36) + "-" + attempt.stage + "-" + String(attempt.index)))
8740
+ }),
8741
+ verification.cleanup !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CleanupNotice, { cleanup: verification.cleanup })
8007
8742
  ]
8008
8743
  });
8009
8744
  }
8745
+ /**
8746
+ * Retention notice of one execution's acceptance detail.
8747
+ *
8748
+ * It exists so a reader never mistakes a cleaned-up report for a judge that
8749
+ * produced no evidence: the notice says the detail WAS produced and was removed
8750
+ * by the automatic cleanup, and lists the lightweight credential that survives.
8751
+ * A pending or failed cleanup is reported the same way — never as a missing
8752
+ * verdict and never as a revoked pass.
8753
+ */
8754
+ function CleanupNotice({ cleanup }) {
8755
+ const key = cleanup.state === "cleaned" ? "verify.cleanup.cleaned" : cleanup.state === "failed" ? "verify.cleanup.failed" : "verify.cleanup.pending";
8756
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8757
+ className: board_module_css_default.executionTimes,
8758
+ children: cleanup.state === "failed" ? t(key, {
8759
+ attempts: String(cleanup.attempts),
8760
+ error: cleanup.lastError ?? ""
8761
+ }) : t(key)
8762
+ }), cleanup.audit.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8763
+ className: board_module_css_default.executionList,
8764
+ children: cleanup.audit.map((record, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
8765
+ className: board_module_css_default.executionTimes,
8766
+ children: [t("verify.cleanup.audit", {
8767
+ index: String(index + 1),
8768
+ stage: t(AUDIT_STAGE_KEY[record.stage]),
8769
+ score: percent(record.score),
8770
+ hash: record.evidenceHash.slice(0, 12)
8771
+ }), record.findingSummary === void 0 ? "" : " · " + record.findingSummary]
8772
+ }, record.at.toString(36) + "-" + record.stage + "-" + String(index)))
8773
+ })] });
8774
+ }
8010
8775
  //#endregion
8011
8776
  //#region src/client/board/ClampedMarkdown.tsx
8012
8777
  /**
@@ -8079,9 +8844,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8079
8844
  /** One execution-history row. */
8080
8845
  function ExecutionRow({ execution, timeZone, onOpen }) {
8081
8846
  const result = execution.result;
8847
+ const external = execution.external === true;
8082
8848
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
8083
8849
  className: board_module_css_default.executionRow,
8084
8850
  "data-result": result,
8851
+ "data-external": external ? "true" : void 0,
8085
8852
  children: [
8086
8853
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8087
8854
  className: board_module_css_default.executionBadge,
@@ -8097,7 +8864,12 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8097
8864
  execution.endedAt !== void 0 && ` · ${t("detail.executionEnded")} ${formatTime(execution.endedAt, timeZone)}`
8098
8865
  ]
8099
8866
  }),
8100
- execution.initiatedBy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8867
+ external && execution.initiatedBy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8868
+ className: board_module_css_default.executionTimes,
8869
+ title: execution.initiatedBy,
8870
+ children: t("detail.execution.external", { agent: execution.initiatedBy })
8871
+ }),
8872
+ !external && execution.initiatedBy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8101
8873
  className: board_module_css_default.executionTimes,
8102
8874
  title: execution.initiatedBy,
8103
8875
  children: t("detail.execution.initiator", { session: execution.initiatedBy })
@@ -8120,7 +8892,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8120
8892
  });
8121
8893
  }
8122
8894
  /** The execution-target editor: workspace / mode / permission pickers. */
8123
- function ExecutionSettingsSection({ controller, task, pending, executionOptions, teamRunAvailable }) {
8895
+ function ExecutionSettingsSection({ controller, task, pending, executionOptions, teamRunAvailable, goalRunEnabled }) {
8124
8896
  const options = executionOptions;
8125
8897
  const workspaceId = task.workspaceId ?? "";
8126
8898
  const mode = task.mode ?? "";
@@ -8266,7 +9038,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8266
9038
  className: board_module_css_default.scheduleToggle,
8267
9039
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8268
9040
  type: "checkbox",
8269
- checked: task.goalRun !== false,
9041
+ checked: task.goalRun === true,
8270
9042
  disabled: pending,
8271
9043
  onChange: (event) => {
8272
9044
  controller.updateTask(task.id, { goalRun: event.target.checked });
@@ -8277,6 +9049,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8277
9049
  className: board_module_css_default.detailText,
8278
9050
  children: t("exec.goalRunHint")
8279
9051
  }),
9052
+ !goalRunEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9053
+ className: board_module_css_default.detailText,
9054
+ children: t("settings.goalRunGlobalDisabledTaskOption")
9055
+ }),
8280
9056
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
8281
9057
  className: board_module_css_default.scheduleToggle,
8282
9058
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -8310,31 +9086,48 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8310
9086
  ]
8311
9087
  });
8312
9088
  }
8313
- /** The scheduled-runs editor: enable toggle, cron input + presets, zone, next-run info. */
9089
+ /**
9090
+ * The scheduled-runs editor. A one-shot has one planned instant; a recurring
9091
+ * rule has a cron expression, an optional run cap, and a zone. The editor
9092
+ * shows the rule's own state (next target, run counter, stop/skip record) and
9093
+ * can cancel a future plan.
9094
+ */
8314
9095
  function ScheduleSection({ controller, task, pending }) {
8315
9096
  const schedule = task.schedule;
9097
+ const hostTimeZone = controller.getSnapshot().host?.scheduler.timeZone;
9098
+ const [zone, setZone] = (0, react.useState)(schedule?.timeZone ?? "");
9099
+ const [mode, setMode] = (0, react.useState)(schedule?.mode ?? "cron");
8316
9100
  const [cron, setCron] = (0, react.useState)(schedule?.cron ?? "0 9 * * *");
9101
+ const [atText, setAtText] = (0, react.useState)(() => schedule?.at === void 0 ? defaultZonedInputValue(Date.now(), schedule?.timeZone ?? hostTimeZone) : zonedInputValue(schedule.at, schedule?.timeZone ?? hostTimeZone));
9102
+ const [maxRunsChoice, setMaxRunsChoice] = (0, react.useState)(() => runCapOption(schedule?.maxRuns));
9103
+ const [maxRunsText, setMaxRunsText] = (0, react.useState)(schedule?.maxRuns === void 0 ? "" : String(schedule.maxRuns));
8317
9104
  const [enabled, setEnabled] = (0, react.useState)(schedule?.enabled ?? false);
8318
9105
  const [nextRunAt, setNextRunAt] = (0, react.useState)(schedule?.nextRunAt);
8319
9106
  const [lastTriggeredAt, setLastTriggeredAt] = (0, react.useState)(schedule?.lastTriggeredAt);
8320
9107
  const [error, setError] = (0, react.useState)(void 0);
8321
- const hostTimeZone = controller.getSnapshot().host?.scheduler.timeZone;
8322
- const [zone, setZone] = (0, react.useState)(schedule?.timeZone ?? "");
8323
9108
  const now = Date.now();
8324
9109
  (0, react.useEffect)(() => {
9110
+ setMode(schedule?.mode ?? "cron");
8325
9111
  setCron(schedule?.cron ?? "0 9 * * *");
9112
+ setZone(schedule?.timeZone ?? "");
9113
+ setAtText(schedule?.at === void 0 ? defaultZonedInputValue(Date.now(), schedule?.timeZone ?? hostTimeZone) : zonedInputValue(schedule.at, schedule?.timeZone ?? hostTimeZone));
9114
+ setMaxRunsChoice(runCapOption(schedule?.maxRuns));
9115
+ setMaxRunsText(schedule?.maxRuns === void 0 ? "" : String(schedule.maxRuns));
8326
9116
  setEnabled(schedule?.enabled ?? false);
8327
9117
  setNextRunAt(schedule?.nextRunAt);
8328
9118
  setLastTriggeredAt(schedule?.lastTriggeredAt);
8329
- setZone(schedule?.timeZone ?? "");
8330
9119
  setError(void 0);
8331
9120
  }, [
8332
9121
  task.id,
9122
+ hostTimeZone,
9123
+ schedule?.mode,
8333
9124
  schedule?.enabled,
8334
9125
  schedule?.cron,
9126
+ schedule?.at,
8335
9127
  schedule?.timeZone,
8336
9128
  schedule?.nextRunAt,
8337
- schedule?.lastTriggeredAt
9129
+ schedule?.lastTriggeredAt,
9130
+ schedule?.maxRuns
8338
9131
  ]);
8339
9132
  /** Validate + persist the current cron text (Enter or blur). */
8340
9133
  const saveCron = (value) => {
@@ -8345,19 +9138,113 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8345
9138
  return;
8346
9139
  }
8347
9140
  setError(void 0);
8348
- controller.setSchedule(task.id, { cron: trimmed });
9141
+ controller.setSchedule(task.id, {
9142
+ mode: "cron",
9143
+ cron: trimmed
9144
+ });
9145
+ };
9146
+ /** Validate + persist the one-shot's planned instant. */
9147
+ const saveAt = (value) => {
9148
+ setAtText(value);
9149
+ const at = parseZonedInput(value, effectiveZone);
9150
+ if (at === void 0 || at <= now) {
9151
+ setError(t("detail.schedule.atInvalid"));
9152
+ return;
9153
+ }
9154
+ setError(void 0);
9155
+ controller.setSchedule(task.id, {
9156
+ mode: "once",
9157
+ at
9158
+ });
9159
+ };
9160
+ /** Switch the kind of plan, persisting a usable target for the new kind. */
9161
+ const changeMode = (next) => {
9162
+ if (next === "once") {
9163
+ const at = parseZonedInput(atText, effectiveZone);
9164
+ if (at === void 0 || at <= now) {
9165
+ setError(t("detail.schedule.atInvalid"));
9166
+ return;
9167
+ }
9168
+ setMode("once");
9169
+ setError(void 0);
9170
+ controller.setSchedule(task.id, {
9171
+ mode: "once",
9172
+ at
9173
+ });
9174
+ return;
9175
+ }
9176
+ const trimmed = cron.trim();
9177
+ if (trimmed === "" || !isValidCron(trimmed)) {
9178
+ setError(t("detail.schedule.invalid"));
9179
+ return;
9180
+ }
9181
+ setMode("cron");
9182
+ setError(void 0);
9183
+ controller.setSchedule(task.id, {
9184
+ mode: "cron",
9185
+ cron: trimmed
9186
+ });
8349
9187
  };
8350
9188
  /**
8351
9189
  * Change the rule's zone. `''` clears the stored zone, which is how a user
8352
- * returns a rule to following the Host zone.
9190
+ * returns a rule to following the Host zone. A one-shot's instant is
9191
+ * absolute, so only its rendered wall clock follows the new zone; a cron
9192
+ * rule's wall clock itself moves with the zone.
8353
9193
  */
8354
9194
  const changeZone = (value) => {
8355
9195
  setZone(value);
8356
9196
  setError(void 0);
9197
+ if (mode === "once" && schedule?.at !== void 0) {
9198
+ setAtText(zonedInputValue(schedule.at, value === "" ? hostTimeZone : value));
9199
+ controller.setSchedule(task.id, {
9200
+ mode: "once",
9201
+ timeZone: value === "" ? null : value
9202
+ });
9203
+ return;
9204
+ }
8357
9205
  controller.setSchedule(task.id, { timeZone: value === "" ? null : value });
8358
9206
  };
8359
- /** Arm/disarm the schedule (arming first persists the edited cron). */
9207
+ /** Change the recurring rule's run cap (unlimited, a preset, or a custom value). */
9208
+ const applyMaxRuns = (choice) => {
9209
+ setMaxRunsChoice(choice);
9210
+ setError(void 0);
9211
+ if (choice === "unlimited") {
9212
+ controller.setSchedule(task.id, { maxRuns: null });
9213
+ return;
9214
+ }
9215
+ if (choice === "custom") {
9216
+ setMaxRunsText(schedule?.maxRuns === void 0 ? "" : String(schedule.maxRuns));
9217
+ return;
9218
+ }
9219
+ controller.setSchedule(task.id, { maxRuns: Number(choice) });
9220
+ };
9221
+ /** Persist a custom run cap; a non-positive or fractional value is refused. */
9222
+ const saveCustomMaxRuns = (value) => {
9223
+ setMaxRunsText(value);
9224
+ const parsed = Number(value.trim());
9225
+ if (value.trim() === "" || !Number.isInteger(parsed) || parsed < 1) {
9226
+ setError(t("detail.schedule.maxRunsInvalid"));
9227
+ return;
9228
+ }
9229
+ setError(void 0);
9230
+ controller.setSchedule(task.id, { maxRuns: parsed });
9231
+ };
9232
+ /** Arm/disarm the schedule (arming first persists the edited plan). */
8360
9233
  const toggleEnabled = (next) => {
9234
+ if (next && mode === "once") {
9235
+ const at = parseZonedInput(atText, effectiveZone);
9236
+ if (at === void 0 || at <= now) {
9237
+ setError(t("detail.schedule.atInvalid"));
9238
+ return;
9239
+ }
9240
+ setError(void 0);
9241
+ if (controller.setSchedule(task.id, {
9242
+ mode: "once",
9243
+ at,
9244
+ enabled: true
9245
+ }) && !controller.isHostBacked()) setEnabled(true);
9246
+ return;
9247
+ }
8361
9248
  const trimmed = cron.trim();
8362
9249
  if (next && (trimmed === "" || !isValidCron(trimmed))) {
8363
9250
  setError(t("detail.schedule.invalid"));
@@ -8369,16 +9256,28 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8369
9256
  ...next && trimmed !== schedule?.cron ? { cron: trimmed } : {}
8370
9257
  }) && !controller.isHostBacked()) setEnabled(next);
8371
9258
  };
9259
+ /** Drop every future trigger without touching the card's execution history. */
9260
+ const cancelSchedule = () => {
9261
+ setError(void 0);
9262
+ if (controller.setSchedule(task.id, { enabled: false }) && !controller.isHostBacked()) setEnabled(false);
9263
+ };
8372
9264
  const applyPreset = (preset) => {
8373
9265
  if (preset === "") return;
8374
9266
  setCron(preset);
9267
+ setMode("cron");
8375
9268
  setError(void 0);
8376
- controller.setSchedule(task.id, { cron: preset });
9269
+ controller.setSchedule(task.id, {
9270
+ mode: "cron",
9271
+ cron: preset
9272
+ });
8377
9273
  };
8378
9274
  const effectiveZone = zone === "" ? hostTimeZone : zone;
8379
9275
  const nextLabel = !enabled || nextRunAt === void 0 ? t("detail.schedule.notScheduled") : nextRunAt <= now ? t("detail.schedule.dueSoon") : nextRunLabel(nextRunAt, effectiveZone, formatHostTimestamp, now);
8380
9276
  const lastLabel = lastTriggeredAt === void 0 ? "—" : formatHostTimestamp(lastTriggeredAt, effectiveZone);
8381
9277
  const zones = zoneChoices(hostTimeZone, schedule?.timeZone);
9278
+ const budget = schedule === void 0 ? void 0 : budgetLabel(schedule);
9279
+ const ended = schedule === void 0 ? void 0 : endedLabel(schedule);
9280
+ const skipped = schedule === void 0 ? void 0 : skippedLabel(schedule);
8382
9281
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
8383
9282
  className: board_module_css_default.detailSection,
8384
9283
  children: [
@@ -8394,42 +9293,128 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8394
9293
  }
8395
9294
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.enable") })]
8396
9295
  }),
8397
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8398
- className: board_module_css_default.scheduleRow,
8399
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8400
- className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${error !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
8401
- value: cron,
9296
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9297
+ className: board_module_css_default.scheduleZone,
9298
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.mode") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
9299
+ className: board_module_css_default.schedulePreset,
9300
+ value: mode,
8402
9301
  disabled: pending,
8403
- placeholder: "0 9 * * *",
8404
- spellCheck: false,
8405
- "aria-label": t("detail.schedule.cron"),
9302
+ "aria-label": t("detail.schedule.mode"),
8406
9303
  onChange: (event) => {
8407
- setCron(event.target.value);
8408
- setError(void 0);
9304
+ changeMode(event.target.value);
8409
9305
  },
8410
- onBlur: () => {
8411
- saveCron(cron);
8412
- },
8413
- onKeyDown: (event) => {
8414
- if (event.key === "Enter") saveCron(cron);
8415
- }
8416
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
8417
- className: board_module_css_default.schedulePreset,
8418
- value: "",
9306
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9307
+ value: "cron",
9308
+ children: t("detail.schedule.mode.cron")
9309
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9310
+ value: "once",
9311
+ children: t("detail.schedule.mode.once")
9312
+ })]
9313
+ })]
9314
+ }),
9315
+ mode === "once" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9316
+ className: board_module_css_default.scheduleZone,
9317
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.at") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
9318
+ className: board_module_css_default.input,
9319
+ type: "datetime-local",
9320
+ value: atText,
8419
9321
  disabled: pending,
8420
- "aria-label": t("detail.schedule.presets"),
9322
+ "aria-label": t("detail.schedule.at"),
9323
+ title: t("detail.schedule.atHint"),
8421
9324
  onChange: (event) => {
8422
- applyPreset(event.target.value);
9325
+ setAtText(event.target.value);
9326
+ setError(void 0);
8423
9327
  },
8424
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
8425
- value: "",
8426
- children: [t("detail.schedule.presets"), "…"]
8427
- }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
8428
- value: preset.cron,
8429
- children: t(preset.label)
8430
- }, preset.cron))]
9328
+ onBlur: () => {
9329
+ saveAt(atText);
9330
+ }
8431
9331
  })]
8432
- }),
9332
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
9333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9334
+ className: board_module_css_default.scheduleRow,
9335
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
9336
+ className: `${board_module_css_default.input} ${board_module_css_default.scheduleInput}${error !== void 0 ? ` ${board_module_css_default.scheduleInputInvalid}` : ""}`,
9337
+ value: cron,
9338
+ disabled: pending,
9339
+ placeholder: "0 9 * * *",
9340
+ spellCheck: false,
9341
+ "aria-label": t("detail.schedule.cron"),
9342
+ onChange: (event) => {
9343
+ setCron(event.target.value);
9344
+ setError(void 0);
9345
+ },
9346
+ onBlur: () => {
9347
+ saveCron(cron);
9348
+ },
9349
+ onKeyDown: (event) => {
9350
+ if (event.key === "Enter") saveCron(cron);
9351
+ }
9352
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
9353
+ className: board_module_css_default.schedulePreset,
9354
+ value: "",
9355
+ disabled: pending,
9356
+ "aria-label": t("detail.schedule.presets"),
9357
+ onChange: (event) => {
9358
+ applyPreset(event.target.value);
9359
+ },
9360
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
9361
+ value: "",
9362
+ children: [t("detail.schedule.presets"), "…"]
9363
+ }), SCHEDULE_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9364
+ value: preset.cron,
9365
+ children: t(preset.label)
9366
+ }, preset.cron))]
9367
+ })]
9368
+ }),
9369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9370
+ className: board_module_css_default.scheduleZone,
9371
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.maxRuns") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
9372
+ className: board_module_css_default.schedulePreset,
9373
+ value: maxRunsChoice,
9374
+ disabled: pending,
9375
+ "aria-label": t("detail.schedule.maxRuns"),
9376
+ onChange: (event) => {
9377
+ applyMaxRuns(event.target.value);
9378
+ },
9379
+ children: [
9380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9381
+ value: "unlimited",
9382
+ children: t("detail.schedule.maxRunsUnlimited")
9383
+ }),
9384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9385
+ value: "1",
9386
+ children: t("detail.schedule.maxRunsValue", { count: "1" })
9387
+ }),
9388
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9389
+ value: "2",
9390
+ children: t("detail.schedule.maxRunsValue", { count: "2" })
9391
+ }),
9392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
9393
+ value: "custom",
9394
+ children: t("detail.schedule.maxRunsCustom")
9395
+ })
9396
+ ]
9397
+ })]
9398
+ }),
9399
+ maxRunsChoice === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
9400
+ className: board_module_css_default.scheduleZone,
9401
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.maxRuns") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
9402
+ className: board_module_css_default.input,
9403
+ type: "number",
9404
+ min: 1,
9405
+ step: 1,
9406
+ value: maxRunsText,
9407
+ disabled: pending,
9408
+ "aria-label": t("detail.schedule.maxRuns"),
9409
+ onChange: (event) => {
9410
+ setMaxRunsText(event.target.value);
9411
+ },
9412
+ onBlur: () => {
9413
+ saveCustomMaxRuns(maxRunsText);
9414
+ }
9415
+ })]
9416
+ })
9417
+ ] }),
8433
9418
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
8434
9419
  className: board_module_css_default.scheduleZone,
8435
9420
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("detail.schedule.timeZone") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
@@ -8437,7 +9422,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8437
9422
  value: zone,
8438
9423
  disabled: pending,
8439
9424
  "aria-label": t("detail.schedule.timeZone"),
8440
- title: t("detail.schedule.timeZoneHint"),
9425
+ title: mode === "once" ? t("detail.schedule.zoneOnceHint") : t("detail.schedule.timeZoneHint"),
8441
9426
  onChange: (event) => {
8442
9427
  changeZone(event.target.value);
8443
9428
  },
@@ -8462,6 +9447,25 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8462
9447
  " ",
8463
9448
  lastLabel
8464
9449
  ]
9450
+ }),
9451
+ budget !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9452
+ className: board_module_css_default.scheduleMeta,
9453
+ children: budget
9454
+ }),
9455
+ ended !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9456
+ className: board_module_css_default.scheduleMeta,
9457
+ children: ended
9458
+ }),
9459
+ ended === void 0 && skipped !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9460
+ className: board_module_css_default.scheduleMeta,
9461
+ children: skipped
9462
+ }),
9463
+ enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9464
+ type: "button",
9465
+ className: board_module_css_default.ghostButton,
9466
+ disabled: pending,
9467
+ onClick: cancelSchedule,
9468
+ children: t("detail.schedule.cancel")
8465
9469
  })
8466
9470
  ]
8467
9471
  });
@@ -8818,7 +9822,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
8818
9822
  task: current,
8819
9823
  pending,
8820
9824
  executionOptions: snapshot.executionOptions,
8821
- teamRunAvailable: snapshot.host?.teamRunAvailable === true
9825
+ teamRunAvailable: snapshot.host?.teamRunAvailable === true,
9826
+ goalRunEnabled: snapshot.host?.goalRunEnabled === true
8822
9827
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScheduleSection, {
8823
9828
  controller,
8824
9829
  task: current,
@@ -9846,6 +10851,51 @@ Please report this to https://github.com/markedjs/marked.`, e) {
9846
10851
  }) : null]
9847
10852
  });
9848
10853
  }
10854
+ /** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */
10855
+ function ValueField(props) {
10856
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10857
+ className: settings_card_module_css_default.field,
10858
+ children: [
10859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10860
+ className: settings_card_module_css_default.head,
10861
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
10862
+ className: settings_card_module_css_default.label,
10863
+ htmlFor: props.id,
10864
+ children: props.label
10865
+ }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10866
+ className: settings_card_module_css_default.badges,
10867
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10868
+ className: settings_card_module_css_default.badge,
10869
+ children: props.overriddenLabel
10870
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10871
+ type: "button",
10872
+ className: settings_card_module_css_default.reset,
10873
+ disabled: props.disabled,
10874
+ onClick: props.onReset,
10875
+ children: props.resetLabel
10876
+ })]
10877
+ }) : null]
10878
+ }),
10879
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
10880
+ id: props.id,
10881
+ className: props.invalid ? settings_card_module_css_default.inputInvalid : settings_card_module_css_default.input,
10882
+ type: "text",
10883
+ ...props.numeric === true ? { inputMode: "numeric" } : {},
10884
+ ...props.invalid ? { "aria-invalid": true } : {},
10885
+ value: props.text,
10886
+ placeholder: props.placeholder ?? "",
10887
+ disabled: props.disabled,
10888
+ onChange: (event) => {
10889
+ props.onEdit(event.target.value);
10890
+ }
10891
+ }),
10892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10893
+ className: props.invalid ? settings_card_module_css_default.invalid : settings_card_module_css_default.hint,
10894
+ children: props.invalid ? props.invalidLabel : props.hint
10895
+ })
10896
+ ]
10897
+ });
10898
+ }
9849
10899
  const NON_SKIN_BODY_MARKERS = /* @__PURE__ */ new Set(["dshSkinCenter", "dshSidebarCollapsed"]);
9850
10900
  function isSkinActive() {
9851
10901
  return Object.keys(document.body.dataset).some((key) => key.startsWith("dsh") && !NON_SKIN_BODY_MARKERS.has(key));
@@ -10444,6 +11494,28 @@ Please report this to https://github.com/markedjs/marked.`, e) {
10444
11494
  /** The depth choices the card offers, derived from the supported range. */
10445
11495
  const SUBTASK_DEPTH_CHOICES = Array.from({ length: 3 }, (_, index) => String(1 + index));
10446
11496
  /**
11497
+ * The roster-poll cadence field: a whole number of seconds inside the
11498
+ * supported range, because the Host schema (`sessionPollSeconds`) is numeric.
11499
+ * A draft outside the range blocks the save instead of staging a value the
11500
+ * Host would clamp behind the user's back.
11501
+ */
11502
+ function sessionPollField() {
11503
+ return {
11504
+ field: "sessionPollSeconds",
11505
+ format: (value) => typeof value === "number" && Number.isInteger(value) ? String(value) : "",
11506
+ parse: (text) => {
11507
+ const trimmed = text.trim();
11508
+ if (trimmed === "") return { kind: "clear" };
11509
+ if (!/^\d+$/.test(trimmed)) return void 0;
11510
+ const seconds = Number(trimmed);
11511
+ return seconds >= 1 && seconds <= 300 ? {
11512
+ kind: "set",
11513
+ value: seconds
11514
+ } : void 0;
11515
+ }
11516
+ };
11517
+ }
11518
+ /**
10447
11519
  * The depth field: a choice among the supported levels whose draft text is a
10448
11520
  * number, because the Host schema (`maxSubtaskDepth`) is numeric. A draft
10449
11521
  * outside the range blocks the save instead of staging a value the Host
@@ -10513,6 +11585,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
10513
11585
  booleanField("announceToAgent"),
10514
11586
  booleanField("preventIdleSleep"),
10515
11587
  subtaskDepthField(),
11588
+ sessionPollField(),
11589
+ booleanField("goalRunEnabled"),
10516
11590
  booleanField("goalVerification"),
10517
11591
  judgeModelField(),
10518
11592
  judgeEffortField()
@@ -10526,6 +11600,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
10526
11600
  announceToAgent: this.form.field("announceToAgent"),
10527
11601
  preventIdleSleep: this.form.field("preventIdleSleep"),
10528
11602
  maxSubtaskDepth: this.form.field("maxSubtaskDepth"),
11603
+ sessionPollSeconds: this.form.field("sessionPollSeconds"),
11604
+ goalRunEnabled: this.form.field("goalRunEnabled"),
10529
11605
  goalVerification: this.form.field("goalVerification"),
10530
11606
  goalVerificationModel: this.form.field("goalVerificationModel"),
10531
11607
  goalVerificationReasoningEffort: this.form.field("goalVerificationReasoningEffort")
@@ -10728,6 +11804,20 @@ Please report this to https://github.com/markedjs/marked.`, e) {
10728
11804
  props.resetField("preventIdleSleep");
10729
11805
  }
10730
11806
  }),
11807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
11808
+ id: "settings-task-board-session-poll",
11809
+ label: t("settings.sessionPollSeconds"),
11810
+ hint: t("settings.sessionPollSecondsHint"),
11811
+ numeric: true,
11812
+ ...fieldProps,
11813
+ ...state.sessionPollSeconds,
11814
+ onEdit: (text) => {
11815
+ props.edit("sessionPollSeconds", text);
11816
+ },
11817
+ onReset: () => {
11818
+ props.resetField("sessionPollSeconds");
11819
+ }
11820
+ }),
10731
11821
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChoiceField, {
10732
11822
  id: "settings-task-board-subtask-depth",
10733
11823
  label: t("settings.maxSubtaskDepth"),
@@ -10748,6 +11838,32 @@ Please report this to https://github.com/markedjs/marked.`, e) {
10748
11838
  })
10749
11839
  ]
10750
11840
  }),
11841
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginSettingsCard, {
11842
+ t,
11843
+ titleKey: "settings.runBehaviorTitle",
11844
+ descriptionKey: "settings.runBehaviorCardHint",
11845
+ defaultOpen: false,
11846
+ hideFooter: true,
11847
+ state: nestedShell,
11848
+ onSave: props.save,
11849
+ onDiscard: props.discard,
11850
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
11851
+ id: "settings-task-board-goal-run",
11852
+ label: t("settings.goalRunGlobal"),
11853
+ hint: t("settings.goalRunGlobalHint"),
11854
+ inheritLabel: t("settings.inherit"),
11855
+ onLabel: t("settings.on"),
11856
+ offLabel: t("settings.off"),
11857
+ ...fieldProps,
11858
+ ...state.goalRunEnabled,
11859
+ onEdit: (text) => {
11860
+ props.edit("goalRunEnabled", text);
11861
+ },
11862
+ onReset: () => {
11863
+ props.resetField("goalRunEnabled");
11864
+ }
11865
+ })
11866
+ }),
10751
11867
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PluginSettingsCard, {
10752
11868
  t,
10753
11869
  titleKey: "settings.goalVerificationTitle",
@@ -11220,7 +12336,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11220
12336
  /** The building package's version, when the bundle carries it. */
11221
12337
  function bakedVersion() {
11222
12338
  try {
11223
- return "0.4.5";
12339
+ return "0.4.6";
11224
12340
  } catch {
11225
12341
  return;
11226
12342
  }
@@ -11343,40 +12459,33 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11343
12459
  return false;
11344
12460
  }
11345
12461
  }
11346
- //#endregion
11347
- //#region src/client/board-card-seat.ts
11348
- /**
11349
- * Board-owned variant of the family plugin-card seat that can declare child
11350
- * slots on the registration.
11351
- *
11352
- * The shared `installPluginCard` helper owns seat selection but cannot forward
11353
- * a `children` declaration, and the task board's settings card declares the
11354
- * `task-board.settings.section` seat a provider renders into. This wrapper
11355
- * reuses the shared seat decision (`familyGroupLoaded` and the two seat keys)
11356
- * and adds the declaration; it should collapse back into the shared helper once
11357
- * that helper accepts children.
11358
- *
11359
- * @module dsh-task-board/client/board-card-seat
11360
- */
12462
+ /** Report a refused registration instead of leaving the user with no card. */
11361
12463
  function warnRefusedSeat(seat, error) {
11362
12464
  try {
11363
12465
  console.warn(`[dsh-web] plugin card registration into "${seat}" was refused; the card will not render`, error);
11364
12466
  } catch {}
11365
12467
  }
11366
12468
  /**
11367
- * Contribute the board's settings card to the seat this host renders, declaring
11368
- * the child slots it owns. Mirrors the shared helper's reconcile discipline: the
11369
- * entry moves when the family group appears, and is never in two seats at once.
12469
+ * Contribute one family plugin card to the seat this host renders, following
12470
+ * the group if it loads later. The entry is disposed and re-registered on a
12471
+ * seat change, never duplicated.
11370
12472
  * @param ctx - client context (its slot registry decides the seat).
11371
- * @param seat - the card contribution and its children declaration.
12473
+ * @param seat - the card contribution.
11372
12474
  */
11373
- function installBoardCard(ctx, seat) {
12475
+ function installPluginCard(ctx, seat) {
11374
12476
  const slots = ctx.slots;
11375
12477
  const component = seat.component;
11376
12478
  const inject = seat.inject;
11377
12479
  let dispose;
11378
12480
  let current;
12481
+ /**
12482
+ * Re-entrancy latch. The registry emits a change event synchronously from
12483
+ * inside both `register` and the previous entry's disposer, so an unguarded
12484
+ * reconcile would re-enter itself mid-move and register the card twice into
12485
+ * the seat it is leaving ("already has an entry for key ...").
12486
+ */
11379
12487
  let reconciling = false;
12488
+ /** Reconcile the contribution with the currently live seat (no-op when unchanged). */
11380
12489
  const reconcile = () => {
11381
12490
  if (reconciling) return;
11382
12491
  const target = familyGroupLoaded(ctx) ? FAMILY_PLUGIN_CARD_SEAT : OFFICIAL_PLUGIN_CARD_SEAT;
@@ -11393,13 +12502,13 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11393
12502
  ...seat.order === void 0 ? {} : { order: seat.order },
11394
12503
  ...seat.label === void 0 ? {} : { label: seat.label },
11395
12504
  locale: seat.locale,
11396
- children: seat.children,
12505
+ ...seat.children === void 0 ? {} : { children: seat.children },
11397
12506
  ...seat.inject === void 0 ? {} : { inject }
11398
12507
  } : {
11399
12508
  name: OFFICIAL_PLUGIN_CARD_SEAT,
11400
12509
  key: seat.bundle,
11401
12510
  locale: seat.locale,
11402
- children: seat.children,
12511
+ ...seat.children === void 0 ? {} : { children: seat.children },
11403
12512
  ...seat.inject === void 0 ? {} : { inject }
11404
12513
  }, component);
11405
12514
  current = target;
@@ -11601,11 +12710,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11601
12710
  ];
11602
12711
  /**
11603
12712
  * Required services (fiber inject waiting — the runtime must be up first).
11604
- * The generated remote faces are probed at use time instead of injected:
11605
- * `remote.agentPresets` only registers on 0.1.2-alpha.2 hosts (the
11606
- * api-remotes contribution), so a hard wait would pend the entry forever
11607
- * on hosts below that cohort, which serve the same roster through the
11608
- * connection RPC face.
12713
+ * The generated remote namespaces are not all hard injects: `remote.session`
12714
+ * is one, while `remote.agentPresets` is reached through a scoped inject at
12715
+ * use time (see mountUi), because the namespace mounts with the official
12716
+ * api-remotes assembly applied beside this entry rather than before it.
11609
12717
  */
11610
12718
  const inject = [
11611
12719
  "slots",
@@ -11620,41 +12728,22 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11620
12728
  "layout"
11621
12729
  ];
11622
12730
  /**
11623
- * Read the agent-preset roster through whichever face the running host
11624
- * serves: the generated api-remotes face (`remote.agentPresets`,
11625
- * 0.1.2-alpha.2) or the connection RPC face
11626
- * (`connection.api.agentPresets`, hosts below that cohort). Answers
11627
- * undefined when the host serves neither, so the caller leaves the picker
11628
- * options untouched instead of erroring.
12731
+ * Read the agent-preset roster through the generated api-remotes face. The
12732
+ * caller passes the namespace resolved inside its scoped inject, so a host
12733
+ * that serves no such namespace never reaches here; a host that serves it
12734
+ * answers `ok: false` instead, which leaves the picker options untouched.
12735
+ * @param remote - the `remote.agentPresets` namespace.
12736
+ * @returns the roster, or a failed read the caller ignores.
11629
12737
  */
11630
- async function readPresetRoster(ctx, remote) {
11631
- let remotes;
11632
- try {
11633
- remotes = remote.agentPresets;
11634
- } catch {
11635
- remotes = void 0;
11636
- }
11637
- if (remotes !== void 0) {
11638
- const response = await remotes.list();
11639
- if (!response.ok) return {
11640
- ok: false,
11641
- presets: []
11642
- };
11643
- return {
11644
- ok: true,
11645
- presets: response.value.presets
11646
- };
11647
- }
11648
- const legacy = ctx.get("connection").api?.agentPresets;
11649
- if (legacy === void 0) return void 0;
11650
- const response = await legacy.list({});
11651
- if (!response.result.ok || response.result.value === void 0) return {
12738
+ async function readPresetRoster(remote) {
12739
+ const response = await remote.list();
12740
+ if (!response.ok) return {
11652
12741
  ok: false,
11653
12742
  presets: []
11654
12743
  };
11655
12744
  return {
11656
12745
  ok: true,
11657
- presets: response.result.value.presets ?? []
12746
+ presets: response.value.presets
11658
12747
  };
11659
12748
  }
11660
12749
  /**
@@ -11685,7 +12774,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11685
12774
  clientService.detach();
11686
12775
  }, "task-board: extension service");
11687
12776
  const settingsCard = new TaskBoardSettingsCardController(settingsForm, (request) => clientService.dispatch(request));
11688
- installBoardCard(ctx, {
12777
+ installPluginCard(ctx, {
11689
12778
  bundle: "@linxin666/dsh-client-ui-task-board",
11690
12779
  id: "task-board",
11691
12780
  order: 110,
@@ -11733,10 +12822,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11733
12822
  controller.setWorkspaceCreator(async (path) => {
11734
12823
  return { workspaceId: (await workspaces.create({ path })).workspaceId };
11735
12824
  });
11736
- const pushPresetOptions = async () => {
12825
+ const pushPresetOptions = async (presetRemote) => {
11737
12826
  try {
11738
- const roster = await readPresetRoster(ctx, remote);
11739
- if (roster === void 0 || !roster.ok) return;
12827
+ const roster = await readPresetRoster(presetRemote);
12828
+ if (!roster.ok) return;
11740
12829
  controller.setExecutionOptions({ presets: roster.presets.map((preset) => ({
11741
12830
  id: preset.id,
11742
12831
  name: preset.name,
@@ -11748,6 +12837,17 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11748
12837
  console.error("[dsh-task-board] agent preset roster read failed", error);
11749
12838
  }
11750
12839
  };
12840
+ const presetRosterFeed = ctx.inject(["remote.agentPresets"], (inner) => {
12841
+ const presetRemote = inner.get("remote.agentPresets");
12842
+ if (presetRemote === void 0) return;
12843
+ pushPresetOptions(presetRemote);
12844
+ return inner.on("connection/reset", () => {
12845
+ pushPresetOptions(presetRemote);
12846
+ });
12847
+ });
12848
+ disposers.push(() => {
12849
+ presetRosterFeed.dispose();
12850
+ });
11751
12851
  const pushModelOptions = async () => {
11752
12852
  try {
11753
12853
  let models = [];
@@ -11803,10 +12903,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
11803
12903
  console.error("[dsh-task-board] model options read failed", error);
11804
12904
  }
11805
12905
  };
11806
- pushPresetOptions();
11807
12906
  pushModelOptions();
11808
12907
  disposers.push(ctx.on("connection/reset", () => {
11809
- pushPresetOptions();
11810
12908
  pushModelOptions();
11811
12909
  }));
11812
12910
  try {