@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.
- package/README.i18n.yaml +2 -2
- package/README.md +14 -6
- package/README.zh.md +14 -6
- package/lib/client.js +1502 -820
- package/lib/client.js.map +1 -1
- package/lib/index.js +674 -75
- package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +49 -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/locales.d.ts +16 -0
- package/lib/types/core/card-defaults.d.ts +78 -0
- package/lib/types/core/controller.d.ts +32 -7
- package/lib/types/core/launch-width.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 +19 -0
- package/lib/types/core/use-cases/task-create.d.ts +10 -5
- package/lib/types/core/use-cases/task-update.d.ts +1 -1
- package/lib/types/host/launch-admission.d.ts +72 -0
- package/lib/types/host-ledger.d.ts +9 -4
- package/lib/types/host-runner.d.ts +79 -4
- package/lib/types/host-service.d.ts +85 -2
- package/lib/types/index.d.ts +87 -0
- package/lib/types/protocol.d.ts +13 -0
- package/package.json +28 -28
- package/src/client/CardDefaultPresetField.tsx +101 -0
- package/src/client/TaskBoardSettingsCard.tsx +214 -16
- package/src/client/board/LinkSubtaskModal.tsx +8 -0
- package/src/client/board/NewTaskModal.tsx +86 -40
- package/src/client/board/ReasoningEffortField.tsx +50 -0
- package/src/client/board/TaskDetail.tsx +42 -6
- 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/team-run-promotion.ts +39 -0
- package/src/client/index.ts +98 -94
- package/src/client/locales.ts +34 -2
- package/src/client/settings-entry-form.ts +0 -1
- package/src/core/card-defaults.ts +132 -0
- package/src/core/controller.ts +44 -9
- package/src/core/launch-width.ts +38 -0
- package/src/core/reasoning-effort.ts +92 -0
- package/src/core/store.ts +5 -0
- package/src/core/subtask.ts +48 -13
- package/src/core/tasks.ts +20 -0
- package/src/core/use-cases/task-create.ts +27 -16
- package/src/core/use-cases/task-update.ts +6 -1
- package/src/host/agent-tools.ts +21 -8
- package/src/host/launch-admission.ts +140 -0
- package/src/host-ledger.ts +18 -6
- package/src/host-runner.ts +184 -18
- package/src/host-service.ts +247 -23
- package/src/index.ts +149 -2
- 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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
|
263
|
-
* on-board descendants, within the depth limit) under
|
|
264
|
-
* commit the ledger once.
|
|
265
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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.
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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.
|
package/lib/types/protocol.d.ts
CHANGED
|
@@ -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.
|