@pi-unipi/kanboard 3.0.0-alpha.2 → 3.0.0-alpha.21

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/src/settings.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  * @pi-unipi/kanboard — settings (hub section "Kanboard").
3
3
  */
4
4
 
5
- import { getSettings, registerCommandRunner, registerSettings, setSettings } from "@pi-unipi/core";
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { getSettings, globalSettingsPath, projectSettingsPath, registerCommandRunner, registerSettings, setSettings } from "@pi-unipi/core";
6
7
 
7
8
  export type ChainGate = "in_review" | "done";
8
9
 
@@ -25,12 +26,20 @@ export interface KanboardSettings {
25
26
  requireAuth: boolean;
26
27
  /** Reuse <home>/token so board links survive daemon restarts. */
27
28
  keepToken: boolean;
28
- /** Tasks one session may queue (0 = unlimited). */
29
- queueMax: number;
30
29
  /** Distinct sessions running tasks per project (min 1). */
31
30
  maxSessions: number;
32
- /** `add` calls allowed per -do/runner window (0 = unlimited). */
31
+ /** `add` calls allowed per turn (0 = unlimited). */
33
32
  turnAddLimit: number;
33
+ /** Task slots a /unipi:kanboard-do grants (each `start` uses one). */
34
+ doTasks: number;
35
+ /** Board writes a /unipi:kanboard-do grants. */
36
+ doWrites: number;
37
+ /** Whether a blocked-by-confusion task may ask the user (ask) or must
38
+ * assume-and-note (avoid, the default). */
39
+ blocking: "avoid" | "ask";
40
+ /** Progress reminders when the agent works board tasks by hand: `start`
41
+ * before the first edit, `finish` (or block) before the turn ends. */
42
+ reminders: boolean;
34
43
  }
35
44
 
36
45
  export const DEFAULT_SETTINGS: KanboardSettings = {
@@ -43,9 +52,12 @@ export const DEFAULT_SETTINGS: KanboardSettings = {
43
52
  openBrowser: false,
44
53
  requireAuth: false,
45
54
  keepToken: false,
46
- queueMax: 10,
47
55
  maxSessions: 2,
48
56
  turnAddLimit: 20,
57
+ doTasks: 5,
58
+ doWrites: 10,
59
+ blocking: "avoid",
60
+ reminders: true,
49
61
  };
50
62
 
51
63
  export const KANBOARD_NAMESPACE = "kanboard";
@@ -59,11 +71,11 @@ export function registerKanboardSettings(): void {
59
71
  registerSettings({
60
72
  namespace: KANBOARD_NAMESPACE,
61
73
  label: "Kanboard",
62
- defaults: DEFAULT_SETTINGS as unknown as Record<string, unknown>,
74
+ defaults: { ...DEFAULT_SETTINGS } as unknown as Record<string, unknown>,
63
75
  schema: [
64
76
  {
65
77
  title: "Kanboard",
66
- description: "Deferred-work board: task runner, daemon and skill",
78
+ description: "The session's work board: daemon, -do budgets and skill",
67
79
  fields: [
68
80
  {
69
81
  key: "chainGate",
@@ -100,12 +112,16 @@ export function registerKanboardSettings(): void {
100
112
  label: "Keep the access token across restarts",
101
113
  description: "Reuse one token so board links stay valid; rotate it below.",
102
114
  },
103
- { key: "queueMax", type: "number", label: "-do queue limit", min: 0, zeroLabel: "unlimited", description: "Tasks a session may queue for the runner" },
104
115
  { key: "maxSessions", type: "number", label: "Sessions working at once (per project)", min: 1, description: "Distinct sessions holding in_progress tasks" },
105
- { key: "turnAddLimit", type: "number", label: "New tasks per turn", min: 0, zeroLabel: "unlimited", description: "`add` calls allowed per -do turn or runner task" },
106
- { key: "open", type: "action", label: "Open board…", description: "Start the daemon and print the URL", command: ACTION_OPEN },
107
- { key: "stopDaemon", type: "action", label: "Stop daemon", description: "Terminate the running kanboard daemon", command: ACTION_STOP_DAEMON },
108
- { key: "rotateToken", type: "action", label: "Rotate access token", description: "Drop the persistent token; a fresh one mints on the next daemon start", command: ACTION_ROTATE_TOKEN },
116
+ { key: "turnAddLimit", type: "number", label: "New tasks per turn", min: 0, zeroLabel: "unlimited", description: "`add` calls allowed per turn" },
117
+ {
118
+ key: "reminders",
119
+ type: "boolean",
120
+ label: "Progress reminders",
121
+ description: "Remind the agent to `start` a mentioned Todo task before editing (text only, never blocks; off in child sessions)",
122
+ },
123
+ { key: "doTasks", type: "number", label: "-do task slots", min: 0, zeroLabel: "off", description: "Task slots a /unipi:kanboard-do grants; each `start` uses one" },
124
+ { key: "doWrites", type: "number", label: "-do write budget", min: 0, zeroLabel: "off", description: "Board writes a /unipi:kanboard-do grants (add, edit, link, order, move, note on tasks you don't hold)" },
109
125
  ],
110
126
  },
111
127
  ],
@@ -127,9 +143,18 @@ export function readKanboardSettings(cwd: string = process.cwd()): KanboardSetti
127
143
  openBrowser: raw.openBrowser === true,
128
144
  requireAuth: raw.requireAuth === true,
129
145
  keepToken: raw.keepToken === true,
130
- queueMax: typeof raw.queueMax === "number" && raw.queueMax >= 0 ? raw.queueMax : DEFAULT_SETTINGS.queueMax,
131
146
  maxSessions: typeof raw.maxSessions === "number" && raw.maxSessions >= 1 ? raw.maxSessions : DEFAULT_SETTINGS.maxSessions,
132
147
  turnAddLimit: typeof raw.turnAddLimit === "number" && raw.turnAddLimit >= 0 ? raw.turnAddLimit : DEFAULT_SETTINGS.turnAddLimit,
148
+ // Migration: getSettings merges defaults, so a stored doCredits hides
149
+ // behind the doWrites default. Look at the stored layer for the legacy key
150
+ // (project first, then global) — the next settings write persists doWrites.
151
+ doTasks: typeof raw.doTasks === "number" && raw.doTasks >= 0 ? raw.doTasks : DEFAULT_SETTINGS.doTasks,
152
+ doWrites:
153
+ typeof raw.doWrites === "number" && raw.doWrites >= 0 && storedDoWritesSet(cwd)
154
+ ? raw.doWrites
155
+ : (storedLegacyDoCredits(cwd) ?? DEFAULT_SETTINGS.doWrites),
156
+ blocking: raw.blocking === "ask" ? "ask" : "avoid",
157
+ reminders: raw.reminders !== false,
133
158
  };
134
159
  }
135
160
 
@@ -137,6 +162,35 @@ export function writeKanboardSettings(patch: Partial<KanboardSettings>, cwd: str
137
162
  setSettings(KANBOARD_NAMESPACE, patch as unknown as Record<string, unknown>, "global", cwd);
138
163
  }
139
164
 
165
+ /** Whether any stored layer carries an explicit doWrites (defaults don't count). */
166
+ function storedDoWritesSet(cwd: string): boolean {
167
+ for (const file of [projectSettingsPath(cwd, KANBOARD_NAMESPACE), globalSettingsPath(KANBOARD_NAMESPACE)]) {
168
+ try {
169
+ if (!existsSync(file)) continue;
170
+ const parsed = JSON.parse(readFileSync(file, "utf-8")) as Record<string, unknown>;
171
+ if (typeof parsed.doWrites === "number") return true;
172
+ } catch {
173
+ // A corrupt file has no legacy value either.
174
+ }
175
+ }
176
+ return false;
177
+ }
178
+
179
+ /** The pre-slot doCredits value from the stored layers, if any. */
180
+ function storedLegacyDoCredits(cwd: string): number | undefined {
181
+ for (const file of [projectSettingsPath(cwd, KANBOARD_NAMESPACE), globalSettingsPath(KANBOARD_NAMESPACE)]) {
182
+ try {
183
+ if (!existsSync(file)) continue;
184
+ const parsed = JSON.parse(readFileSync(file, "utf-8")) as Record<string, unknown>;
185
+ const value = parsed.doCredits;
186
+ if (typeof value === "number" && value >= 0) return value;
187
+ } catch {
188
+ // A corrupt file has no legacy value either.
189
+ }
190
+ }
191
+ return undefined;
192
+ }
193
+
140
194
  /** Re-exported so index.ts and tests share one registration path. */
141
195
  export function registerKanboardRunners(runners: {
142
196
  open: (ctx: unknown) => void | Promise<void>;
@@ -150,9 +204,9 @@ export function registerKanboardRunners(runners: {
150
204
 
151
205
  /**
152
206
  * Limits reach the Rust CLI through the environment — refresh the process-wide
153
- * values so the agent's bash calls and the runner's CLI calls both see them.
207
+ * values so the agent's bash calls and spawned CLI calls both see them.
208
+ * (queueMax died with the runner; the binary's queue limit is untouched.)
154
209
  */
155
210
  export function applyLimitEnv(settings: KanboardSettings): void {
156
- process.env.UNIPI_KANBOARD_QUEUE_MAX = String(settings.queueMax);
157
211
  process.env.UNIPI_KANBOARD_MAX_SESSIONS = String(settings.maxSessions);
158
212
  }
package/src/shapes.ts CHANGED
@@ -32,6 +32,8 @@ export interface KanboardRun {
32
32
  mode?: string;
33
33
  goal?: string | null;
34
34
  started?: string;
35
+ /** "system" (runner claim) or "agent" (`start` self-claim). */
36
+ owner?: string;
35
37
  }
36
38
 
37
39
  export interface KanboardActivity {
@@ -43,6 +45,8 @@ export interface KanboardActivity {
43
45
  export interface KanboardTask {
44
46
  id: string;
45
47
  title: string;
48
+ /** Title derived from the first body line when `title` is empty (Rust `Task::display_title`). */
49
+ displayTitle?: string;
46
50
  body?: string;
47
51
  status: string;
48
52
  priority?: string;
@@ -113,6 +117,7 @@ export function asTask(command: string, value: unknown): KanboardTask {
113
117
  ...record,
114
118
  id: requireString(command, record, "id"),
115
119
  title: requireString(command, record, "title"),
120
+ displayTitle: typeof record.displayTitle === "string" ? record.displayTitle : undefined,
116
121
  status: requireString(command, record, "status"),
117
122
  deps: optionalArray<string>(record.deps) ?? [],
118
123
  activity: optionalArray<KanboardActivity>(record.activity) ?? [],