dsh-taskboard 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.
Files changed (49) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +169 -0
  3. package/cordis.patch.yml +12 -0
  4. package/lib/client.js +2085 -0
  5. package/lib/host/execution.js +189 -0
  6. package/lib/host/execution.js.map +1 -0
  7. package/lib/host/protocol-text.js +37 -0
  8. package/lib/host/protocol-text.js.map +1 -0
  9. package/lib/host/routes.js +369 -0
  10. package/lib/host/routes.js.map +1 -0
  11. package/lib/host/scheduler.js +91 -0
  12. package/lib/host/scheduler.js.map +1 -0
  13. package/lib/host/sdk.js +145 -0
  14. package/lib/host/sdk.js.map +1 -0
  15. package/lib/host/store.js +112 -0
  16. package/lib/host/store.js.map +1 -0
  17. package/lib/host/tools.js +620 -0
  18. package/lib/host/tools.js.map +1 -0
  19. package/lib/index.js +91 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/invariant.js +22 -0
  22. package/lib/invariant.js.map +1 -0
  23. package/lib/shared/api.js +9 -0
  24. package/lib/shared/api.js.map +1 -0
  25. package/lib/shared/protocol.js +279 -0
  26. package/lib/shared/protocol.js.map +1 -0
  27. package/package.json +74 -0
  28. package/src/client/api.ts +90 -0
  29. package/src/client/board/NewTaskModal.tsx +8 -0
  30. package/src/client/board/TaskBoard.tsx +184 -0
  31. package/src/client/board/TaskCard.tsx +61 -0
  32. package/src/client/board/TaskDetail.tsx +210 -0
  33. package/src/client/board/TaskFormModal.tsx +257 -0
  34. package/src/client/board-mount.tsx +92 -0
  35. package/src/client/controller.ts +241 -0
  36. package/src/client/index.ts +87 -0
  37. package/src/client/sidebar-entry.ts +165 -0
  38. package/src/client/styles.ts +391 -0
  39. package/src/host/execution.ts +244 -0
  40. package/src/host/protocol-text.ts +37 -0
  41. package/src/host/routes.ts +387 -0
  42. package/src/host/scheduler.ts +107 -0
  43. package/src/host/sdk.ts +200 -0
  44. package/src/host/store.ts +139 -0
  45. package/src/host/tools.ts +631 -0
  46. package/src/index.ts +124 -0
  47. package/src/invariant.ts +22 -0
  48. package/src/shared/api.ts +98 -0
  49. package/src/shared/protocol.ts +475 -0
package/src/index.ts ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Host loader entry for dsh-taskboard.
3
+ *
4
+ * Wiring: the ledger store (one JSON file under the DSH home), the eight
5
+ * `taskboard_*` agent tools, the agent workflow-protocol system-prompt
6
+ * section, the /taskboard JSON+SSE routes (when a webServer is served),
7
+ * the host execution service (fresh in-project sessions, pinned models), and
8
+ * the host-side cron scheduler for scheduled tasks.
9
+ *
10
+ * Export shape follows the dsh-tool-todo lesson: a function/namespace plugin —
11
+ * `name` / `inject` / `apply`, NO default export.
12
+ *
13
+ * @module dsh-taskboard
14
+ */
15
+ import type { Context } from '@deepseek-ai/cordis'
16
+ // Type-only module imports: they load the cordis Context augmentations
17
+ // (ctx.tools / ctx.systemPrompt / ctx.agents) and vanish at compile time —
18
+ // the built host half keeps ZERO runtime @deepseek-ai imports.
19
+ import type {} from '@deepseek-ai/dsh-tools'
20
+ import type {} from '@deepseek-ai/dsh-system-prompt'
21
+ import type {} from '@deepseek-ai/dsh-agent'
22
+ import { PROTOCOL_SECTION_NAME, PROTOCOL_SECTION_ORDER, TASKBOARD_PROTOCOL } from './host/protocol-text.ts'
23
+ import { ExecutionService, type EventsFace } from './host/execution.ts'
24
+ import { registerTaskboardRoutes } from './host/routes.ts'
25
+ import { SchedulerService } from './host/scheduler.ts'
26
+ import { dshHomePath } from './host/sdk.ts'
27
+ import { TaskStore } from './host/store.ts'
28
+ import { registerTaskboardTools, workspaceFace } from './host/tools.ts'
29
+
30
+ /** Ledger file name under the DSH home. */
31
+ export const LEDGER_FILE = 'dsh-taskboard.json'
32
+
33
+ /** Cordis plugin name. */
34
+ export const name = 'dsh-taskboard'
35
+
36
+ /** Required host services (tool registry + prompt assembly). */
37
+ export const inject = ['tools', 'systemPrompt']
38
+
39
+ /**
40
+ * Mount the host half.
41
+ * @param ctx - the plugin context (tools + systemPrompt injected).
42
+ */
43
+ export function apply(ctx: Context): void {
44
+ const store = new TaskStore({ file: dshHomePath(LEDGER_FILE) })
45
+ const now = () => Date.now()
46
+
47
+ // Agent workflow protocol (claim discipline, retry rules, done-gate).
48
+ const disposeSection = ctx.systemPrompt.section({
49
+ name: PROTOCOL_SECTION_NAME,
50
+ order: PROTOCOL_SECTION_ORDER,
51
+ text: TASKBOARD_PROTOCOL,
52
+ })
53
+ ctx.effect(() => disposeSection, 'dsh-taskboard: protocol section')
54
+
55
+ // Tools, routes, execution, and the scheduler all come up with the
56
+ // workspace registry (claim boundary + project execution need it).
57
+ ctx.inject(['workspaceRegistry'], (wsCtx: Context) => {
58
+ const disposers: Array<() => void> = []
59
+ disposers.push(...registerTaskboardTools(wsCtx, {
60
+ store,
61
+ workspaces: workspaceFace(wsCtx.workspaceRegistry),
62
+ now,
63
+ }))
64
+
65
+ // Settlement listener over the session event bus.
66
+ const events: EventsFace = {
67
+ onSessionEvent: (listener) => wsCtx.on('session/event', (session, event) => {
68
+ listener(session.id, event as { type: string; data?: unknown })
69
+ }),
70
+ }
71
+
72
+ wsCtx.inject(['agents'], (agentCtx: Context) => {
73
+ const execution = new ExecutionService({
74
+ store,
75
+ agents: {
76
+ create: (options): Promise<never> => agentCtx.agents.create(options as never) as Promise<never>,
77
+ },
78
+ workspaces: {
79
+ get: id => workspaceFace(wsCtx.workspaceRegistry).get(id),
80
+ attach: async (workspaceId, sessionId) => {
81
+ const ws = wsCtx.workspaceRegistry.get(workspaceId as never)
82
+ if (ws !== undefined) await ws.attachSession(sessionId as never)
83
+ },
84
+ },
85
+ events,
86
+ now,
87
+ defaultModel: () => {
88
+ try {
89
+ const selection = agentCtx.get('agentDefaultModel') as { currentSelection?: () => { provider: string; model: string } | undefined } | undefined
90
+ const read = selection?.currentSelection
91
+ return read === undefined ? undefined : read.call(selection)
92
+ } catch { return undefined }
93
+ },
94
+ })
95
+
96
+ // /dsh-taskboard routes (the run action reaches the execution service).
97
+ let disposeRoutes: (() => void) | undefined
98
+ agentCtx.inject(['webServer'], (webCtx: Context) => {
99
+ disposeRoutes = registerTaskboardRoutes(webCtx, {
100
+ store,
101
+ workspaces: workspaceFace(wsCtx.workspaceRegistry),
102
+ now,
103
+ run: (taskId: string) => execution.run(taskId, 'manual'),
104
+ })
105
+ return () => disposeRoutes?.()
106
+ })
107
+
108
+ // Host-side cron scheduler: due scheduled tasks execute even with no
109
+ // browser open.
110
+ const scheduler = new SchedulerService({ store, execution, now })
111
+ scheduler.start()
112
+ disposers.push(() => scheduler.dispose())
113
+
114
+ return () => {
115
+ disposeRoutes?.()
116
+ for (const dispose of disposers.splice(0)) dispose()
117
+ }
118
+ })
119
+
120
+ return () => {
121
+ for (const dispose of disposers.splice(0)) dispose()
122
+ }
123
+ })
124
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Invariant companion for dsh-taskboard.
3
+ *
4
+ * DSH convention (see dsh-base / dsh-workspace): packages may ship an
5
+ * `./invariant` export — a minimal companion plugin that reserves package
6
+ * ownership in compositions that load invariants without the full plugin.
7
+ * No behavior in P0; the real plugin carries everything.
8
+ */
9
+
10
+ /** Cordis plugin name. */
11
+ export const name = 'dsh-taskboard-invariant'
12
+
13
+ /** No services required. */
14
+ export const inject: string[] = []
15
+
16
+ /**
17
+ * Register the invariant companion (no-op in P0).
18
+ * @param _ctx - the plugin context.
19
+ */
20
+ export function apply(_ctx: unknown): void {
21
+ /* nothing to reserve yet */
22
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Wire contract for the /taskboard host routes: the JSON envelope,
3
+ * request/response shapes, and SSE event payloads shared by the host routes
4
+ * and the browser client.
5
+ *
6
+ * @module dsh-taskboard/shared/api
7
+ */
8
+ import type { TaskLedger, TaskRecord, TaskSummary } from './protocol.ts'
9
+
10
+ export type { TaskRecord }
11
+
12
+ /** Route prefix on the shared DSH webserver (same origin as the GUI). */
13
+ export const ROUTE_PREFIX = '/dsh-taskboard'
14
+
15
+ /** SSE stream path (exact route; longest-prefix wins keep it disjoint). */
16
+ export const SSE_PATH = '/dsh-taskboard/events'
17
+
18
+ /** Stable error codes (mirror the tool-level codes plus HTTP mapping). */
19
+ export type ApiErrorCode =
20
+ | 'invalid_input'
21
+ | 'not_found'
22
+ | 'version_conflict'
23
+ | 'invalid_transition'
24
+ | 'forbidden'
25
+ | 'internal'
26
+
27
+ /** Success envelope. */
28
+ export type ApiOk<T> = { ok: true; value: T }
29
+
30
+ /** Failure envelope. */
31
+ export type ApiFail = { ok: false; error: { code: ApiErrorCode; message: string } }
32
+
33
+ /** The envelope either way. */
34
+ export type ApiResult<T> = ApiOk<T> | ApiFail
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // payloads
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /** Full-state response (the reconnect baseline after an SSE gap). */
41
+ export type StateResponse = TaskLedger
42
+
43
+ /** Workspace listing for the UI pickers. */
44
+ export type WorkspaceView = { id: string; path: string; title: string; sessionCount: number }
45
+
46
+ /** Create-task request body (actor is always the GUI user). */
47
+ export type CreateTaskBody = {
48
+ title: string
49
+ workspaceId: string
50
+ urgency: string
51
+ description?: string
52
+ prompt?: string
53
+ execution?: { mode?: string; cron?: string }
54
+ model?: { provider: string; model: string }
55
+ }
56
+
57
+ /** Update-task request body (ifVersion mandatory). */
58
+ export type UpdateTaskBody = {
59
+ ifVersion: number
60
+ title?: string
61
+ description?: string
62
+ prompt?: string
63
+ urgency?: string
64
+ blocked?: boolean
65
+ /** Rebind the task to another project (GUI owner surface only). */
66
+ workspaceId?: string
67
+ execution?: { mode?: string; cron?: string }
68
+ model?: { provider: string; model: string } | null
69
+ }
70
+
71
+ /** Move-task request body (ifVersion mandatory; the user MAY move to done). */
72
+ export type MoveTaskBody = { ifVersion: number; status: string }
73
+
74
+ /** Comment request body. */
75
+ export type CommentBody = { body: string }
76
+
77
+ /** Delete request body (purge=true physically removes a trashed task). */
78
+ export type DeleteTaskBody = { ifVersion?: number; purge?: boolean }
79
+
80
+ /** Run request body (P3). */
81
+ export type RunTaskBody = Record<string, never>
82
+
83
+ /** One task (full record) response. */
84
+ export type TaskResponse = TaskRecord
85
+
86
+ /** Summary response used by list-ish endpoints. */
87
+ export type SummaryResponse = { tasks: TaskSummary[] }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // SSE
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /** Change frame pushed on every committed ledger mutation. */
94
+ export type ChangeEvent = {
95
+ revision: number
96
+ kind: 'task-created' | 'task-updated' | 'task-moved' | 'task-deleted' | 'comment-added' | 'execution-recorded'
97
+ tasks: TaskSummary[]
98
+ }