@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/README.md +91 -26
- package/lib/bin.js +55 -14
- package/lib/cli.js +55 -14
- package/lib/client.js +117 -68
- package/lib/index.js +9 -3
- package/lib/launch.js +109 -0
- package/lib/typert.host.js +1 -0
- package/package.json +5 -3
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
|
+
};
|
package/lib/typert.host.js
CHANGED
|
@@ -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
|
+
"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": {
|