@songsid/agend 2.3.3-beta.1 → 2.3.3-beta.3

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 (84) hide show
  1. package/dist/authority/authority-state.js +1 -1
  2. package/dist/authority/authority-state.js.map +1 -1
  3. package/dist/authority/web-authority.d.ts +9 -4
  4. package/dist/authority/web-authority.js +10 -5
  5. package/dist/authority/web-authority.js.map +1 -1
  6. package/dist/backend/claude-code.js +3 -25
  7. package/dist/backend/claude-code.js.map +1 -1
  8. package/dist/backend/claude-transcript-errors.d.ts +18 -0
  9. package/dist/backend/claude-transcript-errors.js +19 -0
  10. package/dist/backend/claude-transcript-errors.js.map +1 -0
  11. package/dist/backend/codex-session-lookup.d.ts +16 -3
  12. package/dist/backend/codex-session-lookup.js +45 -11
  13. package/dist/backend/codex-session-lookup.js.map +1 -1
  14. package/dist/classic-channel-manager.d.ts +4 -1
  15. package/dist/classic-channel-manager.js +21 -7
  16. package/dist/classic-channel-manager.js.map +1 -1
  17. package/dist/cli.js +18 -3
  18. package/dist/cli.js.map +1 -1
  19. package/dist/config-history.d.ts +50 -0
  20. package/dist/config-history.js +263 -0
  21. package/dist/config-history.js.map +1 -0
  22. package/dist/config-validator.js +1 -4
  23. package/dist/config-validator.js.map +1 -1
  24. package/dist/config.d.ts +3 -0
  25. package/dist/config.js +11 -4
  26. package/dist/config.js.map +1 -1
  27. package/dist/daemon.d.ts +37 -0
  28. package/dist/daemon.js +156 -4
  29. package/dist/daemon.js.map +1 -1
  30. package/dist/daily-summary.d.ts +8 -1
  31. package/dist/daily-summary.js +8 -5
  32. package/dist/daily-summary.js.map +1 -1
  33. package/dist/delivery-outbox.d.ts +4 -0
  34. package/dist/delivery-outbox.js +12 -2
  35. package/dist/delivery-outbox.js.map +1 -1
  36. package/dist/fleet-level-config.js +1 -1
  37. package/dist/fleet-level-config.js.map +1 -1
  38. package/dist/fleet-manager.d.ts +8 -10
  39. package/dist/fleet-manager.js +57 -83
  40. package/dist/fleet-manager.js.map +1 -1
  41. package/dist/fleet-process-handlers.d.ts +5 -0
  42. package/dist/fleet-process-handlers.js +21 -0
  43. package/dist/fleet-process-handlers.js.map +1 -1
  44. package/dist/fleet-service-owner.d.ts +11 -0
  45. package/dist/fleet-service-owner.js +3 -0
  46. package/dist/fleet-service-owner.js.map +1 -0
  47. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +3 -4
  48. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +3 -4
  49. package/dist/instance-lifecycle.js +2 -0
  50. package/dist/instance-lifecycle.js.map +1 -1
  51. package/dist/locale.js +24 -36
  52. package/dist/locale.js.map +1 -1
  53. package/dist/needs-you-hub.d.ts +1 -0
  54. package/dist/needs-you-hub.js +6 -3
  55. package/dist/needs-you-hub.js.map +1 -1
  56. package/dist/needs-you-live.d.ts +3 -1
  57. package/dist/needs-you-live.js +24 -2
  58. package/dist/needs-you-live.js.map +1 -1
  59. package/dist/needs-you.d.ts +13 -2
  60. package/dist/needs-you.js +11 -1
  61. package/dist/needs-you.js.map +1 -1
  62. package/dist/scheduler/task-board.d.ts +0 -13
  63. package/dist/scheduler/task-board.js +0 -14
  64. package/dist/scheduler/task-board.js.map +1 -1
  65. package/dist/setup-wizard.d.ts +3 -0
  66. package/dist/setup-wizard.js +4 -2
  67. package/dist/setup-wizard.js.map +1 -1
  68. package/dist/transcript-monitor.d.ts +7 -0
  69. package/dist/transcript-monitor.js +48 -3
  70. package/dist/transcript-monitor.js.map +1 -1
  71. package/dist/types.d.ts +6 -5
  72. package/dist/ui/panel-needs.js +11 -9
  73. package/dist/ui/settings-strings.js +2 -2
  74. package/dist/ui/settings-web-authority.js +4 -3
  75. package/dist/ui/shared/app-i18n.js +2 -2
  76. package/dist/ui/shared/app-needs.js +5 -1
  77. package/dist/web-api.js +2 -0
  78. package/dist/web-api.js.map +1 -1
  79. package/launcher/agend-install.cjs +7 -1
  80. package/launcher/fleet-service-owner.cjs +138 -0
  81. package/package.json +1 -1
  82. package/dist/task-board-summary.d.ts +0 -101
  83. package/dist/task-board-summary.js +0 -177
  84. package/dist/task-board-summary.js.map +0 -1
@@ -24,6 +24,7 @@ var childProcess = require("child_process");
24
24
 
25
25
  var LOCK = ".agend-install.lock";
26
26
  var MARKER = "update-in-progress.json";
27
+ var fleetServiceOwner = require("./fleet-service-owner.cjs");
27
28
  var ensurePrivateHome = require("./private-home.cjs").ensurePrivateHome;
28
29
 
29
30
  function defaultDeps() {
@@ -670,6 +671,8 @@ function main(argv, injected) {
670
671
  catch (error) { return fail("install", error.message); }
671
672
 
672
673
  log(" AgEnD installer v" + version + (upgrade ? " (upgrade)" : ""));
674
+ var ownerRefusal = fleetServiceOwner.updateRefusal(home, { env: deps.env, uid: deps.uid, run: deps.run });
675
+ if (ownerRefusal) return fail("install", ownerRefusal);
673
676
  var root = last(deps.run("npm", ["root", "-g"], { timeoutMs: 30000 }).stdout);
674
677
  var prefix = last(deps.run("npm", ["prefix", "-g"], { timeoutMs: 30000 }).stdout);
675
678
  if (!root || !prefix) {
@@ -688,6 +691,9 @@ function main(argv, injected) {
688
691
  try {
689
692
  claimed = claimMarker(home, deps, {});
690
693
  setStage(home, claimed, "downloading", { version: version }, deps);
694
+ // Recheck before any package repair/global install, including old update callers.
695
+ var ownerRecheck = fleetServiceOwner.updateRefusal(home, { env: deps.env, uid: deps.uid, run: deps.run });
696
+ if (ownerRecheck) return fail("install", ownerRecheck);
691
697
  var fixed = repair(root, prefix, deps);
692
698
  fixed.notes.forEach(function (n) { log(" ↺ " + n); });
693
699
  pruneStale(prefix, deps).forEach(function (n) { log(" ↺ " + n); });
@@ -785,7 +791,7 @@ function main(argv, injected) {
785
791
  var what = "the previous package (v" + preimage.version + ") and " + name + " were restored";
786
792
  if (back !== preimage.version) restored.push(bin + " reports " + (back || "nothing") + ", not v" + preimage.version);
787
793
  var outcome;
788
- if (untouched) outcome = what + "; the previous service was never stopped and still runs (pid " + fleetBefore + ")";
794
+ if (untouched) outcome = what + "; " + fleetServiceOwner.message("survivor", { pid: fleetBefore }, fleetServiceOwner.locale(home));
789
795
  else if (fleetBefore > 0) {
790
796
  var started = startService(svc, home, deps);
791
797
  outcome = started.running ? what + "; the previous service is running (pid " + started.pid + ")"
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ // Shared by the installed CLI and the TARGET release's installer (#2823).
3
+ // Read-only classification: unknown ownership always refuses; this module never stops a process.
4
+ var fs = require("fs"), path = require("path"), os = require("os"), cp = require("child_process");
5
+ function defaults(options) {
6
+ var o = options || {}, env = o.env || process.env;
7
+ return Object.assign({
8
+ platform: process.platform, env: env, uid: process.getuid ? process.getuid() : -1,
9
+ read: function (p) { return fs.readFileSync(p, "utf8"); },
10
+ stat: function (p) { return fs.statSync(p); },
11
+ real: function (p) { return fs.realpathSync(p); },
12
+ lstat: function (p) { return fs.lstatSync(p); },
13
+ alive: function (pid) { try { process.kill(pid, 0); return true; } catch (e) { if (e.code === "ESRCH") return false; throw e; } },
14
+ run: function (cmd, args, opts) {
15
+ var r = cp.spawnSync(cmd, args, { encoding: "utf8", timeout: 5000, env: opts.env, stdio: ["ignore", "pipe", "pipe"] });
16
+ return { status: r.status, stdout: r.stdout || "" };
17
+ }
18
+ }, o);
19
+ }
20
+ function selectedService(options) {
21
+ var d = defaults(options);
22
+ if (d.platform !== "linux") return null;
23
+ var system = ["/etc/systemd/system/agend.service", "/usr/lib/systemd/system/agend.service", "/lib/systemd/system/agend.service"];
24
+ for (var i = 0; i < system.length; i++) if (present(system[i], d)) return { unit: "agend.service", user: false };
25
+ if (present(path.join(d.env.HOME || os.homedir(), ".config/systemd/user/com.agend.fleet.service"), d)) return { unit: "com.agend.fleet.service", user: true };
26
+ return null;
27
+ }
28
+ // existsSync also returns false for access errors and loops. Only ENOENT proves absence.
29
+ function present(p, d) {
30
+ try { return d.lstat(p); } catch (e) { if (e.code === "ENOENT") return null; throw e; }
31
+ }
32
+ function record(p, d) {
33
+ var st = present(p, d);
34
+ if (!st) return null;
35
+ if (!st.isFile()) throw Error("not a regular record"); // includes dangling/self symlinks
36
+ return d.read(p); // disappearance after lstat is unknown, not proven absence
37
+ }
38
+ function processBirth(pid, d) {
39
+ var stat = d.read("/proc/" + pid + "/stat"), end = stat.lastIndexOf(")"), parts = stat.slice(end + 2).trim().split(/\s+/);
40
+ if (stat.indexOf(pid + " (") !== 0 || end < 0 || !/^\d+$/.test(parts[19] || "")) throw Error("unknown birth");
41
+ return { birth: parts[19], exited: parts[0] === "Z" || parts[0] === "X" };
42
+ }
43
+ function fingerprint(proof) { return JSON.stringify([proof.pid, proof.birth, proof.argv, proof.group]); }
44
+ function fields(text) {
45
+ var out = Object.create(null);
46
+ text.split("\n").forEach(function (line) { var at = line.indexOf("="); if (at > 0) out[line.slice(0, at)] = line.slice(at + 1); });
47
+ return out;
48
+ }
49
+ function cgroup(text) {
50
+ var rows = text.trim().split("\n").filter(function (r) { return /^(?:0::|\d+:name=systemd:)/.test(r); });
51
+ if (rows.length !== 1) throw Error("unknown cgroup");
52
+ var p = rows[0].slice(rows[0].indexOf(":", rows[0].indexOf(":") + 1) + 1);
53
+ if (p[0] !== "/") throw Error("unknown cgroup");
54
+ return p;
55
+ }
56
+ function inside(p, group) { return !!group && group !== "/" && (p === group || p.indexOf(group + "/") === 0); }
57
+ function processProof(pid, home, d) {
58
+ if (!d.alive(pid)) return null;
59
+ var proc = "/proc/" + pid, start = processBirth(pid, d);
60
+ if (start.exited) return null;
61
+ if (d.stat(proc).uid !== d.uid) throw Error("foreign uid");
62
+ var argv = d.read(proc + "/cmdline").split("\0").filter(Boolean);
63
+ // Concrete shipped CLI, not an arbitrary process with 'fleet start' in its title.
64
+ if (argv.length < 4 || argv.length > 5 || argv[2] !== "fleet" || argv[3] !== "start" || (argv.length === 5 && argv[4] !== "--direct")) throw Error("not a fleet command");
65
+ var entry = d.real(argv[1]);
66
+ if (path.basename(entry) !== "cli.js" || path.basename(path.dirname(entry)) !== "dist" || JSON.parse(d.read(path.join(path.dirname(entry), "../package.json"))).name !== "@songsid/agend") throw Error("not an AgEnD CLI");
67
+ if (!/^node(?:js)?$/.test(path.basename(d.real(proc + "/exe")))) throw Error("not node");
68
+ var env = Object.create(null);
69
+ d.read(proc + "/environ").split("\0").forEach(function (v) { var at = v.indexOf("="); if (at > 0) env[v.slice(0, at)] = v.slice(at + 1); });
70
+ var declared = env.AGEND_HOME || (env.HOME ? path.join(env.HOME, ".agend") : null);
71
+ if (!declared || d.real(path.resolve(d.real(proc + "/cwd"), declared)) !== d.real(home)) throw Error("foreign or unreadable home");
72
+ var group = cgroup(d.read(proc + "/cgroup"));
73
+ var end = processBirth(pid, d);
74
+ if (start.birth !== end.birth || end.exited) throw Error("process changed during proof");
75
+ return { pid: pid, birth: start.birth, argv: argv, group: group };
76
+ }
77
+ function inspect(home, service, options) {
78
+ var d = defaults(options), raw, owner;
79
+ if (d.platform !== "linux" || !service) return { kind: "none" };
80
+ try {
81
+ var lock = path.join(home, "fleet.lock");
82
+ raw = record(lock, d);
83
+ if (raw === null) {
84
+ var pidText = record(path.join(home, "fleet.pid"), d);
85
+ if (pidText !== null) {
86
+ var recorded = Number(pidText.trim());
87
+ if (!Number.isSafeInteger(recorded) || recorded <= 1 || d.alive(recorded)) return { kind: "unknown" };
88
+ }
89
+ return { kind: "none" };
90
+ }
91
+ owner = JSON.parse(raw);
92
+ if (!owner || !Number.isSafeInteger(owner.pid) || owner.pid <= 1 || (owner.role !== undefined && owner.role !== "fleet") || typeof owner.nonce !== "string" || !owner.nonce) return { kind: "unknown" };
93
+ var proof = processProof(owner.pid, home, d);
94
+ if (!proof) return { kind: "none" };
95
+ var env = Object.assign({}, d.env);
96
+ if (service.user && !env.XDG_RUNTIME_DIR) env.XDG_RUNTIME_DIR = "/run/user/" + d.uid;
97
+ var r = d.run("systemctl", (service.user ? ["--user"] : []).concat(["show", service.unit, "--property=MainPID,ControlGroup,ActiveState,SubState,WorkingDirectory"]), { env: env, timeoutMs: 5000 });
98
+ if (r.status !== 0) return { kind: "unknown" };
99
+ // systemctl can block. Recheck the process and lock before trusting its ownership answer.
100
+ var after = processProof(owner.pid, home, d);
101
+ if (!after || fingerprint(after) !== fingerprint(proof) || record(lock, d) !== raw) return { kind: "unknown" };
102
+ var unit = fields(r.stdout), main = Number(unit.MainPID);
103
+ if (!/^\d+$/.test(unit.MainPID || "") || !unit.ActiveState || !unit.SubState || d.real(unit.WorkingDirectory) !== d.real(home)) return { kind: "unknown" };
104
+ // Require both the service MainPID and its cgroup to match.
105
+ if (main === owner.pid && inside(proof.group, unit.ControlGroup)) return { kind: "managed", pid: owner.pid, mainPid: main };
106
+ if (main !== 0 || !/^(?:inactive|failed)$/.test(unit.ActiveState)) return { kind: "unknown" };
107
+ return { kind: "outside", pid: owner.pid, unit: service.unit };
108
+ } catch (e) { return { kind: "unknown" }; }
109
+ }
110
+ var messages = {
111
+ en: {
112
+ update_outside: "Update not started: fleet PID {pid} is running outside {unit}. Run kill {pid} to request a normal stop, confirm that process has exited, then run agend start and retry agend update. No package or service definition was changed.",
113
+ update_unknown: "Update not started: fleet ownership could not be verified. Check fleet.lock and systemctl status to identify the fleet PID; only then stop that fleet with kill PID and confirm it has exited. Run agend start, then retry agend update. No package or service definition was changed.",
114
+ restart_outside: "Restart not started: fleet PID {pid} is running outside {unit}. Run kill {pid} to request a normal stop, confirm that process has exited, then run agend start. Nothing was signalled or started.",
115
+ restart_unknown: "Restart not started: fleet ownership could not be verified. Check fleet.lock and systemctl status to identify the fleet PID; only then stop that fleet with kill PID and confirm it has exited. Run agend start. Nothing was signalled or started.",
116
+ survivor: "the previously recorded fleet PID {pid} is still alive; this check does not establish which service owns it"
117
+ },
118
+ "zh-TW": {
119
+ update_outside: "尚未開始更新:Fleet(PID {pid})正在 {unit} 之外執行。請執行 kill {pid} 要求正常停止,確認該程序已結束,再執行 agend start 並重試 agend update。套件與服務設定均未變更。",
120
+ update_unknown: "尚未開始更新:無法確認 Fleet 歸屬。請先檢查 fleet.lock 與 systemctl status,確認 Fleet 的 PID 後才執行 kill PID,並確認該程序已結束。再執行 agend start,然後重試 agend update。套件與服務設定均未變更。",
121
+ restart_outside: "尚未開始重新啟動:Fleet(PID {pid})正在 {unit} 之外執行。請執行 kill {pid} 要求正常停止,確認該程序已結束,再執行 agend start。未傳送任何訊號,也未啟動任何程序。",
122
+ restart_unknown: "尚未開始重新啟動:無法確認 Fleet 歸屬。請先檢查 fleet.lock 與 systemctl status,確認 Fleet 的 PID 後才執行 kill PID,並確認該程序已結束。再執行 agend start。未傳送任何訊號,也未啟動任何程序。",
123
+ survivor: "先前記錄的 Fleet PID {pid} 仍存活;此檢查無法確認它由哪個服務管理"
124
+ }
125
+ };
126
+ function locale(home) {
127
+ try { var value = require("js-yaml").load(fs.readFileSync(path.join(home, "fleet.yaml"), "utf8")); if (value && value.defaults && messages[value.defaults.locale]) return value.defaults.locale; } catch (e) { /* zone fallback */ }
128
+ return new Date().getTimezoneOffset() === -480 ? "zh-TW" : "en";
129
+ }
130
+ function message(key, state, language) { return messages[language === "zh-TW" ? language : "en"][key].replace(/\{(pid|unit)\}/g, function (_, k) { return String(state[k]); }); }
131
+ function refusal(action, home, service, options) {
132
+ var state;
133
+ try { state = inspect(home, service === undefined ? selectedService(options) : service, options); } catch (e) { state = { kind: "unknown" }; }
134
+ return state.kind === "outside" || state.kind === "unknown" ? message(action + "_" + state.kind, state, (options || {}).locale || locale(home)) : null;
135
+ }
136
+ function updateRefusal(home, options) { return refusal("update", home, undefined, options); }
137
+ function restartRefusal(home, service, options) { return refusal("restart", home, service, options); }
138
+ module.exports = { selectedService: selectedService, inspect: inspect, updateRefusal: updateRefusal, restartRefusal: restartRefusal, message: message, locale: locale };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@songsid/agend",
3
- "version": "2.3.3-beta.1",
3
+ "version": "2.3.3-beta.3",
4
4
  "description": "Multi-agent fleet daemon — run coding CLIs (Claude, Muse, Codex, OpenCode) from Discord or Telegram",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,101 +0,0 @@
1
- import type { Card, Project, ProjectStats } from "./scheduler/task-board.js";
2
- /** Cards listed per section; the rest are counted. */
3
- export declare const SUMMARY_LISTED = 10;
4
- /**
5
- * `defaults.task_board.summary_at`: `"HH:MM"` (24 h) → itself, `"off"` (any case) or not set → null (off); anything
6
- * else → undefined (the validator refuses it; the clock then sends nothing). Off unless set (2.3.0-beta.25, the
7
- * user's call after beta.24's task-board flood, #2425): a fleet that wants the summary names its time.
8
- */
9
- export declare function summaryTime(value: unknown): string | null | undefined;
10
- /** The wall clock at `now` in `zone` ("YYYY-MM-DD", "HH:MM"); a zone the runtime does not know reads as UTC. */
11
- export declare function wallClock(now: Date, zone: string): {
12
- date: string;
13
- time: string;
14
- };
15
- /** When the last summary went out: its local date and the end of the time it covered. */
16
- export interface SummaryMark {
17
- date: string;
18
- until: string;
19
- }
20
- /** The mark on disk (`<dataDir>/task-board-summary.json`); a missing or unreadable file is no mark. */
21
- export declare function summaryMarkFile(path: string): {
22
- read(): SummaryMark | null;
23
- write(mark: SummaryMark): void;
24
- };
25
- export interface SummaryClockDeps {
26
- /** `defaults.task_board.summary_at` as it is now ("HH:MM"), or null when off. Read at every look. */
27
- at(): string | null;
28
- zone(): string;
29
- now(): Date;
30
- mark: {
31
- read(): SummaryMark | null;
32
- write(mark: SummaryMark): void;
33
- };
34
- /** Gather and post the summary of [since, until). */
35
- send(since: Date, until: Date): void;
36
- /** A look that threw (the data directory gone, a board that cannot be read): logged, and the next look tries again. */
37
- failed?(err: unknown): void;
38
- setInterval?(fn: () => void, ms: number): unknown;
39
- clearInterval?(handle: unknown): void;
40
- }
41
- export declare class TaskBoardSummaryClock {
42
- private readonly deps;
43
- private timer;
44
- private first;
45
- constructor(deps: SummaryClockDeps);
46
- /** A look 5 s after start (a summary missed while the fleet was down), then every minute. */
47
- start(): void;
48
- stop(): void;
49
- /** One look: true when a summary was due today and is now handled (sent, or quiet). */
50
- tick(): boolean;
51
- }
52
- export interface ProjectLine {
53
- name: string;
54
- percent: number;
55
- done: number;
56
- total: number;
57
- waiting: number;
58
- blocked: number;
59
- }
60
- export interface BoardSummary {
61
- since: Date;
62
- until: Date;
63
- review: Card[];
64
- blocked: Card[];
65
- done: Card[];
66
- created: Card[];
67
- projects: ProjectLine[];
68
- }
69
- export interface SummaryRead {
70
- /** What waits for the person now (TaskBoard.waitingForPerson): review cards that need them, blocked cards they handed out. */
71
- waiting: Card[];
72
- done: Card[];
73
- created: Card[];
74
- /** Every live main task's project and assignee: a project's line goes to the chats whose instances hold one. */
75
- mains: Array<{
76
- projectId: number | null;
77
- assignee: string | null;
78
- }>;
79
- }
80
- /**
81
- * One chat's summary: the cards `inChat` keeps (by assignee), and the active projects one of its cards' main tasks is
82
- * in, with the project's numbers over all its cards (projectStats).
83
- */
84
- export declare function buildSummary(read: SummaryRead, since: Date, until: Date, opts: {
85
- inChat(assignee: string | null): boolean;
86
- projects: Project[];
87
- stats: Record<string, ProjectStats>;
88
- }): BoardSummary;
89
- /** Nothing waits and nothing moved: no summary that day. */
90
- export declare function isQuiet(s: BoardSummary): boolean;
91
- type T = (key: string, ...args: (string | number)[]) => string;
92
- /**
93
- * The summary as plain text in the fleet's language (no markup: Telegram, Discord and the web chat read it the same).
94
- * `projectOf`: a main task's project name.
95
- */
96
- export declare function renderSummary(s: BoardSummary, opts: {
97
- zone: string;
98
- t: T;
99
- projectOf(card: Card): string | null;
100
- }): string;
101
- export {};
@@ -1,177 +0,0 @@
1
- /**
2
- * #2246 step 3: the task board's daily summary for the person, as taskhall sends it. Once a day at
3
- * `defaults.task_board.summary_at` (the fleet's zone): what waits for their confirmation and what is blocked now, what
4
- * got done and what was opened since the last summary, and each project's progress. Each chat world's General gets the
5
- * cards of its own instances (a card nobody in a world holds goes to the primary one); the web chat's Generals too.
6
- *
7
- * The clock is taskhall's: it looks every minute, sends at most once a calendar day, and a summary missed while the
8
- * fleet was down goes out when it is back that day, covering the time since the last one (at most a week). A quiet day
9
- * (nothing waits, nothing moved) sends nothing. The first summary after this ships waits for the next morning.
10
- */
11
- import { atomicWriteFileSync } from "./atomic-write.js";
12
- import { readFileSync } from "node:fs";
13
- /** Cards listed per section; the rest are counted. */
14
- export const SUMMARY_LISTED = 10;
15
- const WEEK_MS = 7 * 86_400_000;
16
- /**
17
- * `defaults.task_board.summary_at`: `"HH:MM"` (24 h) → itself, `"off"` (any case) or not set → null (off); anything
18
- * else → undefined (the validator refuses it; the clock then sends nothing). Off unless set (2.3.0-beta.25, the
19
- * user's call after beta.24's task-board flood, #2425): a fleet that wants the summary names its time.
20
- */
21
- export function summaryTime(value) {
22
- if (value === undefined || value === null)
23
- return null;
24
- if (typeof value !== "string")
25
- return undefined;
26
- const v = value.trim().toLowerCase();
27
- if (v === "off")
28
- return null;
29
- return /^([01]\d|2[0-3]):[0-5]\d$/.test(v) ? v : undefined;
30
- }
31
- /** The wall clock at `now` in `zone` ("YYYY-MM-DD", "HH:MM"); a zone the runtime does not know reads as UTC. */
32
- export function wallClock(now, zone) {
33
- let wall;
34
- try {
35
- wall = now.toLocaleString("sv-SE", { timeZone: zone, hour12: false });
36
- }
37
- catch {
38
- wall = now.toLocaleString("sv-SE", { timeZone: "UTC", hour12: false });
39
- }
40
- return { date: wall.slice(0, 10), time: wall.slice(11, 16) };
41
- }
42
- /** The mark on disk (`<dataDir>/task-board-summary.json`); a missing or unreadable file is no mark. */
43
- export function summaryMarkFile(path) {
44
- return {
45
- read() {
46
- try {
47
- const m = JSON.parse(readFileSync(path, "utf8"));
48
- return typeof m.date === "string" && typeof m.until === "string" ? { date: m.date, until: m.until } : null;
49
- }
50
- catch {
51
- return null;
52
- }
53
- },
54
- write(mark) { atomicWriteFileSync(path, JSON.stringify(mark) + "\n"); },
55
- };
56
- }
57
- export class TaskBoardSummaryClock {
58
- deps;
59
- timer = null;
60
- first = null;
61
- constructor(deps) {
62
- this.deps = deps;
63
- }
64
- /** A look 5 s after start (a summary missed while the fleet was down), then every minute. */
65
- start() {
66
- if (this.timer)
67
- return;
68
- const every = this.deps.setInterval ?? ((fn, ms) => { const h = setInterval(fn, ms); h.unref?.(); return h; });
69
- const look = () => { try {
70
- this.tick();
71
- }
72
- catch (err) {
73
- this.deps.failed?.(err);
74
- } };
75
- this.timer = every(look, 60_000);
76
- this.first = setTimeout(look, 5_000);
77
- this.first.unref?.();
78
- }
79
- stop() {
80
- if (this.timer)
81
- (this.deps.clearInterval ?? ((h) => clearInterval(h)))(this.timer);
82
- if (this.first)
83
- clearTimeout(this.first);
84
- this.timer = null;
85
- this.first = null;
86
- }
87
- /** One look: true when a summary was due today and is now handled (sent, or quiet). */
88
- tick() {
89
- const at = this.deps.at();
90
- if (!at)
91
- return false;
92
- const now = this.deps.now();
93
- const wall = wallClock(now, this.deps.zone());
94
- if (wall.time < at)
95
- return false;
96
- const last = this.deps.mark.read();
97
- if (last?.date === wall.date)
98
- return false;
99
- // Marked before it is sent: a send that throws does not repeat every minute.
100
- this.deps.mark.write({ date: wall.date, until: now.toISOString() });
101
- if (!last)
102
- return false; // the first day: nothing to look back on yet; the next one covers from now
103
- const since = Math.min(Math.max(Date.parse(last.until) || 0, now.getTime() - WEEK_MS), now.getTime());
104
- this.deps.send(new Date(since), now);
105
- return true;
106
- }
107
- }
108
- /** Main tasks first, then by number. */
109
- const byNumber = (a, b) => (a.kind === b.kind ? 0 : a.kind === "main" ? -1 : 1) || Number(a.code.slice(2)) - Number(b.code.slice(2));
110
- /**
111
- * One chat's summary: the cards `inChat` keeps (by assignee), and the active projects one of its cards' main tasks is
112
- * in, with the project's numbers over all its cards (projectStats).
113
- */
114
- export function buildSummary(read, since, until, opts) {
115
- const mine = (cards) => cards.filter((c) => opts.inChat(c.assignee)).sort(byNumber);
116
- const here = new Set(read.mains.filter((m) => m.projectId !== null && opts.inChat(m.assignee)).map((m) => m.projectId));
117
- return {
118
- since, until,
119
- review: mine(read.waiting.filter((c) => c.status === "review")),
120
- blocked: mine(read.waiting.filter((c) => c.status === "blocked")),
121
- done: mine(read.done),
122
- created: mine(read.created),
123
- projects: opts.projects.filter((p) => !p.archived && here.has(p.id)).flatMap((p) => {
124
- const st = opts.stats[String(p.id)];
125
- return st && st.mains ? [{ name: p.name, percent: st.percent, done: st.units.done, total: st.units.total, waiting: st.waiting, blocked: st.blocked }] : [];
126
- }),
127
- };
128
- }
129
- /** Nothing waits and nothing moved: no summary that day. */
130
- export function isQuiet(s) {
131
- return !s.review.length && !s.blocked.length && !s.done.length && !s.created.length;
132
- }
133
- const short = (s, n) => (Array.from(s).length > n ? Array.from(s).slice(0, n - 1).join("") + "…" : s);
134
- /** `M/D HH:MM` in the fleet's zone. */
135
- function shortTime(d, zone) {
136
- const { date, time } = wallClock(d, zone);
137
- return `${Number(date.slice(5, 7))}/${Number(date.slice(8, 10))} ${time}`;
138
- }
139
- /**
140
- * The summary as plain text in the fleet's language (no markup: Telegram, Discord and the web chat read it the same).
141
- * `projectOf`: a main task's project name.
142
- */
143
- export function renderSummary(s, opts) {
144
- const { t } = opts;
145
- const ref = (c) => t("board_summary.card", c.code, short(c.title, 30))
146
- + (c.kind === "main" ? (opts.projectOf(c) ? ` · ${opts.projectOf(c)}` : "") : c.assignee ? ` @${c.assignee}` : "");
147
- const list = (cards, line) => {
148
- const out = cards.slice(0, SUMMARY_LISTED).map((c) => `• ${line(c)}`);
149
- if (cards.length > SUMMARY_LISTED)
150
- out.push(`• ${t("board_summary.more", cards.length - SUMMARY_LISTED)}`);
151
- return out;
152
- };
153
- // Finished and new: a main task a line each, sub tasks folded into one line of numbers.
154
- const folded = (cards) => {
155
- const mains = cards.filter((c) => c.kind === "main"), subs = cards.filter((c) => c.kind === "sub");
156
- const out = list(mains, ref);
157
- if (subs.length) {
158
- const codes = subs.slice(0, SUMMARY_LISTED).map((c) => c.code).join(t("board_summary.join"));
159
- out.push(`• ${t(subs.length > SUMMARY_LISTED ? "board_summary.subs_more" : "board_summary.subs", subs.length, codes)}`);
160
- }
161
- return out;
162
- };
163
- const blocks = [t("board_summary.title", shortTime(s.since, opts.zone), shortTime(s.until, opts.zone))];
164
- const section = (title, lines) => { if (lines.length)
165
- blocks.push([title, ...lines].join("\n")); };
166
- section(t("board_summary.review", s.review.length), list(s.review, ref));
167
- section(t("board_summary.blocked", s.blocked.length), list(s.blocked, (c) => ref(c) + (c.kind === "main" && c.assignee ? ` @${c.assignee}` : "") + (c.blockedReason ? t("board_summary.reason", short(c.blockedReason, 60)) : "")));
168
- section(t("board_summary.done", s.done.length), folded(s.done));
169
- section(t("board_summary.created", s.created.length), folded(s.created));
170
- section(t("board_summary.projects"), s.projects.map((p) => {
171
- const extra = [p.waiting ? t("board_summary.project_waiting", p.waiting) : "", p.blocked ? t("board_summary.project_blocked", p.blocked) : ""].filter(Boolean);
172
- return `• ${t("board_summary.project", p.name, p.percent, p.done, p.total)}${extra.length ? t("board_summary.project_extra", extra.join(t("board_summary.join"))) : ""}`;
173
- }));
174
- blocks.push(t("board_summary.footer"));
175
- return blocks.join("\n\n");
176
- }
177
- //# sourceMappingURL=task-board-summary.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"task-board-summary.js","sourceRoot":"","sources":["../src/task-board-summary.ts"],"names":[],"mappings":"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"}