@linxin666/dsh-client-ui-task-board 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +34 -18
- package/README.zh.md +34 -18
- package/lib/client.js +2804 -1024
- package/lib/client.js.map +1 -1
- package/lib/index.js +2576 -416
- package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +61 -0
- package/lib/types/client/board/ReasoningEffortField.d.ts +25 -0
- package/lib/types/client/board/model-options.d.ts +67 -0
- package/lib/types/client/board/preset-label.d.ts +21 -0
- package/lib/types/client/board/preset-roster.d.ts +45 -0
- package/lib/types/client/board/team-run-promotion.d.ts +26 -0
- package/lib/types/client/index.d.ts +4 -5
- package/lib/types/client/locales.d.ts +81 -0
- package/lib/types/client/plugin-card-seat.d.ts +5 -0
- package/lib/types/client/schedule-zone.d.ts +50 -0
- package/lib/types/core/card-defaults.d.ts +78 -0
- package/lib/types/core/controller.d.ts +41 -17
- package/lib/types/core/launch-width.d.ts +31 -0
- package/lib/types/core/poll-cadence.d.ts +31 -0
- package/lib/types/core/reasoning-effort.d.ts +57 -0
- package/lib/types/core/subtask.d.ts +30 -8
- package/lib/types/core/tasks.d.ts +163 -14
- package/lib/types/core/use-cases/task-create.d.ts +10 -5
- package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
- package/lib/types/core/use-cases/task-update.d.ts +1 -1
- package/lib/types/core/verification-budget.d.ts +52 -0
- package/lib/types/core/verification.d.ts +376 -9
- package/lib/types/host/launch-admission.d.ts +72 -0
- package/lib/types/host/verification-gate.d.ts +24 -0
- package/lib/types/host/verification-runner.d.ts +38 -7
- package/lib/types/host-ledger.d.ts +91 -11
- package/lib/types/host-runner.d.ts +111 -13
- package/lib/types/host-service.d.ts +199 -8
- package/lib/types/index.d.ts +141 -0
- package/lib/types/protocol.d.ts +63 -12
- package/package.json +29 -33
- package/src/client/CardDefaultPresetField.tsx +101 -0
- package/src/client/TaskBoardSettingsCard.tsx +288 -17
- package/src/client/board/LinkSubtaskModal.tsx +8 -0
- package/src/client/board/NewTaskModal.tsx +237 -60
- package/src/client/board/ReasoningEffortField.tsx +50 -0
- package/src/client/board/TaskCard.tsx +3 -3
- package/src/client/board/TaskDetail.tsx +261 -24
- package/src/client/board/VerificationReport.tsx +132 -13
- package/src/client/board/model-options.ts +178 -0
- package/src/client/board/preset-label.ts +32 -0
- package/src/client/board/preset-roster.ts +58 -0
- package/src/client/board/status-key.ts +3 -0
- package/src/client/board/team-run-promotion.ts +39 -0
- package/src/client/index.ts +105 -108
- package/src/client/locales.ts +171 -6
- package/src/client/plugin-card-seat.ts +7 -0
- package/src/client/schedule-zone.ts +100 -2
- package/src/client/settings-entry-form.ts +0 -1
- package/src/core/card-defaults.ts +132 -0
- package/src/core/controller.ts +55 -16
- package/src/core/launch-width.ts +38 -0
- package/src/core/poll-cadence.ts +41 -0
- package/src/core/reasoning-effort.ts +92 -0
- package/src/core/store.ts +69 -15
- package/src/core/subtask.ts +48 -13
- package/src/core/tasks.ts +213 -39
- package/src/core/use-cases/task-create.ts +57 -25
- package/src/core/use-cases/task-schedule.ts +190 -25
- package/src/core/use-cases/task-update.ts +9 -4
- package/src/core/verification-budget.ts +115 -0
- package/src/core/verification.ts +785 -17
- package/src/host/agent-tools.ts +127 -29
- package/src/host/launch-admission.ts +140 -0
- package/src/host/verification-gate.ts +172 -16
- package/src/host/verification-runner.ts +207 -40
- package/src/host-ledger.ts +346 -48
- package/src/host-runner.ts +234 -35
- package/src/host-service.ts +539 -52
- package/src/index.ts +250 -7
- package/src/protocol.ts +160 -32
- package/lib/invariant.js +0 -6
- package/lib/types/client/board-card-seat.d.ts +0 -34
- package/lib/types/invariant.d.ts +0 -3
- package/src/client/board-card-seat.ts +0 -102
- package/src/invariant.ts +0 -4
package/src/client/locales.ts
CHANGED
|
@@ -152,6 +152,7 @@ export const zh = {
|
|
|
152
152
|
'detail.noSession': '暂无会话',
|
|
153
153
|
'detail.executionStarted': '已启动',
|
|
154
154
|
'detail.execution.initiator': '发起会话 {session}',
|
|
155
|
+
'detail.execution.external': 'Host 之外完成:{agent}',
|
|
155
156
|
'detail.executionEnded': '已结束',
|
|
156
157
|
'detail.result.succeeded': '成功',
|
|
157
158
|
'detail.result.failed': '失败',
|
|
@@ -194,7 +195,35 @@ export const zh = {
|
|
|
194
195
|
'detail.schedule.duration.hours': '{count} 小时',
|
|
195
196
|
'detail.schedule.duration.minutes': '{count} 分钟',
|
|
196
197
|
'detail.schedule.duration.seconds': '{count} 秒',
|
|
198
|
+
// 调度方式:单次计划与循环规则是两类计划,不是同一 cron 的日期限定写法。
|
|
199
|
+
'detail.schedule.mode': '调度方式',
|
|
200
|
+
'detail.schedule.mode.cron': '循环',
|
|
201
|
+
'detail.schedule.mode.once': '单次',
|
|
202
|
+
'detail.schedule.at': '执行时间',
|
|
203
|
+
'detail.schedule.atHint': '按所选时区解读的计划时刻;必须是未来时间。',
|
|
204
|
+
'detail.schedule.atInvalid': '请选择未来的有效时间',
|
|
205
|
+
'detail.schedule.plannedAt': '计划时间',
|
|
206
|
+
'detail.schedule.zoneOnceHint': '所选时刻按此时区解读。',
|
|
207
|
+
'detail.schedule.maxRuns': '次数上限',
|
|
208
|
+
'detail.schedule.maxRunsUnlimited': '不限次数',
|
|
209
|
+
'detail.schedule.maxRunsValue': '最多 {count} 次',
|
|
210
|
+
'detail.schedule.maxRunsCustom': '自定义',
|
|
211
|
+
'detail.schedule.maxRunsInvalid': '次数上限必须是正整数',
|
|
212
|
+
'detail.schedule.runs': '已执行 {done}/{total} 次',
|
|
213
|
+
'detail.schedule.runsUnlimited': '已执行 {done} 次',
|
|
214
|
+
'detail.schedule.remaining': '剩余 {count} 次',
|
|
215
|
+
'detail.schedule.cancel': '关闭调度',
|
|
216
|
+
'detail.schedule.ended': '调度已结束:{reason}',
|
|
217
|
+
'detail.schedule.skipped': '上次跳过:{reason}',
|
|
218
|
+
'detail.schedule.stop.fired': '单次计划已执行',
|
|
219
|
+
'detail.schedule.stop.limit': '已达次数上限',
|
|
220
|
+
'detail.schedule.stop.no-target': '计划已无可执行时刻',
|
|
221
|
+
'detail.schedule.stop.missed': '错过执行时刻,已跳过',
|
|
222
|
+
'detail.schedule.stop.busy': '触发时任务正在运行,已跳过',
|
|
223
|
+
'detail.schedule.stop.permission': '权限待人工确认,已跳过',
|
|
224
|
+
'detail.schedule.stop.launch-failed': '启动失败未创建执行,次数已退回',
|
|
197
225
|
'card.scheduled': '定时',
|
|
226
|
+
'card.scheduledOnce': '单次',
|
|
198
227
|
'new.workspace': '工作区',
|
|
199
228
|
'new.agentPreset': 'Agent 预设',
|
|
200
229
|
'new.permission': '权限',
|
|
@@ -218,13 +247,21 @@ export const zh = {
|
|
|
218
247
|
'exec.permission.danger-full-access': '完全访问',
|
|
219
248
|
'exec.model.default': '宿主默认(agent-default-model)',
|
|
220
249
|
'exec.model.unknown': '(未知模型/回退默认)',
|
|
250
|
+
'exec.reasoning': '思考档位',
|
|
251
|
+
'exec.reasoning.inherit': '沿用模型/宿主默认',
|
|
252
|
+
'exec.reasoning.unknown': '(未知档位)',
|
|
253
|
+
'exec.reasoning.hint.pickModel': '先选择要钉住的模型,才能设置它的思考档位。',
|
|
254
|
+
'exec.reasoning.hint.unknownModel': '无法从当前名册确认该模型的可选档位;已保存的值原样保留。',
|
|
255
|
+
'exec.reasoning.hint.noEfforts': '该模型没有可选的思考档位,始终沿用模型/宿主的默认值。',
|
|
256
|
+
'exec.reasoning.hint.inherit': '留空即沿用该模型/宿主的默认档位。',
|
|
257
|
+
'exec.teamRunAutoHint': '创建或关联子任务成功后,父任务会自动开启 Agent Team 执行(可随时手动关闭)。',
|
|
221
258
|
'exec.reuseSession': '在同一对话继续',
|
|
222
259
|
'exec.teamRun': '用 Agent Team 执行(Leader/teammate)',
|
|
223
260
|
'exec.teamRunHint': '开启后:执行本任务只启动一个 Leader 会话,每个子任务由它派生为 teammate 并在该会话内并行工作(子任务自身的权限钉住不适用)。关闭则每个成员各开独立会话。',
|
|
224
261
|
'exec.teamRunUnavailable': '当前部署未提供 Agent Teams 服务,无法启用。',
|
|
225
262
|
'exec.reuseSessionHint': '开启后,本任务的后续执行在上一次会话里继续(该会话空闲且仍存在时),不再每次新建对话;每次复用时都会重新应用上面钉住的权限与模型。',
|
|
226
263
|
'exec.goalRun': '以 dsh 内置的 /goal 开始执行任务',
|
|
227
|
-
'exec.goalRunHint': '
|
|
264
|
+
'exec.goalRunHint': '默认关闭:勾选后执行时把任务目标作为持久目标交给 dsh 内置的 /goal,会话会自动续跑多轮,直到 agent 标记目标完成;看板在该目标真正结束后才结算本次执行。不勾选则只执行一轮普通对话。',
|
|
228
265
|
'exec.skipVerification': '跳过本任务的验收',
|
|
229
266
|
'exec.skipVerificationHint': '默认关闭:勾选后本任务以 goal 形式执行时不再拦截 update_goal 标记完成,也不要求通过验收;执行记录会标注「该卡片跳过验收」。仅对之后新开的执行生效,验收总开关关闭时本项无意义。',
|
|
230
267
|
'detail.executionSettings': '执行设置',
|
|
@@ -258,9 +295,18 @@ export const zh = {
|
|
|
258
295
|
'settings.unsaved': '未保存',
|
|
259
296
|
'settings.saveFailed': '部署未接受这些值,已保留供你修改。',
|
|
260
297
|
'settings.invalidNumber': '请输入数字,留空则使用默认值。',
|
|
298
|
+
'settings.sessionPollSeconds': '会话轮询间隔(秒)',
|
|
299
|
+
'settings.sessionPollSecondsHint': '默认 5 秒。仅在看板有需要核对的事情——运行中的卡片或未结算的执行——时才读取会话名册;两者都没有时不再轮询,Host 不会持续扫描持久化会话目录。取值 1–300 秒:间隔越短结算越快、Host 负载越高。',
|
|
261
300
|
'settings.maxSubtaskDepth': '子任务深度上限',
|
|
262
301
|
'settings.maxSubtaskDepthHint': '默认 1:一个任务只允许一层子任务,子任务不能再创建或关联子任务。最大 3。执行父任务会并发执行它的整棵子任务树,层级越深,一次执行开启的会话越多。',
|
|
263
302
|
'settings.maxSubtaskDepthOption': '{depth} 层',
|
|
303
|
+
'settings.runLaunchWidth': '并发启动上限',
|
|
304
|
+
'settings.runLaunchWidthHint': '默认 3。一次执行会开启整棵子任务树,但同一刻最多只有这么多个成员在创建会话并入队 Prompt,其余成员排队等待——全部都会启动,只是分批进入运行列。默认值取自本仓「重型实施子代理并发 ≤ 3」的实测纪律。取值 1–16:越大启动越快,Host 的内存与 swap 压力越高。执行中修改只影响尚未入列的启动。',
|
|
305
|
+
'settings.runBehaviorTitle': '运行行为',
|
|
306
|
+
'settings.runBehaviorCardHint': '全局原生 /goal 执行开关(总开关,默认关闭)。',
|
|
307
|
+
'settings.goalRunGlobal': '全局启用 DSH 原生 /goal 执行',
|
|
308
|
+
'settings.goalRunGlobalHint': '默认关闭。关闭时所有任务都按普通单回合执行,不调用 dsh 内置的 /goal;只有这里开启、且任务级「以 /goal 开始执行」被勾选时,才执行原生 /goal。任务级勾选始终可操作、也不会被这里的开关改写,总开关只决定它是否生效。开关按执行启动时冻结,只影响之后新开的执行。',
|
|
309
|
+
'settings.goalRunGlobalDisabledTaskOption': '全局「原生 /goal 执行」总开关已关闭:任务级选项仍可随时勾选或取消,但暂不生效,你保存的偏好也不会被改写;开启总开关后即按该偏好执行。本次执行按普通单回合进行,未执行 goal 验收。',
|
|
264
310
|
'settings.goalVerificationTitle': '任务验收',
|
|
265
311
|
'settings.goalVerification': '启用任务验收',
|
|
266
312
|
'settings.goalVerificationCardHint': '验收开关、裁判模型与判据阈值。',
|
|
@@ -285,11 +331,17 @@ export const zh = {
|
|
|
285
331
|
'settings.goalVerificationEffortFallback': '所选推理强度 {requested} 不被目标模型支持,已回退为 {resolved}。',
|
|
286
332
|
'settings.goalVerificationEffortFallbackNone': '所选推理强度 {requested} 不被目标模型支持,已回退为该模型默认档位(不显式传参)。',
|
|
287
333
|
'settings.goalVerificationModelInvalid': '配置的验收模型不是 provider/model 形式,已回退宿主默认。',
|
|
334
|
+
'settings.cardDefaultsTitle': '新建卡片默认值',
|
|
335
|
+
'settings.cardDefaultsCardHint': '新建对话框打开时预填的 Agent 预设、权限、模型与思考档位;留空即不设置,创建卡片的行为与未配置时完全一致。它们只是预填,不改变任何既有语义,新建的卡片仍可随时修改;思考档位默认值只在卡片最终钉住的模型就是这里的默认模型时才生效。',
|
|
336
|
+
'settings.cardDefaultModeHint': '留空不钉住任何值,新建卡片照旧继承部署默认预设。取值优先级为:新建对话框里的显式选择 > 复制源卡或父任务已钉住的值 > 这里的默认值;子任务沿用父卡已钉住的值。',
|
|
337
|
+
'settings.cardDefaultPermissionHint': '留空为不设置(用会话默认)。高于会话默认权限基线的默认值仍只是预填:卡片执行前照旧需要人工确认。子任务从不沿用父卡的权限,只在父卡未钉住、且这里设置了默认值时才预填。',
|
|
338
|
+
'settings.cardDefaultModelHint': '留空为宿主默认(agent-default-model)。对话框里的显式选择优先于源卡/父卡已钉住的值,后者优先于这里的默认值;自己选了模型的调用方不借用任何默认档位。',
|
|
288
339
|
'running.executing': '执行中',
|
|
289
340
|
'running.verifying': '验收中',
|
|
290
341
|
'running.repairing': '验收未通过修复中',
|
|
291
342
|
'running.verificationPassed': '验收通过',
|
|
292
343
|
'running.verificationFailed': '验收未通过',
|
|
344
|
+
'running.verificationInvalid': '验收无效,等待处理',
|
|
293
345
|
'verify.title': '验收报告',
|
|
294
346
|
'verify.status.passed': '通过',
|
|
295
347
|
'verify.status.failed': '未通过',
|
|
@@ -297,6 +349,34 @@ export const zh = {
|
|
|
297
349
|
'verify.status.verifying': '验收中',
|
|
298
350
|
'verify.status.pending': '待验收',
|
|
299
351
|
'verify.status.off': '本次执行未启用验收',
|
|
352
|
+
'verify.status.invalid': '验收无效',
|
|
353
|
+
'verify.status.invalidHeld': '验收无效,等待人工处理',
|
|
354
|
+
'verify.attemptInvalid': '第 {index} 次验收无效',
|
|
355
|
+
'verify.invalidReason.missing-finding': '未达标的判据没有对应的结构化问题反馈',
|
|
356
|
+
'verify.invalidReason.unlocatable-quote': '问题反馈引用的位置不存在于本次裁判所见的证据中',
|
|
357
|
+
'verify.invalidReason.baseline-finding': '问题反馈指向空工作基线,而不是本次执行的工作',
|
|
358
|
+
'verify.invalidReason.vacuous-finding': '问题反馈为空或空泛评价,没有说明实际观测与差异',
|
|
359
|
+
'verify.invalidReason.insufficient-evidence': '裁判看到的证据不足(例如轨迹被截断),无法支撑否决',
|
|
360
|
+
'verify.invalidReason.unknown': '未产生可用的质量判定',
|
|
361
|
+
'verify.invalidCounts': '有效质量判定 {quality}/{max} · 无效验收 {invalid}/{maxInvalid} · 验收异常 {exceptions}/{max}',
|
|
362
|
+
'verify.invalidDetail': '无效原因:{reason}',
|
|
363
|
+
'verify.criterionFindings': '结构化问题反馈(判据 · 引用位置 · 实际引用)',
|
|
364
|
+
'verify.criterionFinding': '{criterion} · {location}:“{quote}”',
|
|
365
|
+
'verify.criterionFindingFull': '{criterion} · {location}:“{quote}” 要求:{requirement} 观测:{observation} 差异:{gap}',
|
|
366
|
+
'verify.location.task': '任务要求',
|
|
367
|
+
'verify.location.trajectory': '执行轨迹',
|
|
368
|
+
'verify.location.workspace': '宿主记录的变更',
|
|
369
|
+
'verify.location.baseline': '空工作基线(不构成否决证据)',
|
|
370
|
+
'verify.location.unknown': '未标明位置',
|
|
371
|
+
'verify.detailRemoved': '本条验收的详细材料已在通过并成功结算后自动清理(不是裁判未举证)。',
|
|
372
|
+
'verify.cleanup.cleaned': '本次执行的验收专用详细材料已在通过并成功结算后自动清理;以下为保留的轻量审计凭据。',
|
|
373
|
+
'verify.cleanup.pending': '验收专用详细材料待清理(将通过并结算后启动)。',
|
|
374
|
+
'verify.cleanup.failed': '验收详细材料清理失败(已重试 {attempts} 次):{error};通过记录仍然有效,宿主重启后会补清。',
|
|
375
|
+
'verify.cleanup.audit': '保留凭据:第 {index} 次 · {stage} · 分数 {score} · 证据哈希 {hash}',
|
|
376
|
+
'verify.cleanup.stage.quality': '质量判定',
|
|
377
|
+
'verify.cleanup.stage.invalid': '无效验收',
|
|
378
|
+
'verify.cleanup.stage.exception': '验收异常',
|
|
379
|
+
'verify.cleanup.stage.budget': '时间预算耗尽',
|
|
300
380
|
'verify.summary': '总分 {score}(阈值 {threshold})· 空工作基线 {baseline}',
|
|
301
381
|
'verify.criteria': '逐项判据',
|
|
302
382
|
'verify.criterion': '{name}:{score}(阈值 {threshold})',
|
|
@@ -315,9 +395,10 @@ export const zh = {
|
|
|
315
395
|
'verify.usageIncomplete': '(至少一次请求的用量未知,以上为下限)',
|
|
316
396
|
'verify.finalFailure': '判定依据:{reason}',
|
|
317
397
|
'verify.applicability.disabled': '本次执行启动时验收开关关闭,按原有回合判定结算。',
|
|
318
|
-
'verify.applicability.goalUnavailable': '本次执行未成为 goal
|
|
398
|
+
'verify.applicability.goalUnavailable': '本次执行未成为 goal 执行(卡片未勾选「以 /goal 开始执行任务」,或 /goal 被拒绝/不可用),验收未强制执行。',
|
|
319
399
|
'verify.applicability.skipped': '该任务卡勾选了「跳过验收」,本次执行未经验收判定。',
|
|
320
400
|
'verify.applicability.teamMember': '团队执行成员:由 Lead 的团队汇总证据统一验收。',
|
|
401
|
+
'verify.applicability.goalDisabled': '本次执行启动时全局「原生 /goal 执行」总开关关闭,按普通单回合执行:本次未执行 goal 验收,既不算已验收通过,也不因此判失败。',
|
|
321
402
|
'verify.effortFallback': '推理强度回退:{requested} → {resolved}',
|
|
322
403
|
'verify.thresholdValue': '{value}%',
|
|
323
404
|
// 子任务(父子关系、级联执行、关联/解除关联)。
|
|
@@ -329,7 +410,8 @@ export const zh = {
|
|
|
329
410
|
'detail.subtasks.link': '关联现有任务',
|
|
330
411
|
'detail.subtasks.detach': '解除关联',
|
|
331
412
|
'detail.subtasks.runningLock': '子任务正在执行,结算后才能解除关联或改挂父任务',
|
|
332
|
-
'detail.subtasks.runHint': '
|
|
413
|
+
'detail.subtasks.runHint': '执行本任务会同时并发执行它 {count} 个未完成的子任务;已在完成列的子任务不会重复执行,子任务失败不影响其它子任务继续执行。',
|
|
414
|
+
'detail.subtasks.runHintSettled': '这个任务的子任务都已在完成列;执行本任务不会重复执行它们,要让某个子任务再跑一次,把它移回待办列或单独执行它。',
|
|
333
415
|
'detail.subtasks.waiting': '等待子任务结算',
|
|
334
416
|
'detail.subtasks.depthLimit': '当前子任务深度上限为 {depth} 层,子任务不能再创建或关联子任务。',
|
|
335
417
|
'detail.subtasks.hostOnly': '连接 Host 后才能创建或关联子任务。',
|
|
@@ -515,6 +597,7 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
515
597
|
'detail.noSession': 'No session',
|
|
516
598
|
'detail.executionStarted': 'Started',
|
|
517
599
|
'detail.execution.initiator': 'Initiated by session {session}',
|
|
600
|
+
'detail.execution.external': 'Completed outside this Host by {agent}',
|
|
518
601
|
'detail.executionEnded': 'Ended',
|
|
519
602
|
'detail.result.succeeded': 'Succeeded',
|
|
520
603
|
'detail.result.failed': 'Failed',
|
|
@@ -557,7 +640,36 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
557
640
|
'detail.schedule.duration.hours': '{count}h',
|
|
558
641
|
'detail.schedule.duration.minutes': '{count}m',
|
|
559
642
|
'detail.schedule.duration.seconds': '{count}s',
|
|
643
|
+
// Scheduling mode: a one-shot and a recurring rule are two kinds of plan,
|
|
644
|
+
// not the same cron expression restricted to a date.
|
|
645
|
+
'detail.schedule.mode': 'Mode',
|
|
646
|
+
'detail.schedule.mode.cron': 'Recurring',
|
|
647
|
+
'detail.schedule.mode.once': 'Once',
|
|
648
|
+
'detail.schedule.at': 'Run at',
|
|
649
|
+
'detail.schedule.atHint': 'Planned instant, read in the selected zone; it must be in the future.',
|
|
650
|
+
'detail.schedule.atInvalid': 'Choose a valid future time',
|
|
651
|
+
'detail.schedule.plannedAt': 'Planned time',
|
|
652
|
+
'detail.schedule.zoneOnceHint': 'The chosen instant is read in this zone.',
|
|
653
|
+
'detail.schedule.maxRuns': 'Run limit',
|
|
654
|
+
'detail.schedule.maxRunsUnlimited': 'Unlimited',
|
|
655
|
+
'detail.schedule.maxRunsValue': 'At most {count} runs',
|
|
656
|
+
'detail.schedule.maxRunsCustom': 'Custom',
|
|
657
|
+
'detail.schedule.maxRunsInvalid': 'The run limit must be a positive whole number',
|
|
658
|
+
'detail.schedule.runs': 'Ran {done}/{total}',
|
|
659
|
+
'detail.schedule.runsUnlimited': 'Ran {done}',
|
|
660
|
+
'detail.schedule.remaining': '{count} left',
|
|
661
|
+
'detail.schedule.cancel': 'Cancel schedule',
|
|
662
|
+
'detail.schedule.ended': 'Schedule ended: {reason}',
|
|
663
|
+
'detail.schedule.skipped': 'Last skip: {reason}',
|
|
664
|
+
'detail.schedule.stop.fired': 'the one-shot ran',
|
|
665
|
+
'detail.schedule.stop.limit': 'the run limit was reached',
|
|
666
|
+
'detail.schedule.stop.no-target': 'the plan has no reachable occurrence',
|
|
667
|
+
'detail.schedule.stop.missed': 'the instant was missed and skipped',
|
|
668
|
+
'detail.schedule.stop.busy': 'the task was already running, so the occurrence was skipped',
|
|
669
|
+
'detail.schedule.stop.permission': 'a permission awaited human confirmation, so the occurrence was skipped',
|
|
670
|
+
'detail.schedule.stop.launch-failed': 'the launch failed before any execution existed, so the run was refunded',
|
|
560
671
|
'card.scheduled': 'scheduled',
|
|
672
|
+
'card.scheduledOnce': 'once',
|
|
561
673
|
'new.workspace': 'Workspace',
|
|
562
674
|
'new.agentPreset': 'Agent preset',
|
|
563
675
|
'new.permission': 'Permission',
|
|
@@ -581,13 +693,21 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
581
693
|
'exec.permission.danger-full-access': 'Full Access',
|
|
582
694
|
'exec.model.default': 'Host default (agent-default-model)',
|
|
583
695
|
'exec.model.unknown': ' (unknown / fallback to default)',
|
|
696
|
+
'exec.reasoning': 'Reasoning effort',
|
|
697
|
+
'exec.reasoning.inherit': 'Model or Host default',
|
|
698
|
+
'exec.reasoning.unknown': ' (unknown effort)',
|
|
699
|
+
'exec.reasoning.hint.pickModel': 'Pin a model first, then choose its reasoning effort.',
|
|
700
|
+
'exec.reasoning.hint.unknownModel': "The current roster cannot confirm this model's effort levels; the stored value is kept as it is.",
|
|
701
|
+
'exec.reasoning.hint.noEfforts': 'This model offers no selectable reasoning effort, so the model or Host default always applies.',
|
|
702
|
+
'exec.reasoning.hint.inherit': 'Leave it empty to follow the model or Host default.',
|
|
703
|
+
'exec.teamRunAutoHint': 'Once a subtask is created or linked, the parent task turns on Agent Team execution automatically (you can switch it off at any time).',
|
|
584
704
|
'exec.reuseSession': 'Continue in the same conversation',
|
|
585
705
|
'exec.teamRun': 'Run with an Agent Team (Lead + teammates)',
|
|
586
706
|
'exec.teamRunHint': 'On: running this task starts one Lead session and spawns a teammate per subtask inside it, working in parallel (a subtask permission pin does not apply). Off: every member gets its own independent session.',
|
|
587
707
|
'exec.teamRunUnavailable': 'This deployment serves no Agent Teams service, so team runs cannot be enabled.',
|
|
588
708
|
'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.',
|
|
589
709
|
'exec.goalRun': "Start the run with dsh's built-in /goal",
|
|
590
|
-
'exec.goalRunHint': '
|
|
710
|
+
'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.',
|
|
591
711
|
'exec.skipVerification': 'Skip acceptance for this task',
|
|
592
712
|
'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.',
|
|
593
713
|
'detail.executionSettings': 'Execution Settings',
|
|
@@ -621,9 +741,18 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
621
741
|
'settings.unsaved': 'Unsaved',
|
|
622
742
|
'settings.saveFailed': 'The deployment did not accept these values; they were left for you to correct.',
|
|
623
743
|
'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',
|
|
744
|
+
'settings.sessionPollSeconds': 'Session poll interval (seconds)',
|
|
745
|
+
'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.',
|
|
624
746
|
'settings.maxSubtaskDepth': 'Subtask depth limit',
|
|
625
747
|
'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.',
|
|
626
748
|
'settings.maxSubtaskDepthOption': '{depth} levels',
|
|
749
|
+
'settings.runLaunchWidth': 'Concurrent run launches',
|
|
750
|
+
'settings.runLaunchWidthHint': 'Default 3. A run opens its whole subtree, but only this many members create their session and queue their prompt at the same time; the rest wait their turn. Every member still starts, entering the running column in batches. The default comes from this repository\'s measured guidance for heavy implementation subagents (at most three). Range 1 to 16: a larger width starts runs sooner and costs the Host more memory and swap. Edited during a run, it bounds the launches that have not been admitted yet.',
|
|
751
|
+
'settings.runBehaviorTitle': 'Run behavior',
|
|
752
|
+
'settings.runBehaviorCardHint': 'The global native /goal switch (master control, off by default).',
|
|
753
|
+
'settings.goalRunGlobal': 'Global: run tasks with dsh native /goal',
|
|
754
|
+
'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.',
|
|
755
|
+
'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.',
|
|
627
756
|
'settings.goalVerificationTitle': 'Task acceptance',
|
|
628
757
|
'settings.goalVerification': 'Enable task acceptance',
|
|
629
758
|
'settings.goalVerificationCardHint': 'The switch, the judge model and the criteria threshold.',
|
|
@@ -647,12 +776,18 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
647
776
|
'settings.goalVerificationRouteMissing': 'The host serves no model catalog, so no judge route resolves: with acceptance on, a goal completion claim is refused and recorded as an acceptance anomaly.',
|
|
648
777
|
'settings.goalVerificationEffortFallback': 'The selected reasoning effort {requested} is not supported by the target model; it falls back to {resolved}.',
|
|
649
778
|
'settings.goalVerificationEffortFallbackNone': 'The selected reasoning effort {requested} is not supported by the target model; it falls back to that model default level (not sent explicitly).',
|
|
779
|
+
'settings.cardDefaultsTitle': 'New-card defaults',
|
|
780
|
+
'settings.cardDefaultsCardHint': 'What the new-task dialog opens with: agent preset, permission, model and reasoning effort. Blank sets nothing, so creating a card behaves exactly as it does unconfigured. They are only prefills and change no existing meaning, and every new card stays editable; the default reasoning effort applies only while a card ends up on the configured default model.',
|
|
781
|
+
'settings.cardDefaultModeHint': 'Blank pins nothing: a new card keeps following the deployment default preset. A target resolves as the explicit choice in the new-task dialog, then the value carried over from the source card or parent, then this default; a subtask keeps what its parent already pinned.',
|
|
782
|
+
'settings.cardDefaultPermissionHint': 'Blank sets nothing (the session default applies). A default above the session permission baseline is still only a prefill: the card still needs a human confirmation before it may run. A subtask never carries its parent permission; this default fills in only where the parent pinned none.',
|
|
783
|
+
'settings.cardDefaultModelHint': 'Blank is the host default (agent-default-model). An explicit choice in the dialog wins over the value carried over from the source card or parent, which wins over this default; a caller that pins a model of its own borrows no default reasoning level.',
|
|
650
784
|
'settings.goalVerificationModelInvalid': 'The configured judge model is not a provider/model route; the host default is used instead.',
|
|
651
785
|
'running.executing': 'Executing',
|
|
652
786
|
'running.verifying': 'Verifying',
|
|
653
787
|
'running.repairing': 'Fixing a failed acceptance',
|
|
654
788
|
'running.verificationPassed': 'Acceptance passed',
|
|
655
789
|
'running.verificationFailed': 'Acceptance failed',
|
|
790
|
+
'running.verificationInvalid': 'Invalid acceptance, waiting for a human',
|
|
656
791
|
'verify.title': 'Acceptance report',
|
|
657
792
|
'verify.status.passed': 'Passed',
|
|
658
793
|
'verify.status.failed': 'Failed',
|
|
@@ -660,6 +795,34 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
660
795
|
'verify.status.verifying': 'Verifying',
|
|
661
796
|
'verify.status.pending': 'Not yet verified',
|
|
662
797
|
'verify.status.off': 'Acceptance was off for this execution',
|
|
798
|
+
'verify.status.invalid': 'Invalid acceptance',
|
|
799
|
+
'verify.status.invalidHeld': 'Invalid acceptance, waiting for a human',
|
|
800
|
+
'verify.attemptInvalid': 'Invalid acceptance {index}',
|
|
801
|
+
'verify.invalidReason.missing-finding': 'A failing criterion carries no structured finding of its own',
|
|
802
|
+
'verify.invalidReason.unlocatable-quote': 'A finding cites a passage that is not in the evidence the judge saw',
|
|
803
|
+
'verify.invalidReason.baseline-finding': 'A finding points at the empty-work baseline instead of this execution\'s work',
|
|
804
|
+
'verify.invalidReason.vacuous-finding': 'A finding is empty or a generic evaluation with no observation or difference',
|
|
805
|
+
'verify.invalidReason.insufficient-evidence': 'The judge saw too little evidence (for example a truncated trace) to support a veto',
|
|
806
|
+
'verify.invalidReason.unknown': 'No usable quality verdict was produced',
|
|
807
|
+
'verify.invalidCounts': 'Valid quality verdicts {quality}/{max} · invalid acceptances {invalid}/{maxInvalid} · anomalies {exceptions}/{max}',
|
|
808
|
+
'verify.invalidDetail': 'Invalid because: {reason}',
|
|
809
|
+
'verify.criterionFindings': 'Structured findings (criterion · cited location · verbatim citation)',
|
|
810
|
+
'verify.criterionFinding': '{criterion} · {location}: "{quote}"',
|
|
811
|
+
'verify.criterionFindingFull': '{criterion} · {location}: "{quote}" requirement: {requirement} observed: {observation} gap: {gap}',
|
|
812
|
+
'verify.location.task': 'task statement',
|
|
813
|
+
'verify.location.trajectory': 'execution trajectory',
|
|
814
|
+
'verify.location.workspace': 'host-recorded changes',
|
|
815
|
+
'verify.location.baseline': 'empty-work baseline (not evidence against the work)',
|
|
816
|
+
'verify.location.unknown': 'location not named',
|
|
817
|
+
'verify.detailRemoved': 'This attempt detail was cleaned up automatically after the execution passed and settled (the judge did report evidence).',
|
|
818
|
+
'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.',
|
|
819
|
+
'verify.cleanup.pending': 'The acceptance-only detail is pending cleanup (started once the execution passes and settles).',
|
|
820
|
+
'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.',
|
|
821
|
+
'verify.cleanup.audit': 'Kept credential: attempt {index} · {stage} · score {score} · evidence hash {hash}',
|
|
822
|
+
'verify.cleanup.stage.quality': 'quality verdict',
|
|
823
|
+
'verify.cleanup.stage.invalid': 'invalid acceptance',
|
|
824
|
+
'verify.cleanup.stage.exception': 'acceptance anomaly',
|
|
825
|
+
'verify.cleanup.stage.budget': 'time budget exhausted',
|
|
663
826
|
'verify.summary': 'Total {score} (threshold {threshold}) · empty-work baseline {baseline}',
|
|
664
827
|
'verify.criteria': 'Per-criterion scores',
|
|
665
828
|
'verify.criterion': '{name}: {score} (threshold {threshold})',
|
|
@@ -678,9 +841,10 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
678
841
|
'verify.usageIncomplete': ' (at least one request usage is unknown; these counts are a floor)',
|
|
679
842
|
'verify.finalFailure': 'Verdict basis: {reason}',
|
|
680
843
|
'verify.applicability.disabled': 'Acceptance was off when this execution started; it settles on the historical verdict.',
|
|
681
|
-
'verify.applicability.goalUnavailable': 'This execution never became a goal run (/goal was refused or unavailable), so acceptance was not enforced.',
|
|
844
|
+
'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.',
|
|
682
845
|
'verify.applicability.skipped': 'This card checked Skip acceptance, so the execution was never judged.',
|
|
683
846
|
'verify.applicability.teamMember': 'Team member: the Lead team-summary evidence is accepted as one execution.',
|
|
847
|
+
'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.',
|
|
684
848
|
'verify.effortFallback': 'Reasoning effort fallback: {requested} → {resolved}',
|
|
685
849
|
'verify.thresholdValue': '{value}%',
|
|
686
850
|
// Subtasks (parent/child links, cascade runs, link/detach).
|
|
@@ -692,7 +856,8 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
692
856
|
'detail.subtasks.link': 'Link existing task',
|
|
693
857
|
'detail.subtasks.detach': 'Detach',
|
|
694
858
|
'detail.subtasks.runningLock': 'This subtask is running; it can be detached or re-linked once it settles',
|
|
695
|
-
'detail.subtasks.runHint': 'Running this task also runs its {count} subtasks concurrently; one subtask failing does not stop the others.',
|
|
859
|
+
'detail.subtasks.runHint': 'Running this task also runs its {count} unfinished subtasks concurrently; a subtask already in the done column is not run again, and one subtask failing does not stop the others.',
|
|
860
|
+
'detail.subtasks.runHintSettled': 'Every subtask of this task is already in the done column; running this task does not repeat them. To run one again, move it back to the todo column or run it on its own.',
|
|
696
861
|
'detail.subtasks.waiting': 'Waiting for subtasks',
|
|
697
862
|
'detail.subtasks.depthLimit': 'The subtask depth limit is {depth}; a subtask cannot create or link further subtasks.',
|
|
698
863
|
'detail.subtasks.hostOnly': 'Connect to the Host to create or link subtasks.',
|
|
@@ -86,6 +86,11 @@ export interface PluginCardSeat {
|
|
|
86
86
|
label?: () => string
|
|
87
87
|
/** Locale namespace the card renders with. */
|
|
88
88
|
locale: string
|
|
89
|
+
/**
|
|
90
|
+
* Child slots this card declares and renders. Forwarded to whichever seat
|
|
91
|
+
* the host renders; absent for a card that declares none.
|
|
92
|
+
*/
|
|
93
|
+
children?: Record<string, unknown>
|
|
89
94
|
/** Business-face factory of the registration. */
|
|
90
95
|
inject?: () => object
|
|
91
96
|
/**
|
|
@@ -162,12 +167,14 @@ export function installPluginCard(ctx: PluginCardContext, seat: PluginCardSeat):
|
|
|
162
167
|
...(seat.order === undefined ? {} : { order: seat.order }),
|
|
163
168
|
...(seat.label === undefined ? {} : { label: seat.label }),
|
|
164
169
|
locale: seat.locale,
|
|
170
|
+
...(seat.children === undefined ? {} : { children: seat.children }),
|
|
165
171
|
...(seat.inject === undefined ? {} : { inject }),
|
|
166
172
|
}
|
|
167
173
|
: {
|
|
168
174
|
name: OFFICIAL_PLUGIN_CARD_SEAT,
|
|
169
175
|
key: seat.bundle,
|
|
170
176
|
locale: seat.locale,
|
|
177
|
+
...(seat.children === undefined ? {} : { children: seat.children }),
|
|
171
178
|
...(seat.inject === undefined ? {} : { inject }),
|
|
172
179
|
}) as never, component) as () => void
|
|
173
180
|
current = target
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* the Host schedules with (`core/schedule.ts`), so the preview a user sees is
|
|
8
8
|
* computed by the code that will actually arm the rule.
|
|
9
9
|
*/
|
|
10
|
-
import { isValidTimeZone } from '../core/schedule.ts'
|
|
11
|
-
import {
|
|
10
|
+
import { isValidTimeZone, partsInZone, resolveHostTimeZone, zonedEpoch } from '../core/schedule.ts'
|
|
11
|
+
import { scheduleRunBudget, type ScheduleRule, type ScheduleStopReason } from '../core/tasks.ts'
|
|
12
|
+
import { t, type TaskBoardKey } from './locales.ts'
|
|
12
13
|
|
|
13
14
|
/** One selectable zone, in the order the picker shows it. */
|
|
14
15
|
export interface ZoneChoice {
|
|
@@ -112,6 +113,103 @@ export function relativeTimeLabel(targetMs: number, now: number): string {
|
|
|
112
113
|
return `${durationParts(delta)}`
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
/** Zero-pad a calendar/clock field for a `datetime-local` value. */
|
|
117
|
+
function pad(value: number): string {
|
|
118
|
+
return String(value).padStart(2, '0')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The `datetime-local` input value for an instant, read in an explicit zone:
|
|
123
|
+
* `YYYY-MM-DDTHH:mm`. Uses the shared engine's zone reader, so the editor
|
|
124
|
+
* shows the same wall clock the Host schedules by.
|
|
125
|
+
* @param epochMs - the instant to render.
|
|
126
|
+
* @param timeZone - zone to read it in; absent uses the Host zone.
|
|
127
|
+
* @returns the input value.
|
|
128
|
+
*/
|
|
129
|
+
export function zonedInputValue(epochMs: number, timeZone?: string): string {
|
|
130
|
+
const parts = partsInZone(usableZone(timeZone), epochMs)
|
|
131
|
+
return `${parts.year}-${pad(parts.month)}-${pad(parts.day)}T${pad(parts.hour)}:${pad(parts.minute)}`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse a `datetime-local` input value back to an instant, read in an explicit
|
|
136
|
+
* zone. Returns undefined when the text is not a whole calendar wall clock or
|
|
137
|
+
* names one the zone skips (a spring-forward gap), so the editor can refuse an
|
|
138
|
+
* unusable instant instead of silently moving it.
|
|
139
|
+
* @param value - the input text.
|
|
140
|
+
* @param timeZone - zone to interpret it in; absent uses the Host zone.
|
|
141
|
+
* @returns the instant, or undefined when it is not a usable wall clock.
|
|
142
|
+
*/
|
|
143
|
+
export function parseZonedInput(value: string, timeZone?: string): number | undefined {
|
|
144
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(value.trim())
|
|
145
|
+
if (match === null) return undefined
|
|
146
|
+
const [year, month, day, hour, minute] = match.slice(1).map(Number)
|
|
147
|
+
if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59) return undefined
|
|
148
|
+
return zonedEpoch(usableZone(timeZone), year, month, day, hour, minute)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The default instant a new one-shot editor opens on: the next whole hour,
|
|
153
|
+
* rendered in the zone the rule will be read in.
|
|
154
|
+
* @param now - reference instant.
|
|
155
|
+
* @param timeZone - zone the rule is read in.
|
|
156
|
+
* @returns the input value for a near-future default.
|
|
157
|
+
*/
|
|
158
|
+
export function defaultZonedInputValue(now: number, timeZone?: string): string {
|
|
159
|
+
const nextHour = Math.ceil(now / 3_600_000) * 3_600_000
|
|
160
|
+
return zonedInputValue(nextHour, timeZone)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The zone the input helpers resolve against: an explicit resolved zone, or the Host zone. */
|
|
164
|
+
function usableZone(timeZone: string | undefined): string {
|
|
165
|
+
return timeZone !== undefined && isValidTimeZone(timeZone) ? timeZone : resolveHostTimeZone()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The editor's run-cap option for a stored cap: the two named presets, the
|
|
170
|
+
* unlimited entry, or a custom value the editor shows in its own field.
|
|
171
|
+
* @param maxRuns - the stored cap, when any.
|
|
172
|
+
* @returns the select option value.
|
|
173
|
+
*/
|
|
174
|
+
export function runCapOption(maxRuns: number | undefined): string {
|
|
175
|
+
if (maxRuns === undefined) return 'unlimited'
|
|
176
|
+
if (maxRuns === 1) return '1'
|
|
177
|
+
if (maxRuns === 2) return '2'
|
|
178
|
+
return 'custom'
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The locale key describing why a rule stopped or skipped an occurrence. */
|
|
182
|
+
export function stopReasonKey(reason: ScheduleStopReason): TaskBoardKey {
|
|
183
|
+
return `detail.schedule.stop.${reason}` as TaskBoardKey
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The run-budget line: how many scheduled executions the Host opened and how
|
|
188
|
+
* many remain. Derived from the counter and the cap rather than stored, so the
|
|
189
|
+
* display cannot drift from the ledger.
|
|
190
|
+
* @param rule - the schedule rule.
|
|
191
|
+
* @returns localized wording.
|
|
192
|
+
*/
|
|
193
|
+
export function budgetLabel(rule: ScheduleRule): string {
|
|
194
|
+
const budget = scheduleRunBudget(rule)
|
|
195
|
+
const done = String(rule.runCount)
|
|
196
|
+
if (budget === undefined) return t('detail.schedule.runsUnlimited', { done })
|
|
197
|
+
const remaining = Math.max(budget - rule.runCount, 0)
|
|
198
|
+
return `${t('detail.schedule.runs', { done, total: String(budget) })} · ${t('detail.schedule.remaining', { count: String(remaining) })}`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The line describing a rule that stopped itself, or undefined while it may still fire. */
|
|
202
|
+
export function endedLabel(rule: ScheduleRule): string | undefined {
|
|
203
|
+
if (rule.endedReason === undefined) return undefined
|
|
204
|
+
return t('detail.schedule.ended', { reason: t(stopReasonKey(rule.endedReason)) })
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The line describing the last skipped occurrence, or undefined when none. */
|
|
208
|
+
export function skippedLabel(rule: ScheduleRule): string | undefined {
|
|
209
|
+
if (rule.skippedReason === undefined) return undefined
|
|
210
|
+
return t('detail.schedule.skipped', { reason: t(stopReasonKey(rule.skippedReason)) })
|
|
211
|
+
}
|
|
212
|
+
|
|
115
213
|
/**
|
|
116
214
|
* The full next-run label: the absolute wall clock in the schedule's own zone,
|
|
117
215
|
* plus the relative distance in parentheses. Showing both matters because a
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* New-card defaults: the execution targets a freshly created card starts from,
|
|
3
|
+
* and how they combine with what the caller picked and with the card a subtask
|
|
4
|
+
* or a duplicate is created from.
|
|
5
|
+
*
|
|
6
|
+
* Framework-free (no cordis, no runtime imports) because BOTH halves compile
|
|
7
|
+
* it: the Host resolves the seed when it writes the card, and the browser
|
|
8
|
+
* resolves the same seed to open the new-task dialog on it. One pure function
|
|
9
|
+
* is the only place the rules live, so the dialog and the ledger cannot drift.
|
|
10
|
+
*
|
|
11
|
+
* Every target is resolved in ONE order: what the caller picked wins over the
|
|
12
|
+
* value carried over from the card this one is created from, which wins over
|
|
13
|
+
* the board's new-card defaults, and a target nothing supplies stays blank
|
|
14
|
+
* ("no pin, resolved at run time"). A duplicate is a copy of its source and a
|
|
15
|
+
* subtask is derived from its parent, so both keep the source's own pins and
|
|
16
|
+
* fill only the fields that source left unset.
|
|
17
|
+
*
|
|
18
|
+
* The permission of a PARENT is the one exception: it never participates. The
|
|
19
|
+
* permission is not copied onto a card at creation — doing so would also have
|
|
20
|
+
* to copy the parent's human confirmation to stay runnable, and a detached
|
|
21
|
+
* child would keep both, so one confirmed parent would mint unbounded confirmed
|
|
22
|
+
* elevated roots. It is resolved along the ancestor chain at launch instead
|
|
23
|
+
* (core/subtask.ts), which is what makes detaching remove the inheritance by
|
|
24
|
+
* construction. A duplicate is not a hierarchy link, so it copies the
|
|
25
|
+
* permission like every other target. The board's own default permission is not
|
|
26
|
+
* a parent binding either and does apply to a subtask; it only pre-fills the
|
|
27
|
+
* card, and a value above the session baseline still needs a human
|
|
28
|
+
* confirmation before the card may run.
|
|
29
|
+
*
|
|
30
|
+
* The reasoning level is bound to the model route it was chosen for and never
|
|
31
|
+
* crosses to another one:
|
|
32
|
+
*
|
|
33
|
+
* - a level the caller picked is used as it stands;
|
|
34
|
+
* - a caller that pinned a model of its own inherits no level at all, not
|
|
35
|
+
* even one the same model happens to carry on the source card;
|
|
36
|
+
* - otherwise the source card's level travels with the source value it was
|
|
37
|
+
* stored on, exactly as the card's own model/level pair does, and wins over
|
|
38
|
+
* the board default level, so a board that configures none behaves exactly
|
|
39
|
+
* as the create path always has;
|
|
40
|
+
* - a board default level applies only while the card's final model IS the
|
|
41
|
+
* configured default model, and a blank default model makes it inert —
|
|
42
|
+
* a level the resolved model does not declare could only be refused by
|
|
43
|
+
* model selection at run time.
|
|
44
|
+
*/
|
|
45
|
+
import type { TaskRecord } from './tasks.ts'
|
|
46
|
+
|
|
47
|
+
/** The board's configured new-card defaults; a blank target means "not set". */
|
|
48
|
+
export interface CardDefaults {
|
|
49
|
+
/** Agent preset a new card starts from. */
|
|
50
|
+
mode?: string
|
|
51
|
+
/** Permission a new card starts from. */
|
|
52
|
+
permission?: string
|
|
53
|
+
/** Model a new card starts from. */
|
|
54
|
+
model?: string
|
|
55
|
+
/** Reasoning level a new card starts from, for the model above. */
|
|
56
|
+
reasoningEffort?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The card a new one is created from, and how it is derived from it. */
|
|
60
|
+
export interface CardTargetSource {
|
|
61
|
+
/** A copy of the source card, or a subtask of it. */
|
|
62
|
+
kind: 'duplicate' | 'parent'
|
|
63
|
+
/** The source card's execution targets. */
|
|
64
|
+
task: Pick<TaskRecord, 'mode' | 'permission' | 'model' | 'reasoningEffort'>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The resolved targets of the card about to be created; blank means "unset". */
|
|
68
|
+
export interface CardTargetSeed {
|
|
69
|
+
mode: string
|
|
70
|
+
permission: string
|
|
71
|
+
model: string
|
|
72
|
+
reasoningEffort: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Trim one optional target: blank text means "not set", like every stored pin. */
|
|
76
|
+
function textOf(value: string | undefined): string {
|
|
77
|
+
return value?.trim() ?? ''
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve the execution targets a new card starts from.
|
|
82
|
+
* @param defaults - the board's configured new-card defaults, if any.
|
|
83
|
+
* @param source - the card this one is created from (a duplicate or a parent), if any.
|
|
84
|
+
* @param picked - what the caller chose explicitly; a non-blank value wins.
|
|
85
|
+
* @returns the four targets, blank where nothing supplies a value.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveCardTargets(
|
|
88
|
+
defaults: CardDefaults | undefined,
|
|
89
|
+
source: CardTargetSource | undefined,
|
|
90
|
+
picked?: Partial<CardTargetSeed>,
|
|
91
|
+
): CardTargetSeed {
|
|
92
|
+
const defaultMode = textOf(defaults?.mode)
|
|
93
|
+
const defaultPermission = textOf(defaults?.permission)
|
|
94
|
+
const defaultModel = textOf(defaults?.model)
|
|
95
|
+
// A default level with no default model of its own names no route to bind to
|
|
96
|
+
// (only a hand-edited settings document can express that), so it is inert.
|
|
97
|
+
const defaultEffort = defaultModel === '' ? '' : textOf(defaults?.reasoningEffort)
|
|
98
|
+
const chosen = {
|
|
99
|
+
mode: textOf(picked?.mode),
|
|
100
|
+
permission: textOf(picked?.permission),
|
|
101
|
+
model: textOf(picked?.model),
|
|
102
|
+
reasoningEffort: textOf(picked?.reasoningEffort),
|
|
103
|
+
}
|
|
104
|
+
const copied = source === undefined ? undefined : {
|
|
105
|
+
mode: textOf(source.task.mode),
|
|
106
|
+
permission: textOf(source.task.permission),
|
|
107
|
+
model: textOf(source.task.model),
|
|
108
|
+
reasoningEffort: textOf(source.task.reasoningEffort),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The value carried over from the source card, or the board default. */
|
|
112
|
+
const inherited = (key: 'mode' | 'permission' | 'model'): string => {
|
|
113
|
+
const fallback = key === 'mode' ? defaultMode : key === 'model' ? defaultModel : defaultPermission
|
|
114
|
+
// A parent's binding never travels; a duplicate's does (it is not a link).
|
|
115
|
+
if (copied === undefined || (key === 'permission' && source?.kind === 'parent')) return fallback
|
|
116
|
+
return copied[key] === '' ? fallback : copied[key]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const mode = chosen.mode === '' ? inherited('mode') : chosen.mode
|
|
120
|
+
const permission = chosen.permission === '' ? inherited('permission') : chosen.permission
|
|
121
|
+
const model = chosen.model === '' ? inherited('model') : chosen.model
|
|
122
|
+
|
|
123
|
+
let reasoningEffort = chosen.reasoningEffort
|
|
124
|
+
if (reasoningEffort === '' && chosen.model === '') {
|
|
125
|
+
// The caller pinned no model of its own, so the level follows the source
|
|
126
|
+
// pair first and the board default second.
|
|
127
|
+
const sourced = copied === undefined ? '' : copied.reasoningEffort
|
|
128
|
+
reasoningEffort = sourced !== '' ? sourced : (model !== '' && model === defaultModel ? defaultEffort : '')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { mode, permission, model, reasoningEffort }
|
|
132
|
+
}
|