@fonlan/dsh-task-kanban 0.2.0 → 0.2.1
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.md +12 -0
- package/lib/client.js +282 -69
- package/lib/client.js.map +1 -1
- package/lib/index.js +140 -23
- package/lib/types/server/models.d.ts +16 -0
- package/lib/types/server/runner.d.ts +18 -1
- package/lib/types/server/settings.d.ts +35 -7
- package/lib/types/shared/card.d.ts +12 -1
- package/package.json +5 -1
- package/src/client/api.ts +19 -1
- package/src/client/board.css +61 -6
- package/src/client/board.tsx +0 -7
- package/src/client/index.tsx +11 -9
- package/src/client/locales.ts +16 -2
- package/src/client/sections.tsx +244 -48
- package/src/server/models.ts +40 -0
- package/src/server/rpc.ts +33 -4
- package/src/server/runner.ts +71 -18
- package/src/server/settings.ts +52 -7
- package/src/shared/card.ts +13 -1
package/lib/index.js
CHANGED
|
@@ -3,7 +3,8 @@ import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { appendFile, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
6
|
+
import { ReasoningEffortId, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
7
|
+
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
7
8
|
import { execFile } from "node:child_process";
|
|
8
9
|
import { promisify } from "node:util";
|
|
9
10
|
import { isSkillName, isUserInvocable, renderSkillContent } from "@deepseek-ai/dsh-skill";
|
|
@@ -12,12 +13,32 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
|
12
13
|
const KANBAN_SETTINGS_NS = settingsNamespace("task-kanban");
|
|
13
14
|
const KanbanSettingsSchema = z.object({
|
|
14
15
|
maxParallelWorkers: z.natural().min(1).default(1),
|
|
15
|
-
|
|
16
|
+
refinementModel: z.string().default(""),
|
|
17
|
+
refinementProvider: z.string().default(""),
|
|
18
|
+
refinementReasoningEffort: z.string().default(""),
|
|
19
|
+
refinementPreset: z.string().default(""),
|
|
20
|
+
phaseModel: z.string().default(""),
|
|
21
|
+
phaseProvider: z.string().default(""),
|
|
22
|
+
phaseReasoningEffort: z.string().default(""),
|
|
23
|
+
phasePreset: z.string().default("")
|
|
16
24
|
});
|
|
25
|
+
const EMPTY_DEFAULTS = {
|
|
26
|
+
model: "",
|
|
27
|
+
provider: "",
|
|
28
|
+
reasoningEffort: "",
|
|
29
|
+
preset: ""
|
|
30
|
+
};
|
|
17
31
|
function registerSettings(ctx) {
|
|
18
32
|
let current = {
|
|
19
33
|
maxParallelWorkers: 1,
|
|
20
|
-
|
|
34
|
+
refinementModel: "",
|
|
35
|
+
refinementProvider: "",
|
|
36
|
+
refinementReasoningEffort: "",
|
|
37
|
+
refinementPreset: "",
|
|
38
|
+
phaseModel: "",
|
|
39
|
+
phaseProvider: "",
|
|
40
|
+
phaseReasoningEffort: "",
|
|
41
|
+
phasePreset: ""
|
|
21
42
|
};
|
|
22
43
|
let service;
|
|
23
44
|
ctx.inject(["settings"], (sctx) => {
|
|
@@ -30,6 +51,24 @@ function registerSettings(ctx) {
|
|
|
30
51
|
});
|
|
31
52
|
return {
|
|
32
53
|
get: () => current,
|
|
54
|
+
sessionDefaults: (kind) => {
|
|
55
|
+
const s = current;
|
|
56
|
+
switch (kind) {
|
|
57
|
+
case "refine": return {
|
|
58
|
+
model: s.refinementModel,
|
|
59
|
+
provider: s.refinementProvider,
|
|
60
|
+
reasoningEffort: s.refinementReasoningEffort,
|
|
61
|
+
preset: s.refinementPreset
|
|
62
|
+
};
|
|
63
|
+
case "phase": return {
|
|
64
|
+
model: s.phaseModel,
|
|
65
|
+
provider: s.phaseProvider,
|
|
66
|
+
reasoningEffort: s.phaseReasoningEffort,
|
|
67
|
+
preset: s.phasePreset
|
|
68
|
+
};
|
|
69
|
+
case "merge": return EMPTY_DEFAULTS;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
33
72
|
defaultModelRoute: () => {
|
|
34
73
|
if (service === void 0) return {};
|
|
35
74
|
try {
|
|
@@ -976,7 +1015,7 @@ var KanbanRunner = class {
|
|
|
976
1015
|
async runRefinement(cardId, wsPath) {
|
|
977
1016
|
const card = await this.store.get(wsPath, cardId);
|
|
978
1017
|
if (card === void 0) return;
|
|
979
|
-
const route = await this.modelRoute(cardId, wsPath);
|
|
1018
|
+
const route = await this.modelRoute(cardId, wsPath, "refine");
|
|
980
1019
|
const sessionId = this.newSessionId();
|
|
981
1020
|
this.sessionCards.set(sessionId, cardId);
|
|
982
1021
|
await this.store.mutate(cardId, (c) => {
|
|
@@ -985,7 +1024,7 @@ var KanbanRunner = class {
|
|
|
985
1024
|
c.sessions.refinement.push(sessionId);
|
|
986
1025
|
}, wsPath);
|
|
987
1026
|
try {
|
|
988
|
-
const handle = await this.createAgent(sessionId, wsPath, route);
|
|
1027
|
+
const handle = await this.createAgent(sessionId, wsPath, route, "refine");
|
|
989
1028
|
this.keepHandle(cardId, handle);
|
|
990
1029
|
await this.attachRefinementSession(wsPath, sessionId);
|
|
991
1030
|
const skill = card.skill !== void 0 ? await loadCardSkill(this.ctx, card.skill, wsPath, handle.agent) : void 0;
|
|
@@ -1089,19 +1128,47 @@ var KanbanRunner = class {
|
|
|
1089
1128
|
dispose
|
|
1090
1129
|
};
|
|
1091
1130
|
}
|
|
1092
|
-
|
|
1131
|
+
/**
|
|
1132
|
+
* Create one kanban session agent. `kind` decides the per-session preset:
|
|
1133
|
+
* refine/phase use their settings default (falling back to the roster's
|
|
1134
|
+
* default preset when unset), merge keeps the historical 'standard' preset.
|
|
1135
|
+
* When the kind defaults configure a reasoning effort, an
|
|
1136
|
+
* `installModelSelection` ref couples the provider/model + effort into every
|
|
1137
|
+
* prompt assembly and model request. Without a configured effort no
|
|
1138
|
+
* selection is installed: `agentOptions` carries the route and the loop
|
|
1139
|
+
* materializes the model's own default effort — the historical behavior.
|
|
1140
|
+
*/
|
|
1141
|
+
async createAgent(sessionId, cwd, route, kind) {
|
|
1093
1142
|
const agentOptions = route.provider !== void 0 && route.model !== void 0 && route.model !== "" ? {
|
|
1094
1143
|
provider: route.provider,
|
|
1095
1144
|
model: route.model
|
|
1096
1145
|
} : void 0;
|
|
1146
|
+
const selectionRef = {
|
|
1147
|
+
current: route.provider !== void 0 && route.model !== void 0 && route.model !== "" ? {
|
|
1148
|
+
provider: route.provider,
|
|
1149
|
+
model: route.model,
|
|
1150
|
+
...route.reasoningEffort !== void 0 && route.reasoningEffort !== "" ? { reasoningEffort: ReasoningEffortId(route.reasoningEffort) } : {}
|
|
1151
|
+
} : void 0,
|
|
1152
|
+
assembled: void 0
|
|
1153
|
+
};
|
|
1154
|
+
let presetId;
|
|
1155
|
+
const presets = this.ctx.get("agentPresets");
|
|
1156
|
+
if (presets !== void 0) {
|
|
1157
|
+
const requested = kind === "merge" ? "standard" : this.settings.sessionDefaults(kind).preset;
|
|
1158
|
+
presetId = (await presets.resolve(requested === "" ? void 0 : requested)).id;
|
|
1159
|
+
}
|
|
1097
1160
|
return this.ctx.agents.create({
|
|
1098
1161
|
sessionId,
|
|
1099
|
-
meta: {
|
|
1162
|
+
meta: {
|
|
1163
|
+
cwd,
|
|
1164
|
+
...presetId !== void 0 ? { agentPreset: presetId } : {}
|
|
1165
|
+
},
|
|
1100
1166
|
...agentOptions !== void 0 ? { agentOptions } : {},
|
|
1101
1167
|
setup: async (agentCtx) => {
|
|
1102
1168
|
const agentPresets = agentCtx.get("agentPresets");
|
|
1103
|
-
if (agentPresets !== void 0) await agentPresets.mount(agentCtx,
|
|
1169
|
+
if (agentPresets !== void 0) await agentPresets.mount(agentCtx, presetId);
|
|
1104
1170
|
else throw new Error("@fonlan/dsh-task-kanban: agent-presets service is not mounted");
|
|
1171
|
+
if (selectionRef.current !== void 0 && selectionRef.current.reasoningEffort !== void 0) installModelSelection(agentCtx, selectionRef);
|
|
1105
1172
|
}
|
|
1106
1173
|
});
|
|
1107
1174
|
}
|
|
@@ -1133,8 +1200,15 @@ var KanbanRunner = class {
|
|
|
1133
1200
|
if (candidate !== void 0 && typeof candidate.resolveByPath === "function") return candidate;
|
|
1134
1201
|
}
|
|
1135
1202
|
}
|
|
1136
|
-
/**
|
|
1137
|
-
|
|
1203
|
+
/**
|
|
1204
|
+
* Resolve the provider+model route for an agent (both are required), plus
|
|
1205
|
+
* the per-kind reasoning effort. Precedence per kind:
|
|
1206
|
+
* 1. the card's explicit model (chosen at creation) — wins for every kind;
|
|
1207
|
+
* 2. the session-kind defaults (refine/phase settings);
|
|
1208
|
+
* 3. the host `agent-default-model` route.
|
|
1209
|
+
* Merge sessions skip step 2 (no per-type defaults for them).
|
|
1210
|
+
*/
|
|
1211
|
+
async modelRoute(cardId, wsPath, kind) {
|
|
1138
1212
|
const card = await this.store.get(wsPath, cardId);
|
|
1139
1213
|
if (card === void 0) return {};
|
|
1140
1214
|
if (card.model !== "" && card.provider !== void 0 && card.provider !== "") return {
|
|
@@ -1148,14 +1222,12 @@ var KanbanRunner = class {
|
|
|
1148
1222
|
model: card.model
|
|
1149
1223
|
} : { model: card.model };
|
|
1150
1224
|
}
|
|
1151
|
-
const
|
|
1152
|
-
if (
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
} : { model: userDefault };
|
|
1158
|
-
}
|
|
1225
|
+
const kindDefaults = this.settings.sessionDefaults(kind);
|
|
1226
|
+
if (kindDefaults.model !== "") return {
|
|
1227
|
+
...kindDefaults.provider !== "" ? { provider: kindDefaults.provider } : {},
|
|
1228
|
+
model: kindDefaults.model,
|
|
1229
|
+
...kindDefaults.reasoningEffort !== "" ? { reasoningEffort: kindDefaults.reasoningEffort } : {}
|
|
1230
|
+
};
|
|
1159
1231
|
return this.settings.defaultModelRoute();
|
|
1160
1232
|
}
|
|
1161
1233
|
async runCard(wsPath, card, entry) {
|
|
@@ -1252,9 +1324,9 @@ var KanbanRunner = class {
|
|
|
1252
1324
|
startedAt: Date.now()
|
|
1253
1325
|
});
|
|
1254
1326
|
}, wsPath);
|
|
1255
|
-
const route = await this.modelRoute(cardId, wsPath);
|
|
1327
|
+
const route = await this.modelRoute(cardId, wsPath, "phase");
|
|
1256
1328
|
try {
|
|
1257
|
-
const handle = await this.createAgent(sessionId, workdir, route);
|
|
1329
|
+
const handle = await this.createAgent(sessionId, workdir, route, "phase");
|
|
1258
1330
|
this.keepHandle(cardId, handle);
|
|
1259
1331
|
entry.cancel = () => handle.agent.cancel({ kind: "user" });
|
|
1260
1332
|
handle.agent.followup(this.asUserMessage(phasePrompt({
|
|
@@ -1546,9 +1618,9 @@ var KanbanRunner = class {
|
|
|
1546
1618
|
await this.store.mutate(cardId, (c) => {
|
|
1547
1619
|
c.sessions.merge.push(sessionId);
|
|
1548
1620
|
}, wsPath);
|
|
1549
|
-
const route = await this.modelRoute(cardId, wsPath);
|
|
1621
|
+
const route = await this.modelRoute(cardId, wsPath, "merge");
|
|
1550
1622
|
try {
|
|
1551
|
-
const handle = await this.createAgent(sessionId, workdir, route);
|
|
1623
|
+
const handle = await this.createAgent(sessionId, workdir, route, "merge");
|
|
1552
1624
|
this.keepHandle(cardId, handle);
|
|
1553
1625
|
handle.agent.followup(this.asUserMessage(mergePrompt(workdir, conflicts)));
|
|
1554
1626
|
await handle.agent.whenIdle();
|
|
@@ -1605,6 +1677,28 @@ async function listModels(ctx) {
|
|
|
1605
1677
|
} catch {}
|
|
1606
1678
|
return out;
|
|
1607
1679
|
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Resolve the adapter-owned reasoning efforts of one exact provider/model
|
|
1682
|
+
* route. On-demand (one model at a time) so the settings picker can offer the
|
|
1683
|
+
* levels the selected model actually supports; failures yield an empty list.
|
|
1684
|
+
*/
|
|
1685
|
+
async function reasoningOptions(ctx, provider, model) {
|
|
1686
|
+
const llm = ctx.get("llm");
|
|
1687
|
+
if (llm === void 0) return { efforts: [] };
|
|
1688
|
+
try {
|
|
1689
|
+
const info = await llm.resolveModelInfo(provider, model);
|
|
1690
|
+
return {
|
|
1691
|
+
efforts: (info.reasoning?.efforts ?? []).map((e) => ({
|
|
1692
|
+
id: String(e.id),
|
|
1693
|
+
name: e.name,
|
|
1694
|
+
...e.description !== void 0 ? { description: e.description } : {}
|
|
1695
|
+
})),
|
|
1696
|
+
...info.reasoning?.defaultEffort !== void 0 ? { defaultEffort: String(info.reasoning.defaultEffort) } : {}
|
|
1697
|
+
};
|
|
1698
|
+
} catch {
|
|
1699
|
+
return { efforts: [] };
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1608
1702
|
//#endregion
|
|
1609
1703
|
//#region src/server/rpc.ts
|
|
1610
1704
|
const API_PREFIX = "/plugins/@fonlan/dsh-task-kanban/api";
|
|
@@ -1737,14 +1831,37 @@ function registerApiRoutes(ctx, runner, settings) {
|
|
|
1737
1831
|
route("retry", async (p) => runner.retryTask(requireString(p.cardId, "cardId")));
|
|
1738
1832
|
route("remove", async (p) => runner.deleteTask(requireString(p.cardId, "cardId")));
|
|
1739
1833
|
route("settings.get", async () => settings.get());
|
|
1834
|
+
/** Every settings field the client may write (per-session-type defaults included). */
|
|
1835
|
+
const SETTING_STRING_FIELDS = [
|
|
1836
|
+
"refinementModel",
|
|
1837
|
+
"refinementProvider",
|
|
1838
|
+
"refinementReasoningEffort",
|
|
1839
|
+
"refinementPreset",
|
|
1840
|
+
"phaseModel",
|
|
1841
|
+
"phaseProvider",
|
|
1842
|
+
"phaseReasoningEffort",
|
|
1843
|
+
"phasePreset"
|
|
1844
|
+
];
|
|
1740
1845
|
route("settings.set", async (p) => {
|
|
1741
1846
|
const patch = {};
|
|
1742
1847
|
if (typeof p.maxParallelWorkers === "number") patch.maxParallelWorkers = p.maxParallelWorkers;
|
|
1743
|
-
if (typeof p
|
|
1848
|
+
for (const key of SETTING_STRING_FIELDS) if (typeof p[key] === "string") patch[key] = p[key];
|
|
1744
1849
|
await settings.update(patch);
|
|
1745
1850
|
return settings.get();
|
|
1746
1851
|
});
|
|
1747
1852
|
route("models.list", async () => listModels(ctx));
|
|
1853
|
+
route("presets.list", async () => {
|
|
1854
|
+
const presets = ctx.get("agentPresets");
|
|
1855
|
+
if (presets === void 0) return [];
|
|
1856
|
+
return (await presets.list()).map((r) => ({
|
|
1857
|
+
id: r.id,
|
|
1858
|
+
name: r.name,
|
|
1859
|
+
description: r.description
|
|
1860
|
+
}));
|
|
1861
|
+
});
|
|
1862
|
+
route("reasoning.options", async (p) => {
|
|
1863
|
+
return reasoningOptions(ctx, requireString(p.provider, "provider"), requireString(p.model, "model"));
|
|
1864
|
+
});
|
|
1748
1865
|
return () => {
|
|
1749
1866
|
for (const d of disposers) d();
|
|
1750
1867
|
};
|
|
@@ -2,3 +2,19 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
2
2
|
import type { ModelOption } from '../shared/card.js';
|
|
3
3
|
/** Available models from every registered LLM provider (advisory catalog). */
|
|
4
4
|
export declare function listModels(ctx: Context): Promise<ModelOption[]>;
|
|
5
|
+
/** One selectable reasoning effort of a model (display vocabulary for the settings picker). */
|
|
6
|
+
export interface ReasoningEffortOption {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ReasoningOptions {
|
|
12
|
+
efforts: ReasoningEffortOption[];
|
|
13
|
+
defaultEffort?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the adapter-owned reasoning efforts of one exact provider/model
|
|
17
|
+
* route. On-demand (one model at a time) so the settings picker can offer the
|
|
18
|
+
* levels the selected model actually supports; failures yield an empty list.
|
|
19
|
+
*/
|
|
20
|
+
export declare function reasoningOptions(ctx: Context, provider: string, model: string): Promise<ReasoningOptions>;
|
|
@@ -103,6 +103,16 @@ export declare class KanbanRunner implements KanbanToolResolver {
|
|
|
103
103
|
* resolve it — only real human replies drive the interactive refinement.
|
|
104
104
|
*/
|
|
105
105
|
private waitForUserReply;
|
|
106
|
+
/**
|
|
107
|
+
* Create one kanban session agent. `kind` decides the per-session preset:
|
|
108
|
+
* refine/phase use their settings default (falling back to the roster's
|
|
109
|
+
* default preset when unset), merge keeps the historical 'standard' preset.
|
|
110
|
+
* When the kind defaults configure a reasoning effort, an
|
|
111
|
+
* `installModelSelection` ref couples the provider/model + effort into every
|
|
112
|
+
* prompt assembly and model request. Without a configured effort no
|
|
113
|
+
* selection is installed: `agentOptions` carries the route and the loop
|
|
114
|
+
* materializes the model's own default effort — the historical behavior.
|
|
115
|
+
*/
|
|
106
116
|
private createAgent;
|
|
107
117
|
private keepHandle;
|
|
108
118
|
/**
|
|
@@ -114,7 +124,14 @@ export declare class KanbanRunner implements KanbanToolResolver {
|
|
|
114
124
|
private attachRefinementSession;
|
|
115
125
|
/** The host workspace registry, whichever service name carries it. */
|
|
116
126
|
private attachRegistry;
|
|
117
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the provider+model route for an agent (both are required), plus
|
|
129
|
+
* the per-kind reasoning effort. Precedence per kind:
|
|
130
|
+
* 1. the card's explicit model (chosen at creation) — wins for every kind;
|
|
131
|
+
* 2. the session-kind defaults (refine/phase settings);
|
|
132
|
+
* 3. the host `agent-default-model` route.
|
|
133
|
+
* Merge sessions skip step 2 (no per-type defaults for them).
|
|
134
|
+
*/
|
|
118
135
|
private modelRoute;
|
|
119
136
|
private runCard;
|
|
120
137
|
private runPhase;
|
|
@@ -1,20 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* User-facing plugin settings (global parallel worker count,
|
|
3
|
-
* persisted through the settings service
|
|
4
|
-
* settings service requires a lowercase
|
|
5
|
-
* (/^[a-z][a-z0-9-]*$/), so the scoped package name
|
|
2
|
+
* User-facing plugin settings (global parallel worker count, per-session-type
|
|
3
|
+
* model/effort/preset defaults), persisted through the settings service
|
|
4
|
+
* namespace `task-kanban`. The DSH settings service requires a lowercase
|
|
5
|
+
* kebab-case namespace (/^[a-z][a-z0-9-]*$/), so the scoped package name
|
|
6
|
+
* cannot be used here.
|
|
6
7
|
*/
|
|
7
8
|
import type { Context } from '@deepseek-ai/cordis';
|
|
8
9
|
import z from '@deepseek-ai/schemastery';
|
|
9
|
-
import type { KanbanSettingsShape } from '../shared/card.js';
|
|
10
|
+
import type { KanbanSessionKind, KanbanSettingsShape } from '../shared/card.js';
|
|
10
11
|
export declare const KANBAN_SETTINGS_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
11
12
|
export declare const KanbanSettingsSchema: z<Schemastery.ObjectS<{
|
|
12
13
|
maxParallelWorkers: z<number, number>;
|
|
13
|
-
|
|
14
|
+
refinementModel: z<string, string>;
|
|
15
|
+
refinementProvider: z<string, string>;
|
|
16
|
+
refinementReasoningEffort: z<string, string>;
|
|
17
|
+
refinementPreset: z<string, string>;
|
|
18
|
+
phaseModel: z<string, string>;
|
|
19
|
+
phaseProvider: z<string, string>;
|
|
20
|
+
phaseReasoningEffort: z<string, string>;
|
|
21
|
+
phasePreset: z<string, string>;
|
|
14
22
|
}>, Schemastery.ObjectT<{
|
|
15
23
|
maxParallelWorkers: z<number, number>;
|
|
16
|
-
|
|
24
|
+
refinementModel: z<string, string>;
|
|
25
|
+
refinementProvider: z<string, string>;
|
|
26
|
+
refinementReasoningEffort: z<string, string>;
|
|
27
|
+
refinementPreset: z<string, string>;
|
|
28
|
+
phaseModel: z<string, string>;
|
|
29
|
+
phaseProvider: z<string, string>;
|
|
30
|
+
phaseReasoningEffort: z<string, string>;
|
|
31
|
+
phasePreset: z<string, string>;
|
|
17
32
|
}>>;
|
|
33
|
+
/** The per-session-type default slice a session kind resolves. */
|
|
34
|
+
export interface KanbanSessionDefaults {
|
|
35
|
+
model: string;
|
|
36
|
+
provider: string;
|
|
37
|
+
reasoningEffort: string;
|
|
38
|
+
preset: string;
|
|
39
|
+
}
|
|
18
40
|
export interface KanbanSettingsFace {
|
|
19
41
|
get(): KanbanSettingsShape;
|
|
20
42
|
update(patch: Partial<KanbanSettingsShape>): Promise<void>;
|
|
@@ -23,5 +45,11 @@ export interface KanbanSettingsFace {
|
|
|
23
45
|
provider?: string;
|
|
24
46
|
model?: string;
|
|
25
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* The configured model/effort/preset defaults for one session kind.
|
|
50
|
+
* `merge` sessions deliberately carry no per-type defaults: they keep the
|
|
51
|
+
* legacy resolution chain (card model → host default).
|
|
52
|
+
*/
|
|
53
|
+
sessionDefaults(kind: KanbanSessionKind): KanbanSessionDefaults;
|
|
26
54
|
}
|
|
27
55
|
export declare function registerSettings(ctx: Context): KanbanSettingsFace;
|
|
@@ -82,7 +82,18 @@ export interface ModelOption {
|
|
|
82
82
|
id: string;
|
|
83
83
|
name?: string;
|
|
84
84
|
}
|
|
85
|
+
/** Which kanban session kind a per-type default applies to. */
|
|
86
|
+
export type KanbanSessionKind = 'refine' | 'phase' | 'merge';
|
|
85
87
|
export interface KanbanSettingsShape {
|
|
86
88
|
maxParallelWorkers: number;
|
|
87
|
-
|
|
89
|
+
/** Requirement-refinement session defaults. */
|
|
90
|
+
refinementModel: string;
|
|
91
|
+
refinementProvider: string;
|
|
92
|
+
refinementReasoningEffort: string;
|
|
93
|
+
refinementPreset: string;
|
|
94
|
+
/** Phase implementation session defaults. */
|
|
95
|
+
phaseModel: string;
|
|
96
|
+
phaseProvider: string;
|
|
97
|
+
phaseReasoningEffort: string;
|
|
98
|
+
phasePreset: string;
|
|
88
99
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonlan/dsh-task-kanban",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "DSH web plugin: a task kanban that refines requirements into phased plans, auto-executes them in git worktrees (one session per phase), and auto-merges into the main branch on completion.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown",
|
|
47
|
+
"prepare": "npm run build",
|
|
47
48
|
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.client.json",
|
|
48
49
|
"bundle": "tsdown",
|
|
49
50
|
"watch": "tsdown --watch",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"@deepseek-ai/dsh-client-ui-layout",
|
|
65
66
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
66
67
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
67
69
|
"@deepseek-ai/dsh-client-web-react"
|
|
68
70
|
]
|
|
69
71
|
}
|
|
@@ -82,6 +84,7 @@
|
|
|
82
84
|
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
83
85
|
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
84
86
|
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
|
|
87
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.6",
|
|
85
88
|
"react": "^18.2.0",
|
|
86
89
|
"react-dom": "^18.2.0"
|
|
87
90
|
},
|
|
@@ -104,6 +107,7 @@
|
|
|
104
107
|
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.6",
|
|
105
108
|
"@deepseek-ai/dsh-client-ui-layout": "^0.1.0-rc.6",
|
|
106
109
|
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
110
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.6",
|
|
107
111
|
"vitest": "^3.2.0"
|
|
108
112
|
},
|
|
109
113
|
"packageManager": "pnpm@11.21.0"
|
package/src/client/api.ts
CHANGED
|
@@ -69,6 +69,21 @@ export async function gatewaySkillList(sessionId: string): Promise<SkillOption[]
|
|
|
69
69
|
return parsed.result.value?.skills ?? []
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** One row of the host `agentPresets.list()` roster. */
|
|
73
|
+
export interface PresetOption {
|
|
74
|
+
id: string
|
|
75
|
+
name?: string
|
|
76
|
+
description?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Reasoning efforts of one provider/model route (`reasoning.options`). */
|
|
80
|
+
export interface ReasoningOptions {
|
|
81
|
+
efforts: Array<{ id: string; name: string; description?: string }>
|
|
82
|
+
defaultEffort?: string
|
|
83
|
+
/** Client-side marker: the fetch failed; the stored effort must be kept. */
|
|
84
|
+
failed?: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
export const api = {
|
|
73
88
|
list: (workspacePath: string) => call<KanbanCard[]>('list', { workspacePath }),
|
|
74
89
|
create: (workspacePath: string, requirement: string, model: string, provider?: string, skill?: string) =>
|
|
@@ -80,4 +95,7 @@ export const api = {
|
|
|
80
95
|
settingsGet: () => call<KanbanSettingsShape>('settings.get'),
|
|
81
96
|
settingsSet: (patch: Partial<KanbanSettingsShape>) => call<KanbanSettingsShape>('settings.set', patch),
|
|
82
97
|
models: () => call<ModelOption[]>('models.list'),
|
|
83
|
-
|
|
98
|
+
presets: () => call<PresetOption[]>('presets.list'),
|
|
99
|
+
reasoningOptions: (provider: string, model: string) =>
|
|
100
|
+
call<ReasoningOptions>('reasoning.options', { provider, model }),
|
|
101
|
+
}
|
package/src/client/board.css
CHANGED
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
flex: 0 1 auto;
|
|
42
42
|
padding: 6px 10px;
|
|
43
43
|
border-radius: 6px;
|
|
44
|
-
border: 1px solid rgba(128, 128, 128, 0.35);
|
|
45
|
-
background: rgba(128, 128, 128, 0.12);
|
|
46
|
-
color: inherit;
|
|
44
|
+
border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));
|
|
45
|
+
background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.12)));
|
|
46
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
47
47
|
font-size: 14px;
|
|
48
48
|
}
|
|
49
49
|
.kb-btn {
|
|
@@ -272,17 +272,23 @@
|
|
|
272
272
|
text-overflow: ellipsis;
|
|
273
273
|
}
|
|
274
274
|
.kb-field-row { display: flex; align-items: center; gap: 10px; justify-content: space-between; }
|
|
275
|
+
/* Field labels in a row never wrap; selects get the remaining fixed width. */
|
|
276
|
+
.kb-field-row > span { white-space: nowrap; flex-shrink: 0; margin-bottom: 0; }
|
|
275
277
|
.kb-input {
|
|
276
278
|
width: 100%;
|
|
277
279
|
box-sizing: border-box;
|
|
278
280
|
padding: 7px 10px;
|
|
279
281
|
border-radius: 6px;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
/* Form controls follow the shell theme: DSH's input surface token plus a
|
|
283
|
+
theme-tuned border; the fallbacks cover shells without these tokens. */
|
|
284
|
+
border: 1px solid var(--dsw-alias-border-l2-darkmode-thin, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));
|
|
285
|
+
background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.1)));
|
|
286
|
+
color: var(--dsw-alias-label-primary, inherit);
|
|
283
287
|
font-size: 13px;
|
|
284
288
|
}
|
|
285
289
|
.kb-input-number { width: 90px; }
|
|
290
|
+
/* All settings selects share one width so the column looks even. */
|
|
291
|
+
.kb-settings select.kb-input { width: 200px; flex: 0 0 200px; }
|
|
286
292
|
.kb-textarea { min-height: 110px; resize: vertical; font-family: inherit; }
|
|
287
293
|
.kb-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
|
|
288
294
|
.kb-detail-section { margin-bottom: 12px; }
|
|
@@ -350,6 +356,55 @@
|
|
|
350
356
|
.kb-session-links { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
|
351
357
|
.kb-settings { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }
|
|
352
358
|
.kb-settings-saved { font-size: 12px; color: #4ade80; }
|
|
359
|
+
.kb-settings-group { border-top: 1px solid rgba(128, 128, 128, 0.22); padding-top: 12px; }
|
|
360
|
+
.kb-settings-group-title { font-size: 12px; font-weight: 600; opacity: 0.9; margin-bottom: 10px; }
|
|
361
|
+
/* Settings card (设置 → 插件配置): mirrors the built-in plugin cards
|
|
362
|
+
(dsh-client-ui-settings-plugins PluginCard) — same surface, border, head
|
|
363
|
+
layout, and disclosure chevron, spelled with kb-settings-card prefixed
|
|
364
|
+
classes. The prefix deliberately does NOT reuse the board's `.kb-card*`
|
|
365
|
+
classes: the board card carries its own padding/cursor/user-select, and
|
|
366
|
+
sharing the root class would leak those onto the settings card. */
|
|
367
|
+
.kb-settings-card {
|
|
368
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
369
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
370
|
+
border-radius: 12px;
|
|
371
|
+
list-style: none;
|
|
372
|
+
transition: border-color .16s, background .16s;
|
|
373
|
+
}
|
|
374
|
+
.kb-settings-card:hover { border-color: var(--dsw-alias-label-dimmed); }
|
|
375
|
+
.kb-settings-card[data-open] {
|
|
376
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
377
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
378
|
+
}
|
|
379
|
+
.kb-settings-card-head {
|
|
380
|
+
appearance: none;
|
|
381
|
+
width: 100%;
|
|
382
|
+
font: inherit;
|
|
383
|
+
color: inherit;
|
|
384
|
+
text-align: left;
|
|
385
|
+
cursor: pointer;
|
|
386
|
+
background: transparent;
|
|
387
|
+
border: 0;
|
|
388
|
+
border-radius: 12px;
|
|
389
|
+
align-items: center;
|
|
390
|
+
gap: 12px;
|
|
391
|
+
padding: 14px 16px;
|
|
392
|
+
display: flex;
|
|
393
|
+
}
|
|
394
|
+
.kb-settings-card-head:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
|
|
395
|
+
.kb-settings-card-headText { flex-direction: column; flex: 1; gap: 4px; min-width: 0; display: flex; }
|
|
396
|
+
.kb-settings-card-title { color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600; line-height: 1.4; }
|
|
397
|
+
.kb-settings-card-sub { color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.5; }
|
|
398
|
+
.kb-settings-card-chevron {
|
|
399
|
+
color: var(--dsw-alias-label-tertiary);
|
|
400
|
+
flex: none;
|
|
401
|
+
transition: transform .16s;
|
|
402
|
+
display: flex;
|
|
403
|
+
align-items: center;
|
|
404
|
+
justify-content: center;
|
|
405
|
+
}
|
|
406
|
+
.kb-settings-card-chevron[data-open] { transform: rotate(180deg); }
|
|
407
|
+
.kb-settings-card-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding: 12px 0 8px; }
|
|
353
408
|
.kb-footer-action {
|
|
354
409
|
display: inline-flex;
|
|
355
410
|
align-items: center;
|
package/src/client/board.tsx
CHANGED
|
@@ -326,13 +326,6 @@ function NewTaskModal({ t, workspaces, selectedPath, sessionId, onClose, onCreat
|
|
|
326
326
|
api.models()
|
|
327
327
|
.then((list) => { if (alive) setModels(list) })
|
|
328
328
|
.catch(() => { if (alive) setModels([]) })
|
|
329
|
-
api.settingsGet()
|
|
330
|
-
.then((s) => {
|
|
331
|
-
if (alive && s.defaultModel !== '') {
|
|
332
|
-
setModel(s.defaultModel)
|
|
333
|
-
}
|
|
334
|
-
})
|
|
335
|
-
.catch(() => undefined)
|
|
336
329
|
return () => { alive = false }
|
|
337
330
|
}, [])
|
|
338
331
|
|
package/src/client/index.tsx
CHANGED
|
@@ -2,9 +2,9 @@ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
|
2
2
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
3
3
|
import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
|
|
4
4
|
import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
|
|
5
|
-
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
5
|
+
import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
|
|
6
6
|
import { BoardRoot } from './board'
|
|
7
|
-
import { KanbanFooterButton,
|
|
7
|
+
import { KanbanFooterButton, KanbanSettingsCard } from './sections'
|
|
8
8
|
import { LOCALE_NS, zh, en } from './locales'
|
|
9
9
|
import { setClient, setBoardRoot, bindSessionNavigation } from './kanban-state'
|
|
10
10
|
|
|
@@ -35,13 +35,15 @@ export function apply(ctx: ClientContext): void {
|
|
|
35
35
|
}, KanbanFooterButton as never),
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// The plugin's own Settings Card (设置 → 插件配置) rides the `task-kanban`
|
|
39
|
+
// settings namespace: registering into the keyed `settings.plugin.item` slot
|
|
40
|
+
// with the namespace string makes the configurable-plugins tab dispatch the
|
|
41
|
+
// card next to the built-in ones (bash / agent loop / web search).
|
|
42
|
+
ctx.slots.inject('settings.plugin.item', () =>
|
|
39
43
|
ctx.slots.register({
|
|
40
|
-
name: 'settings.
|
|
41
|
-
|
|
42
|
-
order: 200,
|
|
43
|
-
label: () => t('settingsTitle'),
|
|
44
|
+
name: 'settings.plugin.item',
|
|
45
|
+
key: 'task-kanban',
|
|
44
46
|
locale: LOCALE_NS,
|
|
45
|
-
},
|
|
47
|
+
}, KanbanSettingsCard as never),
|
|
46
48
|
)
|
|
47
|
-
}
|
|
49
|
+
}
|
package/src/client/locales.ts
CHANGED
|
@@ -52,8 +52,15 @@ export const zh = {
|
|
|
52
52
|
noWorkspace: '没有可用工作区,请先在侧边栏添加',
|
|
53
53
|
refresh: '刷新',
|
|
54
54
|
settingsTitle: '任务看板',
|
|
55
|
+
settingsDescription: '全局并行 worker 数与各会话类型的默认模型、推理强度、Agent 预设',
|
|
56
|
+
settingsExpand: '展开设置',
|
|
57
|
+
settingsCollapse: '收起设置',
|
|
55
58
|
maxParallelWorkers: '全局并行 worker 数',
|
|
56
|
-
|
|
59
|
+
settingsRefineGroup: '细化需求会话默认',
|
|
60
|
+
settingsPhaseGroup: 'Phase 实现会话默认',
|
|
61
|
+
reasoningEffort: '推理强度',
|
|
62
|
+
agentPreset: 'Agent 预设',
|
|
63
|
+
defaultOption: '(默认)',
|
|
57
64
|
saved: '已保存',
|
|
58
65
|
loadError: '加载任务失败',
|
|
59
66
|
noPlan: '该卡片还没有实现计划,请先点击"添加并细化"',
|
|
@@ -120,8 +127,15 @@ export const en = {
|
|
|
120
127
|
noWorkspace: 'No workspaces available — add one in the sidebar first',
|
|
121
128
|
refresh: 'Refresh',
|
|
122
129
|
settingsTitle: 'Task Kanban',
|
|
130
|
+
settingsDescription: 'Global parallel workers and per-session-type defaults for model, reasoning effort, and agent preset',
|
|
131
|
+
settingsExpand: 'Show settings',
|
|
132
|
+
settingsCollapse: 'Hide settings',
|
|
123
133
|
maxParallelWorkers: 'Global parallel workers',
|
|
124
|
-
|
|
134
|
+
settingsRefineGroup: 'Refinement session defaults',
|
|
135
|
+
settingsPhaseGroup: 'Phase implementation defaults',
|
|
136
|
+
reasoningEffort: 'Reasoning effort',
|
|
137
|
+
agentPreset: 'Agent preset',
|
|
138
|
+
defaultOption: '(default)',
|
|
125
139
|
saved: 'Saved',
|
|
126
140
|
loadError: 'Failed to load tasks',
|
|
127
141
|
noPlan: 'This card has no plan yet — click "Add & Refine" first',
|