@linxin666/dsh-client-ui-task-board 0.4.5 → 0.4.7
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 +34 -18
- package/README.zh.md +34 -18
- package/lib/client.js +2804 -1024
- package/lib/client.js.map +1 -1
- package/lib/index.js +2576 -416
- package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
- package/lib/types/client/TaskBoardSettingsCard.d.ts +61 -0
- package/lib/types/client/board/ReasoningEffortField.d.ts +25 -0
- package/lib/types/client/board/model-options.d.ts +67 -0
- package/lib/types/client/board/preset-label.d.ts +21 -0
- package/lib/types/client/board/preset-roster.d.ts +45 -0
- package/lib/types/client/board/team-run-promotion.d.ts +26 -0
- package/lib/types/client/index.d.ts +4 -5
- package/lib/types/client/locales.d.ts +81 -0
- package/lib/types/client/plugin-card-seat.d.ts +5 -0
- package/lib/types/client/schedule-zone.d.ts +50 -0
- package/lib/types/core/card-defaults.d.ts +78 -0
- package/lib/types/core/controller.d.ts +41 -17
- package/lib/types/core/launch-width.d.ts +31 -0
- package/lib/types/core/poll-cadence.d.ts +31 -0
- package/lib/types/core/reasoning-effort.d.ts +57 -0
- package/lib/types/core/subtask.d.ts +30 -8
- package/lib/types/core/tasks.d.ts +163 -14
- package/lib/types/core/use-cases/task-create.d.ts +10 -5
- package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
- package/lib/types/core/use-cases/task-update.d.ts +1 -1
- package/lib/types/core/verification-budget.d.ts +52 -0
- package/lib/types/core/verification.d.ts +376 -9
- package/lib/types/host/launch-admission.d.ts +72 -0
- package/lib/types/host/verification-gate.d.ts +24 -0
- package/lib/types/host/verification-runner.d.ts +38 -7
- package/lib/types/host-ledger.d.ts +91 -11
- package/lib/types/host-runner.d.ts +111 -13
- package/lib/types/host-service.d.ts +199 -8
- package/lib/types/index.d.ts +141 -0
- package/lib/types/protocol.d.ts +63 -12
- package/package.json +29 -33
- package/src/client/CardDefaultPresetField.tsx +101 -0
- package/src/client/TaskBoardSettingsCard.tsx +288 -17
- package/src/client/board/LinkSubtaskModal.tsx +8 -0
- package/src/client/board/NewTaskModal.tsx +237 -60
- package/src/client/board/ReasoningEffortField.tsx +50 -0
- package/src/client/board/TaskCard.tsx +3 -3
- package/src/client/board/TaskDetail.tsx +261 -24
- package/src/client/board/VerificationReport.tsx +132 -13
- package/src/client/board/model-options.ts +178 -0
- package/src/client/board/preset-label.ts +32 -0
- package/src/client/board/preset-roster.ts +58 -0
- package/src/client/board/status-key.ts +3 -0
- package/src/client/board/team-run-promotion.ts +39 -0
- package/src/client/index.ts +105 -108
- package/src/client/locales.ts +171 -6
- package/src/client/plugin-card-seat.ts +7 -0
- package/src/client/schedule-zone.ts +100 -2
- package/src/client/settings-entry-form.ts +0 -1
- package/src/core/card-defaults.ts +132 -0
- package/src/core/controller.ts +55 -16
- package/src/core/launch-width.ts +38 -0
- package/src/core/poll-cadence.ts +41 -0
- package/src/core/reasoning-effort.ts +92 -0
- package/src/core/store.ts +69 -15
- package/src/core/subtask.ts +48 -13
- package/src/core/tasks.ts +213 -39
- package/src/core/use-cases/task-create.ts +57 -25
- package/src/core/use-cases/task-schedule.ts +190 -25
- package/src/core/use-cases/task-update.ts +9 -4
- package/src/core/verification-budget.ts +115 -0
- package/src/core/verification.ts +785 -17
- package/src/host/agent-tools.ts +127 -29
- package/src/host/launch-admission.ts +140 -0
- package/src/host/verification-gate.ts +172 -16
- package/src/host/verification-runner.ts +207 -40
- package/src/host-ledger.ts +346 -48
- package/src/host-runner.ts +234 -35
- package/src/host-service.ts +539 -52
- package/src/index.ts +250 -7
- package/src/protocol.ts +160 -32
- package/lib/invariant.js +0 -6
- package/lib/types/client/board-card-seat.d.ts +0 -34
- package/lib/types/invariant.d.ts +0 -3
- package/src/client/board-card-seat.ts +0 -102
- package/src/invariant.ts +0 -4
package/src/client/index.ts
CHANGED
|
@@ -24,7 +24,9 @@ import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
|
24
24
|
// Type-only: pulls the workspace plugin's Context merge (ctx.uiWorkspace), the
|
|
25
25
|
// multi-instance navigation face that replaced ISessions.open().
|
|
26
26
|
import type {} from '@deepseek-ai/dsh-client-ui-workspace/client'
|
|
27
|
-
import { BoardController, TASK_BOARD_PANEL_ID } from '../core/controller.ts'
|
|
27
|
+
import { BoardController, TASK_BOARD_PANEL_ID, type ExecutionModelOption, type ExecutionPresetOption } from '../core/controller.ts'
|
|
28
|
+
import { modelOptionsFromCatalog, modelOptionsFromDiscoveredModels } from './board/model-options.ts'
|
|
29
|
+
import { presetOptionsFromRoster, readPresetRoster, type PresetRemote } from './board/preset-roster.ts'
|
|
28
30
|
import { mainViewSessionId } from './main-session.ts'
|
|
29
31
|
import { LocalStorageTaskStore } from '../core/store.ts'
|
|
30
32
|
import { claimTaskboardApply, releaseTaskboardApply } from './apply-guard.ts'
|
|
@@ -33,7 +35,7 @@ import { TaskBoardSettingsCard, TaskBoardSettingsCardController, type TaskBoardS
|
|
|
33
35
|
import { en, zh, setRuntimeTranslate, type TaskBoardKey } from './locales.ts'
|
|
34
36
|
import { HttpTaskBoardHostTransport } from './host-api.ts'
|
|
35
37
|
import { reportDailyHeartbeat } from './telemetry.ts'
|
|
36
|
-
import {
|
|
38
|
+
import { installPluginCard } from './plugin-card-seat.ts'
|
|
37
39
|
import { createServedEntryForm } from './settings-entry-form.ts'
|
|
38
40
|
import { TaskBoardClientService } from './service.ts'
|
|
39
41
|
import {
|
|
@@ -133,62 +135,13 @@ declare module '@deepseek-ai/cordis' {
|
|
|
133
135
|
|
|
134
136
|
/**
|
|
135
137
|
* Required services (fiber inject waiting — the runtime must be up first).
|
|
136
|
-
* The generated remote
|
|
137
|
-
* `remote.agentPresets`
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* connection RPC face.
|
|
138
|
+
* The generated remote namespaces are not all hard injects: `remote.session`
|
|
139
|
+
* is one, while `remote.agentPresets` is reached through a scoped inject at
|
|
140
|
+
* use time (see mountUi), because the namespace mounts with the official
|
|
141
|
+
* api-remotes assembly applied beside this entry rather than before it.
|
|
141
142
|
*/
|
|
142
143
|
export const inject = ['slots', 'sessions', 'workspaces', 'connection', 'configForms', 'locale', 'remote', 'remote.session', 'uiWorkspace', 'layout']
|
|
143
144
|
|
|
144
|
-
/** One agent-preset row the mode picker consumes (either face's wire shape). */
|
|
145
|
-
interface PresetRosterRow {
|
|
146
|
-
id: string
|
|
147
|
-
name?: string
|
|
148
|
-
description?: string
|
|
149
|
-
/** Why this preset cannot compose a session; absent when it can. */
|
|
150
|
-
broken?: string
|
|
151
|
-
isDefault: boolean
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Read the agent-preset roster through whichever face the running host
|
|
156
|
-
* serves: the generated api-remotes face (`remote.agentPresets`,
|
|
157
|
-
* 0.1.2-alpha.2) or the connection RPC face
|
|
158
|
-
* (`connection.api.agentPresets`, hosts below that cohort). Answers
|
|
159
|
-
* undefined when the host serves neither, so the caller leaves the picker
|
|
160
|
-
* options untouched instead of erroring.
|
|
161
|
-
*/
|
|
162
|
-
async function readPresetRoster(
|
|
163
|
-
ctx: ClientContext,
|
|
164
|
-
remote: ClientRemote,
|
|
165
|
-
): Promise<{ ok: boolean; presets: readonly PresetRosterRow[] } | undefined> {
|
|
166
|
-
// The cordis `remote` proxy throws on a property that was never injected
|
|
167
|
-
// ("cannot get property X without inject") rather than returning undefined,
|
|
168
|
-
// so the probe must guard the access — a hard read would abort mounting on
|
|
169
|
-
// hosts below the 0.1.2-alpha.2 cohort instead of degrading to the legacy
|
|
170
|
-
// connection RPC face below.
|
|
171
|
-
let remotes: ClientRemote['agentPresets'] | undefined
|
|
172
|
-
try {
|
|
173
|
-
remotes = (remote as Partial<ClientRemote>).agentPresets
|
|
174
|
-
} catch {
|
|
175
|
-
remotes = undefined
|
|
176
|
-
}
|
|
177
|
-
if (remotes !== undefined) {
|
|
178
|
-
const response = await remotes.list()
|
|
179
|
-
if (!response.ok) return { ok: false, presets: [] }
|
|
180
|
-
return { ok: true, presets: response.value.presets }
|
|
181
|
-
}
|
|
182
|
-
const connection = ctx.get('connection') as unknown as {
|
|
183
|
-
api?: { agentPresets?: { list(request: Record<string, never>): Promise<{ result: { ok: boolean; value?: { presets?: readonly PresetRosterRow[] } } }> } }
|
|
184
|
-
}
|
|
185
|
-
const legacy = connection.api?.agentPresets
|
|
186
|
-
if (legacy === undefined) return undefined
|
|
187
|
-
const response = await legacy.list({})
|
|
188
|
-
if (!response.result.ok || response.result.value === undefined) return { ok: false, presets: [] }
|
|
189
|
-
return { ok: true, presets: response.result.value.presets ?? [] }
|
|
190
|
-
}
|
|
191
|
-
|
|
192
145
|
/**
|
|
193
146
|
* Mount the task board.
|
|
194
147
|
* @param ctx - client root context (services: sessions, workspaces).
|
|
@@ -238,7 +191,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
238
191
|
ctx.effect(() => () => { clientService.detach() }, 'task-board: extension service')
|
|
239
192
|
|
|
240
193
|
const settingsCard = new TaskBoardSettingsCardController(settingsForm, request => clientService.dispatch(request))
|
|
241
|
-
|
|
194
|
+
installPluginCard(ctx, {
|
|
242
195
|
bundle: '@linxin666/dsh-client-ui-task-board',
|
|
243
196
|
id: 'task-board',
|
|
244
197
|
order: 110,
|
|
@@ -251,6 +204,48 @@ export function apply(ctx: ClientContext): void {
|
|
|
251
204
|
})
|
|
252
205
|
ctx.effect(() => () => { settingsCard.dispose() }, 'task-board: settings card')
|
|
253
206
|
|
|
207
|
+
// The agent-preset roster is a runtime fact of the deployment, not ledger
|
|
208
|
+
// state, and TWO surfaces read it: the board's mode pickers and the settings
|
|
209
|
+
// card's new-card-defaults topic. It is wired here, outside the board view,
|
|
210
|
+
// because the settings card stays reachable while the board itself is
|
|
211
|
+
// disabled — that is where it is switched back on — and its default-preset
|
|
212
|
+
// field must not lose the roster with the view. The namespace mounts with the
|
|
213
|
+
// official api-remotes assembly, which the page applies beside this entry: at
|
|
214
|
+
// mount time it is usually still absent, and reading it through
|
|
215
|
+
// `remote.agentPresets` before it lands throws ("cannot get property ...
|
|
216
|
+
// without inject"), so a one-shot probe would leave both pickers with nothing
|
|
217
|
+
// but "inherit" for the page lifetime. A hard inject is no better — it would
|
|
218
|
+
// pend the whole plugin on a deployment that never mounts the namespace. The
|
|
219
|
+
// scoped inject runs the feed exactly when the namespace exists and re-runs it
|
|
220
|
+
// if the namespace is replaced; the reconnect read inside covers a deployment
|
|
221
|
+
// change behind the same namespace.
|
|
222
|
+
let presetRoster: readonly ExecutionPresetOption[] | undefined
|
|
223
|
+
let liveController: BoardController | undefined
|
|
224
|
+
const pushPresetOptions = async (presetRemote: PresetRemote): Promise<void> => {
|
|
225
|
+
try {
|
|
226
|
+
const roster = await readPresetRoster(presetRemote)
|
|
227
|
+
if (!roster.ok) return
|
|
228
|
+
const presets = presetOptionsFromRoster(roster.presets)
|
|
229
|
+
presetRoster = presets
|
|
230
|
+
settingsCard.setPresetRoster(presets)
|
|
231
|
+
liveController?.setExecutionOptions({ presets })
|
|
232
|
+
} catch (error) {
|
|
233
|
+
// A failed roster read leaves the previous options in place: the pickers
|
|
234
|
+
// stay usable, the default-preset field keeps the inherit choice alone,
|
|
235
|
+
// and the next reconnect retries the read.
|
|
236
|
+
console.error('[dsh-task-board] agent preset roster read failed', error)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const presetRosterFeed = ctx.inject(['remote.agentPresets'], (inner) => {
|
|
240
|
+
const presetRemote = inner.get('remote.agentPresets') as PresetRemote | undefined
|
|
241
|
+
if (presetRemote === undefined) return
|
|
242
|
+
void pushPresetOptions(presetRemote)
|
|
243
|
+
// Subscribed inside the injected scope, so a namespace that goes away takes
|
|
244
|
+
// its listener with it instead of leaving one per remount.
|
|
245
|
+
return inner.on('connection/reset', () => { void pushPresetOptions(presetRemote) })
|
|
246
|
+
})
|
|
247
|
+
ctx.effect(() => () => { void presetRosterFeed.dispose() }, 'task-board: agent preset roster')
|
|
248
|
+
|
|
254
249
|
// The sidebar entry and board view mount once the settings form settles;
|
|
255
250
|
// while the form is still loading, the composition default is unknown, so
|
|
256
251
|
// nothing mounts yet. Only an unavailable form (no settings surface served)
|
|
@@ -297,12 +292,35 @@ export function apply(ctx: ClientContext): void {
|
|
|
297
292
|
clientService.attach(controller)
|
|
298
293
|
|
|
299
294
|
const disposers: Array<() => void> = []
|
|
295
|
+
// The board follows the live controller for the roster feed wired above,
|
|
296
|
+
// which serves the settings card whether or not the view is mounted.
|
|
297
|
+
liveController = controller
|
|
298
|
+
if (presetRoster !== undefined) controller.setExecutionOptions({ presets: presetRoster })
|
|
299
|
+
|
|
300
|
+
// The new-card defaults are written through the very settings form this
|
|
301
|
+
// plugin binds (that is the card's save), and the Host republishes its
|
|
302
|
+
// snapshot on that write. Its SSE frame carries the value as of BEFORE the
|
|
303
|
+
// write, though, so a mirror that only followed frames would hand the create
|
|
304
|
+
// dialog the previous defaults for the rest of the page session — the
|
|
305
|
+
// "configure a default, create a card, get nothing" report. Follow the form
|
|
306
|
+
// instead: when the four defaults it holds change, re-read the authoritative
|
|
307
|
+
// snapshot. The signature keeps one write to one GET (the form announces a
|
|
308
|
+
// settled document more than once) and keeps an unrelated settings write, or
|
|
309
|
+
// any board action, from pulling the ledger again.
|
|
310
|
+
let followedDefaults = cardDefaultSignature(settingsForm.getSnapshot().value)
|
|
311
|
+
disposers.push(settingsForm.subscribe(() => {
|
|
312
|
+
const next = cardDefaultSignature(settingsForm.getSnapshot().value)
|
|
313
|
+
if (next === followedDefaults) return
|
|
314
|
+
followedDefaults = next
|
|
315
|
+
controller.refreshHostState()
|
|
316
|
+
}))
|
|
300
317
|
|
|
301
318
|
// Execution-target option feeds: the workspace list drives the workspace
|
|
302
319
|
// picker, and the agent-preset roster drives the mode picker. Both are
|
|
303
320
|
// runtime facts (not ledger state), so the wiring pushes them into the
|
|
304
|
-
// controller on change; the preset roster
|
|
305
|
-
//
|
|
321
|
+
// controller on change; the preset roster feed itself lives beside the
|
|
322
|
+
// settings card (see apply) because that card's new-card-defaults topic
|
|
323
|
+
// reads the same roster while this view may not be mounted at all.
|
|
306
324
|
const pushWorkspaceOptions = (): void => {
|
|
307
325
|
const snapshot = workspaces.list.getSnapshot()
|
|
308
326
|
controller.setExecutionOptions({
|
|
@@ -321,28 +339,9 @@ export function apply(ctx: ClientContext): void {
|
|
|
321
339
|
const created = await workspaces.create({ path })
|
|
322
340
|
return { workspaceId: created.workspaceId }
|
|
323
341
|
})
|
|
324
|
-
const pushPresetOptions = async (): Promise<void> => {
|
|
325
|
-
try {
|
|
326
|
-
const roster = await readPresetRoster(ctx, remote)
|
|
327
|
-
if (roster === undefined || !roster.ok) return
|
|
328
|
-
controller.setExecutionOptions({
|
|
329
|
-
presets: roster.presets.map(preset => ({
|
|
330
|
-
id: preset.id,
|
|
331
|
-
name: preset.name,
|
|
332
|
-
description: preset.description,
|
|
333
|
-
broken: preset.broken,
|
|
334
|
-
isDefault: preset.isDefault,
|
|
335
|
-
})),
|
|
336
|
-
})
|
|
337
|
-
} catch (error) {
|
|
338
|
-
// A failed roster read leaves the previous options in place; the
|
|
339
|
-
// picker stays usable and the next reconnect retries the read.
|
|
340
|
-
console.error('[dsh-task-board] agent preset roster read failed', error)
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
342
|
const pushModelOptions = async (): Promise<void> => {
|
|
344
343
|
try {
|
|
345
|
-
let models:
|
|
344
|
+
let models: ExecutionModelOption[] = []
|
|
346
345
|
let sessionRemote: ClientRemote['session'] | undefined
|
|
347
346
|
try {
|
|
348
347
|
sessionRemote = (remote as Partial<ClientRemote>).session
|
|
@@ -351,15 +350,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
351
350
|
}
|
|
352
351
|
if (typeof sessionRemote?.modelCatalog === 'function') {
|
|
353
352
|
const res = await sessionRemote.modelCatalog()
|
|
354
|
-
if (res.ok
|
|
355
|
-
for (const g of res.value.groups) {
|
|
356
|
-
const provider = (g as { id?: string; provider?: string }).id ?? (g as { id?: string; provider?: string }).provider
|
|
357
|
-
for (const m of g.models ?? []) {
|
|
358
|
-
const qualifiedId = provider ? `${provider}/${m.id}` : m.id
|
|
359
|
-
models.push({ id: qualifiedId, name: m.name ?? m.id, provider })
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
353
|
+
if (res.ok) models = modelOptionsFromCatalog(res.value)
|
|
363
354
|
}
|
|
364
355
|
if (models.length === 0) {
|
|
365
356
|
const conn = ctx.get('connection') as {
|
|
@@ -371,11 +362,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
371
362
|
} | undefined
|
|
372
363
|
if (conn?.api) {
|
|
373
364
|
if (typeof conn.api.llm?.discoverModels === 'function') {
|
|
374
|
-
const res = await conn.api.llm.discoverModels() as { result?: { value?: { models?:
|
|
375
|
-
|
|
376
|
-
if (Array.isArray(list)) {
|
|
377
|
-
models = list.map(m => ({ id: m.id, name: m.name }))
|
|
378
|
-
}
|
|
365
|
+
const res = await conn.api.llm.discoverModels() as { result?: { value?: { models?: unknown } } }
|
|
366
|
+
models = modelOptionsFromDiscoveredModels(res?.result?.value?.models)
|
|
379
367
|
}
|
|
380
368
|
const catalogFn = typeof conn.api.session?.modelCatalog === 'function'
|
|
381
369
|
? conn.api.session.modelCatalog
|
|
@@ -383,17 +371,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
383
371
|
? conn.api.sessions.modelCatalog
|
|
384
372
|
: undefined
|
|
385
373
|
if (models.length === 0 && catalogFn !== undefined) {
|
|
386
|
-
const res = await catalogFn() as { result?: { value?:
|
|
387
|
-
|
|
388
|
-
if (Array.isArray(groups)) {
|
|
389
|
-
for (const g of groups) {
|
|
390
|
-
const provider = g.id ?? g.provider
|
|
391
|
-
for (const m of g.models ?? []) {
|
|
392
|
-
const qualifiedId = provider ? `${provider}/${m.id}` : m.id
|
|
393
|
-
models.push({ id: qualifiedId, name: m.name ?? m.id, provider })
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
374
|
+
const res = await catalogFn() as { result?: { value?: unknown } }
|
|
375
|
+
models = modelOptionsFromCatalog(res?.result?.value)
|
|
397
376
|
}
|
|
398
377
|
}
|
|
399
378
|
}
|
|
@@ -404,12 +383,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
404
383
|
console.error('[dsh-task-board] model options read failed', error)
|
|
405
384
|
}
|
|
406
385
|
}
|
|
407
|
-
void pushPresetOptions()
|
|
408
386
|
void pushModelOptions()
|
|
409
|
-
disposers.push(ctx.on('connection/reset', () => {
|
|
410
|
-
void pushPresetOptions()
|
|
411
|
-
void pushModelOptions()
|
|
412
|
-
}))
|
|
387
|
+
disposers.push(ctx.on('connection/reset', () => { void pushModelOptions() }))
|
|
413
388
|
// Native panel surfaces: one sidebar row and one center-column page, both
|
|
414
389
|
// through the official slot seats the shell itself renders. The layout's
|
|
415
390
|
// panel selection is reconciled back into the controller so the board's own
|
|
@@ -434,6 +409,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
434
409
|
for (const dispose of disposers.splice(0)) dispose()
|
|
435
410
|
clientService.detach()
|
|
436
411
|
controller.dispose()
|
|
412
|
+
liveController = undefined
|
|
437
413
|
uiDisposer = undefined
|
|
438
414
|
}
|
|
439
415
|
}
|
|
@@ -487,6 +463,27 @@ export function bindSettingsForm(ctx: ClientContext): ConfigForm<TaskBoardSettin
|
|
|
487
463
|
})
|
|
488
464
|
}
|
|
489
465
|
|
|
466
|
+
/**
|
|
467
|
+
* The four new-card defaults one settings document holds, as a change
|
|
468
|
+
* signature.
|
|
469
|
+
*
|
|
470
|
+
* Deliberately nothing but those four keys: the settings form also carries the
|
|
471
|
+
* board's own switches and the acceptance configuration, and an edit to one of
|
|
472
|
+
* those says nothing about the defaults the create dialog prefills from. Not a
|
|
473
|
+
* second normalization of Host state — the value never leaves this comparison.
|
|
474
|
+
* @param value - the settings form's effective section, if it has one.
|
|
475
|
+
* @returns a stable string that changes exactly when the defaults do.
|
|
476
|
+
*/
|
|
477
|
+
function cardDefaultSignature(value: unknown): string {
|
|
478
|
+
const section = (value ?? {}) as Record<string, unknown>
|
|
479
|
+
return JSON.stringify([
|
|
480
|
+
section.cardDefaultMode,
|
|
481
|
+
section.cardDefaultPermission,
|
|
482
|
+
section.cardDefaultModel,
|
|
483
|
+
section.cardDefaultReasoningEffort,
|
|
484
|
+
])
|
|
485
|
+
}
|
|
486
|
+
|
|
490
487
|
/**
|
|
491
488
|
* The profile entry id this package's own row carries, for a page that serves
|
|
492
489
|
* no family binder.
|