@songsid/agend 2.3.0-beta.22 → 2.3.0-beta.24
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 +2 -2
- package/README.zh-TW.md +2 -2
- package/dist/agent-cli.js +4 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +1 -0
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +5 -0
- package/dist/agent-whats-new.js +16 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +7 -0
- package/dist/auth-api.js +54 -21
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex-package.d.ts +30 -0
- package/dist/backend/codex-package.js +133 -0
- package/dist/backend/codex-package.js.map +1 -0
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +9 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +3 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +9 -1
- package/dist/backend/kiro.js +102 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode-permission.js +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js +2 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/binary-probe.js +1 -1
- package/dist/bug-report-cli.js +11 -10
- package/dist/bug-report-cli.js.map +1 -1
- package/dist/bug-report.js +11 -10
- package/dist/bug-report.js.map +1 -1
- package/dist/bundled-skills.d.ts +7 -3
- package/dist/bundled-skills.js +16 -7
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +3 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +12 -4
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +3 -1
- package/dist/channel/mcp-tools.js +36 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/child-process.d.ts +21 -0
- package/dist/child-process.js +54 -0
- package/dist/child-process.js.map +1 -0
- package/dist/cli.js +188 -204
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +20 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.js +31 -8
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.js +1 -1
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -1
- package/dist/export-import.js +4 -3
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +45 -0
- package/dist/fleet-manager.js +324 -36
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.d.ts +5 -5
- package/dist/full-restart.js +5 -5
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +48 -10
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +7 -8
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +17 -4
- package/dist/general-knowledge/steering/core-rules.md +1 -1
- package/dist/instance-config-impact.js +10 -3
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +4 -4
- package/dist/locale.js +296 -4
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.js +1 -1
- package/dist/login-shell.js +3 -1
- package/dist/login-shell.js.map +1 -1
- package/dist/ls-rows.js +1 -1
- package/dist/needs-you-hub.d.ts +98 -1
- package/dist/needs-you-hub.js +313 -44
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +5 -2
- package/dist/needs-you-live.js +6 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +58 -3
- package/dist/needs-you.js +36 -3
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-schemas.d.ts +3 -3
- package/dist/public-link-progress.d.ts +12 -1
- package/dist/public-link-progress.js +5 -10
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +5 -0
- package/dist/public-tunnel-config.js +45 -1
- package/dist/public-tunnel-config.js.map +1 -1
- package/dist/public-web-link.js +6 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart.js +15 -9
- package/dist/quickstart.js.map +1 -1
- package/dist/resource-report.js +1 -1
- package/dist/scheduler/task-board.d.ts +41 -1
- package/dist/scheduler/task-board.js +152 -5
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.js +2 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-installer.d.ts +69 -0
- package/dist/service-installer.js +182 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +44 -0
- package/dist/service-owner.js +48 -0
- package/dist/service-owner.js.map +1 -0
- package/dist/service-plan.d.ts +8 -0
- package/dist/service-plan.js +13 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/settings-api.d.ts +4 -0
- package/dist/settings-api.js +21 -3
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +88 -4
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +2 -0
- package/dist/settings-confirmation.js +14 -8
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-control.js +2 -1
- package/dist/settings-control.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +2 -0
- package/dist/settings-http-confirmation.js +3 -2
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-request-capability.d.ts +4 -1
- package/dist/settings-request-capability.js +11 -3
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/setup-auth.d.ts +1 -0
- package/dist/setup-auth.js +2 -1
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-host.d.ts +1 -0
- package/dist/setup-host.js +7 -3
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +1 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-summary.d.ts +101 -0
- package/dist/task-board-summary.js +177 -0
- package/dist/task-board-summary.js.map +1 -0
- package/dist/task-board-waker.d.ts +8 -1
- package/dist/task-board-waker.js +15 -6
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.js +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +13 -3
- package/dist/tmux-exec-helper.js +34 -29
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.js +1 -1
- package/dist/tmux-read.js +1 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/topic-commands.d.ts +3 -0
- package/dist/topic-commands.js +29 -18
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +5 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +2 -0
- package/dist/transcript-sources.js +4 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js +3 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +1 -1
- package/dist/tunnel/cloudflared.js +51 -10
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +13 -0
- package/dist/tunnel/named-diagnostics.js +52 -0
- package/dist/tunnel/named-diagnostics.js.map +1 -0
- package/dist/tunnel/types.d.ts +2 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/ui/app-effects.js +10 -4
- package/dist/ui/chat-strings.js +3 -0
- package/dist/ui/chat-thread.js +14 -7
- package/dist/ui/effect-trial-offer.js +2 -31
- package/dist/ui/effects-waker.js +2 -70
- package/dist/ui/fleet-task-card.js +5 -5
- package/dist/ui/fleet-task-common.js +43 -9
- package/dist/ui/fleet-task-work.js +107 -0
- package/dist/ui/fleet-tasks.js +46 -12
- package/dist/ui/image-lightbox.js +39 -1
- package/dist/ui/image-zoom.js +242 -0
- package/dist/ui/panel-chat.js +24 -13
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +6 -2
- package/dist/ui/panel-needs.js +21 -6
- package/dist/ui/panel-settings.js +35 -13
- package/dist/ui/preview-panel.js +2 -1
- package/dist/ui/search-dialog.js +2 -1
- package/dist/ui/settings-appearance.js +44 -10
- package/dist/ui/settings-dialogs.js +33 -17
- package/dist/ui/settings-discord.js +6 -2
- package/dist/ui/settings-guests.js +2 -1
- package/dist/ui/settings-model.js +2 -11
- package/dist/ui/settings-mods.js +8 -3
- package/dist/ui/settings-strings.js +32 -0
- package/dist/ui/settings-token.js +3 -1
- package/dist/ui/settings-web-authority.js +6 -3
- package/dist/ui/settings-wizard.js +13 -7
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +2 -2
- package/dist/ui/shared/app-store.js +2 -2
- package/dist/ui/shared/app.css +93 -32
- package/dist/ui/shared/app.js +5 -1
- package/dist/ui/shared/effect-app-runtime.js +7 -5
- package/dist/ui/shared/effect-chat-runtime.js +2 -7
- package/dist/ui/shared/effect-cursor.js +3 -2
- package/dist/ui/shared/effect-display-metadata.js +39 -0
- package/dist/ui/shared/effect-registry.js +4 -2
- package/dist/ui/shared/effect-runtime-loader.js +3 -1
- package/dist/ui/shared/effect-stock-trust.js +5 -5
- package/dist/ui/shared/feature-flags.js +127 -0
- package/dist/ui/shared/identity-color.js +6 -3
- package/dist/ui/shared/instance-nav.js +3 -2
- package/dist/ui/shared/panel-view.js +14 -29
- package/dist/ui/shared/text-undo.js +210 -0
- package/dist/ui/shared/ui-dialog.js +4 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/usage-dialog.js +29 -8
- package/dist/ui/shared/view-strings.js +2 -4
- package/dist/ui/web-terminal/terminal.html +10 -10
- package/dist/ui/web-terminal/terminal.js +58 -12
- package/dist/update-dispatch.js +1 -1
- package/dist/update-launch.d.ts +1 -1
- package/dist/update-launch.js +1 -2
- package/dist/update-launch.js.map +1 -1
- package/dist/update-preflight.js +1 -1
- package/dist/usage/providers.js +1 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +1 -1
- package/dist/web-api.d.ts +6 -0
- package/dist/web-api.js +163 -21
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon-worker.js +2 -1
- package/dist/web-app-icon-worker.js.map +1 -1
- package/dist/web-app-icon.d.ts +10 -8
- package/dist/web-app-icon.js +14 -13
- package/dist/web-app-icon.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +1 -0
- package/dist/web-appearance-tools.js +7 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance.d.ts +4 -10
- package/dist/web-appearance.js +4 -3
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-auth.d.ts +8 -4
- package/dist/web-auth.js +28 -17
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +14 -0
- package/dist/web-chat-history.js +26 -1
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-cookie-names.d.ts +5 -0
- package/dist/web-cookie-names.js +12 -0
- package/dist/web-cookie-names.js.map +1 -0
- package/dist/web-experimental-http.d.ts +2 -0
- package/dist/web-experimental-http.js +12 -0
- package/dist/web-experimental-http.js.map +1 -0
- package/dist/web-experimental.d.ts +11 -0
- package/dist/web-experimental.js +49 -0
- package/dist/web-experimental.js.map +1 -0
- package/dist/web-guest-access.d.ts +1 -0
- package/dist/web-guest-access.js +4 -2
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -0
- package/dist/web-mod-store.js +10 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-png.d.ts +4 -0
- package/dist/web-png.js +10 -2
- package/dist/web-png.js.map +1 -1
- package/dist/web-preview.d.ts +2 -0
- package/dist/web-preview.js +2 -0
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +4 -2
- package/dist/web-returning-device.js +12 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +5 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +1 -0
- package/dist/web-settings-tools.js +14 -10
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal.js +1 -1
- package/dist/web-theme-store.d.ts +2 -2
- package/dist/web-theme-store.js +6 -3
- package/dist/web-theme-store.js.map +1 -1
- package/package.json +11 -11
|
@@ -45,7 +45,8 @@ export class TaskBoardError extends Error {
|
|
|
45
45
|
}
|
|
46
46
|
const STATUS_ORDER = `CASE status WHEN 'claimed' THEN 0 WHEN 'blocked' THEN 1 WHEN 'review' THEN 2 WHEN 'open' THEN 3 WHEN 'done' THEN 4 ELSE 5 END`;
|
|
47
47
|
const PRIORITY_ORDER = `CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'normal' THEN 2 ELSE 3 END`;
|
|
48
|
-
|
|
48
|
+
// Keep the second whitespace run inside the hyphen branch: without a hyphen, adjacent runs backtrack quadratically.
|
|
49
|
+
const CODE_RE = /^\s*([MS])\s*(?:-\s*)?(\d+)\s*$/i;
|
|
49
50
|
const length = (s) => [...s].length;
|
|
50
51
|
const isFinished = (c) => c.status === "done" || c.status === "cancelled";
|
|
51
52
|
const isPerson = (a) => a.kind === "person";
|
|
@@ -166,12 +167,26 @@ function toCard(r) {
|
|
|
166
167
|
seenEntryId: Number(r.seen_entry_id ?? 0),
|
|
167
168
|
};
|
|
168
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* #2352: a lead is a name, or none ("" and null alike). Whether it is one of the fleet's instances is the fleet's to
|
|
172
|
+
* check (the board keeps names, as for an assignee); a lead no longer in fleet.yaml reads as none.
|
|
173
|
+
*/
|
|
174
|
+
function checkLeadInstance(raw) {
|
|
175
|
+
const name = (raw ?? "").trim();
|
|
176
|
+
if (!name)
|
|
177
|
+
return null;
|
|
178
|
+
// eslint-disable-next-line no-control-regex
|
|
179
|
+
if (name.length > 256 || /[\s\u0000-\u001f\u007f]/.test(name))
|
|
180
|
+
throw new TaskBoardError("invalid", "a lead is an instance's name", "lead_not_instance");
|
|
181
|
+
return name;
|
|
182
|
+
}
|
|
169
183
|
function toProject(r) {
|
|
170
184
|
return {
|
|
171
185
|
id: Number(r.id),
|
|
172
186
|
name: r.name,
|
|
173
187
|
description: r.description,
|
|
174
188
|
archived: Number(r.archived) === 1,
|
|
189
|
+
leadInstance: typeof r.lead_instance === "string" && r.lead_instance ? r.lead_instance : null,
|
|
175
190
|
sort: Number(r.sort),
|
|
176
191
|
createdAt: r.created_at,
|
|
177
192
|
};
|
|
@@ -269,6 +284,17 @@ export class TaskBoard {
|
|
|
269
284
|
CREATE INDEX IF NOT EXISTS idx_task_events_task ON task_events(task_id, id);
|
|
270
285
|
CREATE TABLE IF NOT EXISTS task_counters (prefix TEXT PRIMARY KEY, next INTEGER NOT NULL);
|
|
271
286
|
`);
|
|
287
|
+
// #2352: a project's lead instance, added in place (null: its waiting cards' buttons stay in General).
|
|
288
|
+
const projectCols = new Set(this.db.prepare("PRAGMA table_info(task_projects)").all().map((c) => c.name));
|
|
289
|
+
if (!projectCols.has("lead_instance")) {
|
|
290
|
+
try {
|
|
291
|
+
this.db.exec("ALTER TABLE task_projects ADD COLUMN lead_instance TEXT");
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
if (!/duplicate column/i.test(String(err)))
|
|
295
|
+
throw err;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
272
298
|
// Every open (the CLI opens this file for each command) only reads: the write lock is taken only when a counter
|
|
273
299
|
// is missing or a row needs a number. (CREATE … IF NOT EXISTS of what exists takes no lock; an INSERT does.)
|
|
274
300
|
if (this.db.prepare("SELECT COUNT(*) AS n FROM task_counters WHERE prefix IN ('M', 'S', 'R')").get().n < 3) {
|
|
@@ -330,8 +356,9 @@ export class TaskBoard {
|
|
|
330
356
|
const name = this.checkProjectName(input.name, null);
|
|
331
357
|
const description = checkText("the description", input.description ?? "", CARD_LIMITS.projectDescription);
|
|
332
358
|
const { s } = this.db.prepare("SELECT COALESCE(MAX(sort), 0) + 1 AS s FROM task_projects").get();
|
|
333
|
-
const
|
|
334
|
-
|
|
359
|
+
const lead = checkLeadInstance(input.leadInstance ?? null);
|
|
360
|
+
const row = this.db.prepare("INSERT INTO task_projects (name, description, sort, created_at, lead_instance) VALUES (?, ?, ?, ?, ?) RETURNING *")
|
|
361
|
+
.get(name, description, s, this.nowIso(), lead);
|
|
335
362
|
this.projectsChanged = true;
|
|
336
363
|
return toProject(row);
|
|
337
364
|
});
|
|
@@ -346,8 +373,10 @@ export class TaskBoard {
|
|
|
346
373
|
project.description = checkText("the description", patch.description, CARD_LIMITS.projectDescription);
|
|
347
374
|
if (patch.archived !== undefined)
|
|
348
375
|
project.archived = patch.archived;
|
|
349
|
-
|
|
350
|
-
|
|
376
|
+
if (patch.leadInstance !== undefined)
|
|
377
|
+
project.leadInstance = checkLeadInstance(patch.leadInstance);
|
|
378
|
+
this.db.prepare("UPDATE task_projects SET name = ?, description = ?, archived = ?, lead_instance = ? WHERE id = ?")
|
|
379
|
+
.run(project.name, project.description, project.archived ? 1 : 0, project.leadInstance, project.id);
|
|
351
380
|
this.projectsChanged = true;
|
|
352
381
|
return project;
|
|
353
382
|
});
|
|
@@ -733,6 +762,11 @@ export class TaskBoard {
|
|
|
733
762
|
* (its own open cards, and the unassigned sub tasks of main tasks it coordinates, dependencies done) and what waits
|
|
734
763
|
* on dependencies; comments it has not seen and work sent back; the main tasks it coordinates and how far along.
|
|
735
764
|
*/
|
|
765
|
+
/** Who holds a live card (open, in progress, blocked, waiting for the person): the instances, and any other name. */
|
|
766
|
+
liveAssignees() {
|
|
767
|
+
return this.db.prepare("SELECT DISTINCT assignee FROM tasks WHERE assignee IS NOT NULL AND status IN ('open', 'claimed', 'blocked', 'review') ORDER BY assignee").all()
|
|
768
|
+
.map((r) => r.assignee);
|
|
769
|
+
}
|
|
736
770
|
myWork(name) {
|
|
737
771
|
const mine = this.db.prepare(`SELECT * FROM tasks WHERE assignee = ? AND status IN ('open', 'claimed', 'blocked', 'review') ORDER BY ${STATUS_ORDER}, ${PRIORITY_ORDER}, sort`)
|
|
738
772
|
.all(name).map(toCard);
|
|
@@ -766,6 +800,113 @@ export class TaskBoard {
|
|
|
766
800
|
mains: coordinating.map((card) => ({ card, progress: this.progress(card) })),
|
|
767
801
|
};
|
|
768
802
|
}
|
|
803
|
+
/**
|
|
804
|
+
* What waits for the person (step 3: Needs you): cards in review that need their confirmation, and blocked cards —
|
|
805
|
+
* oldest first, each with when it started waiting (its last move to that status).
|
|
806
|
+
*/
|
|
807
|
+
waitingForPerson() {
|
|
808
|
+
const rows = this.db.prepare("SELECT * FROM tasks WHERE (status = 'review' AND needs_review = 1) OR status = 'blocked' ORDER BY updated_at").all().map(toCard);
|
|
809
|
+
const moved = this.db.prepare("SELECT at FROM task_events WHERE task_id = ? AND type = 'moved' AND json_extract(data, '$.to') = ? ORDER BY id DESC LIMIT 1");
|
|
810
|
+
return rows.map((card) => {
|
|
811
|
+
const at = moved.get(card.uuid, card.status)?.at;
|
|
812
|
+
return { card, since: at ?? card.submittedAt ?? card.updatedAt };
|
|
813
|
+
}).sort((a, b) => a.since.localeCompare(b.since));
|
|
814
|
+
}
|
|
815
|
+
/** Cards by their numbers, in one read (the ones that exist; #2341 review: the cards a summary line named). */
|
|
816
|
+
cardsByCode(codes) {
|
|
817
|
+
const out = [];
|
|
818
|
+
for (let i = 0; i < codes.length; i += 500) {
|
|
819
|
+
const chunk = codes.slice(i, i + 500);
|
|
820
|
+
out.push(...this.db.prepare(`SELECT * FROM tasks WHERE code IN (${chunk.map(() => "?").join(", ")})`).all(...chunk).map(toCard));
|
|
821
|
+
}
|
|
822
|
+
return out;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* myWork() for many names in one read (the dashboard's Instances list, GET /ui/tasks/work): the live cards, every
|
|
826
|
+
* card's light row (sub tasks, dependencies) and the live cards' unseen entries, each read once — three statements
|
|
827
|
+
* however many names and cards (#2316 review: N myWork() calls cost tens of ms on the fleet's event loop). Same
|
|
828
|
+
* answers as myWork() name by name; tests/task-board-work-2316.test.ts pins both.
|
|
829
|
+
*/
|
|
830
|
+
workOf(names) {
|
|
831
|
+
const out = new Map();
|
|
832
|
+
if (!names.length)
|
|
833
|
+
return out;
|
|
834
|
+
const live = this.db.prepare(`SELECT * FROM tasks WHERE status IN ('open', 'claimed', 'blocked', 'review') ORDER BY ${STATUS_ORDER}, ${PRIORITY_ORDER}, sort`).all().map(toCard);
|
|
835
|
+
const all = new Map();
|
|
836
|
+
const subs = new Map();
|
|
837
|
+
for (const r of this.db.prepare("SELECT id, code, status, depends_on, parent_id FROM tasks").all()) {
|
|
838
|
+
const light = { code: r.code ?? r.id.slice(0, 8), status: r.status, deps: parseJsonArray(r.depends_on, []), parent: r.parent_id ?? null };
|
|
839
|
+
all.set(r.id, light);
|
|
840
|
+
if (light.parent && light.status !== "cancelled") {
|
|
841
|
+
const p = subs.get(light.parent) ?? { total: 0, open: 0, claimed: 0, blocked: 0, review: 0, done: 0 };
|
|
842
|
+
p.total++;
|
|
843
|
+
p[light.status]++;
|
|
844
|
+
subs.set(light.parent, p);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
// Unseen entries of the live cards: comments and rejections by anyone but the card's assignee, after what it saw.
|
|
848
|
+
const fresh = new Map();
|
|
849
|
+
for (const r of this.db.prepare(`SELECT e.* FROM task_entries e JOIN tasks t ON e.task_id = t.id
|
|
850
|
+
WHERE t.status IN ('open', 'claimed', 'blocked', 'review') AND e.id > t.seen_entry_id AND e.kind IN ('comment', 'reject') AND e.author IS NOT t.assignee ORDER BY e.id`).all()) {
|
|
851
|
+
const list = fresh.get(r.task_id) ?? [];
|
|
852
|
+
list.push({ id: Number(r.id), cardId: all.get(r.task_id)?.code ?? r.task_id.slice(0, 8), kind: r.kind, author: r.author,
|
|
853
|
+
authorKind: r.author_kind, text: r.text, at: r.at });
|
|
854
|
+
fresh.set(r.task_id, list);
|
|
855
|
+
}
|
|
856
|
+
/** unmetDeps(), on the light rows: own dependencies that exist and are not done, then the main task's not yet named. */
|
|
857
|
+
const unmet = (deps, parent) => {
|
|
858
|
+
const own = deps.filter((d) => { const dep = all.get(d); return !!dep && dep.status !== "done"; });
|
|
859
|
+
const main = parent ? all.get(parent) : undefined;
|
|
860
|
+
return main ? [...own, ...unmet(main.deps, main.parent).filter((d) => !own.includes(d))] : own;
|
|
861
|
+
};
|
|
862
|
+
const rank = { urgent: 0, high: 1, normal: 2 };
|
|
863
|
+
const byPriority = (a, b) => (rank[a.priority] ?? 3) - (rank[b.priority] ?? 3) || a.sort - b.sort;
|
|
864
|
+
const openUnassigned = live.filter((c) => c.assignee === null && c.status === "open");
|
|
865
|
+
for (const name of new Set(names)) {
|
|
866
|
+
const mine = live.filter((c) => c.assignee === name);
|
|
867
|
+
const coordinating = mine.filter((c) => c.kind === "main" && (subs.get(c.uuid)?.total ?? 0) > 0);
|
|
868
|
+
const work = mine.filter((c) => !coordinating.includes(c));
|
|
869
|
+
const theirs = new Set(coordinating.map((c) => c.uuid));
|
|
870
|
+
const unassigned = coordinating.length ? openUnassigned.filter((c) => c.parent !== null && theirs.has(c.parent)).sort(byPriority) : [];
|
|
871
|
+
const candidates = [...work.filter((c) => c.status === "open"), ...unassigned].map((card) => ({ card, waitsFor: unmet(card.deps, card.parent).map((d) => all.get(d).code) }));
|
|
872
|
+
const newComments = [];
|
|
873
|
+
const sentBack = [];
|
|
874
|
+
for (const card of mine) {
|
|
875
|
+
const entries = fresh.get(card.uuid) ?? [];
|
|
876
|
+
const reject = [...entries].reverse().find((e) => e.kind === "reject");
|
|
877
|
+
if (reject)
|
|
878
|
+
sentBack.push({ card, entry: reject });
|
|
879
|
+
const comments = entries.filter((e) => e.kind === "comment");
|
|
880
|
+
if (comments.length)
|
|
881
|
+
newComments.push({ card, entries: comments });
|
|
882
|
+
}
|
|
883
|
+
out.set(name, {
|
|
884
|
+
doing: work.filter((c) => c.status === "claimed"),
|
|
885
|
+
blocked: work.filter((c) => c.status === "blocked"),
|
|
886
|
+
review: work.filter((c) => c.status === "review"),
|
|
887
|
+
ready: candidates.filter((x) => !x.waitsFor.length).map((x) => x.card),
|
|
888
|
+
waiting: candidates.filter((x) => x.waitsFor.length),
|
|
889
|
+
newComments,
|
|
890
|
+
sentBack,
|
|
891
|
+
mains: coordinating.map((card) => ({ card, progress: { ...subs.get(card.uuid) } })),
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
return out;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Step 3, the daily summary (task-board-summary.ts): what waits for the person now, what got done and what was
|
|
898
|
+
* opened in [since, until) (not cancelled), and every live main task's project and assignee.
|
|
899
|
+
*/
|
|
900
|
+
summaryRead(since, until) {
|
|
901
|
+
const between = (sql) => this.db.prepare(sql).all(since, until).map(toCard);
|
|
902
|
+
return {
|
|
903
|
+
waiting: this.waitingForPerson().map((w) => w.card),
|
|
904
|
+
done: between("SELECT * FROM tasks WHERE status = 'done' AND done_at >= ? AND done_at < ? ORDER BY sort"),
|
|
905
|
+
created: between("SELECT * FROM tasks WHERE status != 'cancelled' AND created_at >= ? AND created_at < ? ORDER BY sort"),
|
|
906
|
+
mains: this.db.prepare("SELECT project_id, assignee FROM tasks WHERE kind = 'main' AND status != 'cancelled'").all()
|
|
907
|
+
.map((r) => ({ projectId: r.project_id ?? null, assignee: r.assignee ?? null })),
|
|
908
|
+
};
|
|
909
|
+
}
|
|
769
910
|
// ── Writing: the board ──────────────────────────────────
|
|
770
911
|
createCards(actor, input) {
|
|
771
912
|
if (input.cards.length === 0)
|
|
@@ -1683,6 +1824,9 @@ export class TaskBoard {
|
|
|
1683
1824
|
add(card, card.assignee, "cancelled", e.actor, why);
|
|
1684
1825
|
continue;
|
|
1685
1826
|
}
|
|
1827
|
+
// Its dispatcher tells the person it waits for them (not the board).
|
|
1828
|
+
if (d.to === "review" && !isPerson(e.actor) && requiresReview(card))
|
|
1829
|
+
add(card, dispatcherOf(), "to_confirm", e.actor);
|
|
1686
1830
|
if (d.auto) {
|
|
1687
1831
|
if (d.to === "review" && card.kind === "main" && wrapUpGap(card))
|
|
1688
1832
|
add(card, card.assignee, "wrap_up", e.actor);
|
|
@@ -1737,6 +1881,9 @@ export class TaskBoard {
|
|
|
1737
1881
|
return false;
|
|
1738
1882
|
if (r === "decide")
|
|
1739
1883
|
return handedOut && card.status === "blocked";
|
|
1884
|
+
// #2341 review: and still waiting for the person — its confirmation not switched off during the gather.
|
|
1885
|
+
if (r === "to_confirm")
|
|
1886
|
+
return handedOut && card.status === "review" && requiresReview(card);
|
|
1740
1887
|
if (!own)
|
|
1741
1888
|
return false;
|
|
1742
1889
|
if (r === "wrap_up")
|