@dennisrongo/dsh-todo 0.3.0 → 0.4.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/lib/index.js CHANGED
@@ -135,6 +135,7 @@ function migrateSchema(db) {
135
135
  add("release", "release TEXT");
136
136
  add("sprint", "sprint TEXT");
137
137
  add("due_date", "due_date TEXT");
138
+ add("session_id", "session_id TEXT");
138
139
  if (addedTitle && columns.has("text")) {
139
140
  db.exec("UPDATE todo SET title = text WHERE title IS NULL");
140
141
  }
@@ -151,7 +152,7 @@ function readList(db) {
151
152
  const updatedAt = Number(db.prepare("SELECT value FROM meta WHERE key = 'updatedAt'").get()?.value ?? 0);
152
153
  const rows = db.prepare(
153
154
  `SELECT id, title, description, status, priority, release, sprint, due_date,
154
- created_at, completed_at, archived_at
155
+ session_id, created_at, completed_at, archived_at
155
156
  FROM todo ORDER BY position ASC`
156
157
  ).all();
157
158
  const text = /* @__PURE__ */ __name((v) => v === null || v === void 0 ? void 0 : String(v), "text");
@@ -166,6 +167,7 @@ function readList(db) {
166
167
  ...normalizeLabel(row.release) !== void 0 ? { release: normalizeLabel(row.release) } : {},
167
168
  ...normalizeLabel(row.sprint) !== void 0 ? { sprint: normalizeLabel(row.sprint) } : {},
168
169
  ...normalizeDueDate(row.due_date) !== void 0 ? { dueDate: normalizeDueDate(row.due_date) } : {},
170
+ ...text(row.session_id) !== void 0 ? { sessionId: text(row.session_id) } : {},
169
171
  createdAt: Number(row.created_at),
170
172
  ...row.completed_at !== null && row.completed_at !== void 0 ? { completedAt: Number(row.completed_at) } : {},
171
173
  ...row.archived_at !== null && row.archived_at !== void 0 ? { archivedAt: Number(row.archived_at) } : {}
@@ -180,8 +182,8 @@ function writeList(db, items, revision, updatedAt = Date.now()) {
180
182
  db.prepare("DELETE FROM todo").run();
181
183
  const insert = db.prepare(
182
184
  `INSERT INTO todo (id, title, description, status, priority, release, sprint, due_date,
183
- text, done, created_at, completed_at, archived_at, position)
184
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
185
+ session_id, text, done, created_at, completed_at, archived_at, position)
186
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
185
187
  );
186
188
  items.forEach((item, index) => {
187
189
  insert.run(
@@ -193,6 +195,7 @@ function writeList(db, items, revision, updatedAt = Date.now()) {
193
195
  item.release ?? null,
194
196
  item.sprint ?? null,
195
197
  item.dueDate ?? null,
198
+ item.sessionId ?? null,
196
199
  item.title,
197
200
  item.status === "done" ? 1 : 0,
198
201
  item.createdAt,
@@ -223,6 +226,7 @@ var todoItemSchema = z.object({
223
226
  release: z.string().max(MAX_LABEL).optional(),
224
227
  sprint: z.string().max(MAX_LABEL).optional(),
225
228
  dueDate: z.string().optional(),
229
+ sessionId: z.string().optional(),
226
230
  createdAt: z.number(),
227
231
  completedAt: z.number().optional(),
228
232
  archivedAt: z.number().optional()
@@ -439,6 +443,7 @@ function sanitizeItems(value) {
439
443
  const release = normalizeLabel(e.release);
440
444
  const sprint = normalizeLabel(e.sprint);
441
445
  const dueDate = normalizeDueDate(e.dueDate);
446
+ const sessionId = typeof e.sessionId === "string" && e.sessionId.length > 0 ? e.sessionId.slice(0, MAX_LABEL) : void 0;
442
447
  const completedAt = typeof e.completedAt === "number" ? e.completedAt : void 0;
443
448
  const archivedAt = typeof e.archivedAt === "number" ? e.archivedAt : void 0;
444
449
  out.push({
@@ -450,6 +455,7 @@ function sanitizeItems(value) {
450
455
  ...release !== void 0 ? { release } : {},
451
456
  ...sprint !== void 0 ? { sprint } : {},
452
457
  ...dueDate !== void 0 ? { dueDate } : {},
458
+ ...sessionId !== void 0 ? { sessionId } : {},
453
459
  createdAt: typeof e.createdAt === "number" ? e.createdAt : 0,
454
460
  // completedAt is meaningless on an unfinished item; drop it rather than store a lie.
455
461
  ...done && completedAt !== void 0 ? { completedAt } : {},
package/lib/launch.js ADDED
@@ -0,0 +1,109 @@
1
+ // src/launch.ts
2
+ function composePrompt(item) {
3
+ const parts = [`# ${item.title}`];
4
+ const description = item.description?.trim();
5
+ if (description) parts.push(description);
6
+ const context = [];
7
+ if (item.priority) context.push(`Priority: ${item.priority.toUpperCase()}`);
8
+ if (item.release) context.push(`Release: ${item.release}`);
9
+ if (item.sprint) context.push(`Sprint: ${item.sprint}`);
10
+ if (item.dueDate) context.push(`Due: ${item.dueDate}`);
11
+ if (context.length > 0) parts.push(context.join(" \xB7 "));
12
+ return parts.join("\n\n");
13
+ }
14
+ var MAX_SESSION_TITLE = 80;
15
+ function sessionTitleFor(item) {
16
+ const normalized = item.title.replace(/\s+/g, " ").trim();
17
+ if (normalized.length === 0) return void 0;
18
+ if (normalized.length <= MAX_SESSION_TITLE) return normalized;
19
+ const clipped = normalized.slice(0, MAX_SESSION_TITLE);
20
+ const lastSpace = clipped.lastIndexOf(" ");
21
+ return `${(lastSpace > MAX_SESSION_TITLE - 20 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}\u2026`;
22
+ }
23
+ function flattenModels(groups) {
24
+ const out = [];
25
+ for (const group of groups) {
26
+ const provider = group.id;
27
+ if (provider === void 0) continue;
28
+ const heading = group.name ?? "";
29
+ for (const model of group.models ?? []) {
30
+ const id = model.id;
31
+ if (id === void 0) continue;
32
+ out.push({
33
+ provider,
34
+ model: id,
35
+ label: model.name ?? id,
36
+ group: heading
37
+ });
38
+ }
39
+ }
40
+ return out;
41
+ }
42
+ var BUILT_IN_PRESET_NAME_KEYS = {
43
+ standard: "presetStandardName",
44
+ ptc: "presetPtcName",
45
+ minimal: "presetMinimalName",
46
+ cordis: "presetCordisName"
47
+ };
48
+ function presetOptions(presets, t) {
49
+ const healthy = presets.filter((preset) => preset.broken === void 0);
50
+ const options = healthy.map((preset) => {
51
+ const key = preset.trust === "system" ? BUILT_IN_PRESET_NAME_KEYS[preset.id] : void 0;
52
+ const translated = key !== void 0 && t !== void 0 ? t(key) : void 0;
53
+ const localized = translated !== void 0 && translated !== key ? translated : void 0;
54
+ return {
55
+ id: preset.id,
56
+ label: localized ?? preset.label ?? preset.name ?? preset.title ?? preset.id
57
+ };
58
+ });
59
+ const defaultId = healthy.find((preset) => preset.isDefault)?.id ?? healthy[0]?.id;
60
+ return { options, defaultId };
61
+ }
62
+ async function launchSession(ctx, request) {
63
+ const { sessionId, presetId, model, prompt, title } = request;
64
+ if (presetId !== void 0 && ctx.remote.agentPresets !== void 0) {
65
+ let applied;
66
+ try {
67
+ applied = await ctx.remote.agentPresets.select(sessionId, presetId);
68
+ } catch (cause) {
69
+ throw new Error(`could not set mode: ${cause instanceof Error ? cause.message : String(cause)}`);
70
+ }
71
+ if (!applied.ok) {
72
+ throw new Error(`could not set mode: ${applied.error.message}`);
73
+ }
74
+ }
75
+ if (model !== void 0 && ctx.modelDirectories !== void 0) {
76
+ const directory = ctx.modelDirectories.directoryFor(sessionId);
77
+ if (directory !== void 0) await directory.select(model);
78
+ }
79
+ const binding = ctx.sessions.binding(sessionId);
80
+ if (binding === void 0) {
81
+ throw new Error("the new session is not addressable yet");
82
+ }
83
+ const sent = await binding.session.prompt([{ type: "text", text: prompt }], "queue");
84
+ if (!sent.ok) {
85
+ throw new Error(`could not send the prompt: ${sent.error?.message ?? "unknown error"}`);
86
+ }
87
+ if (title !== void 0 && typeof binding.session.rename === "function") {
88
+ try {
89
+ await binding.session.rename(title);
90
+ } catch {
91
+ }
92
+ }
93
+ ctx.sessions.open(sessionId);
94
+ return sessionId;
95
+ }
96
+ async function discardSession(ctx, sessionId) {
97
+ try {
98
+ await ctx.uiWorkspace?.archiveSession(sessionId);
99
+ } catch {
100
+ }
101
+ }
102
+ export {
103
+ composePrompt,
104
+ discardSession,
105
+ flattenModels,
106
+ launchSession,
107
+ presetOptions,
108
+ sessionTitleFor
109
+ };
@@ -12,6 +12,7 @@ var todoItemSchema = z.object({
12
12
  release: z.string().optional(),
13
13
  sprint: z.string().optional(),
14
14
  dueDate: z.string().optional(),
15
+ sessionId: z.string().optional(),
15
16
  createdAt: z.number(),
16
17
  completedAt: z.number().optional(),
17
18
  archivedAt: z.number().optional()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dennisrongo/dsh-todo",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Todo list for DeepSeek Harness (dsh) — a per-workspace task list persisted on disk by the host",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,10 +34,12 @@
34
34
  "scripts": {
35
35
  "build": "node build/build.mjs",
36
36
  "typecheck": "tsc --noEmit",
37
- "test": "node test/smoke.mjs && node test/cli.test.mjs",
37
+ "test": "node build/build.mjs && node test/smoke.mjs && node test/context-probe.mjs && node test/launch-effect.mjs && node test/launch-lifecycle.mjs && node test/cli.test.mjs && node test/cli-integration.mjs",
38
38
  "test:icons": "node test/icon-probe.mjs",
39
39
  "test:modal": "node test/modal-probe.mjs",
40
- "test:cli": "node test/cli.test.mjs"
40
+ "test:cli": "node test/cli.test.mjs",
41
+ "test:integration": "node test/cli-integration.mjs",
42
+ "test:agent": "node test/verify-agent.mjs"
41
43
  },
42
44
  "dsh": {
43
45
  "bundle": {