@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.
Files changed (128) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +56 -18
  3. package/README.zh.md +56 -18
  4. package/lib/client.js +6242 -958
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +3475 -234
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +19 -3
  9. package/lib/types/client/board/ClampedMarkdown.d.ts +3 -0
  10. package/lib/types/client/board/ConfirmDialog.d.ts +4 -1
  11. package/lib/types/client/board/EditTaskModal.d.ts +5 -2
  12. package/lib/types/client/board/LinkSubtaskModal.d.ts +5 -2
  13. package/lib/types/client/board/NewTaskModal.d.ts +4 -1
  14. package/lib/types/client/board/TagManagerModal.d.ts +26 -0
  15. package/lib/types/client/board/TaskBoard.d.ts +10 -1
  16. package/lib/types/client/board/TaskCard.d.ts +14 -1
  17. package/lib/types/client/board/TaskDetail.d.ts +3 -1
  18. package/lib/types/client/board/TaskForm.d.ts +23 -2
  19. package/lib/types/client/board/VerificationReport.d.ts +23 -0
  20. package/lib/types/client/board/card-view.d.ts +113 -0
  21. package/lib/types/client/board/icons.d.ts +32 -0
  22. package/lib/types/client/board/overlay.d.ts +69 -0
  23. package/lib/types/client/board/preset-label.d.ts +22 -0
  24. package/lib/types/client/board/status-key.d.ts +15 -0
  25. package/lib/types/client/board/task-markdown.d.ts +19 -0
  26. package/lib/types/client/board-card-seat.d.ts +34 -0
  27. package/lib/types/client/host-api.d.ts +24 -1
  28. package/lib/types/client/index.d.ts +19 -0
  29. package/lib/types/client/locales.d.ts +141 -3
  30. package/lib/types/client/native-panel.d.ts +6 -20
  31. package/lib/types/client/schedule-zone.d.ts +47 -0
  32. package/lib/types/client/seats.d.ts +18 -0
  33. package/lib/types/client/service.d.ts +32 -0
  34. package/lib/types/client/settings-form.d.ts +16 -0
  35. package/lib/types/core/controller.d.ts +38 -1
  36. package/lib/types/core/degraded-reason.d.ts +46 -0
  37. package/lib/types/core/extension.d.ts +283 -0
  38. package/lib/types/core/handover.d.ts +13 -1
  39. package/lib/types/core/issue-form.d.ts +16 -0
  40. package/lib/types/core/package-name.d.ts +9 -0
  41. package/lib/types/core/schedule.d.ts +95 -20
  42. package/lib/types/core/subtask.d.ts +30 -11
  43. package/lib/types/core/tasks.d.ts +110 -5
  44. package/lib/types/core/use-cases/task-create.d.ts +2 -1
  45. package/lib/types/core/use-cases/task-schedule.d.ts +8 -4
  46. package/lib/types/core/use-cases/task-tag.d.ts +48 -0
  47. package/lib/types/core/use-cases/task-update.d.ts +5 -3
  48. package/lib/types/core/verification.d.ts +386 -0
  49. package/lib/types/core/workspace-target.d.ts +41 -0
  50. package/lib/types/host/agent-tools.d.ts +1 -1
  51. package/lib/types/host/extension-registry.d.ts +111 -0
  52. package/lib/types/host/llm-dispatch.d.ts +63 -0
  53. package/lib/types/host/verification-gate.d.ts +71 -0
  54. package/lib/types/host/verification-runner.d.ts +108 -0
  55. package/lib/types/host/workspace-evidence.d.ts +53 -0
  56. package/lib/types/host-ledger.d.ts +107 -11
  57. package/lib/types/host-runner.d.ts +99 -4
  58. package/lib/types/host-service.d.ts +114 -2
  59. package/lib/types/index.d.ts +80 -26
  60. package/lib/types/protocol.d.ts +53 -3
  61. package/lib/types/tool-surface.d.ts +71 -0
  62. package/package.json +27 -26
  63. package/src/client/PluginSettingsCard.tsx +9 -0
  64. package/src/client/TaskBoardSettingsCard.tsx +304 -65
  65. package/src/client/board/ClampedMarkdown.tsx +69 -0
  66. package/src/client/board/ConfirmDialog.tsx +7 -3
  67. package/src/client/board/EditTaskModal.tsx +5 -2
  68. package/src/client/board/LinkSubtaskModal.tsx +30 -15
  69. package/src/client/board/NewTaskModal.tsx +288 -151
  70. package/src/client/board/TagManagerModal.tsx +235 -0
  71. package/src/client/board/TaskBoard.tsx +259 -103
  72. package/src/client/board/TaskCard.tsx +238 -86
  73. package/src/client/board/TaskDetail.tsx +154 -59
  74. package/src/client/board/TaskForm.tsx +61 -6
  75. package/src/client/board/VerificationReport.tsx +203 -0
  76. package/src/client/board/card-view.ts +220 -0
  77. package/src/client/board/icons.tsx +89 -0
  78. package/src/client/board/overlay.tsx +189 -0
  79. package/src/client/board/preset-label.ts +44 -0
  80. package/src/client/board/status-key.ts +27 -0
  81. package/src/client/board/task-markdown.module.css +137 -0
  82. package/src/client/board/task-markdown.tsx +106 -0
  83. package/src/client/board-card-seat.ts +102 -0
  84. package/src/client/board-settings.module.css +66 -0
  85. package/src/client/board.module.css +602 -68
  86. package/src/client/host-api.ts +109 -6
  87. package/src/client/index.ts +48 -6
  88. package/src/client/locales.ts +290 -12
  89. package/src/client/native-panel.tsx +18 -2
  90. package/src/client/schedule-zone.ts +135 -0
  91. package/src/client/seats.tsx +58 -0
  92. package/src/client/service.ts +94 -0
  93. package/src/client/settings-card.module.css +31 -5
  94. package/src/client/settings-form.ts +27 -1
  95. package/src/core/controller.ts +101 -5
  96. package/src/core/degraded-reason.ts +81 -0
  97. package/src/core/extension.ts +376 -0
  98. package/src/core/freeze-snapshot.ts +2 -2
  99. package/src/core/handover.ts +16 -1
  100. package/src/core/issue-form.ts +19 -0
  101. package/src/core/package-name.ts +9 -0
  102. package/src/core/schedule.ts +211 -55
  103. package/src/core/store.ts +26 -1
  104. package/src/core/subtask.ts +77 -22
  105. package/src/core/tasks.ts +138 -10
  106. package/src/core/use-cases/task-create.ts +14 -2
  107. package/src/core/use-cases/task-parent.ts +3 -2
  108. package/src/core/use-cases/task-schedule.ts +17 -6
  109. package/src/core/use-cases/task-tag.ts +110 -0
  110. package/src/core/use-cases/task-update.ts +13 -4
  111. package/src/core/verification.ts +826 -0
  112. package/src/core/workspace-target.ts +59 -0
  113. package/src/host/agent-tools.ts +58 -24
  114. package/src/host/extension-registry.ts +509 -0
  115. package/src/host/llm-dispatch.ts +87 -0
  116. package/src/host/verification-gate.ts +331 -0
  117. package/src/host/verification-runner.ts +535 -0
  118. package/src/host/workspace-evidence.ts +176 -0
  119. package/src/host-ai.ts +15 -9
  120. package/src/host-ledger.ts +342 -51
  121. package/src/host-routes.ts +15 -2
  122. package/src/host-runner.ts +219 -20
  123. package/src/host-service.ts +346 -26
  124. package/src/index.ts +204 -16
  125. package/src/protocol.ts +122 -9
  126. package/src/tool-surface.ts +95 -0
  127. package/lib/types/host/run-guarded.d.ts +0 -39
  128. 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 GenerateOptions, type LlmRuntime } from '@deepseek-ai/dsh-llm'
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
- const options: GenerateOptions = {
131
- provider: route.provider,
132
- model: route.model,
133
- system: SYSTEM_PROMPT,
134
- messages: [createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } })],
135
- signal: timeout.signal,
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 llm.stream(options)) {
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)