@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.
Files changed (83) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +34 -18
  3. package/README.zh.md +34 -18
  4. package/lib/client.js +2804 -1024
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2576 -416
  7. package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +61 -0
  9. package/lib/types/client/board/ReasoningEffortField.d.ts +25 -0
  10. package/lib/types/client/board/model-options.d.ts +67 -0
  11. package/lib/types/client/board/preset-label.d.ts +21 -0
  12. package/lib/types/client/board/preset-roster.d.ts +45 -0
  13. package/lib/types/client/board/team-run-promotion.d.ts +26 -0
  14. package/lib/types/client/index.d.ts +4 -5
  15. package/lib/types/client/locales.d.ts +81 -0
  16. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  17. package/lib/types/client/schedule-zone.d.ts +50 -0
  18. package/lib/types/core/card-defaults.d.ts +78 -0
  19. package/lib/types/core/controller.d.ts +41 -17
  20. package/lib/types/core/launch-width.d.ts +31 -0
  21. package/lib/types/core/poll-cadence.d.ts +31 -0
  22. package/lib/types/core/reasoning-effort.d.ts +57 -0
  23. package/lib/types/core/subtask.d.ts +30 -8
  24. package/lib/types/core/tasks.d.ts +163 -14
  25. package/lib/types/core/use-cases/task-create.d.ts +10 -5
  26. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  27. package/lib/types/core/use-cases/task-update.d.ts +1 -1
  28. package/lib/types/core/verification-budget.d.ts +52 -0
  29. package/lib/types/core/verification.d.ts +376 -9
  30. package/lib/types/host/launch-admission.d.ts +72 -0
  31. package/lib/types/host/verification-gate.d.ts +24 -0
  32. package/lib/types/host/verification-runner.d.ts +38 -7
  33. package/lib/types/host-ledger.d.ts +91 -11
  34. package/lib/types/host-runner.d.ts +111 -13
  35. package/lib/types/host-service.d.ts +199 -8
  36. package/lib/types/index.d.ts +141 -0
  37. package/lib/types/protocol.d.ts +63 -12
  38. package/package.json +29 -33
  39. package/src/client/CardDefaultPresetField.tsx +101 -0
  40. package/src/client/TaskBoardSettingsCard.tsx +288 -17
  41. package/src/client/board/LinkSubtaskModal.tsx +8 -0
  42. package/src/client/board/NewTaskModal.tsx +237 -60
  43. package/src/client/board/ReasoningEffortField.tsx +50 -0
  44. package/src/client/board/TaskCard.tsx +3 -3
  45. package/src/client/board/TaskDetail.tsx +261 -24
  46. package/src/client/board/VerificationReport.tsx +132 -13
  47. package/src/client/board/model-options.ts +178 -0
  48. package/src/client/board/preset-label.ts +32 -0
  49. package/src/client/board/preset-roster.ts +58 -0
  50. package/src/client/board/status-key.ts +3 -0
  51. package/src/client/board/team-run-promotion.ts +39 -0
  52. package/src/client/index.ts +105 -108
  53. package/src/client/locales.ts +171 -6
  54. package/src/client/plugin-card-seat.ts +7 -0
  55. package/src/client/schedule-zone.ts +100 -2
  56. package/src/client/settings-entry-form.ts +0 -1
  57. package/src/core/card-defaults.ts +132 -0
  58. package/src/core/controller.ts +55 -16
  59. package/src/core/launch-width.ts +38 -0
  60. package/src/core/poll-cadence.ts +41 -0
  61. package/src/core/reasoning-effort.ts +92 -0
  62. package/src/core/store.ts +69 -15
  63. package/src/core/subtask.ts +48 -13
  64. package/src/core/tasks.ts +213 -39
  65. package/src/core/use-cases/task-create.ts +57 -25
  66. package/src/core/use-cases/task-schedule.ts +190 -25
  67. package/src/core/use-cases/task-update.ts +9 -4
  68. package/src/core/verification-budget.ts +115 -0
  69. package/src/core/verification.ts +785 -17
  70. package/src/host/agent-tools.ts +127 -29
  71. package/src/host/launch-admission.ts +140 -0
  72. package/src/host/verification-gate.ts +172 -16
  73. package/src/host/verification-runner.ts +207 -40
  74. package/src/host-ledger.ts +346 -48
  75. package/src/host-runner.ts +234 -35
  76. package/src/host-service.ts +539 -52
  77. package/src/index.ts +250 -7
  78. package/src/protocol.ts +160 -32
  79. package/lib/invariant.js +0 -6
  80. package/lib/types/client/board-card-seat.d.ts +0 -34
  81. package/lib/types/invariant.d.ts +0 -3
  82. package/src/client/board-card-seat.ts +0 -102
  83. package/src/invariant.ts +0 -4
package/src/index.ts CHANGED
@@ -18,10 +18,24 @@ import type {} from '@deepseek-ai/dsh-tools'
18
18
  import type { ToolDefinition } from '@deepseek-ai/dsh-tools'
19
19
  import type { LlmRuntime } from '@deepseek-ai/dsh-llm'
20
20
  import { TaskBoardHostService, type HostTimerFace, type TaskBoardTeamDispatcher } from './host-service.ts'
21
+ import { createOwnerPeer } from './host-runner.ts'
21
22
  import { parseTaskDraft, TaskParseError } from './host-ai.ts'
22
23
  import { TASK_PERMISSIONS, isTaskPermission, type TaskPermission } from './core/tasks.ts'
23
24
  import { DEFAULT_SUBTASK_DEPTH, SUBTASK_DEPTH_MAX, SUBTASK_DEPTH_MIN } from './core/subtask.ts'
25
+ import { DEFAULT_SESSION_POLL_SECONDS, SESSION_POLL_MAX_SECONDS, SESSION_POLL_MIN_SECONDS } from './core/poll-cadence.ts'
26
+ import { DEFAULT_RUN_LAUNCH_WIDTH, RUN_LAUNCH_WIDTH_MAX, RUN_LAUNCH_WIDTH_MIN } from './core/launch-width.ts'
27
+ import {
28
+ DEFAULT_VERIFICATION_BUDGET_SECONDS,
29
+ DEFAULT_VERIFICATION_CALL_TIMEOUT_SECONDS,
30
+ VERIFICATION_BUDGET_MAX_SECONDS,
31
+ VERIFICATION_BUDGET_MIN_SECONDS,
32
+ VERIFICATION_CALL_TIMEOUT_MAX_SECONDS,
33
+ VERIFICATION_CALL_TIMEOUT_MIN_SECONDS,
34
+ verificationBudgetMs,
35
+ verificationCallTimeoutMs,
36
+ } from './core/verification-budget.ts'
24
37
  import { DEFAULT_SESSION_PERMISSION } from './core/handover.ts'
38
+ import type { CardDefaults } from './core/card-defaults.ts'
25
39
  import { buildTaskBoardTools, TASK_BOARD_TOOL_NAMES } from './host/agent-tools.ts'
26
40
  import { PLUGIN_TOOL_SECTION_ORDERS, visibleToolText } from './tool-surface.ts'
27
41
  import { makeTaskBoardRoutes } from './host-routes.ts'
@@ -88,12 +102,73 @@ export interface Config {
88
102
  * of the root task opens, so the ceiling is deliberately small.
89
103
  */
90
104
  maxSubtaskDepth?: Volatile<number>
105
+ /**
106
+ * How often, in seconds, the Host re-reads the DSH session roster while the
107
+ * board has something to reconcile: a running card, an open execution, or an
108
+ * active provider extension. An idle board with none of those does not poll
109
+ * at all, so this cadence only bounds how quickly a running card settles.
110
+ */
111
+ sessionPollSeconds?: Volatile<number>
112
+ /**
113
+ * How many run launches this board may have in flight at once (1..16,
114
+ * default 3). One run opens its whole subtree, so without a bound the Host
115
+ * asks DSH for every member's session in the same tick; this is the mechanism
116
+ * that enforces the repository's measured guidance for heavy implementation
117
+ * subagents. Members wait their turn instead of being dropped: the subtree
118
+ * still opens completely, they only enter the running column in batches. Read
119
+ * at each admission, so an edit bounds the next launch of a run that is still
120
+ * queueing.
121
+ */
122
+ runLaunchWidth?: Volatile<number>
91
123
  /**
92
124
  * Continuable-subagent provider the Agent Teams service uses to compose a
93
125
  * teammate. Matches the Agent Teams tool plugin's `freshProvider` default;
94
126
  * only team-mode runs use it.
95
127
  */
96
128
  teamProvider?: string
129
+ /**
130
+ * GLOBAL native /goal switch (default OFF), the master control over whether
131
+ * this board starts a run with dsh's built-in /goal at all. A run uses native
132
+ * /goal only when THIS is on AND the task's own `goalRun` is not off; while
133
+ * it is off every task runs one plain turn.
134
+ *
135
+ * The value is frozen when an execution starts, so toggling it only affects
136
+ * executions that begin afterwards and never re-writes a task's stored
137
+ * `goalRun` preference. A run that consequently does not execute native
138
+ * /goal is marked as such (its acceptance applicability is `goal-disabled`)
139
+ * and is never reported as a passed goal acceptance or failed for lacking a
140
+ * completion call. Independent of `goalVerification`; acceptance governs the
141
+ * completing call of a goal-form run, this switch governs whether the run
142
+ * becomes a goal run.
143
+ */
144
+ goalRunEnabled?: Volatile<boolean>
145
+ /**
146
+ * Agent preset a NEW card starts from (volatile, default blank = not set).
147
+ * A prefill, never a pin: the create dialog opens with it selected, the
148
+ * caller may override it, and the card stores what it was created with. It
149
+ * reaches the create path only when that caller opts in with
150
+ * `applyCardDefaults`, so the extension provider's own create and the legacy
151
+ * import are untouched.
152
+ */
153
+ cardDefaultMode?: Volatile<string>
154
+ /**
155
+ * Permission a NEW card starts from (volatile, default blank = not set), one
156
+ * of the three presets. A prefill like the rest: a value above
157
+ * `sessionDefaultPermission` still has to be confirmed by a human before the
158
+ * card may run, because the pre-execution gate is what decides that.
159
+ */
160
+ cardDefaultPermission?: Volatile<TaskPermission | ''>
161
+ /**
162
+ * Model a NEW card starts from (volatile, default blank = not set).
163
+ */
164
+ cardDefaultModel?: Volatile<string>
165
+ /**
166
+ * Reasoning level a NEW card starts from (volatile, default blank = not set).
167
+ * Bound to `cardDefaultModel` exactly as a card's own level is bound to its
168
+ * own model: it applies only while the card's final model IS that default
169
+ * model, and a blank `cardDefaultModel` makes it inert.
170
+ */
171
+ cardDefaultReasoningEffort?: Volatile<string>
97
172
  /**
98
173
  * Goal acceptance for executions this board starts (default ON). When on,
99
174
  * update_goal(action: complete) inside a task execution is refused until an
@@ -119,6 +194,23 @@ export interface Config {
119
194
  * sent, and the fallback to the model's own default is reported.
120
195
  */
121
196
  goalVerificationReasoningEffort?: Volatile<string>
197
+ /**
198
+ * Ceiling of ONE judge request of a goal acceptance, in seconds
199
+ * (30..600, default 150). One acceptance issues six judge requests (three
200
+ * criteria times two rounds), so a ceiling that is too small turns a slow but
201
+ * healthy judge route into a wall of timeouts, and every timeout was charged
202
+ * as an acceptance anomaly. Raise it for a reasoning model reading a long
203
+ * trace.
204
+ */
205
+ goalVerificationCallTimeoutSeconds?: Volatile<number>
206
+ /**
207
+ * Total ceiling of ONE goal acceptance attempt, in seconds (120..1800,
208
+ * default eight times the per-call default). Keep it at or above
209
+ * `goalVerificationCallTimeoutSeconds` times six or an acceptance can never
210
+ * finish: the attempt then ends early with a budget stop that judges nothing
211
+ * and spends no acceptance budget.
212
+ */
213
+ goalVerificationBudgetSeconds?: Volatile<number>
122
214
  }
123
215
 
124
216
  /**
@@ -152,14 +244,32 @@ export interface ConfigInput {
152
244
  sessionDefaultPermission?: TaskPermission
153
245
  /** Subtask depth limit, 1..3. */
154
246
  maxSubtaskDepth?: number
247
+ /** Roster-poll cadence in seconds while the board has work to reconcile. */
248
+ sessionPollSeconds?: number
249
+ /** Concurrent run launches allowed at once, 1..16. */
250
+ runLaunchWidth?: number
155
251
  /** Continuable-subagent provider the Agent Teams service composes a teammate from. */
156
252
  teamProvider?: string
253
+ /** Global native /goal switch; default off, frozen per execution. */
254
+ goalRunEnabled?: boolean
255
+ /** Agent preset a new card starts from; blank = not set. */
256
+ cardDefaultMode?: string
257
+ /** Permission a new card starts from; blank = not set. */
258
+ cardDefaultPermission?: TaskPermission | ''
259
+ /** Model a new card starts from; blank = not set. */
260
+ cardDefaultModel?: string
261
+ /** Reasoning level a new card starts from; blank = not set. */
262
+ cardDefaultReasoningEffort?: string
157
263
  /** Goal acceptance switch. */
158
264
  goalVerification?: boolean
159
265
  /** Judge model route for goal acceptance; blank inherits the host default. */
160
266
  goalVerificationModel?: string
161
267
  /** Judge reasoning effort; blank inherits the host default. */
162
268
  goalVerificationReasoningEffort?: string
269
+ /** Ceiling of one judge request of a goal acceptance, in seconds. */
270
+ goalVerificationCallTimeoutSeconds?: number
271
+ /** Total ceiling of one goal acceptance attempt, in seconds. */
272
+ goalVerificationBudgetSeconds?: number
163
273
  }
164
274
 
165
275
  export const Config: z<ConfigInput, Config> = z.object({
@@ -170,15 +280,31 @@ export const Config: z<ConfigInput, Config> = z.object({
170
280
  proxyTokenEnv: z.string().min(1).default(DEFAULT_PROXY_TOKEN_ENV),
171
281
  sessionDefaultPermission: z.union(TASK_PERMISSIONS),
172
282
  maxSubtaskDepth: z.number().min(SUBTASK_DEPTH_MIN).max(SUBTASK_DEPTH_MAX).default(DEFAULT_SUBTASK_DEPTH).volatile(),
283
+ sessionPollSeconds: z.number().min(SESSION_POLL_MIN_SECONDS).max(SESSION_POLL_MAX_SECONDS).default(DEFAULT_SESSION_POLL_SECONDS).volatile(),
284
+ runLaunchWidth: z.number().min(RUN_LAUNCH_WIDTH_MIN).max(RUN_LAUNCH_WIDTH_MAX).default(DEFAULT_RUN_LAUNCH_WIDTH).volatile(),
173
285
  teamProvider: z.string().min(1).default(DEFAULT_TEAM_PROVIDER),
286
+ goalRunEnabled: z.boolean().default(false).volatile(),
287
+ cardDefaultMode: z.string().default('').volatile(),
288
+ cardDefaultPermission: z.union([...TASK_PERMISSIONS, '']).default('').volatile(),
289
+ cardDefaultModel: z.string().default('').volatile(),
290
+ cardDefaultReasoningEffort: z.string().default('').volatile(),
174
291
  goalVerification: z.boolean().default(true).volatile(),
175
292
  goalVerificationModel: z.string().default('').volatile(),
176
293
  goalVerificationReasoningEffort: z.string().default('').volatile(),
294
+ goalVerificationCallTimeoutSeconds: z.number().min(VERIFICATION_CALL_TIMEOUT_MIN_SECONDS).max(VERIFICATION_CALL_TIMEOUT_MAX_SECONDS).default(DEFAULT_VERIFICATION_CALL_TIMEOUT_SECONDS).volatile(),
295
+ goalVerificationBudgetSeconds: z.number().min(VERIFICATION_BUDGET_MIN_SECONDS).max(VERIFICATION_BUDGET_MAX_SECONDS).default(DEFAULT_VERIFICATION_BUDGET_SECONDS).volatile(),
177
296
  })
178
297
 
179
298
  /** Schema default for the goal-acceptance switch, re-read for hand-built contexts. */
180
299
  export const DEFAULT_GOAL_VERIFICATION = true
181
300
 
301
+ /**
302
+ * Schema default for the GLOBAL native-/goal switch, re-read for hand-built
303
+ * contexts. Deliberately false: a deployment that never configured it (and
304
+ * every card written before the option existed) runs plain single turns.
305
+ */
306
+ export const DEFAULT_GOAL_RUN = false
307
+
182
308
  declare module '@deepseek-ai/cordis' {
183
309
  interface Events {
184
310
  /**
@@ -395,6 +521,70 @@ export function resolveLlmRuntime(ctx: Context): LlmRuntime | undefined {
395
521
  }
396
522
  }
397
523
 
524
+ /**
525
+ * The subset of `sessionController` the board uses to wake a cold persisted
526
+ * session before running a slash command on it (issue #1861).
527
+ */
528
+ interface SessionControllerFace {
529
+ resolveAgent(
530
+ sessionId: Parameters<Context['agents']['get']>[0],
531
+ ): Promise<
532
+ | { readonly agent: Parameters<Context['commands']['execute']>[0] }
533
+ | { readonly error: { readonly message?: string } }
534
+ >
535
+ }
536
+
537
+ /**
538
+ * Resolve the optional `sessionController` service. Like the tool registry and
539
+ * the llm runtime, it is read dynamically so capture-only test contexts that do
540
+ * not mount the full session controller still activate the board.
541
+ * @param ctx - the plugin context.
542
+ * @returns the session controller face, or undefined when unavailable.
543
+ */
544
+ export function resolveSessionController(ctx: Context): SessionControllerFace | undefined {
545
+ try {
546
+ const controller = (ctx as { get?: (name: string) => unknown }).get?.('sessionController') as SessionControllerFace | undefined
547
+ return controller !== undefined && typeof controller.resolveAgent === 'function' ? controller : undefined
548
+ } catch {
549
+ return undefined
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Resolve the live Agent for an execution session, resuming a cold persisted
555
+ * session through `sessionController.resolveAgent` when it is not yet in
556
+ * `ctx.agents` (issue #1861).
557
+ *
558
+ * A fresh run (`session/create`) or a session the browser is currently
559
+ * following is already in `ctx.agents`, so the fast path returns immediately.
560
+ * A scheduled or manual reuse of a cold session — one that `session/list`
561
+ * reports as idle from disk without activating its Agent — is not in
562
+ * `ctx.agents` when `pinAndPrompt` runs `/permission` ahead of
563
+ * `session/selectModel` and `session/prompt`; delegating to
564
+ * `sessionController.resolveAgent` resumes it with its recorded preset and
565
+ * model-selection state, matching what those gateway endpoints do next.
566
+ * @param ctx - the plugin context.
567
+ * @param sessionId - the target execution session id.
568
+ * @returns the live Agent for `sessionId`.
569
+ */
570
+ export async function resolveExecutionAgent(
571
+ ctx: Context,
572
+ sessionId: Parameters<Context['agents']['get']>[0],
573
+ ): Promise<Parameters<Context['commands']['execute']>[0]> {
574
+ const live = ctx.agents.get(sessionId)
575
+ if (live !== undefined) return live
576
+ const controller = resolveSessionController(ctx)
577
+ if (controller !== undefined) {
578
+ const resolved = await controller.resolveAgent(sessionId)
579
+ if ('agent' in resolved && resolved.agent !== undefined) return resolved.agent
580
+ const detail = 'error' in resolved && typeof resolved.error?.message === 'string' && resolved.error.message !== ''
581
+ ? `: ${resolved.error.message}`
582
+ : ''
583
+ throw new Error(`execution session ${sessionId} is not available${detail}`)
584
+ }
585
+ throw new Error(`execution session ${sessionId} is not available`)
586
+ }
587
+
398
588
  /**
399
589
  * Activate the board's host half: ledger, routes, cron scheduler, power
400
590
  * inhibitor, and the model-facing announcement.
@@ -419,6 +609,14 @@ function applyImpl(ctx: Context, config?: Config): void {
419
609
  const preventIdleSleep = (): boolean => readConfigField(config?.preventIdleSleep, false)
420
610
  /** Current subtask depth limit (read live: the settings card edits it in place). */
421
611
  const maxSubtaskDepth = (): number => readConfigField(config?.maxSubtaskDepth, DEFAULT_SUBTASK_DEPTH)
612
+ /** Current roster-poll cadence in seconds (read live: the settings card edits it in place). */
613
+ const sessionPollSeconds = (): number => readConfigField(config?.sessionPollSeconds, DEFAULT_SESSION_POLL_SECONDS)
614
+ /**
615
+ * Current run-launch admission width. Read live on every admission, so the
616
+ * settings card bounds the next launch of a run that is still queueing without
617
+ * remounting the row; the Host clamps a hand-edited value into the schema range.
618
+ */
619
+ const runLaunchWidth = (): number => readConfigField(config?.runLaunchWidth, DEFAULT_RUN_LAUNCH_WIDTH)
422
620
  /**
423
621
  * The baseline the permission confirmation gate judges against: the row's own
424
622
  * `sessionDefaultPermission` when the deployment pins one, otherwise the Host's
@@ -435,6 +633,15 @@ function applyImpl(ctx: Context, config?: Config): void {
435
633
  model: readConfigField(config?.goalVerificationModel, ''),
436
634
  reasoningEffort: readConfigField(config?.goalVerificationReasoningEffort, ''),
437
635
  })
636
+ /**
637
+ * The acceptance time ceilings, read live: raising one must unblock a card
638
+ * whose acceptance is stuck on a slow judge route without a remount, so
639
+ * these deliberately are NOT frozen into the per-execution contract.
640
+ */
641
+ const verificationCallTimeoutMsLive = (): number =>
642
+ verificationCallTimeoutMs(readConfigField(config?.goalVerificationCallTimeoutSeconds, DEFAULT_VERIFICATION_CALL_TIMEOUT_SECONDS))
643
+ const verificationBudgetMsLive = (): number =>
644
+ verificationBudgetMs(readConfigField(config?.goalVerificationBudgetSeconds, DEFAULT_VERIFICATION_BUDGET_SECONDS))
438
645
  // The catalog reader needs the service, and the service needs the reader;
439
646
  // the reader only runs after start(), so a late holder is enough.
440
647
  let hostForCatalog: TaskBoardHostService | undefined
@@ -442,18 +649,46 @@ function applyImpl(ctx: Context, config?: Config): void {
442
649
  const runner = hostForCatalog?.runner
443
650
  return runner === undefined ? undefined : normalizeCatalog(await runner.modelCatalog())
444
651
  }
652
+ /**
653
+ * Live reader of the GLOBAL native-/goal switch. The service calls it exactly
654
+ * once per execution and carries the value, so a settings edit only reaches
655
+ * executions that start afterwards.
656
+ */
657
+ const goalRunEnabled = (): boolean => readConfigField(config?.goalRunEnabled, DEFAULT_GOAL_RUN)
658
+ /**
659
+ * Live reader of the new-card defaults, read at every use like the switch
660
+ * above: the create dialog prefills from the published snapshot, and each
661
+ * creating session re-reads them when it submits the create. A blank value
662
+ * means "not set" and reaches the resolver as an absent target.
663
+ */
664
+ const cardDefaults = (): CardDefaults => {
665
+ const permission = readConfigField<TaskPermission | ''>(config?.cardDefaultPermission, '')
666
+ return {
667
+ mode: readConfigField(config?.cardDefaultMode, '').trim() || undefined,
668
+ permission: isTaskPermission(permission) ? permission : undefined,
669
+ model: readConfigField(config?.cardDefaultModel, '').trim() || undefined,
670
+ reasoningEffort: readConfigField(config?.cardDefaultReasoningEffort, '').trim() || undefined,
671
+ }
672
+ }
673
+ // The 0.2.1 gateway requires every request to name the peer it speaks for:
674
+ // the board calls as the host-local owner, so its peer is minted on this
675
+ // fiber and aborts with it (see createOwnerPeer).
676
+ const ownerPeer = createOwnerPeer(ctx)
445
677
  const host = new TaskBoardHostService(ctx.typertGateway, {
446
678
  workspaceRegistry: ctx.workspaceRegistry,
679
+ ownerPeer,
447
680
  sessionDefaultPermission,
448
681
  maxSubtaskDepth: maxSubtaskDepth(),
682
+ goalRunEnabled,
683
+ runLaunchWidth,
684
+ cardDefaults,
449
685
  verificationSettings,
450
686
  verificationCatalog,
451
687
  timers: resolveHostTimers(ctx),
452
688
  team: buildTeamDispatcher(ctx, config?.teamProvider ?? DEFAULT_TEAM_PROVIDER),
453
689
  commandDispatcher: {
454
690
  async execute(sessionId, line, signal) {
455
- const agent = ctx.agents.get(sessionId)
456
- if (agent === undefined) throw new Error(`execution session ${sessionId} is not available`)
691
+ const agent = await resolveExecutionAgent(ctx, sessionId)
457
692
  return (await ctx.commands.execute(agent, line, [], signal))?.result
458
693
  },
459
694
  },
@@ -461,7 +696,7 @@ function applyImpl(ctx: Context, config?: Config): void {
461
696
  hostForCatalog = host
462
697
  // Configuration before start(): a disabled row must not take the first
463
698
  // scheduler tick, which would roll schedules the board is not running.
464
- host.setConfiguration(enabled(), preventIdleSleep())
699
+ host.setConfiguration(enabled(), preventIdleSleep(), sessionPollSeconds())
465
700
  host.start()
466
701
 
467
702
  // External providers. The board publishes its registration service so any
@@ -543,6 +778,8 @@ function applyImpl(ctx: Context, config?: Config): void {
543
778
  // deployment records it; the acceptance degrades to the trajectory alone
544
779
  // when it does not.
545
780
  workspaceChanges: () => probeWorkspaceChanges(ctx),
781
+ callTimeoutMs: verificationCallTimeoutMsLive,
782
+ budgetMs: verificationBudgetMsLive,
546
783
  logger: {
547
784
  warn: (message: string, ...rest: unknown[]) => {
548
785
  const logger = (ctx as { logger?: { warn?: (...args: unknown[]) => void } }).logger
@@ -586,7 +823,7 @@ function applyImpl(ctx: Context, config?: Config): void {
586
823
  }, 'task-board: host ledger, scheduler, and routes')
587
824
 
588
825
  let disposeSection: (() => void) | undefined
589
- let applied: { enabled: boolean; announceToAgent: boolean; preventIdleSleep: boolean; maxSubtaskDepth: number } | undefined
826
+ let applied: { enabled: boolean; announceToAgent: boolean; preventIdleSleep: boolean; maxSubtaskDepth: number; sessionPollSeconds: number } | undefined
590
827
 
591
828
  // Apply the current values to the host service and the announcement. A
592
829
  // commit that changes nothing visible is a no-op, so following a coarse
@@ -594,13 +831,19 @@ function applyImpl(ctx: Context, config?: Config): void {
594
831
  // under one disposer: re-registering first tears the old one down so a
595
832
  // duplicate-name registration never throws.
596
833
  const sync = (): void => {
597
- const next = { enabled: enabled(), announceToAgent: announceToAgent(), preventIdleSleep: preventIdleSleep(), maxSubtaskDepth: maxSubtaskDepth() }
598
- if (applied !== undefined && applied.enabled === next.enabled && applied.announceToAgent === next.announceToAgent && applied.preventIdleSleep === next.preventIdleSleep && applied.maxSubtaskDepth === next.maxSubtaskDepth) {
834
+ const next = {
835
+ enabled: enabled(),
836
+ announceToAgent: announceToAgent(),
837
+ preventIdleSleep: preventIdleSleep(),
838
+ maxSubtaskDepth: maxSubtaskDepth(),
839
+ sessionPollSeconds: sessionPollSeconds(),
840
+ }
841
+ if (applied !== undefined && applied.enabled === next.enabled && applied.announceToAgent === next.announceToAgent && applied.preventIdleSleep === next.preventIdleSleep && applied.maxSubtaskDepth === next.maxSubtaskDepth && applied.sessionPollSeconds === next.sessionPollSeconds) {
599
842
  return
600
843
  }
601
844
  applied = next
602
845
  host.ledger.setMaxSubtaskDepth(next.maxSubtaskDepth)
603
- host.setConfiguration(next.enabled, next.preventIdleSleep)
846
+ host.setConfiguration(next.enabled, next.preventIdleSleep, next.sessionPollSeconds)
604
847
  setToolsEnabled(next.enabled)
605
848
  if (disposeSection !== undefined) {
606
849
  disposeSection()