@linxin666/dsh-client-ui-task-board 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +59 -20
- package/README.zh.md +59 -20
- package/lib/client.js +6208 -624
- package/lib/client.js.map +1 -1
- package/lib/index.js +4774 -493
- package/lib/types/client/PluginSettingsCard.d.ts +7 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +31 -3
- package/lib/types/client/board/ClampedMarkdown.d.ts +3 -0
- package/lib/types/client/board/ConfirmDialog.d.ts +4 -1
- package/lib/types/client/board/EditTaskModal.d.ts +5 -2
- package/lib/types/client/board/LinkSubtaskModal.d.ts +5 -2
- package/lib/types/client/board/NewTaskModal.d.ts +4 -1
- package/lib/types/client/board/TagManagerModal.d.ts +26 -0
- package/lib/types/client/board/TaskBoard.d.ts +10 -1
- package/lib/types/client/board/TaskCard.d.ts +14 -1
- package/lib/types/client/board/TaskDetail.d.ts +3 -1
- package/lib/types/client/board/TaskForm.d.ts +4 -1
- package/lib/types/client/board/VerificationReport.d.ts +23 -0
- package/lib/types/client/board/card-view.d.ts +113 -0
- package/lib/types/client/board/icons.d.ts +32 -0
- package/lib/types/client/board/overlay.d.ts +69 -0
- package/lib/types/client/board/status-key.d.ts +15 -0
- package/lib/types/client/board/task-markdown.d.ts +19 -0
- package/lib/types/client/index.d.ts +23 -5
- package/lib/types/client/locales.d.ts +168 -1
- package/lib/types/client/native-panel.d.ts +6 -20
- package/lib/types/client/plugin-card-seat.d.ts +5 -0
- package/lib/types/client/schedule-zone.d.ts +50 -0
- package/lib/types/client/seats.d.ts +18 -0
- package/lib/types/client/service.d.ts +32 -0
- package/lib/types/client/settings-form.d.ts +16 -0
- package/lib/types/core/controller.d.ts +46 -11
- package/lib/types/core/extension.d.ts +283 -0
- package/lib/types/core/handover.d.ts +13 -1
- package/lib/types/core/issue-form.d.ts +16 -0
- package/lib/types/core/poll-cadence.d.ts +31 -0
- package/lib/types/core/tasks.d.ts +228 -17
- package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
- package/lib/types/core/use-cases/task-tag.d.ts +48 -0
- package/lib/types/core/use-cases/task-update.d.ts +5 -3
- package/lib/types/core/verification-budget.d.ts +52 -0
- package/lib/types/core/verification.d.ts +753 -0
- package/lib/types/core/workspace-target.d.ts +41 -0
- package/lib/types/host/agent-tools.d.ts +1 -1
- package/lib/types/host/extension-registry.d.ts +111 -0
- package/lib/types/host/llm-dispatch.d.ts +63 -0
- package/lib/types/host/verification-gate.d.ts +95 -0
- package/lib/types/host/verification-runner.d.ts +139 -0
- package/lib/types/host/workspace-evidence.d.ts +53 -0
- package/lib/types/host-ledger.d.ts +133 -14
- package/lib/types/host-runner.d.ts +55 -10
- package/lib/types/host-service.d.ts +205 -11
- package/lib/types/index.d.ts +134 -26
- package/lib/types/protocol.d.ts +92 -10
- package/lib/types/tool-surface.d.ts +71 -0
- package/package.json +28 -31
- package/src/client/PluginSettingsCard.tsx +9 -0
- package/src/client/TaskBoardSettingsCard.tsx +378 -66
- package/src/client/board/ClampedMarkdown.tsx +69 -0
- package/src/client/board/ConfirmDialog.tsx +7 -3
- package/src/client/board/EditTaskModal.tsx +5 -2
- package/src/client/board/LinkSubtaskModal.tsx +12 -7
- package/src/client/board/NewTaskModal.tsx +168 -21
- package/src/client/board/TagManagerModal.tsx +235 -0
- package/src/client/board/TaskBoard.tsx +259 -103
- package/src/client/board/TaskCard.tsx +214 -81
- package/src/client/board/TaskDetail.tsx +289 -43
- package/src/client/board/TaskForm.tsx +13 -3
- package/src/client/board/VerificationReport.tsx +322 -0
- package/src/client/board/card-view.ts +220 -0
- package/src/client/board/icons.tsx +89 -0
- package/src/client/board/overlay.tsx +189 -0
- package/src/client/board/status-key.ts +30 -0
- package/src/client/board/task-markdown.module.css +137 -0
- package/src/client/board/task-markdown.tsx +106 -0
- package/src/client/board-settings.module.css +66 -0
- package/src/client/board.module.css +488 -63
- package/src/client/index.ts +82 -51
- package/src/client/locales.ts +343 -6
- package/src/client/native-panel.tsx +18 -2
- package/src/client/plugin-card-seat.ts +7 -0
- package/src/client/schedule-zone.ts +100 -2
- package/src/client/seats.tsx +58 -0
- package/src/client/service.ts +94 -0
- package/src/client/settings-card.module.css +31 -5
- package/src/client/settings-form.ts +27 -1
- package/src/core/controller.ts +105 -10
- package/src/core/extension.ts +376 -0
- package/src/core/freeze-snapshot.ts +2 -2
- package/src/core/handover.ts +16 -1
- package/src/core/issue-form.ts +19 -0
- package/src/core/poll-cadence.ts +41 -0
- package/src/core/store.ts +80 -15
- package/src/core/tasks.ts +293 -45
- package/src/core/use-cases/task-create.ts +30 -9
- package/src/core/use-cases/task-parent.ts +3 -2
- package/src/core/use-cases/task-schedule.ts +190 -25
- package/src/core/use-cases/task-tag.ts +110 -0
- package/src/core/use-cases/task-update.ts +13 -7
- package/src/core/verification-budget.ts +115 -0
- package/src/core/verification.ts +1594 -0
- package/src/core/workspace-target.ts +59 -0
- package/src/host/agent-tools.ts +125 -38
- package/src/host/extension-registry.ts +509 -0
- package/src/host/llm-dispatch.ts +87 -0
- package/src/host/verification-gate.ts +487 -0
- package/src/host/verification-runner.ts +702 -0
- package/src/host/workspace-evidence.ts +176 -0
- package/src/host-ai.ts +15 -9
- package/src/host-ledger.ts +468 -71
- package/src/host-routes.ts +15 -2
- package/src/host-runner.ts +106 -24
- package/src/host-service.ts +546 -40
- package/src/index.ts +305 -21
- package/src/protocol.ts +226 -28
- package/src/tool-surface.ts +95 -0
- package/lib/invariant.js +0 -6
- package/lib/types/invariant.d.ts +0 -3
- package/src/invariant.ts +0 -4
package/src/index.ts
CHANGED
|
@@ -19,15 +19,31 @@ 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
21
|
import { parseTaskDraft, TaskParseError } from './host-ai.ts'
|
|
22
|
-
import { TASK_PERMISSIONS, type TaskPermission } from './core/tasks.ts'
|
|
22
|
+
import { TASK_PERMISSIONS, isTaskPermission, type TaskPermission } from './core/tasks.ts'
|
|
23
23
|
import { DEFAULT_SUBTASK_DEPTH, SUBTASK_DEPTH_MAX, SUBTASK_DEPTH_MIN } from './core/subtask.ts'
|
|
24
|
+
import { DEFAULT_SESSION_POLL_SECONDS, SESSION_POLL_MAX_SECONDS, SESSION_POLL_MIN_SECONDS } from './core/poll-cadence.ts'
|
|
25
|
+
import {
|
|
26
|
+
DEFAULT_VERIFICATION_BUDGET_SECONDS,
|
|
27
|
+
DEFAULT_VERIFICATION_CALL_TIMEOUT_SECONDS,
|
|
28
|
+
VERIFICATION_BUDGET_MAX_SECONDS,
|
|
29
|
+
VERIFICATION_BUDGET_MIN_SECONDS,
|
|
30
|
+
VERIFICATION_CALL_TIMEOUT_MAX_SECONDS,
|
|
31
|
+
VERIFICATION_CALL_TIMEOUT_MIN_SECONDS,
|
|
32
|
+
verificationBudgetMs,
|
|
33
|
+
verificationCallTimeoutMs,
|
|
34
|
+
} from './core/verification-budget.ts'
|
|
24
35
|
import { DEFAULT_SESSION_PERMISSION } from './core/handover.ts'
|
|
25
|
-
import { buildTaskBoardTools } from './host/agent-tools.ts'
|
|
36
|
+
import { buildTaskBoardTools, TASK_BOARD_TOOL_NAMES } from './host/agent-tools.ts'
|
|
37
|
+
import { PLUGIN_TOOL_SECTION_ORDERS, visibleToolText } from './tool-surface.ts'
|
|
26
38
|
import { makeTaskBoardRoutes } from './host-routes.ts'
|
|
39
|
+
import { TASK_BOARD_SERVICE_NAME, type TaskBoardExtension } from './core/extension.ts'
|
|
27
40
|
import { mountOnce } from './mount-once.ts'
|
|
41
|
+
import { createGoalVerificationGate, type GoalFace } from './host/verification-gate.ts'
|
|
42
|
+
import { normalizeCatalog, type ModelCatalogView, type VerificationSettings } from './core/verification.ts'
|
|
43
|
+
import { probeWorkspaceChanges } from './host/workspace-evidence.ts'
|
|
28
44
|
|
|
29
|
-
/** Order of the announcement section within the tool-guidance band. */
|
|
30
|
-
const SECTION_ORDER =
|
|
45
|
+
/** Order of the announcement section within the shared tool-guidance band. */
|
|
46
|
+
const SECTION_ORDER = PLUGIN_TOOL_SECTION_ORDERS['task-board']
|
|
31
47
|
|
|
32
48
|
/** Default environment variable holding the authenticated proxy token. */
|
|
33
49
|
export const DEFAULT_PROXY_TOKEN_ENV = 'DSH_TASK_BOARD_PROXY_TOKEN'
|
|
@@ -68,9 +84,12 @@ export interface Config {
|
|
|
68
84
|
/** Environment variable whose value the authenticated proxy injects upstream. */
|
|
69
85
|
proxyTokenEnv?: string
|
|
70
86
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
87
|
+
* An explicit baseline for the permission confirmation gate. Left unset, the
|
|
88
|
+
* board follows the Host's own default permission preset — the value new DSH
|
|
89
|
+
* sessions start at — and falls back to `read-only` when the deployment
|
|
90
|
+
* serves no permission catalog. A card whose effective permission (handover
|
|
91
|
+
* bundle or pin) is above the baseline requires a human confirmation before
|
|
92
|
+
* it may run; cron refuses unconfirmed cards.
|
|
74
93
|
*/
|
|
75
94
|
sessionDefaultPermission?: TaskPermission
|
|
76
95
|
/**
|
|
@@ -80,12 +99,77 @@ export interface Config {
|
|
|
80
99
|
* of the root task opens, so the ceiling is deliberately small.
|
|
81
100
|
*/
|
|
82
101
|
maxSubtaskDepth?: Volatile<number>
|
|
102
|
+
/**
|
|
103
|
+
* How often, in seconds, the Host re-reads the DSH session roster while the
|
|
104
|
+
* board has something to reconcile: a running card, an open execution, or an
|
|
105
|
+
* active provider extension. An idle board with none of those does not poll
|
|
106
|
+
* at all, so this cadence only bounds how quickly a running card settles.
|
|
107
|
+
*/
|
|
108
|
+
sessionPollSeconds?: Volatile<number>
|
|
83
109
|
/**
|
|
84
110
|
* Continuable-subagent provider the Agent Teams service uses to compose a
|
|
85
111
|
* teammate. Matches the Agent Teams tool plugin's `freshProvider` default;
|
|
86
112
|
* only team-mode runs use it.
|
|
87
113
|
*/
|
|
88
114
|
teamProvider?: string
|
|
115
|
+
/**
|
|
116
|
+
* GLOBAL native /goal switch (default OFF), the master control over whether
|
|
117
|
+
* this board starts a run with dsh's built-in /goal at all. A run uses native
|
|
118
|
+
* /goal only when THIS is on AND the task's own `goalRun` is not off; while
|
|
119
|
+
* it is off every task runs one plain turn.
|
|
120
|
+
*
|
|
121
|
+
* The value is frozen when an execution starts, so toggling it only affects
|
|
122
|
+
* executions that begin afterwards and never re-writes a task's stored
|
|
123
|
+
* `goalRun` preference. A run that consequently does not execute native
|
|
124
|
+
* /goal is marked as such (its acceptance applicability is `goal-disabled`)
|
|
125
|
+
* and is never reported as a passed goal acceptance or failed for lacking a
|
|
126
|
+
* completion call. Independent of `goalVerification`; acceptance governs the
|
|
127
|
+
* completing call of a goal-form run, this switch governs whether the run
|
|
128
|
+
* becomes a goal run.
|
|
129
|
+
*/
|
|
130
|
+
goalRunEnabled?: Volatile<boolean>
|
|
131
|
+
/**
|
|
132
|
+
* Goal acceptance for executions this board starts (default ON). When on,
|
|
133
|
+
* update_goal(action: complete) inside a task execution is refused until an
|
|
134
|
+
* acceptance pass is recorded for that execution: the three coding criteria,
|
|
135
|
+
* a 0.65 threshold, two rounds per criterion with the A/B slots swapped, and
|
|
136
|
+
* at most two acceptances per execution — the first failure returns its
|
|
137
|
+
* findings to the fixing agent, the second ends the cycle. The switch only
|
|
138
|
+
* affects goal-form executions of the board; plain chat and a task pinned to
|
|
139
|
+
* goalRun: false are untouched. When a separate third-party verifier also
|
|
140
|
+
* runs its own automatic acceptance, both judges score independently: this
|
|
141
|
+
* one gates completion, the other only steers, and no public interface lets
|
|
142
|
+
* the two share a verdict.
|
|
143
|
+
*/
|
|
144
|
+
goalVerification?: Volatile<boolean>
|
|
145
|
+
/**
|
|
146
|
+
* Judge model for goal acceptance as provider/model. Blank inherits the HOST's
|
|
147
|
+
* own model catalog default — never the card's pinned execution model.
|
|
148
|
+
*/
|
|
149
|
+
goalVerificationModel?: Volatile<string>
|
|
150
|
+
/**
|
|
151
|
+
* Reasoning effort for the judge. Blank inherits the host default level; an
|
|
152
|
+
* effort the resolved model does not declare is dropped instead of being
|
|
153
|
+
* sent, and the fallback to the model's own default is reported.
|
|
154
|
+
*/
|
|
155
|
+
goalVerificationReasoningEffort?: Volatile<string>
|
|
156
|
+
/**
|
|
157
|
+
* Ceiling of ONE judge request of a goal acceptance, in seconds
|
|
158
|
+
* (30..600, default 150). One acceptance issues six judge requests (three
|
|
159
|
+
* criteria times two rounds), so a ceiling that is too small turns a slow but
|
|
160
|
+
* healthy judge route into a wall of timeouts, and every timeout was charged
|
|
161
|
+
* as an acceptance anomaly. Raise it for a reasoning model reading a long
|
|
162
|
+
* trace.
|
|
163
|
+
*/
|
|
164
|
+
goalVerificationCallTimeoutSeconds?: Volatile<number>
|
|
165
|
+
/**
|
|
166
|
+
* Total ceiling of ONE goal acceptance attempt, in seconds (120..1800,
|
|
167
|
+
* default eight times the per-call default). Keep it at or above
|
|
168
|
+
* `goalVerificationCallTimeoutSeconds` times six or an acceptance can never
|
|
169
|
+
* finish: the attempt then ends early with a budget stop that judges nothing
|
|
170
|
+
* and spends no acceptance budget.
|
|
171
|
+
*/
|
|
172
|
+
goalVerificationBudgetSeconds?: Volatile<number>
|
|
89
173
|
}
|
|
90
174
|
|
|
91
175
|
/**
|
|
@@ -95,22 +179,76 @@ export interface Config {
|
|
|
95
179
|
export const DEFAULT_TEAM_PROVIDER = 'spawn'
|
|
96
180
|
|
|
97
181
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
182
|
+
* Profile-patch shape of {@link Config}: what the Host validates the row's
|
|
183
|
+
* config against, before the schema turns volatile fields into live references
|
|
184
|
+
* and applies defaults. Declared separately because the two sides no longer
|
|
185
|
+
* share one shape, so the schema is annotated `z<ConfigInput, Config>` — the
|
|
186
|
+
* same split `dsh-git-graph` uses. The annotation is also what keeps the
|
|
187
|
+
* emitted declaration portable: an unannotated schema holding a nested object
|
|
188
|
+
* array infers a type that cannot be named without reaching into a transitive
|
|
189
|
+
* dependency's own types.
|
|
102
190
|
*/
|
|
103
|
-
export
|
|
191
|
+
export interface ConfigInput {
|
|
192
|
+
/** Announce the board in the system prompt. */
|
|
193
|
+
announceToAgent?: boolean
|
|
194
|
+
/** Master switch for the plugin. */
|
|
195
|
+
enabled?: boolean
|
|
196
|
+
/** Prevent idle system sleep while sessions run or schedules are armed. */
|
|
197
|
+
preventIdleSleep?: boolean
|
|
198
|
+
/** Canonical reverse-proxy Host authorities. */
|
|
199
|
+
trustedProxyHosts?: string[]
|
|
200
|
+
/** Environment variable whose value the authenticated proxy injects. */
|
|
201
|
+
proxyTokenEnv?: string
|
|
202
|
+
/** Explicit baseline for the permission confirmation gate; unset follows the Host default. */
|
|
203
|
+
sessionDefaultPermission?: TaskPermission
|
|
204
|
+
/** Subtask depth limit, 1..3. */
|
|
205
|
+
maxSubtaskDepth?: number
|
|
206
|
+
/** Roster-poll cadence in seconds while the board has work to reconcile. */
|
|
207
|
+
sessionPollSeconds?: number
|
|
208
|
+
/** Continuable-subagent provider the Agent Teams service composes a teammate from. */
|
|
209
|
+
teamProvider?: string
|
|
210
|
+
/** Global native /goal switch; default off, frozen per execution. */
|
|
211
|
+
goalRunEnabled?: boolean
|
|
212
|
+
/** Goal acceptance switch. */
|
|
213
|
+
goalVerification?: boolean
|
|
214
|
+
/** Judge model route for goal acceptance; blank inherits the host default. */
|
|
215
|
+
goalVerificationModel?: string
|
|
216
|
+
/** Judge reasoning effort; blank inherits the host default. */
|
|
217
|
+
goalVerificationReasoningEffort?: string
|
|
218
|
+
/** Ceiling of one judge request of a goal acceptance, in seconds. */
|
|
219
|
+
goalVerificationCallTimeoutSeconds?: number
|
|
220
|
+
/** Total ceiling of one goal acceptance attempt, in seconds. */
|
|
221
|
+
goalVerificationBudgetSeconds?: number
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const Config: z<ConfigInput, Config> = z.object({
|
|
104
225
|
announceToAgent: z.boolean().default(false).volatile(),
|
|
105
226
|
enabled: z.boolean().default(true).volatile(),
|
|
106
227
|
preventIdleSleep: z.boolean().default(false).volatile(),
|
|
107
228
|
trustedProxyHosts: z.array(z.string()).default([]),
|
|
108
229
|
proxyTokenEnv: z.string().min(1).default(DEFAULT_PROXY_TOKEN_ENV),
|
|
109
|
-
sessionDefaultPermission: z.union(TASK_PERMISSIONS)
|
|
230
|
+
sessionDefaultPermission: z.union(TASK_PERMISSIONS),
|
|
110
231
|
maxSubtaskDepth: z.number().min(SUBTASK_DEPTH_MIN).max(SUBTASK_DEPTH_MAX).default(DEFAULT_SUBTASK_DEPTH).volatile(),
|
|
232
|
+
sessionPollSeconds: z.number().min(SESSION_POLL_MIN_SECONDS).max(SESSION_POLL_MAX_SECONDS).default(DEFAULT_SESSION_POLL_SECONDS).volatile(),
|
|
111
233
|
teamProvider: z.string().min(1).default(DEFAULT_TEAM_PROVIDER),
|
|
234
|
+
goalRunEnabled: z.boolean().default(false).volatile(),
|
|
235
|
+
goalVerification: z.boolean().default(true).volatile(),
|
|
236
|
+
goalVerificationModel: z.string().default('').volatile(),
|
|
237
|
+
goalVerificationReasoningEffort: z.string().default('').volatile(),
|
|
238
|
+
goalVerificationCallTimeoutSeconds: z.number().min(VERIFICATION_CALL_TIMEOUT_MIN_SECONDS).max(VERIFICATION_CALL_TIMEOUT_MAX_SECONDS).default(DEFAULT_VERIFICATION_CALL_TIMEOUT_SECONDS).volatile(),
|
|
239
|
+
goalVerificationBudgetSeconds: z.number().min(VERIFICATION_BUDGET_MIN_SECONDS).max(VERIFICATION_BUDGET_MAX_SECONDS).default(DEFAULT_VERIFICATION_BUDGET_SECONDS).volatile(),
|
|
112
240
|
})
|
|
113
241
|
|
|
242
|
+
/** Schema default for the goal-acceptance switch, re-read for hand-built contexts. */
|
|
243
|
+
export const DEFAULT_GOAL_VERIFICATION = true
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Schema default for the GLOBAL native-/goal switch, re-read for hand-built
|
|
247
|
+
* contexts. Deliberately false: a deployment that never configured it (and
|
|
248
|
+
* every card written before the option existed) runs plain single turns.
|
|
249
|
+
*/
|
|
250
|
+
export const DEFAULT_GOAL_RUN = false
|
|
251
|
+
|
|
114
252
|
declare module '@deepseek-ai/cordis' {
|
|
115
253
|
interface Events {
|
|
116
254
|
/**
|
|
@@ -160,6 +298,13 @@ export function resolveProxyAccess(config: Config | undefined, env: NodeJS.Proce
|
|
|
160
298
|
/** The registry face the agent tools register into. */
|
|
161
299
|
interface AgentToolRegistry {
|
|
162
300
|
register(definition: ToolDefinition): () => void
|
|
301
|
+
/** Scoped lookup; present on the real registry, absent on a stub that only registers. */
|
|
302
|
+
get?(name: string, scope?: unknown): unknown
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** The lookup half of a resolved registry, when it exposes one. */
|
|
306
|
+
function lookupOf(registry: AgentToolRegistry | undefined): { get: (name: string, scope?: unknown) => unknown } | undefined {
|
|
307
|
+
return registry !== undefined && typeof registry.get === 'function' ? { get: registry.get.bind(registry) } : undefined
|
|
163
308
|
}
|
|
164
309
|
|
|
165
310
|
/**
|
|
@@ -203,6 +348,33 @@ export function resolveHostTimers(ctx: Context): HostTimerFace | undefined {
|
|
|
203
348
|
/** Schema default, re-read for hand-built test contexts (the loader applies them normally). */
|
|
204
349
|
const DEFAULT_ANNOUNCE = false
|
|
205
350
|
|
|
351
|
+
/** The subset of the official permission-preset service the board reads. */
|
|
352
|
+
interface PermissionPresetsFace {
|
|
353
|
+
catalog(): { defaultPreset?: unknown }
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The Host's own default permission preset, mapped into the board's vocabulary.
|
|
358
|
+
*
|
|
359
|
+
* This is the value new DSH sessions start at (the deployment's `permission`
|
|
360
|
+
* settings row), so a board that follows it treats an ordinary card exactly as
|
|
361
|
+
* the Host would. Absent when the deployment serves no permission catalog, or
|
|
362
|
+
* its default is a current-session identity (`auto`) that names no fixed
|
|
363
|
+
* sandbox mode; the caller then keeps the fail-safe baseline.
|
|
364
|
+
* @param ctx - the plugin context.
|
|
365
|
+
* @returns the Host default as a board permission, or undefined when unreadable.
|
|
366
|
+
*/
|
|
367
|
+
export function resolveHostDefaultPermission(ctx: Context): TaskPermission | undefined {
|
|
368
|
+
try {
|
|
369
|
+
const presets = ctx.get('permissionPresets') as PermissionPresetsFace | undefined
|
|
370
|
+
if (presets === undefined || typeof presets.catalog !== 'function') return undefined
|
|
371
|
+
const preset = presets.catalog()?.defaultPreset
|
|
372
|
+
return isTaskPermission(preset) ? preset : undefined
|
|
373
|
+
} catch {
|
|
374
|
+
return undefined
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
206
378
|
/**
|
|
207
379
|
* How long one teammate may take to reach its durable active or failed edge.
|
|
208
380
|
* Agent Teams provisioning normally resolves in seconds; the bound exists so a
|
|
@@ -317,11 +489,53 @@ function applyImpl(ctx: Context, config?: Config): void {
|
|
|
317
489
|
const preventIdleSleep = (): boolean => readConfigField(config?.preventIdleSleep, false)
|
|
318
490
|
/** Current subtask depth limit (read live: the settings card edits it in place). */
|
|
319
491
|
const maxSubtaskDepth = (): number => readConfigField(config?.maxSubtaskDepth, DEFAULT_SUBTASK_DEPTH)
|
|
492
|
+
/** Current roster-poll cadence in seconds (read live: the settings card edits it in place). */
|
|
493
|
+
const sessionPollSeconds = (): number => readConfigField(config?.sessionPollSeconds, DEFAULT_SESSION_POLL_SECONDS)
|
|
494
|
+
/**
|
|
495
|
+
* The baseline the permission confirmation gate judges against: the row's own
|
|
496
|
+
* `sessionDefaultPermission` when the deployment pins one, otherwise the Host's
|
|
497
|
+
* default preset. Read per call through the ledger, so a Host Settings change
|
|
498
|
+
* reaches the running board without remounting the row; the fail-safe default
|
|
499
|
+
* still applies when no catalog is available.
|
|
500
|
+
*/
|
|
501
|
+
const sessionDefaultPermission = (): TaskPermission =>
|
|
502
|
+
config?.sessionDefaultPermission ?? resolveHostDefaultPermission(ctx) ?? DEFAULT_SESSION_PERMISSION
|
|
320
503
|
|
|
504
|
+
/** Live acceptance settings the contract of each new execution freezes. */
|
|
505
|
+
const verificationSettings = (): VerificationSettings => ({
|
|
506
|
+
enabled: readConfigField(config?.goalVerification, DEFAULT_GOAL_VERIFICATION),
|
|
507
|
+
model: readConfigField(config?.goalVerificationModel, ''),
|
|
508
|
+
reasoningEffort: readConfigField(config?.goalVerificationReasoningEffort, ''),
|
|
509
|
+
})
|
|
510
|
+
/**
|
|
511
|
+
* The acceptance time ceilings, read live: raising one must unblock a card
|
|
512
|
+
* whose acceptance is stuck on a slow judge route without a remount, so
|
|
513
|
+
* these deliberately are NOT frozen into the per-execution contract.
|
|
514
|
+
*/
|
|
515
|
+
const verificationCallTimeoutMsLive = (): number =>
|
|
516
|
+
verificationCallTimeoutMs(readConfigField(config?.goalVerificationCallTimeoutSeconds, DEFAULT_VERIFICATION_CALL_TIMEOUT_SECONDS))
|
|
517
|
+
const verificationBudgetMsLive = (): number =>
|
|
518
|
+
verificationBudgetMs(readConfigField(config?.goalVerificationBudgetSeconds, DEFAULT_VERIFICATION_BUDGET_SECONDS))
|
|
519
|
+
// The catalog reader needs the service, and the service needs the reader;
|
|
520
|
+
// the reader only runs after start(), so a late holder is enough.
|
|
521
|
+
let hostForCatalog: TaskBoardHostService | undefined
|
|
522
|
+
const verificationCatalog = async (): Promise<ModelCatalogView | undefined> => {
|
|
523
|
+
const runner = hostForCatalog?.runner
|
|
524
|
+
return runner === undefined ? undefined : normalizeCatalog(await runner.modelCatalog())
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Live reader of the GLOBAL native-/goal switch. The service calls it exactly
|
|
528
|
+
* once per execution and carries the value, so a settings edit only reaches
|
|
529
|
+
* executions that start afterwards.
|
|
530
|
+
*/
|
|
531
|
+
const goalRunEnabled = (): boolean => readConfigField(config?.goalRunEnabled, DEFAULT_GOAL_RUN)
|
|
321
532
|
const host = new TaskBoardHostService(ctx.typertGateway, {
|
|
322
533
|
workspaceRegistry: ctx.workspaceRegistry,
|
|
323
|
-
sessionDefaultPermission
|
|
534
|
+
sessionDefaultPermission,
|
|
324
535
|
maxSubtaskDepth: maxSubtaskDepth(),
|
|
536
|
+
goalRunEnabled,
|
|
537
|
+
verificationSettings,
|
|
538
|
+
verificationCatalog,
|
|
325
539
|
timers: resolveHostTimers(ctx),
|
|
326
540
|
team: buildTeamDispatcher(ctx, config?.teamProvider ?? DEFAULT_TEAM_PROVIDER),
|
|
327
541
|
commandDispatcher: {
|
|
@@ -332,22 +546,41 @@ function applyImpl(ctx: Context, config?: Config): void {
|
|
|
332
546
|
},
|
|
333
547
|
},
|
|
334
548
|
})
|
|
549
|
+
hostForCatalog = host
|
|
335
550
|
// Configuration before start(): a disabled row must not take the first
|
|
336
551
|
// scheduler tick, which would roll schedules the board is not running.
|
|
337
|
-
host.setConfiguration(enabled(), preventIdleSleep())
|
|
552
|
+
host.setConfiguration(enabled(), preventIdleSleep(), sessionPollSeconds())
|
|
338
553
|
host.start()
|
|
339
554
|
|
|
555
|
+
// External providers. The board publishes its registration service so any
|
|
556
|
+
// provider package can resolve it and admit itself; the board itself knows
|
|
557
|
+
// no provider vocabulary. A capture-only test context implements no service
|
|
558
|
+
// registry and the board still serves its own surfaces.
|
|
559
|
+
if (typeof (ctx as { provide?: unknown }).provide === 'function') {
|
|
560
|
+
ctx.provide(TASK_BOARD_SERVICE_NAME, {
|
|
561
|
+
registerExtension: (extension: TaskBoardExtension) => host.registerExtension(extension),
|
|
562
|
+
isExtensionEnabled: (extensionId: string) => host.extensions.isActive(extensionId),
|
|
563
|
+
})
|
|
564
|
+
}
|
|
565
|
+
|
|
340
566
|
// Agent tools: the same Host ledger the browser drives, so any session can
|
|
341
567
|
// list, create, link, run and settle board work. Registration follows the
|
|
342
568
|
// master switch (a disabled board answers no tool call), and the mount
|
|
343
|
-
// effect below owns the disposers.
|
|
569
|
+
// effect below owns the disposers. Provider tools register through the
|
|
570
|
+
// extension registry, which follows the same gate and rebinds when the tool
|
|
571
|
+
// registry appears late.
|
|
344
572
|
let disposeTools: (() => void) | undefined
|
|
345
573
|
const setToolsEnabled = (active: boolean): void => {
|
|
346
574
|
if (!active) {
|
|
575
|
+
// Releasing is the whole job on the way down. Rebinding the extension
|
|
576
|
+
// registry here would re-register every provider tool, and the teardown
|
|
577
|
+
// path runs this while the fiber is already unloading, so each of those
|
|
578
|
+
// re-registrations is a request the host can only refuse.
|
|
347
579
|
disposeTools?.()
|
|
348
580
|
disposeTools = undefined
|
|
349
581
|
return
|
|
350
582
|
}
|
|
583
|
+
host.extensions.setToolRegistry(() => resolveToolRegistry(ctx))
|
|
351
584
|
if (disposeTools !== undefined) return
|
|
352
585
|
const registry = resolveToolRegistry(ctx)
|
|
353
586
|
if (registry === undefined) return
|
|
@@ -364,6 +597,9 @@ function applyImpl(ctx: Context, config?: Config): void {
|
|
|
364
597
|
if (typeof scopedInject === 'function') {
|
|
365
598
|
scopedInject.call(ctx, ['tools'], () => {
|
|
366
599
|
setToolsEnabled(enabled())
|
|
600
|
+
// The extension registry follows the same rebinding: a registry that
|
|
601
|
+
// appears (or is replaced) after a provider started adopts its tools.
|
|
602
|
+
host.extensions.setToolRegistry(() => resolveToolRegistry(ctx))
|
|
367
603
|
// Cordis unloads and re-runs this callback when the injected service's
|
|
368
604
|
// provider fiber changes, and the old registry dies with its provider.
|
|
369
605
|
// Releasing the guard here is what lets the callback register into the
|
|
@@ -378,6 +614,44 @@ function applyImpl(ctx: Context, config?: Config): void {
|
|
|
378
614
|
ctx.effect(() => {
|
|
379
615
|
const disposers: Array<() => void> = []
|
|
380
616
|
try {
|
|
617
|
+
// The goal acceptance gate: the OFFICIAL tool pre-execution lifecycle,
|
|
618
|
+
// installed before the tool body runs, so a completion claim cannot take
|
|
619
|
+
// effect without a matching acceptance pass record.
|
|
620
|
+
const gate = createGoalVerificationGate({
|
|
621
|
+
ledger: host.ledger,
|
|
622
|
+
llm: () => resolveLlmRuntime(ctx),
|
|
623
|
+
goals: () => {
|
|
624
|
+
try {
|
|
625
|
+
return ctx.get('goals') as GoalFace | undefined
|
|
626
|
+
} catch {
|
|
627
|
+
return undefined
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
// The host's own observation of what a turn changed on disk, when this
|
|
631
|
+
// deployment records it; the acceptance degrades to the trajectory alone
|
|
632
|
+
// when it does not.
|
|
633
|
+
workspaceChanges: () => probeWorkspaceChanges(ctx),
|
|
634
|
+
callTimeoutMs: verificationCallTimeoutMsLive,
|
|
635
|
+
budgetMs: verificationBudgetMsLive,
|
|
636
|
+
logger: {
|
|
637
|
+
warn: (message: string, ...rest: unknown[]) => {
|
|
638
|
+
const logger = (ctx as { logger?: { warn?: (...args: unknown[]) => void } }).logger
|
|
639
|
+
if (typeof logger?.warn === 'function') logger.warn(message, ...rest)
|
|
640
|
+
else console.warn(message, ...rest)
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
})
|
|
644
|
+
// A capture-only context may implement no listener registry (it returns
|
|
645
|
+
// no disposer), so the handle is pushed only when it really is one.
|
|
646
|
+
const offGate = ctx.on('tools/pre-execute', async (exec, next) => {
|
|
647
|
+
const decision = await gate({ name: exec.name, arguments: exec.arguments, agent: exec.agent, signal: exec.signal })
|
|
648
|
+
return decision ?? next()
|
|
649
|
+
})
|
|
650
|
+
if (typeof offGate === 'function') disposers.push(offGate)
|
|
651
|
+
else if (typeof (offGate as { dispose?: unknown } | undefined)?.dispose === 'function') {
|
|
652
|
+
const handle = offGate as { dispose(): void }
|
|
653
|
+
disposers.push(() => { handle.dispose() })
|
|
654
|
+
}
|
|
381
655
|
const routes = makeTaskBoardRoutes(host, resolveProxyAccess(config), {
|
|
382
656
|
parseTask: async (request, signal) => {
|
|
383
657
|
const llm = resolveLlmRuntime(ctx)
|
|
@@ -402,7 +676,7 @@ function applyImpl(ctx: Context, config?: Config): void {
|
|
|
402
676
|
}, 'task-board: host ledger, scheduler, and routes')
|
|
403
677
|
|
|
404
678
|
let disposeSection: (() => void) | undefined
|
|
405
|
-
let applied: { enabled: boolean; announceToAgent: boolean; preventIdleSleep: boolean; maxSubtaskDepth: number } | undefined
|
|
679
|
+
let applied: { enabled: boolean; announceToAgent: boolean; preventIdleSleep: boolean; maxSubtaskDepth: number; sessionPollSeconds: number } | undefined
|
|
406
680
|
|
|
407
681
|
// Apply the current values to the host service and the announcement. A
|
|
408
682
|
// commit that changes nothing visible is a no-op, so following a coarse
|
|
@@ -410,13 +684,19 @@ function applyImpl(ctx: Context, config?: Config): void {
|
|
|
410
684
|
// under one disposer: re-registering first tears the old one down so a
|
|
411
685
|
// duplicate-name registration never throws.
|
|
412
686
|
const sync = (): void => {
|
|
413
|
-
const next = {
|
|
414
|
-
|
|
687
|
+
const next = {
|
|
688
|
+
enabled: enabled(),
|
|
689
|
+
announceToAgent: announceToAgent(),
|
|
690
|
+
preventIdleSleep: preventIdleSleep(),
|
|
691
|
+
maxSubtaskDepth: maxSubtaskDepth(),
|
|
692
|
+
sessionPollSeconds: sessionPollSeconds(),
|
|
693
|
+
}
|
|
694
|
+
if (applied !== undefined && applied.enabled === next.enabled && applied.announceToAgent === next.announceToAgent && applied.preventIdleSleep === next.preventIdleSleep && applied.maxSubtaskDepth === next.maxSubtaskDepth && applied.sessionPollSeconds === next.sessionPollSeconds) {
|
|
415
695
|
return
|
|
416
696
|
}
|
|
417
697
|
applied = next
|
|
418
698
|
host.ledger.setMaxSubtaskDepth(next.maxSubtaskDepth)
|
|
419
|
-
host.setConfiguration(next.enabled, next.preventIdleSleep)
|
|
699
|
+
host.setConfiguration(next.enabled, next.preventIdleSleep, next.sessionPollSeconds)
|
|
420
700
|
setToolsEnabled(next.enabled)
|
|
421
701
|
if (disposeSection !== undefined) {
|
|
422
702
|
disposeSection()
|
|
@@ -426,7 +706,11 @@ function applyImpl(ctx: Context, config?: Config): void {
|
|
|
426
706
|
disposeSection = ctx.systemPrompt.section({
|
|
427
707
|
name: 'plugin:task-board',
|
|
428
708
|
order: SECTION_ORDER,
|
|
429
|
-
|
|
709
|
+
// The announcement names the task_board_* tools, so it renders only while
|
|
710
|
+
// at least one of them is reachable: a runtime that serves no tool
|
|
711
|
+
// registry, or a restriction that withholds them, must not leave guidance
|
|
712
|
+
// telling the model to call tools this session cannot see.
|
|
713
|
+
text: visibleToolText(lookupOf(resolveToolRegistry(ctx)), TASK_BOARD_TOOL_NAMES, TASK_BOARD_GUIDANCE),
|
|
430
714
|
})
|
|
431
715
|
}
|
|
432
716
|
|