@linxin666/dsh-client-ui-task-board 0.4.3 → 0.4.5
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 +56 -18
- package/README.zh.md +56 -18
- package/lib/client.js +6242 -958
- package/lib/client.js.map +1 -1
- package/lib/index.js +3475 -234
- package/lib/types/client/PluginSettingsCard.d.ts +7 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +19 -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 +23 -2
- 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/preset-label.d.ts +22 -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/board-card-seat.d.ts +34 -0
- package/lib/types/client/host-api.d.ts +24 -1
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/locales.d.ts +141 -3
- package/lib/types/client/native-panel.d.ts +6 -20
- package/lib/types/client/schedule-zone.d.ts +47 -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 +38 -1
- package/lib/types/core/degraded-reason.d.ts +46 -0
- 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/package-name.d.ts +9 -0
- package/lib/types/core/schedule.d.ts +95 -20
- package/lib/types/core/subtask.d.ts +30 -11
- package/lib/types/core/tasks.d.ts +110 -5
- package/lib/types/core/use-cases/task-create.d.ts +2 -1
- package/lib/types/core/use-cases/task-schedule.d.ts +8 -4
- 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.d.ts +386 -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 +71 -0
- package/lib/types/host/verification-runner.d.ts +108 -0
- package/lib/types/host/workspace-evidence.d.ts +53 -0
- package/lib/types/host-ledger.d.ts +107 -11
- package/lib/types/host-runner.d.ts +99 -4
- package/lib/types/host-service.d.ts +114 -2
- package/lib/types/index.d.ts +80 -26
- package/lib/types/protocol.d.ts +53 -3
- package/lib/types/tool-surface.d.ts +71 -0
- package/package.json +27 -26
- package/src/client/PluginSettingsCard.tsx +9 -0
- package/src/client/TaskBoardSettingsCard.tsx +304 -65
- 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 +30 -15
- package/src/client/board/NewTaskModal.tsx +288 -151
- package/src/client/board/TagManagerModal.tsx +235 -0
- package/src/client/board/TaskBoard.tsx +259 -103
- package/src/client/board/TaskCard.tsx +238 -86
- package/src/client/board/TaskDetail.tsx +154 -59
- package/src/client/board/TaskForm.tsx +61 -6
- package/src/client/board/VerificationReport.tsx +203 -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/preset-label.ts +44 -0
- package/src/client/board/status-key.ts +27 -0
- package/src/client/board/task-markdown.module.css +137 -0
- package/src/client/board/task-markdown.tsx +106 -0
- package/src/client/board-card-seat.ts +102 -0
- package/src/client/board-settings.module.css +66 -0
- package/src/client/board.module.css +602 -68
- package/src/client/host-api.ts +109 -6
- package/src/client/index.ts +48 -6
- package/src/client/locales.ts +290 -12
- package/src/client/native-panel.tsx +18 -2
- package/src/client/schedule-zone.ts +135 -0
- 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 +101 -5
- package/src/core/degraded-reason.ts +81 -0
- 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/package-name.ts +9 -0
- package/src/core/schedule.ts +211 -55
- package/src/core/store.ts +26 -1
- package/src/core/subtask.ts +77 -22
- package/src/core/tasks.ts +138 -10
- package/src/core/use-cases/task-create.ts +14 -2
- package/src/core/use-cases/task-parent.ts +3 -2
- package/src/core/use-cases/task-schedule.ts +17 -6
- package/src/core/use-cases/task-tag.ts +110 -0
- package/src/core/use-cases/task-update.ts +13 -4
- package/src/core/verification.ts +826 -0
- package/src/core/workspace-target.ts +59 -0
- package/src/host/agent-tools.ts +58 -24
- package/src/host/extension-registry.ts +509 -0
- package/src/host/llm-dispatch.ts +87 -0
- package/src/host/verification-gate.ts +331 -0
- package/src/host/verification-runner.ts +535 -0
- package/src/host/workspace-evidence.ts +176 -0
- package/src/host-ai.ts +15 -9
- package/src/host-ledger.ts +342 -51
- package/src/host-routes.ts +15 -2
- package/src/host-runner.ts +219 -20
- package/src/host-service.ts +346 -26
- package/src/index.ts +204 -16
- package/src/protocol.ts +122 -9
- package/src/tool-surface.ts +95 -0
- package/lib/types/host/run-guarded.d.ts +0 -39
- package/src/host/run-guarded.ts +0 -76
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace targeting for cards that pin none.
|
|
3
|
+
*
|
|
4
|
+
* The Host answers two questions here instead of leaving them to
|
|
5
|
+
* `session.create`, whose own fallback is the Host process working directory
|
|
6
|
+
* (the desktop app's profile directory): which workspace a root card inherits
|
|
7
|
+
* from the session that created it, and which workspace an unpinned run lands
|
|
8
|
+
* in when nothing could be inherited.
|
|
9
|
+
*
|
|
10
|
+
* Structural types only — the Host's `Workspace` entity satisfies them — so the
|
|
11
|
+
* browser program compiles this module without importing an SDK value.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-task-board/core/workspace-target
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The workspace facts these rules read (a `Workspace` entity subset). */
|
|
17
|
+
export interface WorkspaceCandidate {
|
|
18
|
+
/** Stable workspace record id. */
|
|
19
|
+
readonly id: string
|
|
20
|
+
/** ISO-8601 instant of the workspace's last durable mutation. */
|
|
21
|
+
readonly updatedAt: string
|
|
22
|
+
/** Header-validated session ids the workspace owns, most recent first. */
|
|
23
|
+
readonly sessionIds: readonly string[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The workspace that owns one session.
|
|
28
|
+
* @param items - the deployment's workspaces.
|
|
29
|
+
* @param sessionId - the session whose workspace is requested.
|
|
30
|
+
* @returns the owning workspace id, or undefined when none claims the session.
|
|
31
|
+
*/
|
|
32
|
+
export function workspaceOwningSession(
|
|
33
|
+
items: readonly WorkspaceCandidate[],
|
|
34
|
+
sessionId: string,
|
|
35
|
+
): string | undefined {
|
|
36
|
+
return items.find(item => item.sessionIds.includes(sessionId))?.id
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The workspace the board treats as most recently used: the one whose record
|
|
41
|
+
* changed last. Attaching a session stamps that instant, so the newest stamp
|
|
42
|
+
* tracks where the user most recently opened or resumed a session. The
|
|
43
|
+
* deployment's own list order breaks ties, and a stamp that does not parse
|
|
44
|
+
* never outranks one that does; a registry whose stamps are all unreadable
|
|
45
|
+
* falls back to list order rather than to "no workspace at all".
|
|
46
|
+
* @param items - the deployment's workspaces.
|
|
47
|
+
* @returns the most recently used workspace id, or undefined when there is none.
|
|
48
|
+
*/
|
|
49
|
+
export function mostRecentWorkspaceId(items: readonly WorkspaceCandidate[]): string | undefined {
|
|
50
|
+
let best: WorkspaceCandidate | undefined
|
|
51
|
+
let bestAt = Number.NEGATIVE_INFINITY
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
const at = Date.parse(item.updatedAt)
|
|
54
|
+
if (!Number.isFinite(at) || at <= bestAt) continue
|
|
55
|
+
best = item
|
|
56
|
+
bestAt = at
|
|
57
|
+
}
|
|
58
|
+
return best?.id ?? items[0]?.id
|
|
59
|
+
}
|
package/src/host/agent-tools.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface TaskBoardToolHost {
|
|
|
39
39
|
/** Current board snapshot (tasks + scheduler + power + deployment constants). */
|
|
40
40
|
snapshot(): TaskBoardSnapshot
|
|
41
41
|
/** Submit one confirmed Host action; returns the resulting snapshot. */
|
|
42
|
-
apply(requestId: string, action: TaskBoardAction, initiator?: string): TaskBoardSnapshot
|
|
42
|
+
apply(requestId: string, action: TaskBoardAction, initiator?: string): TaskBoardSnapshot | Promise<TaskBoardSnapshot>
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/** Registered tool names, in registration order. */
|
|
@@ -94,6 +94,8 @@ function scheduleView(schedule: ScheduleRule): Record<string, unknown> {
|
|
|
94
94
|
return {
|
|
95
95
|
enabled: schedule.enabled,
|
|
96
96
|
cron: schedule.cron,
|
|
97
|
+
// Absent means the rule follows the Host zone, which the board view reports.
|
|
98
|
+
...(schedule.timeZone === undefined ? {} : { timeZone: schedule.timeZone }),
|
|
97
99
|
...(schedule.nextRunAt === undefined ? {} : { nextRunAt: schedule.nextRunAt }),
|
|
98
100
|
...(schedule.lastTriggeredAt === undefined ? {} : { lastTriggeredAt: schedule.lastTriggeredAt }),
|
|
99
101
|
}
|
|
@@ -150,6 +152,10 @@ function taskSummary(
|
|
|
150
152
|
...(task.model === undefined ? {} : { model: task.model }),
|
|
151
153
|
...(task.reuseSession === true ? { reuseSession: true } : {}),
|
|
152
154
|
...(task.teamRun === true ? { teamRun: true } : {}),
|
|
155
|
+
// Default-ON switch: only the opt-out is a deviation worth reporting.
|
|
156
|
+
...(task.goalRun === false ? { goalRun: false } : {}),
|
|
157
|
+
// Default-OFF opt-out: the deviation is what a reader must be told.
|
|
158
|
+
...(task.skipVerification === true ? { skipVerification: true } : {}),
|
|
153
159
|
...(task.schedule === undefined ? {} : { schedule: scheduleView(task.schedule) }),
|
|
154
160
|
...(task.freeze === undefined ? {} : { continuationCard: true }),
|
|
155
161
|
...(task.permissionConfirmedAt === undefined ? {} : { permissionConfirmedAt: task.permissionConfirmedAt }),
|
|
@@ -256,7 +262,6 @@ function buildSetParentTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
256
262
|
'Pass an empty parentId to detach.',
|
|
257
263
|
'The Host refuses the link when the parent is unknown or archived, when the moved task is one of its own descendants, when the resulting depth exceeds the deployment subtask limit (default one level, maximum three), or while the task has an unsettled execution (its link may still be a running cascade parent pointer).',
|
|
258
264
|
'Running a task runs its whole subtree concurrently; a parent card settles only after its own turn and every direct subtask settle.',
|
|
259
|
-
'Triggers: 任务看板, task board, 看板, 子任务, subtask, 关联任务, link task, 解除关联, detach.',
|
|
260
265
|
].join(' '),
|
|
261
266
|
parameters: {
|
|
262
267
|
taskId: { type: 'string', required: true, description: 'The task whose parent link changes.' },
|
|
@@ -265,7 +270,7 @@ function buildSetParentTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
265
270
|
output: { schema: { type: 'json' }, render: renderJson },
|
|
266
271
|
async execute(args, exec) {
|
|
267
272
|
try {
|
|
268
|
-
const snapshot = host.apply(
|
|
273
|
+
const snapshot = await host.apply(
|
|
269
274
|
crypto.randomUUID(),
|
|
270
275
|
{ kind: 'set-parent', taskId: args.taskId, parentId: args.parentId === '' ? null : args.parentId },
|
|
271
276
|
callingSessionId(exec),
|
|
@@ -290,7 +295,6 @@ function buildRunTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
290
295
|
'A card whose effective permission is above the deployment session default is refused with confirmation-required until a HUMAN confirms the binding in the board UI; this tool cannot confirm it.',
|
|
291
296
|
'A card whose task carries teamRun instead starts ONE Team Lead session and the Host spawns a teammate per subtask inside it, so those cards run as an Agent Team rather than as independent sessions; that mode needs the deployment Agent Teams service and is refused when it is missing.',
|
|
292
297
|
'Executions consume real API quota. Sessions are created asynchronously, so read their ids and outcomes back with task_board_get.',
|
|
293
|
-
'Triggers: 任务看板, task board, 看板, 执行任务, run task, 重新执行, rerun, 子任务, subtask.',
|
|
294
298
|
].join(' '),
|
|
295
299
|
parameters: {
|
|
296
300
|
taskId: { type: 'string', required: true, description: 'The task to run; running a root task runs its whole subtree.' },
|
|
@@ -300,7 +304,7 @@ function buildRunTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
300
304
|
async execute(args, exec) {
|
|
301
305
|
const before = host.snapshot()
|
|
302
306
|
try {
|
|
303
|
-
const snapshot = host.apply(
|
|
307
|
+
const snapshot = await host.apply(
|
|
304
308
|
crypto.randomUUID(),
|
|
305
309
|
{ kind: args.rerun === true ? 'rerun' : 'run', taskId: args.taskId },
|
|
306
310
|
callingSessionId(exec),
|
|
@@ -334,18 +338,19 @@ function buildManageTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
334
338
|
return defineTool({
|
|
335
339
|
name: 'task_board_manage',
|
|
336
340
|
description: [
|
|
337
|
-
'Move, archive, restore, or
|
|
338
|
-
'move-todo and move-
|
|
341
|
+
'Move, archive, restore, delete, or settle one task board card.',
|
|
342
|
+
'move-backlog, move-todo, move-running, move-done and move-failed are the manual column moves and cover every column; a card the runner is executing (one with an open execution) cannot be moved.',
|
|
343
|
+
'A manual move writes the card column only and never fabricates an execution record, so the card reports a declaration rather than evidence of a run: done/failed declare work finished (or failed) outside a Host-run execution — human work, an external system, a decision made elsewhere — and running says the work is under way without a tracked session. Use task_board_run when the work should actually run in a session here.',
|
|
344
|
+
'settle force-closes the open execution of a card the board can no longer observe (a stuck running card) and records it cancelled with the caller as the reason, so the card returns to the todo column and can be run again.',
|
|
339
345
|
'archive takes the whole subtask tree off the board and is refused while any member has an unsettled execution; restore brings the task, its ancestors and its subtree back; delete removes one card and is refused while it still has subtasks (detach or delete them first) or while it runs.',
|
|
340
346
|
'It cannot confirm a permission binding: the confirmation gate is a human act performed in the board UI.',
|
|
341
|
-
'Triggers: 任务看板, task board, 看板, 归档, archive, 删除任务, delete task, 移动任务, move task.',
|
|
342
347
|
].join(' '),
|
|
343
348
|
parameters: {
|
|
344
349
|
taskId: { type: 'string', required: true, description: 'The task to act on.' },
|
|
345
350
|
action: {
|
|
346
351
|
type: 'string',
|
|
347
352
|
required: true,
|
|
348
|
-
enum: ['move-todo', 'move-backlog', 'archive', 'restore', 'delete'],
|
|
353
|
+
enum: ['move-todo', 'move-backlog', 'move-running', 'move-done', 'move-failed', 'archive', 'restore', 'delete', 'settle'],
|
|
349
354
|
description: 'The lifecycle operation to perform.',
|
|
350
355
|
},
|
|
351
356
|
},
|
|
@@ -355,7 +360,11 @@ function buildManageTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
355
360
|
const actions: Record<string, TaskBoardAction> = {
|
|
356
361
|
'move-todo': { kind: 'move', taskId: args.taskId, status: 'todo' },
|
|
357
362
|
'move-backlog': { kind: 'move', taskId: args.taskId, status: 'backlog' },
|
|
363
|
+
'move-running': { kind: 'move', taskId: args.taskId, status: 'running' },
|
|
364
|
+
'move-done': { kind: 'move', taskId: args.taskId, status: 'done' },
|
|
365
|
+
'move-failed': { kind: 'move', taskId: args.taskId, status: 'failed' },
|
|
358
366
|
archive: { kind: 'archive', taskId: args.taskId },
|
|
367
|
+
settle: { kind: 'settle', taskId: args.taskId },
|
|
359
368
|
restore: { kind: 'restore', taskId: args.taskId },
|
|
360
369
|
delete: { kind: 'delete', taskId: args.taskId },
|
|
361
370
|
}
|
|
@@ -365,7 +374,7 @@ function buildManageTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
365
374
|
return refused('task-not-found', 'no task with id ' + args.taskId)
|
|
366
375
|
}
|
|
367
376
|
try {
|
|
368
|
-
const snapshot = host.apply(crypto.randomUUID(), action, initiator)
|
|
377
|
+
const snapshot = await host.apply(crypto.randomUUID(), action, initiator)
|
|
369
378
|
if (args.action === 'delete') return json({ ok: true, action: args.action, taskId: args.taskId })
|
|
370
379
|
const task = snapshot.tasks.find(item => item.id === args.taskId)
|
|
371
380
|
if (task === undefined) return refused('task-not-found', 'no task with id ' + args.taskId)
|
|
@@ -381,33 +390,40 @@ function buildScheduleTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
381
390
|
return defineTool({
|
|
382
391
|
name: 'task_board_schedule',
|
|
383
392
|
description: [
|
|
384
|
-
'Arm, change, or disarm a task scheduled runs (5-field cron
|
|
393
|
+
'Arm, change, or disarm a task scheduled runs (5-field cron; day-of-month and day-of-week follow Vixie semantics: both restricted means OR, otherwise AND).',
|
|
394
|
+
'The cron wall clock is read in the rule stored IANA time zone; omit timeZone to use the Host zone reported by task_board_list/task_board_get. DST gaps are skipped and an ambiguous fall-back time fires once, at the earlier instant.',
|
|
385
395
|
'A due scheduled task runs the same cascade a manual run does, so a root task runs its whole subtask tree.',
|
|
386
396
|
'A schedule whose tree contains an unconfirmed above-default permission is refused and rolls to its next occurrence; the reason is reported in the list board summary as schedulerError.',
|
|
387
397
|
'Missed occurrences during Host downtime are skipped, never queued; a task that is already running skips its occurrence.',
|
|
388
|
-
'Triggers: 任务看板, task board, 看板, 定时任务, schedule, cron, 每天执行.',
|
|
389
398
|
].join(' '),
|
|
390
399
|
parameters: {
|
|
391
400
|
taskId: { type: 'string', required: true, description: 'The task whose schedule changes.' },
|
|
392
401
|
enabled: { type: 'boolean', description: 'Arm (true) or disarm (false) the schedule.' },
|
|
393
402
|
cron: { type: 'string', description: '5-field cron expression: minute hour day-of-month month day-of-week.' },
|
|
403
|
+
timeZone: {
|
|
404
|
+
type: 'string',
|
|
405
|
+
description: 'IANA zone the cron wall clock is read in (for example Asia/Shanghai). Omit to keep the stored zone, or pass an empty string to clear it back to the Host zone.',
|
|
406
|
+
},
|
|
394
407
|
},
|
|
395
408
|
output: { schema: { type: 'json' }, render: renderJson },
|
|
396
409
|
async execute(args, exec) {
|
|
397
|
-
if (args.enabled === undefined && args.cron === undefined) {
|
|
398
|
-
return refused('nothing-to-change', 'pass enabled and/or cron')
|
|
410
|
+
if (args.enabled === undefined && args.cron === undefined && args.timeZone === undefined) {
|
|
411
|
+
return refused('nothing-to-change', 'pass enabled and/or cron and/or timeZone')
|
|
399
412
|
}
|
|
400
413
|
// A missing card must read as a missing card: the ledger reports an
|
|
401
414
|
// unknown id through the same refusal as a malformed cron.
|
|
402
415
|
if (!host.snapshot().tasks.some(task => task.id === args.taskId)) {
|
|
403
416
|
return refused('task-not-found', 'no task with id ' + args.taskId)
|
|
404
417
|
}
|
|
405
|
-
const patch: { enabled?: boolean; cron?: string } = {
|
|
418
|
+
const patch: { enabled?: boolean; cron?: string; timeZone?: string | null } = {
|
|
406
419
|
...(args.enabled === undefined ? {} : { enabled: args.enabled }),
|
|
407
420
|
...(args.cron === undefined ? {} : { cron: args.cron }),
|
|
421
|
+
// An empty string is the documented way to clear the stored zone, so
|
|
422
|
+
// the model never has to emit an explicit null through JSON args.
|
|
423
|
+
...(args.timeZone === undefined ? {} : { timeZone: args.timeZone === '' ? null : args.timeZone }),
|
|
408
424
|
}
|
|
409
425
|
try {
|
|
410
|
-
const snapshot = host.apply(crypto.randomUUID(), { kind: 'set-schedule', taskId: args.taskId, patch }, callingSessionId(exec))
|
|
426
|
+
const snapshot = await host.apply(crypto.randomUUID(), { kind: 'set-schedule', taskId: args.taskId, patch }, callingSessionId(exec))
|
|
411
427
|
const task = snapshot.tasks.find(item => item.id === args.taskId)
|
|
412
428
|
if (task === undefined) return refused('task-not-found', 'no task with id ' + args.taskId)
|
|
413
429
|
return json({
|
|
@@ -431,7 +447,6 @@ function buildListTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
431
447
|
'Filters: status column, parentId (direct subtasks), rootsOnly, tag, free-text query, and archived inclusion.',
|
|
432
448
|
'Returns each task id, title, column, parent/subtask link, execution targets, schedule and latest run outcome, plus the board summary (revision, time zone, subtask depth limit, session-default permission).',
|
|
433
449
|
'Use task_board_get for one task in full, task_board_create to add work, and task_board_run to execute it.',
|
|
434
|
-
'Triggers: 任务看板, task board, 看板, 子任务, subtask, task list, 定时任务, scheduled task.',
|
|
435
450
|
].join(' '),
|
|
436
451
|
parameters: {
|
|
437
452
|
status: { type: 'string', enum: ['backlog', 'todo', 'running', 'done', 'failed'], description: 'Only cards in this column.' },
|
|
@@ -477,7 +492,6 @@ function buildGetTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
477
492
|
description: [
|
|
478
493
|
'Read one task board card in full: prompt, description, labels, column, parent link, direct subtasks, execution targets, schedule, permission gate state, and the last ten execution attempts with their session ids and outcomes.',
|
|
479
494
|
'Use it before running or editing a card, and after a run to read the settled outcome.',
|
|
480
|
-
'Triggers: 任务看板, task board, 看板, 子任务, subtask, task detail.',
|
|
481
495
|
].join(' '),
|
|
482
496
|
parameters: {
|
|
483
497
|
taskId: { type: 'string', required: true, description: 'Task id, as reported by task_board_list or task_board_create.' },
|
|
@@ -501,19 +515,20 @@ function buildCreateTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
501
515
|
'A subtask inherits every execution target it does not override (workspace, agent preset, permission, model) from its parent, and inherits the parent human permission confirmation together with an inherited binding.',
|
|
502
516
|
'The deployment caps subtask depth (default one level, maximum three): a subtask of a subtask is refused unless the board is configured deeper.',
|
|
503
517
|
'The task lands in the todo column and does NOT run; call task_board_run to execute it.',
|
|
504
|
-
'Triggers: 任务看板, task board, 看板, 新建任务, create task, 子任务, subtask, 定时任务.',
|
|
505
518
|
].join(' '),
|
|
506
519
|
parameters: {
|
|
507
520
|
title: { type: 'string', required: true, description: 'Short display title (one line).' },
|
|
508
521
|
description: { type: 'string', description: 'Longer human description shown in the detail view.' },
|
|
509
522
|
prompt: { type: 'string', description: 'The instruction sent to the execution agent; the title is used when blank.' },
|
|
510
523
|
parentId: { type: 'string', description: 'Parent task id, making this a subtask. Omit for a root task.' },
|
|
511
|
-
workspaceId: { type: 'string', description: 'Workspace id the execution must run in; omit to inherit the
|
|
524
|
+
workspaceId: { type: 'string', description: 'Workspace id the execution must run in; omit to inherit the workspace this session is in (root task) or the parent value (subtask).' },
|
|
512
525
|
mode: { type: 'string', description: 'Agent preset id the execution session is composed from; omit for the deployment default or the parent value.' },
|
|
513
526
|
permission: { type: 'string', enum: [...TASK_PERMISSIONS], description: 'Permission preset for the execution session. Omit to inherit the parent binding; a value above the session default needs a human confirmation in the board UI before the card can run.' },
|
|
514
527
|
model: { type: 'string', description: 'Pinned model as provider/model (or a model id); omit for the host default or the parent value.' },
|
|
515
528
|
reuseSession: { type: 'boolean', description: 'Continue later runs in the previous execution session instead of a fresh conversation.' },
|
|
516
529
|
teamRun: { type: 'boolean', description: 'Run this task as an Agent Team: running it starts one Team Lead session and the Host spawns a teammate per subtask inside it. Omit or false for one independent session per member. Refused when the deployment serves no Agent Teams service.' },
|
|
530
|
+
goalRun: { type: 'boolean', description: 'Start each run with dsh built-in /goal so the session keeps working continuation rounds until the goal completes. Default true (omit to keep it); pass false for a single plain turn.' },
|
|
531
|
+
skipVerification: { type: 'boolean', description: 'Run this task without the board acceptance gate: update_goal may mark the goal complete without a passing acceptance, and the execution record is marked skipped. Default false (omit to inherit the board-wide acceptance switch). Only affects executions started afterwards.' },
|
|
517
532
|
tags: {
|
|
518
533
|
type: 'array',
|
|
519
534
|
description: 'Labels: the name renders as a badge and drives the board filter; promptPrefix is injected ahead of the execution prompt on every run.',
|
|
@@ -555,11 +570,13 @@ function buildCreateTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
555
570
|
...(args.model === undefined || args.model === '' ? {} : { model: args.model }),
|
|
556
571
|
...(args.reuseSession === true ? { reuseSession: true } : {}),
|
|
557
572
|
...(args.teamRun === true ? { teamRun: true } : {}),
|
|
573
|
+
...(args.goalRun === false ? { goalRun: false } : {}),
|
|
574
|
+
...(args.skipVerification === true ? { skipVerification: true } : {}),
|
|
558
575
|
...(tags.length === 0 ? {} : { tags }),
|
|
559
576
|
...(args.schedule === undefined ? {} : { schedule: { enabled: args.schedule.enabled, cron: args.schedule.cron } }),
|
|
560
577
|
}
|
|
561
578
|
try {
|
|
562
|
-
const snapshot = host.apply(crypto.randomUUID(), { kind: 'create', id, input }, callingSessionId(exec))
|
|
579
|
+
const snapshot = await host.apply(crypto.randomUUID(), { kind: 'create', id, input }, callingSessionId(exec))
|
|
563
580
|
const task = snapshot.tasks.find(item => item.id === id)
|
|
564
581
|
if (task === undefined) return refused('create-failed', 'the Host did not confirm the new task')
|
|
565
582
|
return json({ ok: true, task: taskDetail(task, snapshot.tasks, snapshot.sessionDefaultPermission) })
|
|
@@ -578,7 +595,6 @@ function buildUpdateTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
578
595
|
'Only the fields you pass change; pass an empty string to clear a target, or an empty labels array to clear the labels.',
|
|
579
596
|
'Title, description and prompt freeze once the task has started executing; the execution targets stay editable because they only affect future runs.',
|
|
580
597
|
'Changing the pinned permission re-arms the permission confirmation gate.',
|
|
581
|
-
'Triggers: 任务看板, task board, 看板, 修改任务, edit task, 子任务, 标签, tags.',
|
|
582
598
|
].join(' '),
|
|
583
599
|
parameters: {
|
|
584
600
|
taskId: { type: 'string', required: true, description: 'Task id to edit.' },
|
|
@@ -587,10 +603,26 @@ function buildUpdateTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
587
603
|
prompt: { type: 'string', description: 'New execution prompt.' },
|
|
588
604
|
workspaceId: { type: 'string', description: 'New workspace id; an empty string clears it.' },
|
|
589
605
|
mode: { type: 'string', description: 'New agent preset id; an empty string clears it.' },
|
|
590
|
-
|
|
606
|
+
// `enum` with a clearing value would advertise the empty string as a
|
|
607
|
+
// legal member of the enum. Some OpenAI-compatible gateways forward the
|
|
608
|
+
// tool schema to Gemini, which rejects an empty enum member outright
|
|
609
|
+
// ("cannot be empty") and fails the whole request - not just this tool -
|
|
610
|
+
// even for a plain greeting, because the schema ships with every call
|
|
611
|
+
// (issue #1748). Splitting the clear value into its own `const` branch
|
|
612
|
+
// keeps both the real enum intact and the wire representation valid for
|
|
613
|
+
// those gateways.
|
|
614
|
+
permission: {
|
|
615
|
+
oneOf: [
|
|
616
|
+
{ type: 'string', enum: [...TASK_PERMISSIONS] },
|
|
617
|
+
{ type: 'string', const: '' },
|
|
618
|
+
],
|
|
619
|
+
description: 'New permission preset; an empty string clears it back to the session default.',
|
|
620
|
+
},
|
|
591
621
|
model: { type: 'string', description: 'New pinned model; an empty string clears it.' },
|
|
592
622
|
reuseSession: { type: 'boolean', description: 'Continue later runs in the previous execution session.' },
|
|
593
623
|
teamRun: { type: 'boolean', description: 'Switches this task between a plain cascade (one session per member) and an Agent Team run (Lead session plus a teammate per subtask).' },
|
|
624
|
+
goalRun: { type: 'boolean', description: 'Whether each run starts with dsh built-in /goal (default true). Pass false to run one plain turn instead.' },
|
|
625
|
+
skipVerification: { type: 'boolean', description: 'Whether this task runs without the board acceptance gate (default false, inherit the board-wide switch). Pass true to opt out; pass false to opt back in. Only affects executions started afterwards.' },
|
|
594
626
|
tags: {
|
|
595
627
|
type: 'array',
|
|
596
628
|
description: 'Replacement label set; an empty array clears all labels.',
|
|
@@ -619,6 +651,8 @@ function buildUpdateTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
619
651
|
}
|
|
620
652
|
if (has(args, 'reuseSession')) patch.reuseSession = args.reuseSession === true
|
|
621
653
|
if (has(args, 'teamRun')) patch.teamRun = args.teamRun === true
|
|
654
|
+
if (has(args, 'goalRun')) patch.goalRun = args.goalRun === false ? false : true
|
|
655
|
+
if (has(args, 'skipVerification')) patch.skipVerification = args.skipVerification === true ? true : false
|
|
622
656
|
if (has(args, 'tags')) {
|
|
623
657
|
const tags = args.tags ?? []
|
|
624
658
|
patch.tags = tags.length === 0
|
|
@@ -630,7 +664,7 @@ function buildUpdateTool(host: TaskBoardToolHost): ToolDefinition {
|
|
|
630
664
|
}
|
|
631
665
|
if (Object.keys(patch).length === 0) return refused('nothing-to-change', 'pass at least one field to update')
|
|
632
666
|
try {
|
|
633
|
-
const snapshot = host.apply(crypto.randomUUID(), { kind: 'update', taskId: args.taskId, patch }, callingSessionId(exec))
|
|
667
|
+
const snapshot = await host.apply(crypto.randomUUID(), { kind: 'update', taskId: args.taskId, patch }, callingSessionId(exec))
|
|
634
668
|
const task = snapshot.tasks.find(item => item.id === args.taskId)
|
|
635
669
|
if (task === undefined) return refused('task-not-found', 'no task with id ' + args.taskId)
|
|
636
670
|
return json({ ok: true, task: taskDetail(task, snapshot.tasks, snapshot.sessionDefaultPermission) })
|