@fonlan/dsh-task-kanban 0.1.1 → 0.2.0
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/lib/client.js +90 -54
- package/lib/client.js.map +1 -1
- package/lib/index.js +78 -5
- package/lib/types/server/runner.d.ts +21 -0
- package/package.json +1 -1
- package/src/client/board.css +46 -10
- package/src/client/board.tsx +65 -22
- package/src/client/locales.ts +8 -2
- package/src/server/runner.ts +100 -5
package/src/client/board.css
CHANGED
|
@@ -21,14 +21,20 @@
|
|
|
21
21
|
.kb-board-header {
|
|
22
22
|
display: flex;
|
|
23
23
|
align-items: center;
|
|
24
|
-
justify-content: space-between;
|
|
25
24
|
gap: 12px;
|
|
26
25
|
padding-bottom: 10px;
|
|
27
26
|
border-bottom: 1px solid rgba(128, 128, 128, 0.25);
|
|
28
27
|
flex: 0 0 auto;
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
.kb-board-
|
|
29
|
+
/* Left-aligned group: workspace selector + new-task button sit adjacent. */
|
|
30
|
+
.kb-board-title {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: 8px;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
flex: 0 1 auto;
|
|
36
|
+
}
|
|
37
|
+
.kb-board-title .kb-btn { flex: none; }
|
|
32
38
|
.kb-ws-select {
|
|
33
39
|
max-width: 320px;
|
|
34
40
|
min-width: 0;
|
|
@@ -302,16 +308,46 @@
|
|
|
302
308
|
.kb-status-error { background: rgba(239, 68, 68, 0.3) !important; color: #f87171; }
|
|
303
309
|
.kb-status-merged { background: rgba(34, 197, 94, 0.3) !important; }
|
|
304
310
|
.kb-plan-summary { font-size: 13px; margin-bottom: 10px; }
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
.kb-plan-
|
|
311
|
+
/* Phase cards: each plan phase is a card with an ordered number (kept from the
|
|
312
|
+
parent <ol>) and three labeled sections (goal / sessions / conclusion) split
|
|
313
|
+
by section titles and thin divider lines. */
|
|
314
|
+
.kb-plan-phases { margin: 0; padding-left: 24px; }
|
|
315
|
+
.kb-plan-phase {
|
|
316
|
+
margin-bottom: 10px;
|
|
317
|
+
padding: 8px 10px;
|
|
318
|
+
background: rgba(128, 128, 128, 0.08);
|
|
319
|
+
border: 1px solid rgba(128, 128, 128, 0.2);
|
|
320
|
+
border-radius: 8px;
|
|
321
|
+
}
|
|
322
|
+
.kb-plan-phase-head {
|
|
323
|
+
display: flex;
|
|
324
|
+
gap: 8px;
|
|
325
|
+
align-items: baseline;
|
|
326
|
+
padding-bottom: 6px;
|
|
327
|
+
margin-bottom: 6px;
|
|
328
|
+
border-bottom: 1px solid rgba(128, 128, 128, 0.16);
|
|
329
|
+
}
|
|
309
330
|
.kb-plan-phase-title { font-weight: 600; font-size: 13px; }
|
|
310
331
|
.kb-plan-phase-id { font-size: 11px; opacity: 0.5; }
|
|
311
|
-
.kb-plan-phase-
|
|
332
|
+
.kb-plan-phase-body { display: flex; flex-direction: column; }
|
|
333
|
+
/* Section isolation: label on top, thin divider between adjacent sections. */
|
|
334
|
+
.kb-plan-phase-section { display: flex; flex-direction: column; }
|
|
335
|
+
.kb-plan-phase-section + .kb-plan-phase-section {
|
|
336
|
+
margin-top: 6px;
|
|
337
|
+
padding-top: 6px;
|
|
338
|
+
border-top: 1px solid rgba(128, 128, 128, 0.14);
|
|
339
|
+
}
|
|
340
|
+
.kb-plan-phase-section-title { font-size: 11px; opacity: 0.55; margin-bottom: 3px; }
|
|
341
|
+
.kb-plan-phase-goal { font-size: 12px; opacity: 0.85; white-space: pre-wrap; word-break: break-all; }
|
|
342
|
+
.kb-plan-phase-sessions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
|
343
|
+
.kb-plan-phase-conclusion { font-size: 12px; opacity: 0.85; white-space: pre-wrap; word-break: break-all; }
|
|
344
|
+
.kb-plan-phase-conclusion-item + .kb-plan-phase-conclusion-item { margin-top: 6px; }
|
|
345
|
+
/* Running phase keeps the blue highlight; completed phases get success color. */
|
|
346
|
+
.kb-plan-phase-current { border-color: rgba(59, 130, 246, 0.6); background: rgba(59, 130, 246, 0.08); }
|
|
347
|
+
.kb-plan-phase-current .kb-plan-phase-title { color: #60a5fa; }
|
|
348
|
+
.kb-plan-phase-completed { border-color: rgba(34, 197, 94, 0.45); background: rgba(34, 197, 94, 0.06); }
|
|
349
|
+
.kb-plan-phase-completed .kb-plan-phase-title { color: #4ade80; }
|
|
312
350
|
.kb-session-links { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
|
313
|
-
.kb-session-attempt { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
|
314
|
-
.kb-session-summary { font-size: 12px; opacity: 0.75; }
|
|
315
351
|
.kb-settings { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }
|
|
316
352
|
.kb-settings-saved { font-size: 12px; color: #4ade80; }
|
|
317
353
|
.kb-footer-action {
|
package/src/client/board.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
import type { KanbanCard, Lane } from '../shared/card'
|
|
2
|
+
import type { KanbanCard, Lane, PhaseAttempt, PlanPhase } from '../shared/card'
|
|
3
3
|
import { DROP_RULES, laneOf } from '../shared/lanes'
|
|
4
4
|
import { api, gatewaySkillList, type SkillOption } from './api'
|
|
5
5
|
import { exitBoard, getClient } from './kanban-state'
|
|
@@ -189,8 +189,6 @@ export function BoardRoot(props: BoardProps): JSX.Element {
|
|
|
189
189
|
<option key={w.workspaceId} value={w.path}>{w.title}</option>
|
|
190
190
|
))}
|
|
191
191
|
</select>
|
|
192
|
-
</div>
|
|
193
|
-
<div className="kb-board-actions">
|
|
194
192
|
<button type="button" className="kb-btn kb-btn-primary" onClick={() => setNewTaskOpen(true)}>{t('newTask')}</button>
|
|
195
193
|
</div>
|
|
196
194
|
</div>
|
|
@@ -564,14 +562,14 @@ function DetailPanel({ card, t, onClose, onChanged, onToast, onOpenSession }: De
|
|
|
564
562
|
<div className="kb-plan-summary">{card.plan.summary}</div>
|
|
565
563
|
<ol className="kb-plan-phases">
|
|
566
564
|
{card.plan.phases.map((phase, i) => (
|
|
567
|
-
<
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
565
|
+
<PhaseCard
|
|
566
|
+
key={phase.id}
|
|
567
|
+
phase={phase}
|
|
568
|
+
phaseIndex={i}
|
|
569
|
+
card={card}
|
|
570
|
+
t={t}
|
|
571
|
+
onOpenSession={onOpenSession}
|
|
572
|
+
/>
|
|
575
573
|
))}
|
|
576
574
|
</ol>
|
|
577
575
|
</div>
|
|
@@ -628,23 +626,68 @@ function DetailPanel({ card, t, onClose, onChanged, onToast, onOpenSession }: De
|
|
|
628
626
|
)
|
|
629
627
|
}
|
|
630
628
|
|
|
631
|
-
|
|
629
|
+
/** One plan phase rendered as a card with goal / sessions / conclusion sections. */
|
|
630
|
+
function PhaseCard({ phase, phaseIndex, card, t, onOpenSession }: {
|
|
631
|
+
phase: PlanPhase
|
|
632
632
|
phaseIndex: number
|
|
633
633
|
card: KanbanCard
|
|
634
|
-
t: (key: string) => string
|
|
634
|
+
t: (key: string, params?: Record<string, unknown>) => string
|
|
635
635
|
onOpenSession: (sessionId: string) => void
|
|
636
|
-
}): JSX.Element
|
|
636
|
+
}): JSX.Element {
|
|
637
637
|
const attempts = card.sessions.phases.filter((a) => a.phaseIndex === phaseIndex)
|
|
638
|
-
|
|
638
|
+
const summaries = attempts
|
|
639
|
+
.filter((a) => a.summary !== undefined && a.summary.trim() !== '')
|
|
640
|
+
.map((a) => a.summary as string)
|
|
641
|
+
const isCurrent = phaseIndex === card.currentPhase && card.status === 'running'
|
|
642
|
+
// A phase with at least one concluded attempt counts as completed.
|
|
643
|
+
const isCompleted = summaries.length > 0
|
|
644
|
+
const cls = 'kb-plan-phase'
|
|
645
|
+
+ (isCurrent ? ' kb-plan-phase-current' : '')
|
|
646
|
+
+ (isCompleted ? ' kb-plan-phase-completed' : '')
|
|
647
|
+
return (
|
|
648
|
+
<li className={cls}>
|
|
649
|
+
<div className="kb-plan-phase-head">
|
|
650
|
+
<span className="kb-plan-phase-title">{phase.title}</span>
|
|
651
|
+
<span className="kb-plan-phase-id">{phase.id}</span>
|
|
652
|
+
</div>
|
|
653
|
+
<div className="kb-plan-phase-body">
|
|
654
|
+
<div className="kb-plan-phase-section">
|
|
655
|
+
<div className="kb-plan-phase-section-title">{t('phaseGoal')}</div>
|
|
656
|
+
<div className="kb-plan-phase-goal">{phase.goal}</div>
|
|
657
|
+
</div>
|
|
658
|
+
{attempts.length > 0 && (
|
|
659
|
+
<div className="kb-plan-phase-section">
|
|
660
|
+
<div className="kb-plan-phase-section-title">{t('phaseSessions')}</div>
|
|
661
|
+
<PhaseSessions attempts={attempts} t={t} onOpenSession={onOpenSession} />
|
|
662
|
+
</div>
|
|
663
|
+
)}
|
|
664
|
+
{summaries.length > 0 && (
|
|
665
|
+
<div className="kb-plan-phase-section">
|
|
666
|
+
<div className="kb-plan-phase-section-title">{t('phaseConclusion')}</div>
|
|
667
|
+
<div className="kb-plan-phase-conclusion">
|
|
668
|
+
{summaries.map((s, idx) => (
|
|
669
|
+
<div key={idx} className="kb-plan-phase-conclusion-item">{s}</div>
|
|
670
|
+
))}
|
|
671
|
+
</div>
|
|
672
|
+
</div>
|
|
673
|
+
)}
|
|
674
|
+
</div>
|
|
675
|
+
</li>
|
|
676
|
+
)
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/** Open-session buttons for one phase's attempts (retries append more). */
|
|
680
|
+
function PhaseSessions({ attempts, t, onOpenSession }: {
|
|
681
|
+
attempts: PhaseAttempt[]
|
|
682
|
+
t: (key: string) => string
|
|
683
|
+
onOpenSession: (sessionId: string) => void
|
|
684
|
+
}): JSX.Element {
|
|
639
685
|
return (
|
|
640
|
-
<div className="kb-
|
|
686
|
+
<div className="kb-plan-phase-sessions">
|
|
641
687
|
{attempts.map((a) => (
|
|
642
|
-
<
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
</button>
|
|
646
|
-
{a.summary !== undefined && <span className="kb-session-summary">{a.summary}</span>}
|
|
647
|
-
</span>
|
|
688
|
+
<button key={a.sessionId} type="button" className="kb-btn kb-btn-small" onClick={() => onOpenSession(a.sessionId)}>
|
|
689
|
+
{t('openSession')} · {a.sessionId.slice(0, 13)}
|
|
690
|
+
</button>
|
|
648
691
|
))}
|
|
649
692
|
</div>
|
|
650
693
|
)
|
package/src/client/locales.ts
CHANGED
|
@@ -38,6 +38,9 @@ export const zh = {
|
|
|
38
38
|
plan: '实现方案',
|
|
39
39
|
summary: '摘要',
|
|
40
40
|
phases: '阶段',
|
|
41
|
+
phaseGoal: '目标',
|
|
42
|
+
phaseSessions: '会话',
|
|
43
|
+
phaseConclusion: '结论',
|
|
41
44
|
error: '错误',
|
|
42
45
|
requirementLabel: '需求文本',
|
|
43
46
|
requirementPlaceholder: '描述你要实现的需求……',
|
|
@@ -48,7 +51,7 @@ export const zh = {
|
|
|
48
51
|
modelPlaceholder: '(使用默认模型)',
|
|
49
52
|
noWorkspace: '没有可用工作区,请先在侧边栏添加',
|
|
50
53
|
refresh: '刷新',
|
|
51
|
-
settingsTitle: '
|
|
54
|
+
settingsTitle: '任务看板',
|
|
52
55
|
maxParallelWorkers: '全局并行 worker 数',
|
|
53
56
|
defaultModel: '默认模型',
|
|
54
57
|
saved: '已保存',
|
|
@@ -103,6 +106,9 @@ export const en = {
|
|
|
103
106
|
plan: 'Plan',
|
|
104
107
|
summary: 'Summary',
|
|
105
108
|
phases: 'Phases',
|
|
109
|
+
phaseGoal: 'Goal',
|
|
110
|
+
phaseSessions: 'Sessions',
|
|
111
|
+
phaseConclusion: 'Conclusion',
|
|
106
112
|
error: 'Error',
|
|
107
113
|
requirementLabel: 'Requirement',
|
|
108
114
|
requirementPlaceholder: 'Describe what you want to build…',
|
|
@@ -113,7 +119,7 @@ export const en = {
|
|
|
113
119
|
modelPlaceholder: '(use default model)',
|
|
114
120
|
noWorkspace: 'No workspaces available — add one in the sidebar first',
|
|
115
121
|
refresh: 'Refresh',
|
|
116
|
-
settingsTitle: 'Task Kanban
|
|
122
|
+
settingsTitle: 'Task Kanban',
|
|
117
123
|
maxParallelWorkers: 'Global parallel workers',
|
|
118
124
|
defaultModel: 'Default model',
|
|
119
125
|
saved: 'Saved',
|
package/src/server/runner.ts
CHANGED
|
@@ -49,6 +49,11 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
49
49
|
private merging = new Set<string>()
|
|
50
50
|
/** Retained agent handles (kept so ended sessions stay in the store/UI). */
|
|
51
51
|
private handles = new Map<string, AgentHandle[]>()
|
|
52
|
+
/** Whether startup recovery + session indexing ran (deferred until workspaces exist). */
|
|
53
|
+
private recovered = false
|
|
54
|
+
/** Agents that already got their scoped kanban tools (event idempotency). */
|
|
55
|
+
private toolScopedAgents = new WeakSet<object>()
|
|
56
|
+
private agentStartOff: (() => void) | undefined
|
|
52
57
|
|
|
53
58
|
constructor(ctx: Context, store: TaskStore, settings: KanbanSettingsFace) {
|
|
54
59
|
this.ctx = ctx
|
|
@@ -62,10 +67,22 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
62
67
|
|
|
63
68
|
start(): void {
|
|
64
69
|
this.pumpTimer = setInterval(() => { void this.pump() }, 2500)
|
|
70
|
+
// `agent/session-start` fires for BOTH agents.create (startup) and
|
|
71
|
+
// agents.resume (host re-open after a restart). Kanban tools are scoped
|
|
72
|
+
// per agent, so a resumed refinement/phase/merge session would otherwise
|
|
73
|
+
// lose them — register there instead of only in createAgent's setup.
|
|
74
|
+
this.agentStartOff = this.ctx.on('agent/session-start', (payload: { agent?: { id?: string; ctx?: Context } }) => {
|
|
75
|
+
void this.onAgentSessionStart(payload.agent)
|
|
76
|
+
})
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
stop(): void {
|
|
68
80
|
if (this.pumpTimer !== undefined) clearInterval(this.pumpTimer)
|
|
81
|
+
this.pumpTimer = undefined
|
|
82
|
+
if (this.agentStartOff !== undefined) {
|
|
83
|
+
this.agentStartOff()
|
|
84
|
+
this.agentStartOff = undefined
|
|
85
|
+
}
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
private newSessionId(): SessionId {
|
|
@@ -157,6 +174,63 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
157
174
|
}
|
|
158
175
|
}
|
|
159
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Rebuild sessionId → cardId from the task store. Runs once the workspace
|
|
179
|
+
* registry is available (see pump), so kanban sessions resumed after a
|
|
180
|
+
* restart still resolve their card.
|
|
181
|
+
*/
|
|
182
|
+
private async indexSessions(): Promise<void> {
|
|
183
|
+
for (const ws of await this.workspaceList()) {
|
|
184
|
+
const cards = await this.store.list(ws.path)
|
|
185
|
+
for (const card of cards) this.mapCardSessions(card.id, card)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private mapCardSessions(cardId: string, card: KanbanCard): void {
|
|
190
|
+
for (const sid of card.sessions.refinement) this.sessionCards.set(sid, cardId)
|
|
191
|
+
for (const attempt of card.sessions.phases) this.sessionCards.set(attempt.sessionId, cardId)
|
|
192
|
+
for (const sid of card.sessions.merge) this.sessionCards.set(sid, cardId)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Fallback lookup when a session starts before indexSessions ran. */
|
|
196
|
+
private async lookupCardOfSession(sessionId: string): Promise<string | undefined> {
|
|
197
|
+
for (const ws of await this.workspaceList()) {
|
|
198
|
+
const cards = await this.store.list(ws.path)
|
|
199
|
+
for (const card of cards) {
|
|
200
|
+
const hit =
|
|
201
|
+
card.sessions.refinement.includes(sessionId) ||
|
|
202
|
+
card.sessions.phases.some((a) => a.sessionId === sessionId) ||
|
|
203
|
+
card.sessions.merge.includes(sessionId)
|
|
204
|
+
if (hit) {
|
|
205
|
+
this.mapCardSessions(card.id, card)
|
|
206
|
+
return card.id
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return undefined
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The single place kanban tools get registered. `agent/session-start` fires
|
|
215
|
+
* for BOTH `agents.create` (startup) and `agents.resume` (host re-open after
|
|
216
|
+
* a restart), so a resumed refinement/phase/merge session keeps its scoped
|
|
217
|
+
* tools. Idempotent per agent object; non-kanban sessions are skipped.
|
|
218
|
+
*/
|
|
219
|
+
private async onAgentSessionStart(agent: { id?: string; ctx?: Context } | undefined): Promise<void> {
|
|
220
|
+
if (agent === undefined || agent.id === undefined || agent.ctx === undefined) return
|
|
221
|
+
if (this.toolScopedAgents.has(agent)) return
|
|
222
|
+
let cardId = this.sessionCards.get(agent.id)
|
|
223
|
+
if (cardId === undefined) cardId = await this.lookupCardOfSession(agent.id)
|
|
224
|
+
if (cardId === undefined) return
|
|
225
|
+
this.sessionCards.set(agent.id, cardId)
|
|
226
|
+
try {
|
|
227
|
+
registerKanbanTools(agent.ctx, this.toolResolver())
|
|
228
|
+
this.toolScopedAgents.add(agent)
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('[task-kanban] register kanban tools for ' + agent.id + ' failed:', String(error))
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
160
234
|
// ── Card operations (RPC entry points) ────────────────────────────────────
|
|
161
235
|
|
|
162
236
|
async listCards(workspacePath: string): Promise<KanbanCard[]> {
|
|
@@ -287,6 +361,26 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
287
361
|
}
|
|
288
362
|
|
|
289
363
|
async pump(): Promise<void> {
|
|
364
|
+
// Recovery and session indexing need the workspace registry, which may
|
|
365
|
+
// not be populated when the plugin first applies (the old code silently
|
|
366
|
+
// no-opped and left interrupted cards stuck in refining). Retry on the
|
|
367
|
+
// pump tick until the first workspace appears.
|
|
368
|
+
if (!this.recovered) {
|
|
369
|
+
const ws = await this.workspaceList()
|
|
370
|
+
if (ws.length > 0) {
|
|
371
|
+
this.recovered = true
|
|
372
|
+
try {
|
|
373
|
+
await this.recoverInterrupted()
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error('[task-kanban] recoverInterrupted failed:', String(error))
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
await this.indexSessions()
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('[task-kanban] indexSessions failed:', String(error))
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
290
384
|
if (this.slots >= this.maxParallel()) return
|
|
291
385
|
const workspaces = await this.workspaceList()
|
|
292
386
|
for (const ws of workspaces) {
|
|
@@ -340,7 +434,7 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
340
434
|
c.sessions.refinement.push(sessionId)
|
|
341
435
|
}, wsPath)
|
|
342
436
|
try {
|
|
343
|
-
const handle = await this.createAgent(sessionId, wsPath, route
|
|
437
|
+
const handle = await this.createAgent(sessionId, wsPath, route)
|
|
344
438
|
this.keepHandle(cardId, handle)
|
|
345
439
|
await this.attachRefinementSession(wsPath, sessionId)
|
|
346
440
|
// The agent scope is the layer where preset skill providers live
|
|
@@ -466,7 +560,6 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
466
560
|
sessionId: SessionId,
|
|
467
561
|
cwd: string,
|
|
468
562
|
route: { provider?: string; model?: string },
|
|
469
|
-
resolver: KanbanToolResolver,
|
|
470
563
|
) {
|
|
471
564
|
const agentOptions =
|
|
472
565
|
route.provider !== undefined && route.model !== undefined && route.model !== ''
|
|
@@ -476,6 +569,9 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
476
569
|
sessionId,
|
|
477
570
|
meta: { cwd },
|
|
478
571
|
...(agentOptions !== undefined ? { agentOptions } : {}),
|
|
572
|
+
// Kanban tools are NOT registered here: `agent/session-start` (see
|
|
573
|
+
// start()) fires for both create and resume, keeping them available
|
|
574
|
+
// even when the host re-opens a persisted kanban session.
|
|
479
575
|
setup: async (agentCtx) => {
|
|
480
576
|
const agentPresets = agentCtx.get('agentPresets') as
|
|
481
577
|
| { mount(agentCtx: unknown, id?: string): Promise<unknown> }
|
|
@@ -485,7 +581,6 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
485
581
|
} else {
|
|
486
582
|
throw new Error('@fonlan/dsh-task-kanban: agent-presets service is not mounted')
|
|
487
583
|
}
|
|
488
|
-
registerKanbanTools(agentCtx, resolver)
|
|
489
584
|
},
|
|
490
585
|
})
|
|
491
586
|
}
|
|
@@ -638,7 +733,7 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
638
733
|
}, wsPath)
|
|
639
734
|
const route = await this.modelRoute(cardId, wsPath)
|
|
640
735
|
try {
|
|
641
|
-
const handle = await this.createAgent(sessionId, workdir, route
|
|
736
|
+
const handle = await this.createAgent(sessionId, workdir, route)
|
|
642
737
|
this.keepHandle(cardId, handle)
|
|
643
738
|
entry.cancel = () => handle.agent.cancel({ kind: 'user' })
|
|
644
739
|
handle.agent.followup(this.asUserMessage(phasePrompt({ ...(await this.store.get(wsPath, cardId)) ?? { plan } as KanbanCard, plan } as KanbanCard, phaseIndex, workdir)))
|
|
@@ -891,7 +986,7 @@ export class KanbanRunner implements KanbanToolResolver {
|
|
|
891
986
|
await this.store.mutate(cardId, (c) => { c.sessions.merge.push(sessionId) }, wsPath)
|
|
892
987
|
const route = await this.modelRoute(cardId, wsPath)
|
|
893
988
|
try {
|
|
894
|
-
const handle = await this.createAgent(sessionId, workdir, route
|
|
989
|
+
const handle = await this.createAgent(sessionId, workdir, route)
|
|
895
990
|
this.keepHandle(cardId, handle)
|
|
896
991
|
handle.agent.followup(this.asUserMessage(mergePrompt(workdir, conflicts)))
|
|
897
992
|
await handle.agent.whenIdle()
|