@fonlan/dsh-task-kanban 0.2.6 → 0.2.8
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 +7 -5
- package/lib/client.js +146 -221
- package/lib/client.js.map +1 -1
- package/lib/index.js +65 -41
- package/lib/types/index.d.ts +23 -3
- package/lib/types/server/runner.d.ts +6 -5
- package/lib/types/server/settings.d.ts +34 -26
- package/package.json +39 -39
- package/src/client/board.css +0 -107
- package/src/client/board.tsx +5 -9
- package/src/client/icons.tsx +74 -0
- package/src/client/index.tsx +27 -11
- package/src/client/kanban-state.ts +79 -107
- package/src/client/sections.tsx +10 -136
- package/src/index.ts +7 -5
- package/src/server/runner.ts +33 -18
- package/src/server/settings.ts +69 -36
package/lib/index.js
CHANGED
|
@@ -10,48 +10,67 @@ import { isSkillName, isUserInvocable, renderSkillContent } from "@deepseek-ai/d
|
|
|
10
10
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
11
11
|
//#region src/server/settings.ts
|
|
12
12
|
const KANBAN_SETTINGS_NS = "task-kanban";
|
|
13
|
+
/**
|
|
14
|
+
* The entry config schema. Deliberately UNANNOTATED: schemastery >= 3.18.3
|
|
15
|
+
* added a third `Mode` type parameter, so `.default().volatile()` fields have
|
|
16
|
+
* output type `Volatile<T>` and an explicit `z<KanbanSettingsShape>` no longer
|
|
17
|
+
* compiles (the schema's `meta.default` then carries `Volatile<number>`, which
|
|
18
|
+
* is not a `number`). The plain `KanbanSettingsShape` interface stays the
|
|
19
|
+
* plugin-facing shape: `registerSettings` unwraps the live cells below.
|
|
20
|
+
*/
|
|
13
21
|
const KanbanSettingsSchema = z.object({
|
|
14
|
-
maxParallelWorkers: z.natural().min(1).default(1),
|
|
15
|
-
refinementModel: z.string().default(""),
|
|
16
|
-
refinementProvider: z.string().default(""),
|
|
17
|
-
refinementReasoningEffort: z.string().default(""),
|
|
18
|
-
refinementPreset: z.string().default(""),
|
|
19
|
-
phaseModel: z.string().default(""),
|
|
20
|
-
phaseProvider: z.string().default(""),
|
|
21
|
-
phaseReasoningEffort: z.string().default(""),
|
|
22
|
-
phasePreset: z.string().default("")
|
|
22
|
+
maxParallelWorkers: z.natural().min(1).default(1).volatile(),
|
|
23
|
+
refinementModel: z.string().default("").volatile(),
|
|
24
|
+
refinementProvider: z.string().default("").volatile(),
|
|
25
|
+
refinementReasoningEffort: z.string().default("").volatile(),
|
|
26
|
+
refinementPreset: z.string().default("").volatile(),
|
|
27
|
+
phaseModel: z.string().default("").volatile(),
|
|
28
|
+
phaseProvider: z.string().default("").volatile(),
|
|
29
|
+
phaseReasoningEffort: z.string().default("").volatile(),
|
|
30
|
+
phasePreset: z.string().default("").volatile()
|
|
23
31
|
});
|
|
32
|
+
/**
|
|
33
|
+
* Dereference one config field. `Config` fields marked `.volatile()` are handed
|
|
34
|
+
* to the plugin as references (`{ get() }`) instead of plain values, so reading
|
|
35
|
+
* them raw yields the reference object — which serializes to `{}` and makes
|
|
36
|
+
* every setting look empty. Non-volatile (plain) configs keep working as-is.
|
|
37
|
+
*/
|
|
38
|
+
function fieldValue(value, fallback) {
|
|
39
|
+
if (value !== null && typeof value === "object" && typeof value.get === "function") return value.get() ?? fallback;
|
|
40
|
+
return value ?? fallback;
|
|
41
|
+
}
|
|
24
42
|
const EMPTY_DEFAULTS = {
|
|
25
43
|
model: "",
|
|
26
44
|
provider: "",
|
|
27
45
|
reasoningEffort: "",
|
|
28
46
|
preset: ""
|
|
29
47
|
};
|
|
30
|
-
function registerSettings(ctx) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
function registerSettings(ctx, config) {
|
|
49
|
+
const fields = config ?? {};
|
|
50
|
+
const snapshot = () => ({
|
|
51
|
+
maxParallelWorkers: fieldValue(fields.maxParallelWorkers, 1),
|
|
52
|
+
refinementModel: fieldValue(fields.refinementModel, ""),
|
|
53
|
+
refinementProvider: fieldValue(fields.refinementProvider, ""),
|
|
54
|
+
refinementReasoningEffort: fieldValue(fields.refinementReasoningEffort, ""),
|
|
55
|
+
refinementPreset: fieldValue(fields.refinementPreset, ""),
|
|
56
|
+
phaseModel: fieldValue(fields.phaseModel, ""),
|
|
57
|
+
phaseProvider: fieldValue(fields.phaseProvider, ""),
|
|
58
|
+
phaseReasoningEffort: fieldValue(fields.phaseReasoningEffort, ""),
|
|
59
|
+
phasePreset: fieldValue(fields.phasePreset, "")
|
|
60
|
+
});
|
|
61
|
+
let pending = {};
|
|
62
|
+
const current = () => ({
|
|
63
|
+
...snapshot(),
|
|
64
|
+
...pending
|
|
65
|
+
});
|
|
42
66
|
let service;
|
|
43
67
|
ctx.inject(["settings"], (sctx) => {
|
|
44
68
|
service = sctx.settings;
|
|
45
|
-
const scope = service.register(KANBAN_SETTINGS_NS, KanbanSettingsSchema);
|
|
46
|
-
current = scope.get();
|
|
47
|
-
scope.watch((next) => {
|
|
48
|
-
current = next;
|
|
49
|
-
});
|
|
50
69
|
});
|
|
51
70
|
return {
|
|
52
|
-
get: () => current,
|
|
71
|
+
get: () => current(),
|
|
53
72
|
sessionDefaults: (kind) => {
|
|
54
|
-
const s = current;
|
|
73
|
+
const s = current();
|
|
55
74
|
switch (kind) {
|
|
56
75
|
case "refine": return {
|
|
57
76
|
model: s.refinementModel,
|
|
@@ -85,6 +104,10 @@ function registerSettings(ctx) {
|
|
|
85
104
|
update: async (patch) => {
|
|
86
105
|
if (service === void 0) throw new Error("@fonlan/dsh-task-kanban: settings service is not available in this profile");
|
|
87
106
|
await service.update(KANBAN_SETTINGS_NS, patch);
|
|
107
|
+
pending = {
|
|
108
|
+
...pending,
|
|
109
|
+
...patch
|
|
110
|
+
};
|
|
88
111
|
}
|
|
89
112
|
};
|
|
90
113
|
}
|
|
@@ -653,7 +676,7 @@ var KanbanRunner = class {
|
|
|
653
676
|
recovered = false;
|
|
654
677
|
/** Agents that already got their scoped kanban tools (event idempotency). */
|
|
655
678
|
toolScopedAgents = /* @__PURE__ */ new WeakSet();
|
|
656
|
-
|
|
679
|
+
agentCreatedOff;
|
|
657
680
|
constructor(ctx, store, settings) {
|
|
658
681
|
this.ctx = ctx;
|
|
659
682
|
this.store = store;
|
|
@@ -666,16 +689,16 @@ var KanbanRunner = class {
|
|
|
666
689
|
this.pumpTimer = setInterval(() => {
|
|
667
690
|
this.pump();
|
|
668
691
|
}, 2500);
|
|
669
|
-
this.
|
|
670
|
-
this.
|
|
692
|
+
this.agentCreatedOff = this.ctx.on("agent/created", (payload) => {
|
|
693
|
+
this.onAgentCreated(payload.agent);
|
|
671
694
|
});
|
|
672
695
|
}
|
|
673
696
|
stop() {
|
|
674
697
|
if (this.pumpTimer !== void 0) clearInterval(this.pumpTimer);
|
|
675
698
|
this.pumpTimer = void 0;
|
|
676
|
-
if (this.
|
|
677
|
-
this.
|
|
678
|
-
this.
|
|
699
|
+
if (this.agentCreatedOff !== void 0) {
|
|
700
|
+
this.agentCreatedOff();
|
|
701
|
+
this.agentCreatedOff = void 0;
|
|
679
702
|
}
|
|
680
703
|
}
|
|
681
704
|
newSessionId() {
|
|
@@ -781,12 +804,13 @@ var KanbanRunner = class {
|
|
|
781
804
|
}
|
|
782
805
|
}
|
|
783
806
|
/**
|
|
784
|
-
* The single place kanban tools get registered.
|
|
785
|
-
* for BOTH `agents.create` (startup) and
|
|
786
|
-
* a restart)
|
|
807
|
+
* The single place kanban tools get registered. Subscribed to
|
|
808
|
+
* `agent/created`, which fires for BOTH `agents.create` (startup) and
|
|
809
|
+
* `agents.resume` (host re-open after a restart) and carries the path in its
|
|
810
|
+
* `source`, so a resumed refinement/phase/merge session keeps its scoped
|
|
787
811
|
* tools. Idempotent per agent object; non-kanban sessions are skipped.
|
|
788
812
|
*/
|
|
789
|
-
async
|
|
813
|
+
async onAgentCreated(agent) {
|
|
790
814
|
if (agent === void 0 || agent.id === void 0 || agent.ctx === void 0) return;
|
|
791
815
|
if (this.toolScopedAgents.has(agent)) return;
|
|
792
816
|
let cardId = this.sessionCards.get(agent.id);
|
|
@@ -1901,9 +1925,9 @@ const inject = [
|
|
|
1901
1925
|
"agents",
|
|
1902
1926
|
"skills"
|
|
1903
1927
|
];
|
|
1904
|
-
const Config =
|
|
1905
|
-
function apply(ctx,
|
|
1906
|
-
const settings = registerSettings(ctx);
|
|
1928
|
+
const Config = KanbanSettingsSchema;
|
|
1929
|
+
function apply(ctx, config) {
|
|
1930
|
+
const settings = registerSettings(ctx, config);
|
|
1907
1931
|
const runner = new KanbanRunner(ctx, new TaskStore(), settings);
|
|
1908
1932
|
ctx.effect(() => {
|
|
1909
1933
|
runner.recoverInterrupted();
|
package/lib/types/index.d.ts
CHANGED
|
@@ -7,8 +7,28 @@
|
|
|
7
7
|
* serves the fenced JSON API the board UI calls.
|
|
8
8
|
*/
|
|
9
9
|
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
-
import
|
|
10
|
+
import type { KanbanSettingsShape } from './shared/card.js';
|
|
11
11
|
export declare const name = "@fonlan/dsh-task-kanban";
|
|
12
12
|
export declare const inject: string[];
|
|
13
|
-
export declare const Config:
|
|
14
|
-
|
|
13
|
+
export declare const Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<NoInfer<{
|
|
14
|
+
maxParallelWorkers: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
15
|
+
refinementModel: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
16
|
+
refinementProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
17
|
+
refinementReasoningEffort: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
18
|
+
refinementPreset: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
19
|
+
phaseModel: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
20
|
+
phaseProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
21
|
+
phaseReasoningEffort: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
22
|
+
phasePreset: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
23
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
24
|
+
maxParallelWorkers: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
|
|
25
|
+
refinementModel: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
26
|
+
refinementProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
27
|
+
refinementReasoningEffort: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
28
|
+
refinementPreset: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
29
|
+
phaseModel: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
30
|
+
phaseProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
31
|
+
phaseReasoningEffort: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
32
|
+
phasePreset: import("@deepseek-ai/schemastery").default<string, string, "volatile-defined">;
|
|
33
|
+
}>>, "plain">;
|
|
34
|
+
export declare function apply(ctx: Context, config: KanbanSettingsShape): void;
|
|
@@ -21,7 +21,7 @@ export declare class KanbanRunner implements KanbanToolResolver {
|
|
|
21
21
|
private recovered;
|
|
22
22
|
/** Agents that already got their scoped kanban tools (event idempotency). */
|
|
23
23
|
private toolScopedAgents;
|
|
24
|
-
private
|
|
24
|
+
private agentCreatedOff;
|
|
25
25
|
constructor(ctx: Context, store: TaskStore, settings: KanbanSettingsFace);
|
|
26
26
|
register(ctx: Context): void;
|
|
27
27
|
start(): void;
|
|
@@ -45,12 +45,13 @@ export declare class KanbanRunner implements KanbanToolResolver {
|
|
|
45
45
|
/** Fallback lookup when a session starts before indexSessions ran. */
|
|
46
46
|
private lookupCardOfSession;
|
|
47
47
|
/**
|
|
48
|
-
* The single place kanban tools get registered.
|
|
49
|
-
* for BOTH `agents.create` (startup) and
|
|
50
|
-
* a restart)
|
|
48
|
+
* The single place kanban tools get registered. Subscribed to
|
|
49
|
+
* `agent/created`, which fires for BOTH `agents.create` (startup) and
|
|
50
|
+
* `agents.resume` (host re-open after a restart) and carries the path in its
|
|
51
|
+
* `source`, so a resumed refinement/phase/merge session keeps its scoped
|
|
51
52
|
* tools. Idempotent per agent object; non-kanban sessions are skipped.
|
|
52
53
|
*/
|
|
53
|
-
private
|
|
54
|
+
private onAgentCreated;
|
|
54
55
|
listCards(workspacePath: string): Promise<KanbanCard[]>;
|
|
55
56
|
createTask(input: {
|
|
56
57
|
workspacePath: string;
|
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User-facing plugin settings (global parallel worker count, per-session-type
|
|
3
|
-
* model/effort/preset defaults),
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* model/effort/preset defaults), carried by this bundle's entry config: dsh
|
|
4
|
+
* >= 0.1.7 owns plugin settings as entry config (the plugin's `Config`
|
|
5
|
+
* schema), so the face reads the config it was applied with and persists
|
|
6
|
+
* updates through `settings.update`.
|
|
7
7
|
*/
|
|
8
8
|
import type { Context } from '@deepseek-ai/cordis';
|
|
9
9
|
import z from '@deepseek-ai/schemastery';
|
|
10
10
|
import type { KanbanSessionKind, KanbanSettingsShape } from '../shared/card.js';
|
|
11
11
|
export declare const KANBAN_SETTINGS_NS = "task-kanban";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The entry config schema. Deliberately UNANNOTATED: schemastery >= 3.18.3
|
|
14
|
+
* added a third `Mode` type parameter, so `.default().volatile()` fields have
|
|
15
|
+
* output type `Volatile<T>` and an explicit `z<KanbanSettingsShape>` no longer
|
|
16
|
+
* compiles (the schema's `meta.default` then carries `Volatile<number>`, which
|
|
17
|
+
* is not a `number`). The plain `KanbanSettingsShape` interface stays the
|
|
18
|
+
* plugin-facing shape: `registerSettings` unwraps the live cells below.
|
|
19
|
+
*/
|
|
20
|
+
export declare const KanbanSettingsSchema: z<Schemastery.ObjectS<NoInfer<{
|
|
21
|
+
maxParallelWorkers: z<number, number, "volatile-defined">;
|
|
22
|
+
refinementModel: z<string, string, "volatile-defined">;
|
|
23
|
+
refinementProvider: z<string, string, "volatile-defined">;
|
|
24
|
+
refinementReasoningEffort: z<string, string, "volatile-defined">;
|
|
25
|
+
refinementPreset: z<string, string, "volatile-defined">;
|
|
26
|
+
phaseModel: z<string, string, "volatile-defined">;
|
|
27
|
+
phaseProvider: z<string, string, "volatile-defined">;
|
|
28
|
+
phaseReasoningEffort: z<string, string, "volatile-defined">;
|
|
29
|
+
phasePreset: z<string, string, "volatile-defined">;
|
|
30
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
31
|
+
maxParallelWorkers: z<number, number, "volatile-defined">;
|
|
32
|
+
refinementModel: z<string, string, "volatile-defined">;
|
|
33
|
+
refinementProvider: z<string, string, "volatile-defined">;
|
|
34
|
+
refinementReasoningEffort: z<string, string, "volatile-defined">;
|
|
35
|
+
refinementPreset: z<string, string, "volatile-defined">;
|
|
36
|
+
phaseModel: z<string, string, "volatile-defined">;
|
|
37
|
+
phaseProvider: z<string, string, "volatile-defined">;
|
|
38
|
+
phaseReasoningEffort: z<string, string, "volatile-defined">;
|
|
39
|
+
phasePreset: z<string, string, "volatile-defined">;
|
|
40
|
+
}>>, "plain">;
|
|
33
41
|
/** The per-session-type default slice a session kind resolves. */
|
|
34
42
|
export interface KanbanSessionDefaults {
|
|
35
43
|
model: string;
|
|
@@ -52,4 +60,4 @@ export interface KanbanSettingsFace {
|
|
|
52
60
|
*/
|
|
53
61
|
sessionDefaults(kind: KanbanSessionKind): KanbanSessionDefaults;
|
|
54
62
|
}
|
|
55
|
-
export declare function registerSettings(ctx: Context): KanbanSettingsFace;
|
|
63
|
+
export declare function registerSettings(ctx: Context, config: KanbanSettingsShape): KanbanSettingsFace;
|
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.8",
|
|
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": {
|
|
@@ -78,54 +78,54 @@
|
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
80
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
81
|
-
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
|
|
82
|
-
"@deepseek-ai/dsh-agent-
|
|
83
|
-
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
84
|
-
"@deepseek-ai/dsh-
|
|
85
|
-
"@deepseek-ai/dsh-
|
|
86
|
-
"@deepseek-ai/dsh-
|
|
87
|
-
"@deepseek-ai/dsh-
|
|
81
|
+
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
82
|
+
"@deepseek-ai/dsh-agent-preset-registry": "^0.1.7-rc.1 || ^0.2.0-rc.1",
|
|
83
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
86
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
87
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
92
|
+
"@deepseek-ai/dsh-client-ui-workspace": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
93
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
94
|
+
"@deepseek-ai/dsh-session": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
95
|
+
"@deepseek-ai/dsh-settings": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
96
|
+
"@deepseek-ai/dsh-skill": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
97
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2 || ^0.2.0-rc.1",
|
|
88
98
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
89
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
|
|
90
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
|
|
91
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
|
|
92
|
-
"@deepseek-ai/dsh-client-ui-layout": "^0.1.5-rc.2",
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2",
|
|
95
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-workspace": "^0.1.5-rc.2",
|
|
97
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
|
|
98
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
|
|
99
99
|
"react": "^18.2.0",
|
|
100
100
|
"react-dom": "^18.2.0"
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
+
"@deepseek-ai/dsh-agent": "0.2.0-rc.1",
|
|
104
|
+
"@deepseek-ai/dsh-agent-preset": "0.2.0-rc.1",
|
|
105
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.1",
|
|
106
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
|
|
107
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.1",
|
|
108
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.1",
|
|
109
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1",
|
|
110
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
|
|
111
|
+
"@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.1",
|
|
112
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1",
|
|
113
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.0-rc.1",
|
|
114
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.0-rc.1",
|
|
115
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
|
|
116
|
+
"@deepseek-ai/dsh-client-ui-workspace": "0.2.0-rc.1",
|
|
117
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1",
|
|
118
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.1",
|
|
119
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.1",
|
|
120
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.1",
|
|
121
|
+
"@deepseek-ai/dsh-skill": "0.2.0-rc.1",
|
|
122
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.1",
|
|
123
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
103
124
|
"@types/node": "^22.0.0",
|
|
104
125
|
"@types/react": "~18.3.1",
|
|
105
126
|
"@types/react-dom": "~18.3.0",
|
|
106
127
|
"tsdown": "^0.22.14",
|
|
107
128
|
"typescript": "^5.9.0",
|
|
108
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
|
|
109
|
-
"@deepseek-ai/dsh-agent-presets": "^0.1.5-rc.2",
|
|
110
|
-
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
|
|
111
|
-
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
112
|
-
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
|
|
113
|
-
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
114
|
-
"@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
|
|
115
|
-
"@deepseek-ai/dsh-skill": "^0.1.5-rc.2",
|
|
116
|
-
"@deepseek-ai/schemastery": "^3.18.2",
|
|
117
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
|
|
118
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
|
|
119
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
|
|
120
|
-
"@deepseek-ai/dsh-client-ui-layout": "^0.1.5-rc.2",
|
|
121
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
|
|
122
|
-
"@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2",
|
|
123
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.5-rc.2",
|
|
124
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
|
|
125
|
-
"@deepseek-ai/dsh-client-ui-workspace": "^0.1.5-rc.2",
|
|
126
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
|
|
127
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
|
|
128
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.2",
|
|
129
129
|
"vitest": "^3.2.0"
|
|
130
130
|
},
|
|
131
131
|
"packageManager": "pnpm@11.21.0"
|
package/src/client/board.css
CHANGED
|
@@ -386,110 +386,3 @@
|
|
|
386
386
|
font-size: 13px;
|
|
387
387
|
line-height: 1.5;
|
|
388
388
|
}
|
|
389
|
-
.kb-footer-action {
|
|
390
|
-
display: inline-flex;
|
|
391
|
-
align-items: center;
|
|
392
|
-
justify-content: center;
|
|
393
|
-
gap: 6px;
|
|
394
|
-
width: 32px;
|
|
395
|
-
height: 32px;
|
|
396
|
-
border-radius: 6px;
|
|
397
|
-
border: none;
|
|
398
|
-
background: transparent;
|
|
399
|
-
/* same foreground as the settings trigger (its trigger css uses this var) */
|
|
400
|
-
color: var(--dsw-alias-label-primary, inherit);
|
|
401
|
-
cursor: pointer;
|
|
402
|
-
}
|
|
403
|
-
.kb-footer-action:hover { background: rgba(128, 128, 128, 0.18); }
|
|
404
|
-
.kb-footer-action-active { background: rgba(59, 130, 246, 0.25); }
|
|
405
|
-
/* Wide mode borrows the settings trigger's metrics (34px tall, 14px label,
|
|
406
|
-
18px icon) so both controls read as siblings on the same line. */
|
|
407
|
-
.kb-footer-action-wide {
|
|
408
|
-
width: auto;
|
|
409
|
-
height: 34px;
|
|
410
|
-
padding: 0 8px;
|
|
411
|
-
border-radius: 12px;
|
|
412
|
-
font-size: 14px;
|
|
413
|
-
}
|
|
414
|
-
.kb-footer-action-wide .kb-footer-label {
|
|
415
|
-
font-size: 14px;
|
|
416
|
-
line-height: 22px;
|
|
417
|
-
}
|
|
418
|
-
/* Theme following is handled by the --dsw-alias-* tokens above: they change
|
|
419
|
-
value with the shell's selected light/dark appearance, so no OS-level
|
|
420
|
-
prefers-color-scheme override is needed (and one would fight the app
|
|
421
|
-
theme when the two disagree). */
|
|
422
|
-
/* Same-line placement WITHOUT moving any DOM: the sidebar foot area becomes
|
|
423
|
-
a reversed flex row, so its LAST child (the settings area) renders on the
|
|
424
|
-
LEFT and the FIRST child (footer actions = the kanban button) on the RIGHT
|
|
425
|
-
— exactly "settings, then kanban". The settings trigger flexes to fill the
|
|
426
|
-
remaining width; the button keeps its content width. */
|
|
427
|
-
.kb-foot-row {
|
|
428
|
-
display: flex;
|
|
429
|
-
flex-direction: row-reverse;
|
|
430
|
-
align-items: stretch;
|
|
431
|
-
gap: 4px;
|
|
432
|
-
}
|
|
433
|
-
.kb-foot-row > div:first-child {
|
|
434
|
-
flex: none;
|
|
435
|
-
width: auto;
|
|
436
|
-
min-width: 0;
|
|
437
|
-
display: flex;
|
|
438
|
-
align-items: center;
|
|
439
|
-
}
|
|
440
|
-
.kb-foot-row > div:last-child {
|
|
441
|
-
flex: 1;
|
|
442
|
-
min-width: 0;
|
|
443
|
-
}
|
|
444
|
-
/* Rail (collapsed sidebar): column-reverse stacks the settings circle on top
|
|
445
|
-
and the kanban icon below it, both centered; the kanban button borrows the
|
|
446
|
-
settings trigger's rail chrome (36px circle) so the two match. */
|
|
447
|
-
.kb-foot-rail {
|
|
448
|
-
flex-direction: column-reverse;
|
|
449
|
-
align-items: center;
|
|
450
|
-
}
|
|
451
|
-
.kb-foot-rail .kb-footer-action {
|
|
452
|
-
width: 36px;
|
|
453
|
-
height: 36px;
|
|
454
|
-
border-radius: 50%;
|
|
455
|
-
margin: 8px 0 10px;
|
|
456
|
-
}
|
|
457
|
-
.kb-footer-label {
|
|
458
|
-
max-width: 110px;
|
|
459
|
-
overflow: hidden;
|
|
460
|
-
text-overflow: ellipsis;
|
|
461
|
-
white-space: nowrap;
|
|
462
|
-
}
|
|
463
|
-
/* Narrow screens (mobile drawer / auto-collapse breakpoint): the drawer's
|
|
464
|
-
foot area is ~256-280px but the footer actions row carries the kanban
|
|
465
|
-
button PLUS mobile-nav's drawer actions (Files / session log), which
|
|
466
|
-
together exceed the row and squeeze the settings trigger to zero width
|
|
467
|
-
(row-reverse overflows left, clipping the button). Below the shell's
|
|
468
|
-
auto-collapse breakpoint, stack the settings row above the actions and
|
|
469
|
-
let the actions wrap so nothing is squeezed or clipped. */
|
|
470
|
-
@media (max-width: 1023px) {
|
|
471
|
-
.kb-foot-row {
|
|
472
|
-
flex-direction: column-reverse;
|
|
473
|
-
align-items: stretch;
|
|
474
|
-
}
|
|
475
|
-
.kb-foot-row > div:first-child {
|
|
476
|
-
flex: none;
|
|
477
|
-
width: 100%;
|
|
478
|
-
display: flex;
|
|
479
|
-
flex-wrap: wrap;
|
|
480
|
-
justify-content: flex-start;
|
|
481
|
-
gap: 4px;
|
|
482
|
-
padding-top: 4px;
|
|
483
|
-
}
|
|
484
|
-
.kb-foot-row > div:last-child {
|
|
485
|
-
flex: none;
|
|
486
|
-
width: 100%;
|
|
487
|
-
}
|
|
488
|
-
/* The footer-actions entries are display:contents wrappers, so the kanban
|
|
489
|
-
button itself is a flex item of the wrapping actions row; pull it to the
|
|
490
|
-
first wrap position so it sits directly below the settings row (the
|
|
491
|
-
mobile-nav drawer actions follow on their own wrapped line). */
|
|
492
|
-
.kb-foot-row .kb-footer-action {
|
|
493
|
-
order: -1;
|
|
494
|
-
}
|
|
495
|
-
}
|
package/src/client/board.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
2
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
|
-
import {
|
|
5
|
+
import { openSessionView } from './kanban-state'
|
|
6
6
|
import { en, zh } from './locales'
|
|
7
7
|
import './board.css'
|
|
8
8
|
|
|
@@ -157,14 +157,10 @@ export function BoardRoot(props: BoardProps): JSX.Element {
|
|
|
157
157
|
}, [cards, refresh, showToast, t])
|
|
158
158
|
|
|
159
159
|
const openSession = useCallback((sessionId: string) => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
;(ctx.sessions as { open(id: string): void }).open(sessionId)
|
|
165
|
-
} catch {
|
|
166
|
-
showToast(t('sessionNotListed'))
|
|
167
|
-
}
|
|
160
|
+
// The shell's view owner retains the session and reveals the Conversation
|
|
161
|
+
// in one action, so the board itself needs no panel teardown; a runtime
|
|
162
|
+
// without that face cannot show the session at all.
|
|
163
|
+
if (!openSessionView(sessionId)) showToast(t('sessionNotListed'))
|
|
168
164
|
}, [showToast, t])
|
|
169
165
|
|
|
170
166
|
if (items.length === 0) {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
|
|
2
|
+
|
|
3
|
+
/** Props every shell icon accepts: square edge in px, color rides currentColor. */
|
|
4
|
+
export interface IconProps {
|
|
5
|
+
size?: number
|
|
6
|
+
className?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** One shell icon component. */
|
|
10
|
+
export type IconComponent = (props: IconProps) => JSX.Element
|
|
11
|
+
|
|
12
|
+
/** The runtime export table, read by capability instead of by one hard-coded name. */
|
|
13
|
+
const table = primitives as unknown as Record<string, IconComponent | undefined>
|
|
14
|
+
|
|
15
|
+
/** First name the runtime actually provides (a missing export is `undefined`). */
|
|
16
|
+
function firstDefined(...names: string[]): IconComponent | undefined {
|
|
17
|
+
for (const name of names) {
|
|
18
|
+
let candidate: IconComponent | undefined
|
|
19
|
+
try {
|
|
20
|
+
candidate = table[name]
|
|
21
|
+
} catch {
|
|
22
|
+
// Some module namespaces reject unknown names instead of yielding
|
|
23
|
+
// `undefined`; a rename must never throw on its way to the fallback.
|
|
24
|
+
continue
|
|
25
|
+
}
|
|
26
|
+
if (typeof candidate === 'function') return candidate
|
|
27
|
+
}
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Self-contained 16×16 checklist glyph (the 0.1.7 artwork) used when the shell
|
|
33
|
+
* primitives expose none of the names above: the footer entry must render
|
|
34
|
+
* something even on a runtime whose icon exports we do not know.
|
|
35
|
+
*/
|
|
36
|
+
function ChecklistFallback({ size = 14, className }: IconProps): JSX.Element {
|
|
37
|
+
return (
|
|
38
|
+
<svg
|
|
39
|
+
width={size}
|
|
40
|
+
height={size}
|
|
41
|
+
className={className}
|
|
42
|
+
viewBox="0 0 16 16"
|
|
43
|
+
fill="none"
|
|
44
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
45
|
+
aria-hidden="true"
|
|
46
|
+
strokeWidth={1}
|
|
47
|
+
>
|
|
48
|
+
<path d="M3.75 6.25C4.7165 6.25 5.5 5.4665 5.5 4.5C5.5 3.5335 4.7165 2.75 3.75 2.75C2.7835 2.75 2 3.5335 2 4.5C2 5.4665 2.7835 6.25 3.75 6.25Z" stroke="currentColor" />
|
|
49
|
+
<path d="M7.5 4.5H13.5" stroke="currentColor" />
|
|
50
|
+
<path d="M3.75 13.25C4.7165 13.25 5.5 12.4665 5.5 11.5C5.5 10.5335 4.7165 9.75 3.75 9.75C2.7835 9.75 2 10.5335 2 11.5C2 12.4665 2.7835 13.25 3.75 13.25Z" stroke="currentColor" />
|
|
51
|
+
<path d="M7.5 11.5H13.5" stroke="currentColor" />
|
|
52
|
+
</svg>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The checklist icon used by the sidebar panel row entry.
|
|
58
|
+
*
|
|
59
|
+
* DSH 0.1.7 renamed every size-suffixed shell icon (`IconChecklistOutline14`)
|
|
60
|
+
* into stroke-weight variants (`…Regular` 1px / `…Medium` 1.3px) inside
|
|
61
|
+
* `@deepseek-ai/dsh-client-ui-primitives`. A stale named import resolves to
|
|
62
|
+
* `undefined` and React then throws "Element type is invalid" while rendering
|
|
63
|
+
* the slot entry — the entry is dropped and the button silently disappears
|
|
64
|
+
* (exactly what upgrading to 0.1.7 did to this plugin). Resolve the name at
|
|
65
|
+
* runtime, newest first, so the next rename degrades to another shipped
|
|
66
|
+
* variant instead of taking the button down with it.
|
|
67
|
+
*/
|
|
68
|
+
export const ChecklistIcon: IconComponent =
|
|
69
|
+
firstDefined(
|
|
70
|
+
'IconChecklistOutlineRegular',
|
|
71
|
+
'IconChecklistOutlineMedium',
|
|
72
|
+
'IconChecklistOutline',
|
|
73
|
+
'IconChecklistOutline14',
|
|
74
|
+
) ?? ChecklistFallback
|