@fonlan/dsh-task-kanban 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +1128 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +1514 -0
- package/lib/types/index.d.ts +14 -0
- package/lib/types/server/git.d.ts +16 -0
- package/lib/types/server/models.d.ts +4 -0
- package/lib/types/server/prompts.d.ts +7 -0
- package/lib/types/server/rpc.d.ts +4 -0
- package/lib/types/server/runner.d.ts +84 -0
- package/lib/types/server/settings.d.ts +27 -0
- package/lib/types/server/task-store.d.ts +27 -0
- package/lib/types/server/tools.d.ts +11 -0
- package/lib/types/shared/card.d.ts +86 -0
- package/lib/types/shared/lanes.d.ts +9 -0
- package/lib/types/shared/plan.d.ts +12 -0
- package/package.json +105 -0
- package/src/client/api.ts +42 -0
- package/src/client/board.css +361 -0
- package/src/client/board.tsx +516 -0
- package/src/client/index.tsx +47 -0
- package/src/client/kanban-state.ts +104 -0
- package/src/client/locales.ts +127 -0
- package/src/client/sections.tsx +209 -0
- package/src/client/slot-augment.d.ts +15 -0
- package/src/index.ts +34 -0
- package/src/server/git.ts +68 -0
- package/src/server/models.ts +20 -0
- package/src/server/prompts.ts +54 -0
- package/src/server/rpc.ts +167 -0
- package/src/server/runner.ts +834 -0
- package/src/server/settings.ts +69 -0
- package/src/server/task-store.ts +128 -0
- package/src/server/tools.ts +131 -0
- package/src/shared/card.ts +115 -0
- package/src/shared/lanes.ts +43 -0
- package/src/shared/plan.ts +54 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fonlan/dsh-task-kanban host half: the task kanban engine.
|
|
3
|
+
*
|
|
4
|
+
* Mounts a file-backed task store under each workspace's
|
|
5
|
+
* `.dsh/task-kanban/`, runs refinement/phase/merge sessions through
|
|
6
|
+
* `ctx.agents` (standard coding-agent preset + kanban-scoped tools), and
|
|
7
|
+
* serves the fenced JSON API the board UI calls.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import z from '@deepseek-ai/schemastery';
|
|
11
|
+
export declare const name = "@fonlan/dsh-task-kanban";
|
|
12
|
+
export declare const inject: string[];
|
|
13
|
+
export declare const Config: z<Schemastery.ObjectS<{}>, Schemastery.ObjectT<{}>>;
|
|
14
|
+
export declare function apply(ctx: Context, _config: unknown): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface GitResult {
|
|
2
|
+
code: number;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function runGit(cwd: string, args: string[]): Promise<GitResult>;
|
|
7
|
+
export declare function isGitRepo(dir: string): Promise<boolean>;
|
|
8
|
+
export declare function hasBranch(dir: string, name: string): Promise<boolean>;
|
|
9
|
+
export declare function detectBaseRef(dir: string): Promise<string | undefined>;
|
|
10
|
+
export declare function currentBranch(dir: string): Promise<string | undefined>;
|
|
11
|
+
export declare function revParse(dir: string, ref: string): Promise<string | undefined>;
|
|
12
|
+
/** True when the working tree has tracked changes (staged or unstaged). */
|
|
13
|
+
export declare function isTreeDirty(dir: string): Promise<boolean>;
|
|
14
|
+
export declare function hasStashMessage(dir: string, message: string): Promise<boolean>;
|
|
15
|
+
/** Unmerged path list after a conflicted merge (empty when clean). */
|
|
16
|
+
export declare function unmergedPaths(dir: string): Promise<string>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { KanbanCard } from '../shared/card.js';
|
|
2
|
+
/** Refinement session prompt: analyze the repo read-only and write back a plan. */
|
|
3
|
+
export declare function refinementPrompt(requirement: string, workspacePath: string): string;
|
|
4
|
+
/** Phase implementation session prompt; identical on retries (the model reads git status/diff itself). */
|
|
5
|
+
export declare function phasePrompt(card: KanbanCard, phaseIndex: number, workdir: string): string;
|
|
6
|
+
/** Merge-conflict resolution session prompt: resolve conflicts only, never commit. */
|
|
7
|
+
export declare function mergePrompt(workdir: string, conflicts: string): string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { KanbanRunner } from './runner.js';
|
|
3
|
+
import type { KanbanSettingsFace } from './settings.js';
|
|
4
|
+
export declare function registerApiRoutes(ctx: Context, runner: KanbanRunner, settings: KanbanSettingsFace): () => void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { KanbanCard, Lane, Plan } from '../shared/card.js';
|
|
3
|
+
import type { TaskStore } from './task-store.js';
|
|
4
|
+
import type { KanbanSettingsFace } from './settings.js';
|
|
5
|
+
import { type KanbanToolResolver } from './tools.js';
|
|
6
|
+
export declare class KanbanRunner implements KanbanToolResolver {
|
|
7
|
+
private ctx;
|
|
8
|
+
private store;
|
|
9
|
+
private settings;
|
|
10
|
+
private runningByWs;
|
|
11
|
+
private slots;
|
|
12
|
+
private pumpTimer;
|
|
13
|
+
/** sessionId → cardId (sessions created for this kanban). */
|
|
14
|
+
private sessionCards;
|
|
15
|
+
/** sessionId → true once the scoped completion tool ran. */
|
|
16
|
+
private completed;
|
|
17
|
+
private merging;
|
|
18
|
+
/** Retained agent handles (kept so ended sessions stay in the store/UI). */
|
|
19
|
+
private handles;
|
|
20
|
+
constructor(ctx: Context, store: TaskStore, settings: KanbanSettingsFace);
|
|
21
|
+
register(ctx: Context): void;
|
|
22
|
+
start(): void;
|
|
23
|
+
stop(): void;
|
|
24
|
+
private newSessionId;
|
|
25
|
+
private asUserMessage;
|
|
26
|
+
private workspaceList;
|
|
27
|
+
cardOfSession(sessionId?: string): string | undefined;
|
|
28
|
+
writePlan(cardId: string, plan: Plan): Promise<void>;
|
|
29
|
+
phaseComplete(sessionId: string, summary: string): Promise<string>;
|
|
30
|
+
mergeResolved(sessionId: string): Promise<void>;
|
|
31
|
+
private toolResolver;
|
|
32
|
+
recoverInterrupted(): Promise<void>;
|
|
33
|
+
listCards(workspacePath: string): Promise<KanbanCard[]>;
|
|
34
|
+
createTask(input: {
|
|
35
|
+
workspacePath: string;
|
|
36
|
+
requirement: string;
|
|
37
|
+
model: string;
|
|
38
|
+
provider?: string;
|
|
39
|
+
}): Promise<KanbanCard>;
|
|
40
|
+
moveTask(cardId: string, toLane: Lane): Promise<{
|
|
41
|
+
ok: boolean;
|
|
42
|
+
message?: string;
|
|
43
|
+
}>;
|
|
44
|
+
stopTask(cardId: string): Promise<{
|
|
45
|
+
ok: boolean;
|
|
46
|
+
message?: string;
|
|
47
|
+
}>;
|
|
48
|
+
retryTask(cardId: string): Promise<{
|
|
49
|
+
ok: boolean;
|
|
50
|
+
message?: string;
|
|
51
|
+
}>;
|
|
52
|
+
deleteTask(cardId: string): Promise<{
|
|
53
|
+
ok: boolean;
|
|
54
|
+
message?: string;
|
|
55
|
+
}>;
|
|
56
|
+
private findCard;
|
|
57
|
+
private maxParallel;
|
|
58
|
+
pump(): Promise<void>;
|
|
59
|
+
private requestStop;
|
|
60
|
+
private fail;
|
|
61
|
+
private runRefinement;
|
|
62
|
+
private createAgent;
|
|
63
|
+
private keepHandle;
|
|
64
|
+
/**
|
|
65
|
+
* Account the refinement session to its host workspace so it shows in that
|
|
66
|
+
* workspace's session list. The session's cwd IS the workspace root, so the
|
|
67
|
+
* registry's strict `cwd === workspace.path` check passes. Best-effort only:
|
|
68
|
+
* an unregistered workspace (or a failing registry) must not break the card.
|
|
69
|
+
*/
|
|
70
|
+
private attachRefinementSession;
|
|
71
|
+
/** The host workspace registry, whichever service name carries it. */
|
|
72
|
+
private attachRegistry;
|
|
73
|
+
/** Resolve the provider+model route for an agent (both are required). */
|
|
74
|
+
private modelRoute;
|
|
75
|
+
private runCard;
|
|
76
|
+
private runPhase;
|
|
77
|
+
private resolveConflictsIn;
|
|
78
|
+
/** Check resolved files for leftover conflict markers. */
|
|
79
|
+
private verifyConflictMarkers;
|
|
80
|
+
runMerge(cardId: string, wsPath: string): Promise<boolean>;
|
|
81
|
+
private mergeCard;
|
|
82
|
+
private runMergeSession;
|
|
83
|
+
private ensureGitignoreDsh;
|
|
84
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing plugin settings (global parallel worker count, default model),
|
|
3
|
+
* persisted through the settings service namespace `task-kanban`. The DSH
|
|
4
|
+
* settings service requires a lowercase kebab-case namespace
|
|
5
|
+
* (/^[a-z][a-z0-9-]*$/), so the scoped package name cannot be used here.
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
import z from '@deepseek-ai/schemastery';
|
|
9
|
+
import type { KanbanSettingsShape } from '../shared/card.js';
|
|
10
|
+
export declare const KANBAN_SETTINGS_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
11
|
+
export declare const KanbanSettingsSchema: z<Schemastery.ObjectS<{
|
|
12
|
+
maxParallelWorkers: z<number, number>;
|
|
13
|
+
defaultModel: z<string, string>;
|
|
14
|
+
}>, Schemastery.ObjectT<{
|
|
15
|
+
maxParallelWorkers: z<number, number>;
|
|
16
|
+
defaultModel: z<string, string>;
|
|
17
|
+
}>>;
|
|
18
|
+
export interface KanbanSettingsFace {
|
|
19
|
+
get(): KanbanSettingsShape;
|
|
20
|
+
update(patch: Partial<KanbanSettingsShape>): Promise<void>;
|
|
21
|
+
/** Default provider/model route from the agent-default-model settings namespace. */
|
|
22
|
+
defaultModelRoute(): {
|
|
23
|
+
provider?: string;
|
|
24
|
+
model?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export declare function registerSettings(ctx: Context): KanbanSettingsFace;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type KanbanCard } from '../shared/card.js';
|
|
2
|
+
/**
|
|
3
|
+
* File-backed task store: one JSON file per card under
|
|
4
|
+
* `<workspace>/.dsh/task-kanban/tasks/<id>.json`. In-process cache plus
|
|
5
|
+
* atomic (tmp+rename) writes, with a per-card mutex so worker and RPC
|
|
6
|
+
* mutations never interleave.
|
|
7
|
+
*/
|
|
8
|
+
export declare class TaskStore {
|
|
9
|
+
private cache;
|
|
10
|
+
private locks;
|
|
11
|
+
private taskDir;
|
|
12
|
+
private fileOf;
|
|
13
|
+
private withLock;
|
|
14
|
+
list(workspacePath: string): Promise<KanbanCard[]>;
|
|
15
|
+
get(workspacePath: string, id: string): Promise<KanbanCard | undefined>;
|
|
16
|
+
create(input: {
|
|
17
|
+
workspacePath: string;
|
|
18
|
+
requirement: string;
|
|
19
|
+
model: string;
|
|
20
|
+
provider?: string;
|
|
21
|
+
status?: KanbanCard['status'];
|
|
22
|
+
}): Promise<KanbanCard>;
|
|
23
|
+
/** Run a mutation under the card's lock; returns the updated card. */
|
|
24
|
+
mutate(id: string, fn: (card: KanbanCard) => void | Promise<void>, workspacePath?: string): Promise<KanbanCard | undefined>;
|
|
25
|
+
private write;
|
|
26
|
+
remove(workspacePath: string, id: string): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { Plan } from '../shared/card.js';
|
|
3
|
+
export interface KanbanToolResolver {
|
|
4
|
+
/** Resolve the card id attached to a calling session id. */
|
|
5
|
+
cardOfSession(sessionId?: string): string | undefined;
|
|
6
|
+
writePlan(cardId: string, plan: Plan): Promise<void>;
|
|
7
|
+
/** Mark the phase done; returns the phase id for the tool output. */
|
|
8
|
+
phaseComplete(sessionId: string, summary: string): Promise<string>;
|
|
9
|
+
mergeResolved(sessionId: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function registerKanbanTools(agentCtx: Context, resolver: KanbanToolResolver): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared card/plan vocabulary used by both the server half (task store,
|
|
3
|
+
* runners) and the client half (board UI). Pure types: no runtime imports so
|
|
4
|
+
* it survives the client bundle's purity gate and gets inlined into both.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CARD_SCHEMA_VERSION = 1;
|
|
7
|
+
/** Coarse card lifecycle status. */
|
|
8
|
+
export type CardStatus = 'draft' | 'refining' | 'planned' | 'queued' | 'running' | 'completed' | 'merging' | 'merged' | 'error';
|
|
9
|
+
/** Error kinds; each maps back to a resume action (retry/stop/delete). */
|
|
10
|
+
export type ErrorKind = 'refine_failed' | 'phase_failed' | 'interrupted' | 'merge_failed' | 'no_base_branch' | 'worktree_failed' | 'create_failed';
|
|
11
|
+
export interface CardError {
|
|
12
|
+
kind: ErrorKind;
|
|
13
|
+
message: string;
|
|
14
|
+
at: number;
|
|
15
|
+
/** Lane the card was in when it failed (interrupted cards need this). */
|
|
16
|
+
stage?: Lane;
|
|
17
|
+
}
|
|
18
|
+
/** One phase of a plan; phases execute strictly serially, each in its own session. */
|
|
19
|
+
export interface PlanPhase {
|
|
20
|
+
id: string;
|
|
21
|
+
title: string;
|
|
22
|
+
goal: string;
|
|
23
|
+
}
|
|
24
|
+
export interface Plan {
|
|
25
|
+
schemaVersion: number;
|
|
26
|
+
title: string;
|
|
27
|
+
summary: string;
|
|
28
|
+
phases: PlanPhase[];
|
|
29
|
+
}
|
|
30
|
+
/** One session attempt of a phase (retries append new attempts). */
|
|
31
|
+
export interface PhaseAttempt {
|
|
32
|
+
phaseIndex: number;
|
|
33
|
+
sessionId: string;
|
|
34
|
+
summary?: string;
|
|
35
|
+
startedAt: number;
|
|
36
|
+
completedAt?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Merge flow steps; retry resumes from the recorded step. */
|
|
39
|
+
export type MergeStep = 'prepare' | 'wt-merge' | 'ws-merge' | 'pop' | 'cleanup';
|
|
40
|
+
export interface MergeState {
|
|
41
|
+
step: MergeStep;
|
|
42
|
+
stashApplied?: boolean;
|
|
43
|
+
mergeCommit?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface KanbanCard {
|
|
46
|
+
schemaVersion: number;
|
|
47
|
+
id: string;
|
|
48
|
+
workspacePath: string;
|
|
49
|
+
requirement: string;
|
|
50
|
+
model: string;
|
|
51
|
+
/** Provider route for the model (from the modal selection). */
|
|
52
|
+
provider?: string;
|
|
53
|
+
status: CardStatus;
|
|
54
|
+
error?: CardError;
|
|
55
|
+
/** Git-mode: implementation happens in a worktree and merges back. */
|
|
56
|
+
gitMode?: boolean;
|
|
57
|
+
baseRef?: string;
|
|
58
|
+
baseSha?: string;
|
|
59
|
+
branch?: string;
|
|
60
|
+
worktreePath?: string;
|
|
61
|
+
plan?: Plan;
|
|
62
|
+
/** Index of the phase currently running / to resume from. */
|
|
63
|
+
currentPhase: number;
|
|
64
|
+
phaseCount: number;
|
|
65
|
+
sessions: {
|
|
66
|
+
refinement: string[];
|
|
67
|
+
phases: PhaseAttempt[];
|
|
68
|
+
merge: string[];
|
|
69
|
+
};
|
|
70
|
+
merge?: MergeState;
|
|
71
|
+
queuedAt?: number;
|
|
72
|
+
createdAt: number;
|
|
73
|
+
stoppedAt?: number;
|
|
74
|
+
}
|
|
75
|
+
/** The five board lanes. */
|
|
76
|
+
export type Lane = 'demand' | 'queue' | 'running' | 'completed' | 'merged';
|
|
77
|
+
/** Model entry for the new-task dropdown. */
|
|
78
|
+
export interface ModelOption {
|
|
79
|
+
provider: string;
|
|
80
|
+
id: string;
|
|
81
|
+
name?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface KanbanSettingsShape {
|
|
84
|
+
maxParallelWorkers: number;
|
|
85
|
+
defaultModel: string;
|
|
86
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lane mapping and drag rules (shared by the board UI and tests).
|
|
3
|
+
*/
|
|
4
|
+
import type { KanbanCard, Lane } from './card.js';
|
|
5
|
+
/** Which lane a card renders in, based on status + error stage. */
|
|
6
|
+
export declare function laneOf(card: KanbanCard): Lane;
|
|
7
|
+
/** Lanes a drop onto `to` may come from. */
|
|
8
|
+
export declare const DROP_RULES: Record<Lane, Lane[]>;
|
|
9
|
+
export declare function canDrop(from: Lane, to: Lane): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan payload validation (shared by the write-back tool and tests).
|
|
3
|
+
*/
|
|
4
|
+
import type { Plan } from './card.js';
|
|
5
|
+
export type ValidatePlanResult = {
|
|
6
|
+
ok: true;
|
|
7
|
+
plan: Plan;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function validatePlan(input: unknown): ValidatePlanResult;
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fonlan/dsh-task-kanban",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/fonlan/dsh-task-kanban.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/fonlan/dsh-task-kanban/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/fonlan/dsh-task-kanban",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"dsh",
|
|
16
|
+
"kanban",
|
|
17
|
+
"plugin",
|
|
18
|
+
"task-management",
|
|
19
|
+
"worktree"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "lib/index.js",
|
|
23
|
+
"types": "lib/types/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./lib/types/index.d.ts",
|
|
27
|
+
"default": "./lib/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./client": {
|
|
30
|
+
"types": "./lib/types/client/index.d.ts",
|
|
31
|
+
"default": "./lib/client.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"lib",
|
|
37
|
+
"src",
|
|
38
|
+
"cordis.patch.yml",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown",
|
|
44
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.client.json",
|
|
45
|
+
"bundle": "tsdown",
|
|
46
|
+
"watch": "tsdown --watch",
|
|
47
|
+
"test": "vitest run"
|
|
48
|
+
},
|
|
49
|
+
"dsh": {
|
|
50
|
+
"bundle": {
|
|
51
|
+
"patch": "./cordis.patch.yml"
|
|
52
|
+
},
|
|
53
|
+
"client": {
|
|
54
|
+
"platform": "web",
|
|
55
|
+
"inject": [
|
|
56
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
57
|
+
"@deepseek-ai/dsh-client-locale",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-primitives",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
64
|
+
"@deepseek-ai/dsh-client-web-react"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
70
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
|
|
71
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6",
|
|
72
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
73
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
74
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
75
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
76
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
78
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
|
|
81
|
+
"react": "^18.2.0",
|
|
82
|
+
"react-dom": "^18.2.0"
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@types/node": "^22.0.0",
|
|
86
|
+
"@types/react": "~18.3.1",
|
|
87
|
+
"@types/react-dom": "~18.3.0",
|
|
88
|
+
"tsdown": "^0.22.14",
|
|
89
|
+
"typescript": "^5.9.0",
|
|
90
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
91
|
+
"@deepseek-ai/dsh-agent-presets": "^0.1.0-rc.6",
|
|
92
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
|
|
93
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
94
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
95
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
96
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
97
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
98
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.6",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.1.0-rc.6",
|
|
101
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
102
|
+
"vitest": "^3.2.0"
|
|
103
|
+
},
|
|
104
|
+
"packageManager": "pnpm@11.21.0"
|
|
105
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { KanbanCard, KanbanSettingsShape, Lane, ModelOption } from '../shared/card'
|
|
2
|
+
|
|
3
|
+
export class KanbanApiError extends Error {
|
|
4
|
+
constructor(readonly code: string, message: string) {
|
|
5
|
+
super(message)
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function call<T>(method: string, payload: Record<string, unknown> = {}): Promise<T> {
|
|
10
|
+
let response: Response
|
|
11
|
+
try {
|
|
12
|
+
response = await fetch(`/plugins/@fonlan/dsh-task-kanban/api/${method}`, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'content-type': 'application/json' },
|
|
15
|
+
body: JSON.stringify(payload),
|
|
16
|
+
})
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new KanbanApiError('network', error instanceof Error ? error.message : String(error))
|
|
19
|
+
}
|
|
20
|
+
const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null
|
|
21
|
+
= await response.json().catch(() => null)
|
|
22
|
+
if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {
|
|
23
|
+
throw new KanbanApiError(
|
|
24
|
+
parsed?.error?.code ?? 'http',
|
|
25
|
+
parsed?.error?.message ?? `HTTP ${response.status}`,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
return parsed.value as T
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const api = {
|
|
32
|
+
list: (workspacePath: string) => call<KanbanCard[]>('list', { workspacePath }),
|
|
33
|
+
create: (workspacePath: string, requirement: string, model: string, provider?: string) =>
|
|
34
|
+
call<KanbanCard>('create', { workspacePath, requirement, model, provider }),
|
|
35
|
+
move: (cardId: string, toLane: Lane) => call<{ ok: boolean; message?: string }>('move', { cardId, toLane }),
|
|
36
|
+
stop: (cardId: string) => call<{ ok: boolean; message?: string }>('stop', { cardId }),
|
|
37
|
+
retry: (cardId: string) => call<{ ok: boolean; message?: string }>('retry', { cardId }),
|
|
38
|
+
remove: (cardId: string) => call<{ ok: boolean; message?: string }>('remove', { cardId }),
|
|
39
|
+
settingsGet: () => call<KanbanSettingsShape>('settings.get'),
|
|
40
|
+
settingsSet: (patch: Partial<KanbanSettingsShape>) => call<KanbanSettingsShape>('settings.set', patch),
|
|
41
|
+
models: () => call<ModelOption[]>('models.list'),
|
|
42
|
+
}
|