@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,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-observed workspace changes, as acceptance evidence.
|
|
3
|
+
*
|
|
4
|
+
* The judge must not take the agent's word for what changed on disk: a patch
|
|
5
|
+
* that was never applied, or a file edited again afterwards, looks identical to
|
|
6
|
+
* a real edit when only the tool-call arguments are read. The DSH host records
|
|
7
|
+
* what a turn actually changed and serves it through the optional
|
|
8
|
+
* @@workspaceChanges@@ service, so that observation is offered next to the
|
|
9
|
+
* trajectory instead of being trusted from the transcript.
|
|
10
|
+
*
|
|
11
|
+
* The service is described STRUCTURALLY and resolved optionally: a deployment
|
|
12
|
+
* that serves none (or a cohort whose shape differs) degrades to "no extra
|
|
13
|
+
* evidence" and never breaks an acceptance that has already spent judge budget.
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from 'node:crypto'
|
|
16
|
+
|
|
17
|
+
/** The subset of the host change service this module drives. */
|
|
18
|
+
export interface WorkspaceChangeSource {
|
|
19
|
+
summary(sessionId: string, seq: number): unknown
|
|
20
|
+
diff(sessionId: string, seq: number, index: number, signal: AbortSignal): Promise<unknown>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One session event, as the host log carries it. */
|
|
24
|
+
interface RawEvent {
|
|
25
|
+
type?: unknown
|
|
26
|
+
seq?: unknown
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** How many changed files one evidence block renders. */
|
|
30
|
+
export const MAX_WORKSPACE_FILES = 8
|
|
31
|
+
/** Character ceiling for the whole rendered block. */
|
|
32
|
+
export const WORKSPACE_EVIDENCE_MAX_CHARS = 24_000
|
|
33
|
+
/** Character ceiling for one file's rendered comparison. */
|
|
34
|
+
export const WORKSPACE_EVIDENCE_MAX_FILE_CHARS = 6_000
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the optional host change service from a context-like object.
|
|
38
|
+
* @param ctx - anything exposing @@get(name)@@.
|
|
39
|
+
* @returns the service, or undefined when this deployment serves none.
|
|
40
|
+
*/
|
|
41
|
+
export function probeWorkspaceChanges(ctx: { get(name: string): unknown }): WorkspaceChangeSource | undefined {
|
|
42
|
+
let service: unknown
|
|
43
|
+
try {
|
|
44
|
+
service = ctx.get('workspaceChanges')
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
if (typeof service !== 'object' || service === null) return undefined
|
|
49
|
+
const candidate = service as { summary?: unknown; diff?: unknown }
|
|
50
|
+
if (typeof candidate.summary !== 'function' || typeof candidate.diff !== 'function') return undefined
|
|
51
|
+
return service as WorkspaceChangeSource
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Read a finite number, else 0. */
|
|
55
|
+
function count(value: unknown): number {
|
|
56
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Read a string, else ''. */
|
|
60
|
+
function text(value: unknown): string {
|
|
61
|
+
return typeof value === 'string' ? value : ''
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Bound one text to a character budget, keeping the head. */
|
|
65
|
+
function bound(value: string, maxChars: number): string {
|
|
66
|
+
if (value.length <= maxChars) return value
|
|
67
|
+
const notice = '\n[Truncated ' + (value.length - maxChars) + ' characters]'
|
|
68
|
+
if (notice.length >= maxChars) return value.slice(0, maxChars)
|
|
69
|
+
return value.slice(0, maxChars - notice.length) + notice
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The sequence of the newest change event inside the reviewed window. */
|
|
73
|
+
export function latestChangeEvent(events: readonly RawEvent[]): { seq: number; turn?: number } | undefined {
|
|
74
|
+
let latest: { seq: number; turn?: number } | undefined
|
|
75
|
+
for (const event of events) {
|
|
76
|
+
if (event.type !== 'workspace/changes') continue
|
|
77
|
+
if (typeof event.seq !== 'number') continue
|
|
78
|
+
if (latest === undefined || event.seq >= latest.seq) latest = { seq: event.seq }
|
|
79
|
+
}
|
|
80
|
+
return latest
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Render one file's comparison from whatever shape the service answered. */
|
|
84
|
+
function renderDiff(value: unknown, maxChars: number): string {
|
|
85
|
+
if (typeof value !== 'object' || value === null) return '[no comparison available]'
|
|
86
|
+
const diff = value as Record<string, unknown>
|
|
87
|
+
if (diff.binary === true || text(diff.kind) === 'binary') return '[binary file]'
|
|
88
|
+
if (diff.oversized === true) return '[file too large to compare]'
|
|
89
|
+
const patch = text(diff.patch)
|
|
90
|
+
if (patch !== '') return bound(patch, maxChars)
|
|
91
|
+
const hunks = Array.isArray(diff.hunks) ? diff.hunks : []
|
|
92
|
+
const lines: string[] = []
|
|
93
|
+
for (const entry of hunks) {
|
|
94
|
+
if (typeof entry !== 'object' || entry === null) continue
|
|
95
|
+
const hunk = entry as Record<string, unknown>
|
|
96
|
+
lines.push('@@ -' + count(hunk.oldStart) + ',' + count(hunk.oldLines) + ' +' + count(hunk.newStart) + ',' + count(hunk.newLines) + ' @@')
|
|
97
|
+
const body = Array.isArray(hunk.lines) ? hunk.lines : []
|
|
98
|
+
for (const line of body) {
|
|
99
|
+
if (typeof line === 'string') lines.push(line)
|
|
100
|
+
else if (typeof line === 'object' && line !== null) {
|
|
101
|
+
const row = line as Record<string, unknown>
|
|
102
|
+
const marker = text(row.kind) === 'add' ? '+' : text(row.kind) === 'delete' ? '-' : ' '
|
|
103
|
+
lines.push(marker + text(row.text))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return lines.length === 0 ? '' : bound(lines.join('\n'), maxChars)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Render the host's own record of what the reviewed window changed.
|
|
112
|
+
*
|
|
113
|
+
* Bounded twice — at most {@link MAX_WORKSPACE_FILES} files, and a shared
|
|
114
|
+
* character budget split across them — so one enormous generated file cannot
|
|
115
|
+
* crowd out every other change. Every failure degrades to less evidence (or an
|
|
116
|
+
* empty block) instead of an error, because the acceptance being prepared must
|
|
117
|
+
* still reach a verdict.
|
|
118
|
+
* @param events - session events already bounded to the execution's window.
|
|
119
|
+
* @param sessionId - the session whose changes these are.
|
|
120
|
+
* @param source - the probed host service.
|
|
121
|
+
* @param options - budget, cancellation and the diagnostic sink.
|
|
122
|
+
* @returns the rendered block plus how many files it shows.
|
|
123
|
+
*/
|
|
124
|
+
export async function renderWorkspaceEvidence(
|
|
125
|
+
events: readonly RawEvent[],
|
|
126
|
+
sessionId: string,
|
|
127
|
+
source: WorkspaceChangeSource,
|
|
128
|
+
options: { signal: AbortSignal; warn?: (message: string) => void; maxChars?: number },
|
|
129
|
+
): Promise<{ text: string; files: number; hash: string }> {
|
|
130
|
+
const warn = options.warn ?? ((): void => {})
|
|
131
|
+
const maxChars = options.maxChars ?? WORKSPACE_EVIDENCE_MAX_CHARS
|
|
132
|
+
try {
|
|
133
|
+
const event = latestChangeEvent(events)
|
|
134
|
+
if (event === undefined) return { text: '', files: 0, hash: '' }
|
|
135
|
+
const summary = source.summary(sessionId, event.seq)
|
|
136
|
+
if (typeof summary !== 'object' || summary === null) return { text: '', files: 0, hash: '' }
|
|
137
|
+
const view = summary as Record<string, unknown>
|
|
138
|
+
const listed = (Array.isArray(view.files) ? view.files : [])
|
|
139
|
+
.map((file, index) => ({ file, index }))
|
|
140
|
+
.filter((entry): entry is { file: Record<string, unknown>, index: number } => typeof entry.file === 'object' && entry.file !== null)
|
|
141
|
+
if (listed.length === 0) return { text: '', files: 0, hash: '' }
|
|
142
|
+
const shown = listed.slice(0, MAX_WORKSPACE_FILES)
|
|
143
|
+
const added = count(view.added)
|
|
144
|
+
const deleted = count(view.deleted)
|
|
145
|
+
const header = 'Host-recorded workspace changes'
|
|
146
|
+
+ (typeof view.turn === 'number' ? ' for turn ' + view.turn : '')
|
|
147
|
+
+ ': ' + listed.length + ' changed file(s)'
|
|
148
|
+
+ (added > 0 || deleted > 0 ? ' (+' + added + '/-' + deleted + ' lines)' : '')
|
|
149
|
+
+ (listed.length > shown.length ? '; showing the first ' + shown.length : '')
|
|
150
|
+
+ '.\nObserved by the DSH host from the workspace itself, not reported by the agent.'
|
|
151
|
+
const perFile = Math.max(1, Math.min(WORKSPACE_EVIDENCE_MAX_FILE_CHARS, Math.floor((maxChars - header.length) / shown.length) - 40))
|
|
152
|
+
const sections: string[] = []
|
|
153
|
+
for (const entry of shown) {
|
|
154
|
+
const path = text(entry.file.path) !== '' ? text(entry.file.path) : text(entry.file.display) !== '' ? text(entry.file.display) : 'file'
|
|
155
|
+
const fileAdded = count(entry.file.added)
|
|
156
|
+
const fileDeleted = count(entry.file.deleted)
|
|
157
|
+
let comparison = ''
|
|
158
|
+
try {
|
|
159
|
+
comparison = renderDiff(await source.diff(sessionId, event.seq, entry.index, options.signal), perFile)
|
|
160
|
+
} catch (error) {
|
|
161
|
+
comparison = '[comparison unavailable]'
|
|
162
|
+
warn('task-board workspace evidence unavailable for one file: ' + (error instanceof Error ? error.message : String(error)))
|
|
163
|
+
}
|
|
164
|
+
sections.push('- ' + path + (fileAdded > 0 || fileDeleted > 0 ? ' (+' + fileAdded + '/-' + fileDeleted + ')' : '') + '\n' + comparison)
|
|
165
|
+
}
|
|
166
|
+
const rendered = bound([header, ...sections].join('\n'), maxChars)
|
|
167
|
+
return {
|
|
168
|
+
text: rendered,
|
|
169
|
+
files: shown.length,
|
|
170
|
+
hash: createHash('sha256').update(rendered).digest('hex'),
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
warn('task-board workspace evidence unavailable: ' + (error instanceof Error ? error.message : String(error)))
|
|
174
|
+
return { text: '', files: 0, hash: '' }
|
|
175
|
+
}
|
|
176
|
+
}
|
package/src/host-ai.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* stripped, the first JSON object wins, and an unusable reply falls back to
|
|
10
10
|
* the user's own words instead of an empty form.
|
|
11
11
|
*/
|
|
12
|
-
import { createUserMessage, type
|
|
12
|
+
import { createUserMessage, type LlmRuntime } from '@deepseek-ai/dsh-llm'
|
|
13
|
+
import { openOneShotStream } from './host/llm-dispatch.ts'
|
|
13
14
|
import type { TaskBoardParseDraft, TaskBoardParseRequest } from './protocol.ts'
|
|
14
15
|
|
|
15
16
|
/** How long one parse may take before the Host gives up on the model. */
|
|
@@ -127,15 +128,20 @@ export async function parseTaskDraft(
|
|
|
127
128
|
const abortFromCaller = (): void => { timeout.abort() }
|
|
128
129
|
signal?.addEventListener('abort', abortFromCaller, { once: true })
|
|
129
130
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
// Same registration-bound dispatch as the acceptance judge: the public
|
|
132
|
+
// `llm.stream()` is a mutable instance property a third-party provider
|
|
133
|
+
// plugin may replace with its `llm/stream` listener signature.
|
|
134
|
+
const stream = await openOneShotStream(
|
|
135
|
+
llm,
|
|
136
|
+
{ provider: route.provider, model: route.model },
|
|
137
|
+
{
|
|
138
|
+
system: SYSTEM_PROMPT,
|
|
139
|
+
messages: [createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } })],
|
|
140
|
+
signal: timeout.signal,
|
|
141
|
+
},
|
|
142
|
+
)
|
|
137
143
|
let reply = ''
|
|
138
|
-
for await (const chunk of
|
|
144
|
+
for await (const chunk of stream) {
|
|
139
145
|
if (chunk.type === 'text-delta') reply += chunk.text
|
|
140
146
|
}
|
|
141
147
|
return draftFromReply(reply, text)
|