dsh-taskboard 0.5.3 → 0.5.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.
@@ -16,7 +16,51 @@ import { MAX_CHECKLIST_ITEMS, defaultIsolationOf, nextCronTime, parseCron } from
16
16
  import { fmtTime } from './format.ts'
17
17
 
18
18
  /** One row of the configured model catalog (from llm.models). */
19
- export interface CatalogModel { provider: string; model: string; name?: string }
19
+ export interface CatalogModel {
20
+ provider: string
21
+ model: string
22
+ name?: string
23
+ reasoning?: {
24
+ efforts: Array<{ id: string; name: string; description?: string }>
25
+ defaultEffort?: string
26
+ }
27
+ }
28
+
29
+ /** Local storage key for remembering the last selected model in create mode. */
30
+ export const LAST_MODEL_KEY = 'dsh-taskboard-last-model-v1'
31
+
32
+ /** Read the remembered model from localStorage. */
33
+ export function loadLastModel(): { provider: string; model: string; reasoningEffort?: string } | undefined {
34
+ try {
35
+ const raw = localStorage.getItem(LAST_MODEL_KEY)
36
+ if (raw === null) return undefined
37
+ const parsed = JSON.parse(raw) as unknown
38
+ if (typeof parsed === 'object' && parsed !== null) {
39
+ const { provider, model, reasoningEffort } = parsed as { provider?: unknown; model?: unknown; reasoningEffort?: unknown }
40
+ if (typeof provider === 'string' && typeof model === 'string' && provider.trim().length > 0 && model.trim().length > 0) {
41
+ return {
42
+ provider: provider.trim(),
43
+ model: model.trim(),
44
+ ...(typeof reasoningEffort === 'string' && reasoningEffort.trim().length > 0 ? { reasoningEffort: reasoningEffort.trim() } : {}),
45
+ }
46
+ }
47
+ }
48
+ return undefined
49
+ } catch {
50
+ return undefined
51
+ }
52
+ }
53
+
54
+ /** Save the remembered model to localStorage. */
55
+ export function saveLastModel(model?: { provider: string; model: string; reasoningEffort?: string }): void {
56
+ try {
57
+ if (model === undefined) {
58
+ localStorage.removeItem(LAST_MODEL_KEY)
59
+ } else {
60
+ localStorage.setItem(LAST_MODEL_KEY, JSON.stringify(model))
61
+ }
62
+ } catch { /* storage unavailable */ }
63
+ }
20
64
 
21
65
  /** Urgency segmented options with a one-line hint each. */
22
66
  const URGENCY_OPTIONS: ReadonlyArray<{ value: Urgency; label: string; hint: string }> = [
@@ -126,7 +170,12 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
126
170
  const [mode, setMode] = useState<'claim' | 'scheduled'>(task?.execution.mode === 'scheduled' || prefill?.execution?.mode === 'scheduled' ? 'scheduled' : 'claim')
127
171
  const [cron, setCron] = useState(task?.execution.cron ?? prefill?.execution?.cron ?? '0 9 * * *')
128
172
  const [catalog, setCatalog] = useState<CatalogModel[]>([])
129
- const [model, setModel] = useState(task?.model !== undefined || prefill?.model !== undefined ? JSON.stringify(task?.model ?? prefill?.model) : '')
173
+
174
+ // Model & reasoning effort selection:
175
+ // In create mode (when not pinned by template), prefill from remembered last choice.
176
+ const initialModel = task?.model ?? prefill?.model ?? (!editing ? loadLastModel() : undefined)
177
+ const [model, setModel] = useState(initialModel !== undefined ? JSON.stringify({ provider: initialModel.provider, model: initialModel.model }) : '')
178
+ const [reasoningEffort, setReasoningEffort] = useState(initialModel?.reasoningEffort ?? '')
130
179
  // Preset roster (0.3.3): create mode PRE-SELECTS the deployment default
131
180
  // (标准模式 in this deployment); '' = 跟随部署默认 (submit omits the field).
132
181
  const initialPreset = task?.presetId ?? prefill?.presetId ?? ''
@@ -215,9 +264,24 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
215
264
  /** Checklist rows with non-empty text (blank rows are dropped on submit). */
216
265
  const filledRows = (): CheckRow[] => checkRows.map(r => ({ ...r, text: r.text.trim() })).filter(r => r.text.length > 0)
217
266
 
267
+ const parsedModel = model !== '' ? (JSON.parse(model) as { provider: string; model: string }) : undefined
268
+ const currentCatalogModel = parsedModel !== undefined ? catalog.find(m => m.provider === parsedModel.provider && m.model === parsedModel.model) : undefined
269
+ const modelReasoning = currentCatalogModel?.reasoning
270
+
271
+ const buildPickedModel = (): { provider: string; model: string; reasoningEffort?: string } | undefined => {
272
+ if (parsedModel === undefined) return undefined
273
+ const eff = reasoningEffort.trim()
274
+ return {
275
+ provider: parsedModel.provider,
276
+ model: parsedModel.model,
277
+ ...(eff.length > 0 ? { reasoningEffort: eff } : {}),
278
+ }
279
+ }
280
+
218
281
  const submit = (): void => {
219
282
  if (!valid || busy) return
220
- const picked = model !== '' ? (JSON.parse(model) as { provider: string; model: string }) : undefined
283
+ const picked = buildPickedModel()
284
+ if (!editing) saveLastModel(picked)
221
285
  const isolationOut = isolationPayload()
222
286
  const presetOut = presetPayload()
223
287
  const rows = filledRows()
@@ -255,7 +319,8 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
255
319
  /** Save the form, then immediately trigger a manual run of the task. */
256
320
  const submitAndRun = (): void => {
257
321
  if (!valid || runBlocked || busy) return
258
- const picked = model !== '' ? (JSON.parse(model) as { provider: string; model: string }) : undefined
322
+ const picked = buildPickedModel()
323
+ if (!editing) saveLastModel(picked)
259
324
  const isolationOut = isolationPayload()
260
325
  const presetOut = presetPayload()
261
326
  const rows = filledRows()
@@ -325,7 +390,24 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
325
390
  </Field>
326
391
 
327
392
  <Field label="模型(默认 = 会话默认模型)">
328
- <select value={model} onChange={e => setModel(e.target.value)}>
393
+ <select
394
+ value={model}
395
+ onChange={e => {
396
+ const val = e.target.value
397
+ setModel(val)
398
+ if (val === '') {
399
+ setReasoningEffort('')
400
+ } else {
401
+ const pm = JSON.parse(val) as { provider: string; model: string }
402
+ const cm = catalog.find(m => m.provider === pm.provider && m.model === pm.model)
403
+ if (cm?.reasoning?.defaultEffort !== undefined) {
404
+ setReasoningEffort(cm.reasoning.defaultEffort)
405
+ } else {
406
+ setReasoningEffort('')
407
+ }
408
+ }
409
+ }}
410
+ >
329
411
  <option value="">默认模型</option>
330
412
  {catalog.map(m => (
331
413
  <option key={`${m.provider}/${m.model}`} value={JSON.stringify({ provider: m.provider, model: m.model })}>
@@ -335,6 +417,32 @@ export function TaskFormModal({ controller, task }: { controller: BoardControlle
335
417
  </select>
336
418
  </Field>
337
419
 
420
+ {parsedModel !== undefined && (
421
+ <Field label="思考强度(Reasoning Effort)">
422
+ <select
423
+ value={reasoningEffort}
424
+ onChange={e => setReasoningEffort(e.target.value)}
425
+ title="设置模型的思考强度(如 low/medium/high);默认 = 跟随模型/提供商默认"
426
+ >
427
+ <option value="">跟随模型默认{modelReasoning?.defaultEffort !== undefined ? `(当前:${modelReasoning.efforts.find(ef => ef.id === modelReasoning.defaultEffort)?.name ?? modelReasoning.defaultEffort})` : ''}</option>
428
+ {modelReasoning !== undefined && modelReasoning.efforts.length > 0 ? (
429
+ modelReasoning.efforts.map(eff => (
430
+ <option key={eff.id} value={eff.id}>
431
+ {eff.name}{eff.description ? ` (${eff.description})` : ''}
432
+ </option>
433
+ ))
434
+ ) : (
435
+ <>
436
+ <option value="low">低 (low)</option>
437
+ <option value="medium">中 (medium)</option>
438
+ <option value="high">高 (high)</option>
439
+ <option value="none">关闭思考 (none)</option>
440
+ </>
441
+ )}
442
+ </select>
443
+ </Field>
444
+ )}
445
+
338
446
  {presets.length > 0 && (
339
447
  <Field label="执行模式(preset)">
340
448
  <select value={presetId} onChange={e => setPresetId(e.target.value)} title="执行会话按该 preset 组合(决定工具集与人设);默认 = 部署默认 preset">
@@ -484,7 +592,7 @@ interface TaskRecordLike {
484
592
  workspaceId: string
485
593
  urgency: Urgency
486
594
  execution: { mode: 'claim' | 'scheduled'; cron?: string }
487
- model?: { provider: string; model: string }
595
+ model?: { provider: string; model: string; reasoningEffort?: string }
488
596
  isolation?: IsolationMode
489
597
  presetId?: string
490
598
  checklist?: ChecklistItem[]
@@ -64,7 +64,10 @@ export function TemplateManager({ controller }: { controller: BoardController })
64
64
  if (e.key === 'Enter') save(t.id, nameOf(t.id, t.name))
65
65
  }}
66
66
  />
67
- <span className="dsh-atb-tplm-meta">
67
+ <span
68
+ className="dsh-atb-tplm-meta"
69
+ title={`${t.builtin === true ? '内置' : '自建'}${t.task.checklist !== undefined && t.task.checklist.length > 0 ? ` · 清单 ${t.task.checklist.length} 项` : ''}${t.task.urgency !== undefined ? ` · ${t.task.urgency}` : ''}`}
70
+ >
68
71
  {t.builtin === true ? '内置' : '自建'}
69
72
  {t.task.checklist !== undefined && t.task.checklist.length > 0 ? ` · 清单 ${t.task.checklist.length} 项` : ''}
70
73
  {t.task.urgency !== undefined ? ` · ${t.task.urgency}` : ''}
@@ -22,7 +22,7 @@ export interface BoardFilters {
22
22
  }
23
23
 
24
24
  /** Column sort orders. */
25
- export type SortBy = 'default' | 'updated' | 'urgency' | 'created'
25
+ export type SortBy = 'default' | 'updated' | 'urgency' | 'created' | 'title'
26
26
 
27
27
  /** localStorage key for persisted view state (filters + sort). */
28
28
  const VIEW_KEY = 'dsh-taskboard-view-v1'
@@ -33,7 +33,7 @@ function loadView(): { workspaceId?: string; urgencies: Urgency[]; sortBy: SortB
33
33
  const raw = localStorage.getItem(VIEW_KEY)
34
34
  if (raw === null) return { urgencies: [], sortBy: 'default' }
35
35
  const parsed = JSON.parse(raw) as { workspaceId?: string; urgencies?: Urgency[]; sortBy?: SortBy }
36
- const sortBy = parsed.sortBy === 'updated' || parsed.sortBy === 'urgency' || parsed.sortBy === 'created' ? parsed.sortBy : 'default'
36
+ const sortBy = parsed.sortBy === 'updated' || parsed.sortBy === 'urgency' || parsed.sortBy === 'created' || parsed.sortBy === 'title' ? parsed.sortBy : 'default'
37
37
  return {
38
38
  workspaceId: typeof parsed.workspaceId === 'string' ? parsed.workspaceId : undefined,
39
39
  urgencies: Array.isArray(parsed.urgencies) ? parsed.urgencies.filter(u => u === 'urgent' || u === 'normal' || u === 'relaxed') : [],
@@ -114,7 +114,15 @@ export class BoardController {
114
114
  private sessionJumper: ((sessionId: string) => Promise<SessionJumpResult>) | undefined
115
115
  /** Composer catalog faces, installed formally by the client entry (T13). */
116
116
  private readonly catalogFaces: {
117
- models?: () => Promise<Array<{ provider: string; model: string; name?: string }>>
117
+ models?: () => Promise<Array<{
118
+ provider: string
119
+ model: string
120
+ name?: string
121
+ reasoning?: {
122
+ efforts: Array<{ id: string; name: string; description?: string }>
123
+ defaultEffort?: string
124
+ }
125
+ }>>
118
126
  presets?: () => Promise<{ presets: Array<{ id: string; name?: string }>; defaultId?: string }>
119
127
  } = {}
120
128
 
@@ -278,7 +286,15 @@ export class BoardController {
278
286
  }
279
287
 
280
288
  /** T13: formal installers for the composer catalog faces (was a monkeypatch from the client entry). */
281
- installModelCatalog(fn: () => Promise<Array<{ provider: string; model: string; name?: string }>>): void {
289
+ installModelCatalog(fn: () => Promise<Array<{
290
+ provider: string
291
+ model: string
292
+ name?: string
293
+ reasoning?: {
294
+ efforts: Array<{ id: string; name: string; description?: string }>
295
+ defaultEffort?: string
296
+ }
297
+ }>>): void {
282
298
  this.catalogFaces.models = fn
283
299
  }
284
300
 
@@ -288,7 +304,15 @@ export class BoardController {
288
304
  }
289
305
 
290
306
  /** The installed model catalog face, when the runtime provides one. */
291
- get modelCatalog(): (() => Promise<Array<{ provider: string; model: string; name?: string }>>) | undefined {
307
+ get modelCatalog(): (() => Promise<Array<{
308
+ provider: string
309
+ model: string
310
+ name?: string
311
+ reasoning?: {
312
+ efforts: Array<{ id: string; name: string; description?: string }>
313
+ defaultEffort?: string
314
+ }
315
+ }>>) | undefined {
292
316
  return this.catalogFaces.models
293
317
  }
294
318
 
@@ -28,7 +28,25 @@ export const inject = ['connection']
28
28
  interface ConnectionFace {
29
29
  api: {
30
30
  llm: {
31
- models(payload: Record<string, never>): Promise<{ result: { ok: true; value: { groups: Array<{ id: string; name: string; models: Array<{ id: string; name?: string }> }> } } | { ok: false } }>
31
+ models(payload: Record<string, never>): Promise<{
32
+ result: {
33
+ ok: true
34
+ value: {
35
+ groups: Array<{
36
+ id: string
37
+ name: string
38
+ models: Array<{
39
+ id: string
40
+ name?: string
41
+ reasoning?: {
42
+ efforts: Array<{ id: string; name: string; description?: string }>
43
+ defaultEffort?: string
44
+ }
45
+ }>
46
+ }>
47
+ }
48
+ } | { ok: false }
49
+ }>
32
50
  }
33
51
  agentPresets?: {
34
52
  list(payload: Record<string, never>): Promise<{ result: { ok: true; value: { presets: Array<{ id: string; name?: string; isDefault: boolean }> } } | { ok: false } }>
@@ -43,8 +61,8 @@ interface ClientContextFace {
43
61
  }
44
62
 
45
63
  /**
46
- * Mount the client half.
47
- * @param ctx - the client context (connection injected).
64
+ * Client entry: installs styles, starts the controller, mounts DOM seats.
65
+ * @param ctx - the cordis client context.
48
66
  */
49
67
  export function apply(ctx: ClientContextFace): void {
50
68
  try {
@@ -57,14 +75,27 @@ export function apply(ctx: ClientContextFace): void {
57
75
  // monkeypatched instance properties).
58
76
  const connection = ctx.get?.('connection') as ConnectionFace | undefined
59
77
  if (connection !== undefined) {
60
- type CatalogRow = { provider: string; model: string; name?: string }
78
+ type CatalogRow = {
79
+ provider: string
80
+ model: string
81
+ name?: string
82
+ reasoning?: {
83
+ efforts: Array<{ id: string; name: string; description?: string }>
84
+ defaultEffort?: string
85
+ }
86
+ }
61
87
  controller.installModelCatalog(async (): Promise<CatalogRow[]> => {
62
88
  const response = await connection.api.llm.models({})
63
89
  if (!response.result.ok) return []
64
90
  const out: CatalogRow[] = []
65
91
  for (const group of response.result.value.groups) {
66
92
  for (const model of group.models) {
67
- out.push({ provider: group.id, model: model.id, name: model.name })
93
+ out.push({
94
+ provider: group.id,
95
+ model: model.id,
96
+ name: model.name,
97
+ ...(model.reasoning !== undefined ? { reasoning: model.reasoning } : {}),
98
+ })
68
99
  }
69
100
  }
70
101
  return out
@@ -95,8 +95,43 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
95
95
  .dsh-atb-spacer { flex: 1; }
96
96
  .dsh-atb-select, .dsh-atb-input {
97
97
  font: inherit; font-size: 12.5px; padding: 5px 8px; border-radius: 7px;
98
- border: 1px solid var(--dsw-border, rgba(128,128,128,.35));
99
- background: var(--dsw-bg, transparent); color: inherit;
98
+ border: 1px solid var(--dsw-border, var(--dsw-alias-border-l2, rgba(128,128,128,.35)));
99
+ background: var(--dsw-alias-bg-module-platform, var(--dsw-bg-elevated, var(--dsw-bg, transparent)));
100
+ color: var(--dsw-alias-label-primary, var(--dsw-text-primary, inherit));
101
+ color-scheme: light dark;
102
+ }
103
+ .dsh-atb-select option,
104
+ .dsh-atb-modal-body select option {
105
+ background: var(--dsw-alias-bg-overlay, var(--dsw-alias-bg-module-platform, #252830));
106
+ color: var(--dsw-alias-label-primary, var(--dsw-text-primary, #e6e8eb));
107
+ }
108
+ body[data-ds-dark-theme] .dsh-atb-board,
109
+ body[data-ds-dark-theme] .dsh-atb-modal,
110
+ body[data-ds-dark-theme] .dsh-atb-select,
111
+ body[data-ds-dark-theme] .dsh-atb-input,
112
+ body[data-ds-dark-theme] .dsh-atb-modal-body select,
113
+ body[data-ds-dark-theme] .dsh-atb-modal-body input,
114
+ body[data-ds-dark-theme] .dsh-atb-modal-body textarea {
115
+ color-scheme: dark;
116
+ }
117
+ body[data-ds-dark-theme] .dsh-atb-select option,
118
+ body[data-ds-dark-theme] .dsh-atb-modal-body select option {
119
+ background: #252830;
120
+ color: #e6e8eb;
121
+ }
122
+ @media (prefers-color-scheme: dark) {
123
+ .dsh-atb-select,
124
+ .dsh-atb-input,
125
+ .dsh-atb-modal-body select,
126
+ .dsh-atb-modal-body input,
127
+ .dsh-atb-modal-body textarea {
128
+ color-scheme: dark;
129
+ }
130
+ .dsh-atb-select option,
131
+ .dsh-atb-modal-body select option {
132
+ background: #252830;
133
+ color: #e6e8eb;
134
+ }
100
135
  }
101
136
  .dsh-atb-chip {
102
137
  display: inline-flex; align-items: center; gap: 5px;
@@ -172,6 +207,19 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
172
207
  .dsh-atb-badge[data-kind="trashed"] { background: rgba(229,72,77,.14); color: #e5484d; text-decoration: line-through; }
173
208
  .dsh-atb-badge[data-kind="done"] { background: rgba(46,160,67,.16); color: #2ea043; }
174
209
  .dsh-atb-badge[data-kind="running"] { background: rgba(229,152,42,.16); color: #e69842; }
210
+ .dsh-atb-card-session {
211
+ font: inherit; font-size: 10.5px; line-height: 1; padding: 2px 6px; border-radius: 5px;
212
+ border: 1px solid var(--dsw-border, rgba(128,128,128,.3));
213
+ background: var(--dsw-bg-elevated, rgba(128,128,128,.14));
214
+ color: var(--dsw-text-secondary, inherit);
215
+ cursor: pointer; display: inline-flex; align-items: center; gap: 3px;
216
+ transition: all .15s ease;
217
+ }
218
+ .dsh-atb-card-session:hover {
219
+ border-color: var(--dsw-alias-brand-primary, #1f2328);
220
+ background: var(--dsw-hover, rgba(128,128,128,.24));
221
+ color: var(--dsw-alias-label-primary, inherit);
222
+ }
175
223
 
176
224
  /* ---------- card quick review (in_review column) ---------- */
177
225
  .dsh-atb-quick { display: flex; gap: 6px; margin-top: 7px; }
@@ -219,6 +267,17 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
219
267
  background: var(--dsw-bg-elevated, rgba(128,128,128,.07)); color: var(--dsw-text-secondary, inherit);
220
268
  }
221
269
  .dsh-atb-detail-edit:hover { border-color: var(--dsw-alias-brand-primary, #1f2328); color: var(--dsw-alias-label-primary, inherit); }
270
+ .dsh-atb-detail-session {
271
+ font: inherit; font-size: 12px; padding: 4px 10px; border-radius: 7px; cursor: pointer;
272
+ border: 1px solid var(--dsw-border, rgba(128,128,128,.32));
273
+ background: var(--dsw-bg-elevated, rgba(128,128,128,.1));
274
+ color: var(--dsw-alias-label-primary, inherit);
275
+ display: inline-flex; align-items: center; gap: 4px;
276
+ }
277
+ .dsh-atb-detail-session:hover {
278
+ border-color: var(--dsw-alias-brand-primary, #1f2328);
279
+ background: var(--dsw-hover, rgba(128,128,128,.22));
280
+ }
222
281
 
223
282
  .dsh-atb-statuspill {
224
283
  flex: none; font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 999px; letter-spacing: .02em;
@@ -236,6 +295,14 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
236
295
  padding: 3px 8px; border-radius: 6px;
237
296
  background: var(--dsw-bg-inset, rgba(128,128,128,.09)); color: var(--dsw-text-secondary, #999);
238
297
  }
298
+ button.dsh-atb-chip2.dsh-atb-chip-btn {
299
+ font: inherit; border: 1px solid var(--dsw-border, rgba(128,128,128,.25)); cursor: pointer;
300
+ }
301
+ button.dsh-atb-chip2.dsh-atb-chip-btn:hover {
302
+ border-color: var(--dsw-alias-brand-primary, #1f2328);
303
+ color: var(--dsw-alias-label-primary, inherit);
304
+ background: var(--dsw-hover, rgba(128,128,128,.2));
305
+ }
239
306
  .dsh-atb-chip2-icon { font-size: 10.5px; opacity: .85; }
240
307
  .dsh-atb-chip2[data-tone="urgent"] { background: rgba(229,72,77,.15); color: #e5484d; }
241
308
  .dsh-atb-chip2[data-tone="normal"] { background: rgba(142,78,198,.14); color: #a06ce0; }
@@ -649,19 +716,30 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
649
716
  .dsh-atb-newmenu-opt:hover { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.1)); }
650
717
  .dsh-atb-newmenu-sep { height: 1px; margin: 4px 6px; background: var(--dsw-alias-border-l2, rgba(128,128,128,.25)); }
651
718
 
652
- .dsh-atb-tplm { max-width: 560px; width: min(560px, 92vw); }
653
- .dsh-atb-tplm-list { display: flex; flex-direction: column; gap: 8px; }
719
+ .dsh-atb-tplm { max-width: 600px; width: min(600px, 92vw); }
720
+ .dsh-atb-tplm .dsh-atb-modal-body,
721
+ .dsh-atb-set .dsh-atb-modal-body,
722
+ .dsh-atb-diag .dsh-atb-modal-body,
723
+ .dsh-atb-imp .dsh-atb-modal-body {
724
+ display: flex; flex-direction: column; gap: 10px;
725
+ }
726
+ .dsh-atb-tplm-list { display: flex; flex-direction: column; gap: 8px; width: 100%; }
654
727
  .dsh-atb-tplm-row {
655
- display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px;
728
+ display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 12px; border-radius: 8px;
656
729
  border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.25));
730
+ background: var(--dsw-alias-bg-layer-1, rgba(128,128,128,.02));
657
731
  }
658
732
  .dsh-atb-tplm-name {
659
- flex: 0 0 160px; font-size: 12.5px; padding: 5px 8px; border-radius: 7px;
733
+ flex: 0 0 150px; width: 150px; max-width: 150px; font-size: 12.5px; padding: 5px 8px; border-radius: 7px;
660
734
  border: 1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.3));
661
735
  background: var(--dsw-alias-bg-layer-1, transparent); color: inherit;
736
+ box-sizing: border-box;
737
+ }
738
+ .dsh-atb-tplm-meta {
739
+ flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, gray);
740
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
662
741
  }
663
- .dsh-atb-tplm-meta { flex: 1; min-width: 0; font-size: 10.5px; color: var(--dsw-alias-label-tertiary, gray); }
664
- .dsh-atb-tplm-btns { display: flex; gap: 6px; flex-shrink: 0; }
742
+ .dsh-atb-tplm-btns { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
665
743
 
666
744
  .dsh-atb-imp { max-width: 600px; width: min(600px, 92vw); }
667
745
  .dsh-atb-imp-picker { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
@@ -18,6 +18,7 @@ import {
18
18
  normalizeBody,
19
19
  type ExecutionRecord,
20
20
  type IsolationMode,
21
+ type TaskModel,
21
22
  type TaskRecord,
22
23
  } from '../shared/protocol.ts'
23
24
  import { sanitizeBranchName, worktreePathOf, type GitFace, type SettlementFacts } from './git.ts'
@@ -32,7 +33,7 @@ export interface AgentsFace {
32
33
  create(options: {
33
34
  sessionId: string
34
35
  meta?: { cwd?: string; agentPreset?: string }
35
- agentOptions?: { provider?: string; model?: string }
36
+ agentOptions?: { provider?: string; model?: string; reasoningEffort?: string }
36
37
  /** Preset composition callback: mounts tools/persona into the agent's scoped context. */
37
38
  setup?: (agentCtx: unknown) => Promise<void> | void
38
39
  }): Promise<{
@@ -66,7 +67,7 @@ export interface ExecutionWorkspaceFace {
66
67
 
67
68
  /** Narrow event-bus face for settlement listening. */
68
69
  export interface EventsFace {
69
- onSessionEvent(listener: (sessionId: string, event: { type: string; data?: unknown }) => void): () => void
70
+ onSessionEvent(listener: (sessionId: string, event: { type: string; data?: unknown }, sessionMeta?: { header?: { cwd?: string } }) => void): () => void
70
71
  }
71
72
 
72
73
  /** Everything the execution service needs. */
@@ -77,7 +78,7 @@ export interface ExecutionDeps {
77
78
  events: EventsFace
78
79
  now: () => number
79
80
  /** The deployment default model (fills sessions of unpinned tasks). */
80
- defaultModel?: () => { provider: string; model: string } | undefined
81
+ defaultModel?: () => TaskModel | undefined
81
82
  /** Mint session ids (injectable for tests). */
82
83
  mintSessionId?: () => string
83
84
  /** Mint message ids (injectable for tests). */
@@ -404,7 +405,13 @@ export class ExecutionService {
404
405
  cwd: workspace.path,
405
406
  ...(composition !== undefined ? { agentPreset: composition.agentPreset } : {}),
406
407
  },
407
- ...(model !== undefined ? { agentOptions: { provider: model.provider, model: model.model } } : {}),
408
+ ...(model !== undefined ? {
409
+ agentOptions: {
410
+ provider: model.provider,
411
+ model: model.model,
412
+ ...(model.reasoningEffort !== undefined ? { reasoningEffort: model.reasoningEffort } : {}),
413
+ },
414
+ } : {}),
408
415
  ...(composition !== undefined ? { setup: composition.setup } : {}),
409
416
  })
410
417
  } catch (error) {