@linxin666/dsh-client-ui-task-board 0.4.6 → 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 (58) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +14 -6
  3. package/README.zh.md +14 -6
  4. package/lib/client.js +1502 -820
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +674 -75
  7. package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +49 -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/locales.d.ts +16 -0
  15. package/lib/types/core/card-defaults.d.ts +78 -0
  16. package/lib/types/core/controller.d.ts +32 -7
  17. package/lib/types/core/launch-width.d.ts +31 -0
  18. package/lib/types/core/reasoning-effort.d.ts +57 -0
  19. package/lib/types/core/subtask.d.ts +30 -8
  20. package/lib/types/core/tasks.d.ts +19 -0
  21. package/lib/types/core/use-cases/task-create.d.ts +10 -5
  22. package/lib/types/core/use-cases/task-update.d.ts +1 -1
  23. package/lib/types/host/launch-admission.d.ts +72 -0
  24. package/lib/types/host-ledger.d.ts +9 -4
  25. package/lib/types/host-runner.d.ts +79 -4
  26. package/lib/types/host-service.d.ts +85 -2
  27. package/lib/types/index.d.ts +87 -0
  28. package/lib/types/protocol.d.ts +13 -0
  29. package/package.json +28 -28
  30. package/src/client/CardDefaultPresetField.tsx +101 -0
  31. package/src/client/TaskBoardSettingsCard.tsx +214 -16
  32. package/src/client/board/LinkSubtaskModal.tsx +8 -0
  33. package/src/client/board/NewTaskModal.tsx +86 -40
  34. package/src/client/board/ReasoningEffortField.tsx +50 -0
  35. package/src/client/board/TaskDetail.tsx +42 -6
  36. package/src/client/board/model-options.ts +178 -0
  37. package/src/client/board/preset-label.ts +32 -0
  38. package/src/client/board/preset-roster.ts +58 -0
  39. package/src/client/board/team-run-promotion.ts +39 -0
  40. package/src/client/index.ts +98 -94
  41. package/src/client/locales.ts +34 -2
  42. package/src/client/settings-entry-form.ts +0 -1
  43. package/src/core/card-defaults.ts +132 -0
  44. package/src/core/controller.ts +44 -9
  45. package/src/core/launch-width.ts +38 -0
  46. package/src/core/reasoning-effort.ts +92 -0
  47. package/src/core/store.ts +5 -0
  48. package/src/core/subtask.ts +48 -13
  49. package/src/core/tasks.ts +20 -0
  50. package/src/core/use-cases/task-create.ts +27 -16
  51. package/src/core/use-cases/task-update.ts +6 -1
  52. package/src/host/agent-tools.ts +21 -8
  53. package/src/host/launch-admission.ts +140 -0
  54. package/src/host-ledger.ts +18 -6
  55. package/src/host-runner.ts +184 -18
  56. package/src/host-service.ts +247 -23
  57. package/src/index.ts +149 -2
  58. package/src/protocol.ts +28 -4
@@ -325,6 +325,15 @@ export interface TaskRecord {
325
325
  * absent falls back to the host default (agent-default-model).
326
326
  */
327
327
  model?: string;
328
+ /**
329
+ * Reasoning effort pinned for that execution route (an adapter-owned id such
330
+ * as "high"). Absent (or blank) means INHERIT: the session runs at the
331
+ * model's own default level, exactly as a card written before the field
332
+ * existed does. The launch sends the value only when the target model's
333
+ * adapter declares that exact id, so switching a card to a model without
334
+ * that level never pushes an invalid selection (see core/reasoning-effort.ts).
335
+ */
336
+ reasoningEffort?: string;
328
337
  /**
329
338
  * Whether later executions continue in the previous execution's session
330
339
  * (issue #1419) instead of minting a fresh conversation per run. Absent or
@@ -446,6 +455,16 @@ export interface NewTaskInput {
446
455
  permission?: TaskPermission;
447
456
  /** Optional pinned model for the execution session; absent = host default. */
448
457
  model?: string;
458
+ /** Optional reasoning effort for that model route; empty/absent = the model's own default. */
459
+ reasoningEffort?: string;
460
+ /**
461
+ * Opt in to the deployment's new-card defaults: a target this input leaves
462
+ * out is filled from the configured default (agent preset, permission, model
463
+ * and the level bound to that model). Absent means the create stores exactly
464
+ * what it was given, which is what the extension provider's own create path
465
+ * and the legacy import rely on.
466
+ */
467
+ applyCardDefaults?: boolean;
449
468
  /** Reuse the previous execution's session for later runs (issue #1419). */
450
469
  reuseSession?: boolean;
451
470
  /** Run the subtree as an Agent Team (Team Lead session plus one teammate per direct subtask). */
@@ -1,3 +1,4 @@
1
+ import { type CardDefaults } from '../card-defaults.ts';
1
2
  import { type NewTaskInput, type TaskRecord } from '../tasks.ts';
2
3
  /** Result of a create transition: the new task (when accepted) + the next ledger. */
3
4
  export interface CreateTaskResult {
@@ -13,15 +14,19 @@ export interface CreateTaskResult {
13
14
  * appended ledger, or the unchanged ledger when the title is blank or the
14
15
  * requested parent link is refused.
15
16
  *
16
- * A subtask inherits the execution targets its parent already pins (workspace,
17
- * agent preset, permission, model) unless the form supplied its own; the
18
- * parent human permission confirmation travels with an inherited permission,
19
- * so a subtask of an already-confirmed elevated card is runnable at once.
17
+ * The execution targets resolve through {@link resolveCardTargets}: what the
18
+ * caller sent wins, then the parent's own pins, then - only when the caller
19
+ * opted in with `applyCardDefaults` - the deployment's configured new-card
20
+ * defaults. Without the flag the resolution sees no defaults at all, which is
21
+ * exactly the inline parent inheritance this path has always applied. The
22
+ * workspace is not part of that resolution: a subtask still takes the parent's
23
+ * workspace, and the defaults never name one.
20
24
  * @param tasks - current ledger.
21
25
  * @param input - raw user input (title/description/prompt + optional schedule/parent).
22
26
  * @param now - clock instant (ms epoch).
23
27
  * @param id - minted task id.
24
28
  * @param maxSubtaskDepth - deployment subtask depth limit.
25
29
  * @param hostTimeZone - zone a rule that stores no zone of its own follows.
30
+ * @param cardDefaults - the deployment's new-card defaults, read live at creation.
26
31
  */
27
- export declare function applyCreateTask(tasks: readonly TaskRecord[], input: NewTaskInput, now: number, id: string, maxSubtaskDepth?: number, hostTimeZone?: string): CreateTaskResult;
32
+ export declare function applyCreateTask(tasks: readonly TaskRecord[], input: NewTaskInput, now: number, id: string, maxSubtaskDepth?: number, hostTimeZone?: string, cardDefaults?: CardDefaults): CreateTaskResult;
@@ -15,7 +15,7 @@ import type { TaskHandoverInput } from '../handover.ts';
15
15
  * Editable fields on a task (the update patch surface). `freeze` replaces the
16
16
  * continuation-card snapshot (restamping frozenAt); an explicit null clears it.
17
17
  */
18
- export type TaskUpdatePatch = Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt' | 'workspaceId' | 'mode' | 'permission' | 'model' | 'reuseSession' | 'teamRun' | 'goalRun' | 'skipVerification'>> & {
18
+ export type TaskUpdatePatch = Partial<Pick<TaskRecord, 'title' | 'description' | 'prompt' | 'workspaceId' | 'mode' | 'permission' | 'model' | 'reasoningEffort' | 'reuseSession' | 'teamRun' | 'goalRun' | 'skipVerification'>> & {
19
19
  freeze?: FreezeSnapshot & {
20
20
  redacted?: boolean;
21
21
  } | null;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Admission control for the session-creating half of a run.
3
+ *
4
+ * One board action can open a whole subtree, and a team run hands every member
5
+ * of it to the Lead at once. Both paths used to fire every launch in the same
6
+ * tick, so the Host asked DSH for N sessions at once — the burst the repository's
7
+ * measured guidance caps at three heavy implementation subagents
8
+ * (docs/multi-agent-resources.md), with no mechanism enforcing it.
9
+ *
10
+ * The queue is SERVICE-wide, not per run group: the pressure it bounds is the
11
+ * machine's (the Host process's memory and the swap it drives), and two run
12
+ * groups opened from two tabs share one Host. Admission is round-robin across
13
+ * run groups — FIFO inside a group, so a group's first run (the Lead, which
14
+ * carries the run shape and the teammate namespace) always starts before its
15
+ * members — so a large subtree cannot starve a run opened after it, and no
16
+ * entry can be starved.
17
+ *
18
+ * The width is read through a live callback, so a settings edit takes effect on
19
+ * the next admission without remounting anything.
20
+ *
21
+ * @module dsh-task-board/host/launch-admission
22
+ */
23
+ /** One launch the queue has not started yet. */
24
+ export interface QueuedLaunch {
25
+ /** Run group this launch belongs to; the unit of round-robin fairness. */
26
+ readonly groupId: string;
27
+ /**
28
+ * Perform the launch. The slot is held until this promise settles, which
29
+ * makes "in flight" mean "still creating and prompting its session".
30
+ * A rejection is swallowed here so one broken launch can never wedge the
31
+ * slot; the caller owns reporting.
32
+ */
33
+ start(): Promise<void>;
34
+ /**
35
+ * Called by {@link LaunchAdmissionQueue.drain} for a launch that never
36
+ * started, so the caller can take its execution out of the running column.
37
+ */
38
+ abandon(): void;
39
+ }
40
+ export declare class LaunchAdmissionQueue {
41
+ private readonly width;
42
+ /** Waiting launches per run group, in arrival order. */
43
+ private readonly waiting;
44
+ /** Run groups in round-robin order. */
45
+ private rotation;
46
+ /** Slot holders of the launches in flight, one per admitted launch. */
47
+ private readonly inFlight;
48
+ /** Highest number of launches in flight at once; observability and tests. */
49
+ peakInFlight: number;
50
+ /**
51
+ * @param width - live admission width (already normalized by the caller).
52
+ */
53
+ constructor(width: () => number);
54
+ /** Launches the queue has not admitted yet. */
55
+ get pendingCount(): number;
56
+ /** Launches currently holding a slot. */
57
+ get inFlightCount(): number;
58
+ /** Admit one launch, immediately if a slot is free. */
59
+ enqueue(launch: QueuedLaunch): void;
60
+ /**
61
+ * Abandon every waiting launch: the queue is being torn down, so an entry
62
+ * that never started can never start. Launches already in flight are left
63
+ * alone — their session may already exist, and the callers that own them
64
+ * settle them through the normal path.
65
+ * @returns the abandoned launches, in abandonment order.
66
+ */
67
+ drain(): QueuedLaunch[];
68
+ private pump;
69
+ /** The next launch by round-robin across groups and FIFO inside one. */
70
+ private takeNext;
71
+ private admit;
72
+ }
@@ -1,4 +1,5 @@
1
1
  import { type ExecutionOutcome, type ExecutionRecord, type ScheduleMode, type TaskRecord } from './core/tasks.ts';
2
+ import type { CardDefaults } from './core/card-defaults.ts';
2
3
  import { type TaskBoardAction, type TaskBoardSchedulerSnapshot } from './protocol.ts';
3
4
  import { type ExecutionVerification } from './core/verification.ts';
4
5
  import { type TaskPermission } from './core/handover.ts';
@@ -125,9 +126,12 @@ export declare class HostTaskLedger {
125
126
  * instead of remounting the row.
126
127
  */
127
128
  private depthLimit;
129
+ /** Live reader of the deployment's new-card defaults (a volatile config read). */
130
+ private readonly cardDefaults;
128
131
  constructor(dir?: string, now?: () => number, options?: {
129
132
  sessionDefaultPermission?: TaskPermission | (() => TaskPermission);
130
133
  maxSubtaskDepth?: number;
134
+ cardDefaults?: () => CardDefaults;
131
135
  });
132
136
  /** Current subtask depth limit (1..3). */
133
137
  get maxSubtaskDepth(): number;
@@ -259,10 +263,11 @@ export declare class HostTaskLedger {
259
263
  /** Whether a task or any of its subtasks still has an open execution. */
260
264
  private subtreeHasOpenExecution;
261
265
  /**
262
- * Open one execution per cascade participant (the requested task and its
263
- * on-board descendants, within the depth limit) under a single run group, and
264
- * commit the ledger once. The returned runs are ordered root-first, so the
265
- * Host launches the parent before its subtasks.
266
+ * Open one execution per cascade participant that still has work (the
267
+ * requested task and its on-board descendants, within the depth limit) under
268
+ * a single run group, and commit the ledger once. A descendant in 'done' is
269
+ * not a participant ({@link joinsCascadeRun}). The returned runs are ordered
270
+ * root-first, so the Host launches the parent before its subtasks.
266
271
  * @param root - the task the user ran.
267
272
  * @param now - clock instant (ms epoch).
268
273
  * @param initiator - the DSH session that asked for the run (audit only).
@@ -1,7 +1,9 @@
1
- import type { TypertGateway } from '@deepseek-ai/dsh-api-gateway';
1
+ import { Context } from '@deepseek-ai/cordis';
2
+ import type { InvokeRemoteRequest, TypertGateway } from '@deepseek-ai/dsh-api-gateway';
2
3
  import type { SessionSummary } from '@deepseek-ai/dsh-api-session-controller/types';
3
4
  import type { CommandResult } from '@deepseek-ai/dsh-commands/types';
4
5
  import type { Workspace } from '@deepseek-ai/dsh-workspace/types';
6
+ import type { ModelCatalogView } from './core/verification.ts';
5
7
  import type { TaskRecord } from './core/tasks.ts';
6
8
  /**
7
9
  * Host services needed to validate a task's workspace before creating a
@@ -10,12 +12,47 @@ import type { TaskRecord } from './core/tasks.ts';
10
12
  export interface TaskBoardWorkspaceRegistry {
11
13
  list(): readonly Workspace[];
12
14
  }
15
+ /**
16
+ * The peer every gateway request names: derived from the public
17
+ * `InvokeRemoteRequest` type so this package needs no direct dependency on
18
+ * `@deepseek-ai/dsh-typert-protocol` for the shape.
19
+ */
20
+ export type GatewayPeer = InvokeRemoteRequest['peer'];
13
21
  interface GatewayRequest {
14
22
  namespace: string;
15
23
  method: string;
16
24
  args: Record<string, unknown>;
25
+ peer: GatewayPeer;
17
26
  signal?: AbortSignal;
18
27
  }
28
+ /**
29
+ * Mint the in-process owner peer this plugin's Host calls speak for.
30
+ *
31
+ * The 0.2.1 gateway made `peer` mandatory on every request, and the
32
+ * authorization layer reads only `peer.principal`: a principal of
33
+ * `{ kind: 'owner' }` is how host-local code states that the call IS the local
34
+ * user driving their own Host, so the gateway answers it directly instead of
35
+ * running a remote-access decision (only non-owner principals are judged).
36
+ * That is exactly the task board's position — it creates, renames, prompts,
37
+ * and reads sessions as the Host itself, which before 0.2.1 was the implicit
38
+ * privilege of every in-process caller. The board claims owner explicitly, and
39
+ * only ever inside its own Host process: no wire field can set a principal,
40
+ * and this plugin never forwards a browser-supplied one.
41
+ *
42
+ * `interactions` stays `'none'`: the board only invokes methods and opens the
43
+ * short `session/follow` stream. The gateway's interactions check gates
44
+ * exactly the forwarded-event endpoints (opening an event stream, answering a
45
+ * waterfall), which this caller never touches; an `'answer'` peer would
46
+ * additionally receive forwarded Host events, a capability the board neither
47
+ * needs nor wants.
48
+ *
49
+ * The peer's signal aborts once `ctx` disposes, so streams opened for it end
50
+ * with the plugin fiber, and `dispose()` is idempotent with racing calls
51
+ * sharing one completion. The id carries no credential — the protocol fixes
52
+ * `principal` as the only identity persisted in a session log — so a fresh
53
+ * unique id per mount is sufficient.
54
+ */
55
+ export declare function createOwnerPeer(ctx: Context): GatewayPeer;
19
56
  interface SessionGateway {
20
57
  invoke(request: GatewayRequest): Promise<unknown>;
21
58
  stream?(request: GatewayRequest): Promise<AsyncIterable<unknown>>;
@@ -111,15 +148,25 @@ export declare class HostExecutionRunner {
111
148
  private readonly gateway;
112
149
  private readonly commands?;
113
150
  private readonly workspaceRegistry?;
114
- /** Newest scanned event sequence per session with no matching execution end. */
151
+ /**
152
+ * Newest event sequence per session whose COMPLETE history walk (reaching
153
+ * the execution boundary) found no matching execution end. A later walk may
154
+ * trust everything at or below it and examine only newer records.
155
+ */
115
156
  private readonly scanMemos;
116
157
  private readonly unavailableAttempts;
117
158
  private readonly unavailableBackoffMs;
159
+ /**
160
+ * The peer every request speaks for. Production passes the peer bound to the
161
+ * plugin fiber; a bare runner (unit tests around fake gateways) gets one
162
+ * bound to a fresh root context instead.
163
+ */
164
+ private readonly ownerPeer;
118
165
  private unsupportedSessionListWarned;
119
166
  constructor(gateway: SessionGateway | TypertGateway, commands?: SessionCommandDispatcher | undefined, workspaceRegistry?: TaskBoardWorkspaceRegistry | undefined, unavailableRetry?: {
120
167
  attempts?: number;
121
168
  backoffMs?: number;
122
- });
169
+ }, ownerPeer?: GatewayPeer);
123
170
  private invoke;
124
171
  private stream;
125
172
  /**
@@ -157,6 +204,14 @@ export declare class HostExecutionRunner {
157
204
  * `goalRun` still applies on top of it (both must ASK for a goal).
158
205
  */
159
206
  goalEnabled?: boolean;
207
+ /**
208
+ * The host model catalog, read ONCE per execution by the caller. The card's
209
+ * pinned reasoning effort is sent only when this catalog declares it for the
210
+ * route the execution runs at, so one snapshot decides both the acceptance
211
+ * contract and the effort this run freezes. Absent means "no capability
212
+ * evidence": an effort is then not sent at all.
213
+ */
214
+ catalog?: ModelCatalogView;
160
215
  }): Promise<string>;
161
216
  /**
162
217
  * Fail closed when a reused session records a different Agent preset than
@@ -173,10 +228,30 @@ export declare class HostExecutionRunner {
173
228
  * @param pinned - the preset id the task pins, when it pins one.
174
229
  */
175
230
  private assertReusedPreset;
231
+ /**
232
+ * Split a `provider/model` pin. A pin without a provider keeps only the model
233
+ * id: the provider is the one the host catalog declares for it, resolved at
234
+ * launch because the session wire contract requires a provider.
235
+ */
236
+ private static pinnedRoute;
237
+ /**
238
+ * The route a card that pins no model actually runs at: the session's own
239
+ * recorded selection (the durable fold the GUI reads; `next` is the pending
240
+ * user choice, `lastUsed` what the last request consumed), or — for a session
241
+ * that has selected none yet, which is exactly a session that would start at
242
+ * the deployment default — the host default from the catalog.
243
+ *
244
+ * A route that cannot be READ is undefined and the effort is not sent:
245
+ * falling back to the host default there would silently switch the model of a
246
+ * session this run only meant to continue.
247
+ */
248
+ private sessionRoute;
176
249
  /**
177
250
  * Re-assert the pinned execution contract on a session and queue the task
178
251
  * prompt. Shared by the fresh-session and reuse paths so both apply exactly
179
- * the same permission/model pins before the prompt.
252
+ * the same permission/model/effort pins before the prompt. A session that
253
+ * refuses a selection carrying a catalog-confirmed effort throws here, so the
254
+ * prompt is never queued behind a run at the wrong level.
180
255
  */
181
256
  private pinAndPrompt;
182
257
  /**
@@ -1,11 +1,12 @@
1
1
  import type { TypertGateway } from '@deepseek-ai/dsh-api-gateway';
2
2
  import { HostTaskLedger } from './host-ledger.ts';
3
- import { HostExecutionRunner, type SessionCommandDispatcher, type TaskBoardWorkspaceRegistry } from './host-runner.ts';
3
+ import { HostExecutionRunner, type GatewayPeer, type SessionCommandDispatcher, type TaskBoardWorkspaceRegistry } from './host-runner.ts';
4
4
  import { PowerInhibitor } from './power-inhibitor.ts';
5
5
  import { type TaskBoardAction, type TaskBoardEventPayload, type TaskBoardSnapshot } from './protocol.ts';
6
+ import type { CardDefaults } from './core/card-defaults.ts';
6
7
  import { TaskBoardExtensionRegistry } from './host/extension-registry.ts';
7
8
  import type { TaskBoardExtension } from './core/extension.ts';
8
- import type { ScheduleMode } from './core/tasks.ts';
9
+ import { type ScheduleMode } from './core/tasks.ts';
9
10
  import { type ModelCatalogView, type VerificationContract, type VerificationSettings } from './core/verification.ts';
10
11
  import type { TaskPermission } from './core/handover.ts';
11
12
  /** One teammate the Host asks the Agent Teams service to spawn for a team run. */
@@ -103,6 +104,12 @@ export declare class TaskBoardHostService {
103
104
  private disposed;
104
105
  private pollInFlight;
105
106
  private active;
107
+ /**
108
+ * Admission control for the session-creating half of a run: one board action
109
+ * opens a whole subtree, and this is what keeps the Host from asking DSH for
110
+ * every member's session at once (see {@link LaunchAdmissionQueue}).
111
+ */
112
+ private readonly launches;
106
113
  /**
107
114
  * Consecutive unreadable-history polls per open execution (see
108
115
  * {@link noteUnreadableInspection}). Cleared as soon as an inspection
@@ -116,6 +123,14 @@ export declare class TaskBoardHostService {
116
123
  private readonly timers;
117
124
  /** Live reader of the global native-/goal switch; frozen per execution. */
118
125
  private readonly goalRunEnabled;
126
+ /** Live reader of the deployment's new-card defaults (published and applied per create). */
127
+ private readonly cardDefaults;
128
+ /**
129
+ * Live reader of the run-launch admission width: how many launches may be in
130
+ * flight at once. Read at every admission, so a settings edit bounds the next
131
+ * launch without remounting the row. Absent means the default.
132
+ */
133
+ private readonly runLaunchWidth;
119
134
  private lastPowerJson;
120
135
  private readonly now;
121
136
  /** The deployment's workspaces, consulted to resolve a card that pins none. */
@@ -132,12 +147,26 @@ export declare class TaskBoardHostService {
132
147
  now?: () => number;
133
148
  commandDispatcher?: SessionCommandDispatcher;
134
149
  workspaceRegistry?: TaskBoardWorkspaceRegistry;
150
+ /**
151
+ * The owner peer every gateway request the runner makes speaks for.
152
+ * Production passes the peer bound to the plugin fiber (see
153
+ * {@link createOwnerPeer}); absent is fine for tests and bare mounts,
154
+ * where the runner binds one to a fresh root context.
155
+ */
156
+ ownerPeer?: GatewayPeer;
135
157
  /**
136
158
  * Permission baseline of the confirmation gate: a fixed value, or a live
137
159
  * resolver the board re-reads so a Host Settings change needs no remount.
138
160
  */
139
161
  sessionDefaultPermission?: TaskPermission | (() => TaskPermission);
140
162
  maxSubtaskDepth?: number;
163
+ /**
164
+ * Live reader of the deployment's new-card defaults (volatile config reads).
165
+ * The board publishes them for the create dialog and re-reads them for every
166
+ * create that opts in, so a settings edit reaches the next created card
167
+ * without remounting the row. Absent means the deployment configures none.
168
+ */
169
+ cardDefaults?: () => CardDefaults;
141
170
  team?: TaskBoardTeamDispatcher;
142
171
  timers?: HostTimerFace;
143
172
  /**
@@ -156,6 +185,13 @@ export declare class TaskBoardHostService {
156
185
  verificationSettings?: () => VerificationSettings;
157
186
  /** The host model catalog the acceptance contract resolves its route from. */
158
187
  verificationCatalog?: () => Promise<ModelCatalogView | undefined>;
188
+ /**
189
+ * Live admission width for run launches: the number of launches (one
190
+ * session creation and its prompt each) that may be in flight at once. Read
191
+ * per admission, so editing the setting bounds the next launch of a run that
192
+ * is still queueing. Absent means the default width.
193
+ */
194
+ runLaunchWidth?: () => number;
159
195
  });
160
196
  start(): void;
161
197
  /**
@@ -230,6 +266,26 @@ export declare class TaskBoardHostService {
230
266
  private emitStatusChanges;
231
267
  dispose(): void;
232
268
  private launch;
269
+ /**
270
+ * Read the acceptance contract one run group freezes, in the order the board
271
+ * has always read it: the acceptance settings, then the global native-/goal
272
+ * switch, then the model catalog that both resolves the contract's route and
273
+ * decides the reasoning effort the session may be given.
274
+ */
275
+ private freezeRunContract;
276
+ /**
277
+ * A {@link RunContractFreezer} that reads once and hands the SAME value to
278
+ * every later caller of one run group. A read that fails is not cached: the
279
+ * next member reads again, exactly as a per-execution read would have.
280
+ */
281
+ private contractFreezerForRunGroup;
282
+ /**
283
+ * Give back the occurrence a launch consumed, for a launch that never reached
284
+ * a session: a failed launch, or one the Host abandoned while it was still
285
+ * waiting for an admission slot. A manual run, and a launch that DID reach a
286
+ * session, are never refunded.
287
+ */
288
+ private refundUnstartedSchedule;
233
289
  private scheduleTeammate;
234
290
  /**
235
291
  * Spawn one teammate inside the Lead session and attach the teammate's
@@ -335,6 +391,13 @@ export declare class TaskBoardHostService {
335
391
  * member then launches on its own so one refused participant cannot hold the
336
392
  * others back. A team run's members are spawned inside the root's Lead
337
393
  * session instead, once that session exists.
394
+ *
395
+ * Every launch of one run set goes through the admission queue, which admits
396
+ * at most `runLaunchWidth` of them at a time — the whole subtree still opens,
397
+ * members just enter the running column in batches. Order inside the group is
398
+ * the order they are enqueued here, so the root's session always exists before
399
+ * any member's, and one member's launch settling (or failing) is what admits
400
+ * the next. All members share the one contract this group freezes.
338
401
  */
339
402
  private dispatchRuns;
340
403
  /**
@@ -343,6 +406,26 @@ export declare class TaskBoardHostService {
343
406
  */
344
407
  private setApplicability;
345
408
  private scheduleLaunch;
409
+ /**
410
+ * Hand one launch to the admission queue, or abandon it on the spot when the
411
+ * Host is already stopping: a queue entry created after {@link dispose} would
412
+ * otherwise never be drained, and its card would sit in the running column
413
+ * until the next Host start recovered it.
414
+ */
415
+ private enqueueLaunch;
416
+ /** Whether one opened execution is still open in the ledger. */
417
+ private isExecutionOpen;
418
+ /**
419
+ * Take one launch that never started out of the running column.
420
+ *
421
+ * Settled as CANCELLED, not failed: nothing about the run's work went wrong —
422
+ * the Host stopped before the launch got a slot, which is exactly what the
423
+ * next start's interrupted-start recovery records for an execution that never
424
+ * reached a session. A scheduled occurrence that never reached a session is
425
+ * refunded on the same terms as any other pre-session failure, so a run the
426
+ * Host never started cannot silently spend the user's run budget.
427
+ */
428
+ private abandonQueuedRun;
346
429
  private schedulePoll;
347
430
  /**
348
431
  * Count one failed pass and arm the retry that follows it.
@@ -69,6 +69,17 @@ export interface Config {
69
69
  * at all, so this cadence only bounds how quickly a running card settles.
70
70
  */
71
71
  sessionPollSeconds?: Volatile<number>;
72
+ /**
73
+ * How many run launches this board may have in flight at once (1..16,
74
+ * default 3). One run opens its whole subtree, so without a bound the Host
75
+ * asks DSH for every member's session in the same tick; this is the mechanism
76
+ * that enforces the repository's measured guidance for heavy implementation
77
+ * subagents. Members wait their turn instead of being dropped: the subtree
78
+ * still opens completely, they only enter the running column in batches. Read
79
+ * at each admission, so an edit bounds the next launch of a run that is still
80
+ * queueing.
81
+ */
82
+ runLaunchWidth?: Volatile<number>;
72
83
  /**
73
84
  * Continuable-subagent provider the Agent Teams service uses to compose a
74
85
  * teammate. Matches the Agent Teams tool plugin's `freshProvider` default;
@@ -91,6 +102,33 @@ export interface Config {
91
102
  * becomes a goal run.
92
103
  */
93
104
  goalRunEnabled?: Volatile<boolean>;
105
+ /**
106
+ * Agent preset a NEW card starts from (volatile, default blank = not set).
107
+ * A prefill, never a pin: the create dialog opens with it selected, the
108
+ * caller may override it, and the card stores what it was created with. It
109
+ * reaches the create path only when that caller opts in with
110
+ * `applyCardDefaults`, so the extension provider's own create and the legacy
111
+ * import are untouched.
112
+ */
113
+ cardDefaultMode?: Volatile<string>;
114
+ /**
115
+ * Permission a NEW card starts from (volatile, default blank = not set), one
116
+ * of the three presets. A prefill like the rest: a value above
117
+ * `sessionDefaultPermission` still has to be confirmed by a human before the
118
+ * card may run, because the pre-execution gate is what decides that.
119
+ */
120
+ cardDefaultPermission?: Volatile<TaskPermission | ''>;
121
+ /**
122
+ * Model a NEW card starts from (volatile, default blank = not set).
123
+ */
124
+ cardDefaultModel?: Volatile<string>;
125
+ /**
126
+ * Reasoning level a NEW card starts from (volatile, default blank = not set).
127
+ * Bound to `cardDefaultModel` exactly as a card's own level is bound to its
128
+ * own model: it applies only while the card's final model IS that default
129
+ * model, and a blank `cardDefaultModel` makes it inert.
130
+ */
131
+ cardDefaultReasoningEffort?: Volatile<string>;
94
132
  /**
95
133
  * Goal acceptance for executions this board starts (default ON). When on,
96
134
  * update_goal(action: complete) inside a task execution is refused until an
@@ -166,10 +204,20 @@ export interface ConfigInput {
166
204
  maxSubtaskDepth?: number;
167
205
  /** Roster-poll cadence in seconds while the board has work to reconcile. */
168
206
  sessionPollSeconds?: number;
207
+ /** Concurrent run launches allowed at once, 1..16. */
208
+ runLaunchWidth?: number;
169
209
  /** Continuable-subagent provider the Agent Teams service composes a teammate from. */
170
210
  teamProvider?: string;
171
211
  /** Global native /goal switch; default off, frozen per execution. */
172
212
  goalRunEnabled?: boolean;
213
+ /** Agent preset a new card starts from; blank = not set. */
214
+ cardDefaultMode?: string;
215
+ /** Permission a new card starts from; blank = not set. */
216
+ cardDefaultPermission?: TaskPermission | '';
217
+ /** Model a new card starts from; blank = not set. */
218
+ cardDefaultModel?: string;
219
+ /** Reasoning level a new card starts from; blank = not set. */
220
+ cardDefaultReasoningEffort?: string;
173
221
  /** Goal acceptance switch. */
174
222
  goalVerification?: boolean;
175
223
  /** Judge model route for goal acceptance; blank inherits the host default. */
@@ -304,6 +352,45 @@ export declare function buildTeamDispatcher(ctx: Context, provider: string): Tas
304
352
  * @returns the llm service, or undefined when this deployment serves none.
305
353
  */
306
354
  export declare function resolveLlmRuntime(ctx: Context): LlmRuntime | undefined;
355
+ /**
356
+ * The subset of `sessionController` the board uses to wake a cold persisted
357
+ * session before running a slash command on it (issue #1861).
358
+ */
359
+ interface SessionControllerFace {
360
+ resolveAgent(sessionId: Parameters<Context['agents']['get']>[0]): Promise<{
361
+ readonly agent: Parameters<Context['commands']['execute']>[0];
362
+ } | {
363
+ readonly error: {
364
+ readonly message?: string;
365
+ };
366
+ }>;
367
+ }
368
+ /**
369
+ * Resolve the optional `sessionController` service. Like the tool registry and
370
+ * the llm runtime, it is read dynamically so capture-only test contexts that do
371
+ * not mount the full session controller still activate the board.
372
+ * @param ctx - the plugin context.
373
+ * @returns the session controller face, or undefined when unavailable.
374
+ */
375
+ export declare function resolveSessionController(ctx: Context): SessionControllerFace | undefined;
376
+ /**
377
+ * Resolve the live Agent for an execution session, resuming a cold persisted
378
+ * session through `sessionController.resolveAgent` when it is not yet in
379
+ * `ctx.agents` (issue #1861).
380
+ *
381
+ * A fresh run (`session/create`) or a session the browser is currently
382
+ * following is already in `ctx.agents`, so the fast path returns immediately.
383
+ * A scheduled or manual reuse of a cold session — one that `session/list`
384
+ * reports as idle from disk without activating its Agent — is not in
385
+ * `ctx.agents` when `pinAndPrompt` runs `/permission` ahead of
386
+ * `session/selectModel` and `session/prompt`; delegating to
387
+ * `sessionController.resolveAgent` resumes it with its recorded preset and
388
+ * model-selection state, matching what those gateway endpoints do next.
389
+ * @param ctx - the plugin context.
390
+ * @param sessionId - the target execution session id.
391
+ * @returns the live Agent for `sessionId`.
392
+ */
393
+ export declare function resolveExecutionAgent(ctx: Context, sessionId: Parameters<Context['agents']['get']>[0]): Promise<Parameters<Context['commands']['execute']>[0]>;
307
394
  /**
308
395
  * Activate the board's host half: ledger, routes, cron scheduler, power
309
396
  * inhibitor, and the model-facing announcement.
@@ -74,6 +74,19 @@ export interface TaskBoardSnapshot {
74
74
  * flight.
75
75
  */
76
76
  goalRunEnabled?: boolean;
77
+ /**
78
+ * New-card defaults this deployment configures: what the create dialog opens
79
+ * with, and what the create path fills an omitted target from when the caller
80
+ * opts in (`applyCardDefaults`). A target the board configures nothing for is
81
+ * omitted rather than published blank, so the browser can tell "no default
82
+ * model" from "a blank one".
83
+ */
84
+ cardDefaults?: {
85
+ mode?: string;
86
+ permission?: TaskPermission;
87
+ model?: string;
88
+ reasoningEffort?: string;
89
+ };
77
90
  /**
78
91
  * Read-only summaries published by running extensions, keyed by extension id.
79
92
  * The board forwards them verbatim; it never interprets their shape.