aibroker 0.25.0 → 0.26.0

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 (35) hide show
  1. package/dist/daemon/core-handlers.d.ts.map +1 -1
  2. package/dist/daemon/core-handlers.js +89 -0
  3. package/dist/daemon/core-handlers.js.map +1 -1
  4. package/dist/daemon/inbound.d.ts +123 -0
  5. package/dist/daemon/inbound.d.ts.map +1 -0
  6. package/dist/daemon/inbound.js +266 -0
  7. package/dist/daemon/inbound.js.map +1 -0
  8. package/dist/daemon/index.d.ts.map +1 -1
  9. package/dist/daemon/index.js +23 -0
  10. package/dist/daemon/index.js.map +1 -1
  11. package/dist/daemon/todoist-cli.d.ts.map +1 -1
  12. package/dist/daemon/todoist-cli.js +73 -1
  13. package/dist/daemon/todoist-cli.js.map +1 -1
  14. package/dist/daemon/todoist-inbox.d.ts +84 -0
  15. package/dist/daemon/todoist-inbox.d.ts.map +1 -0
  16. package/dist/daemon/todoist-inbox.js +169 -0
  17. package/dist/daemon/todoist-inbox.js.map +1 -0
  18. package/dist/daemon/todoist-mirror.d.ts +97 -0
  19. package/dist/daemon/todoist-mirror.d.ts.map +1 -0
  20. package/dist/daemon/todoist-mirror.js +472 -0
  21. package/dist/daemon/todoist-mirror.js.map +1 -0
  22. package/dist/daemon/todoist-oauth.d.ts +0 -6
  23. package/dist/daemon/todoist-oauth.d.ts.map +1 -1
  24. package/dist/daemon/todoist-oauth.js +34 -1
  25. package/dist/daemon/todoist-oauth.js.map +1 -1
  26. package/dist/daemon/todoist-reply.d.ts +15 -0
  27. package/dist/daemon/todoist-reply.d.ts.map +1 -1
  28. package/dist/daemon/todoist-reply.js +31 -0
  29. package/dist/daemon/todoist-reply.js.map +1 -1
  30. package/dist/daemon/todoist-webhook.d.ts.map +1 -1
  31. package/dist/daemon/todoist-webhook.js +91 -0
  32. package/dist/daemon/todoist-webhook.js.map +1 -1
  33. package/dist/mcp/index.js +23 -1
  34. package/dist/mcp/index.js.map +1 -1
  35. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todoist-inbox.d.ts","sourceRoot":"","sources":["../../src/daemon/todoist-inbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAuBH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAkBD,kEAAkE;AAClE,wBAAgB,QAAQ,IAAI,MAAM,GAAG,SAAS,CAE7C;AAED,uDAAuD;AACvD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,cAAc,EAAE,CAAC,CAuB3B;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CA+C9E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAuBhD"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * daemon/todoist-inbox.ts — what has been commented on since you last looked.
3
+ *
4
+ * A push per reply tells you something happened the moment it happens. That is
5
+ * the wrong shape for the actual problem: a tree of hundreds of tasks, comments
6
+ * arriving on any of them, and no way to see which ones are waiting for you.
7
+ * Todoist itself will not help — it does not notify an account about its own
8
+ * activity, and every comment the bridge writes is written as you, so the one
9
+ * event you most want to know about is precisely the one Todoist stays quiet
10
+ * about.
11
+ *
12
+ * So this reads the activity log — one request, not one per task — groups the
13
+ * comments by the task they landed on, and hands back a list you can jump from.
14
+ * The read marker is stored, so "what's new" means new since you last asked
15
+ * rather than new since some arbitrary window.
16
+ *
17
+ * The marker is only advanced when the caller says so. A digest that marks
18
+ * everything read as a side effect of being built would lose the whole backlog
19
+ * the first time a scheduled job ran before you saw it.
20
+ */
21
+ import { join } from "node:path";
22
+ import { homedir } from "node:os";
23
+ import { loadJson, saveJson } from "../core/json-store.js";
24
+ import { log } from "../core/log.js";
25
+ import { getAccessToken } from "./todoist-oauth.js";
26
+ import { fetchTaskBrief } from "./todoist-reply.js";
27
+ const FILE = join(homedir(), ".aibroker", "todoist-inbox.json");
28
+ /** How many activity records to pull. Todoist caps the page; this is the ask. */
29
+ const ACTIVITY_LIMIT = 100;
30
+ /**
31
+ * How many tasks get their title resolved.
32
+ *
33
+ * Each title is one API call. A digest of forty tasks is not a digest, and
34
+ * forty extra calls to build one is worse, so the list is capped and the
35
+ * remainder is reported as a count rather than silently dropped.
36
+ */
37
+ const RESOLVE_LIMIT = 20;
38
+ function read() {
39
+ const r = loadJson(FILE);
40
+ if (r.status === "ok" && r.data)
41
+ return r.data;
42
+ if (r.status === "unreadable") {
43
+ // Treat an unreadable marker as "never looked" rather than "all seen":
44
+ // showing a comment twice is a nuisance, hiding one is the failure this
45
+ // whole module exists to end.
46
+ log(`todoist-inbox: ${FILE} is unreadable — treating as never read`);
47
+ }
48
+ return {};
49
+ }
50
+ /** When the inbox was last marked read. Undefined means never. */
51
+ export function lastSeen() {
52
+ return read().lastSeen;
53
+ }
54
+ /** Record that everything up to `at` has been seen. */
55
+ export function markSeen(at) {
56
+ saveJson(FILE, { lastSeen: at });
57
+ }
58
+ /**
59
+ * Comment events, newest first.
60
+ *
61
+ * Returns an empty list rather than throwing when Todoist cannot be reached —
62
+ * the caller is building a digest, and a digest that fails loudly on a network
63
+ * blip is a digest that gets turned off.
64
+ */
65
+ export async function fetchCommentActivity(fetchImpl = fetch) {
66
+ const token = await getAccessToken();
67
+ if (!token) {
68
+ log("todoist-inbox: no Todoist authorisation on file");
69
+ return [];
70
+ }
71
+ const url = `https://api.todoist.com/api/v1/activities` +
72
+ `?object_type=note&event_type=added&limit=${ACTIVITY_LIMIT}`;
73
+ try {
74
+ const res = await fetchImpl(url, {
75
+ headers: { authorization: `${token.token_type} ${token.access_token}` },
76
+ });
77
+ if (!res.ok) {
78
+ log(`todoist-inbox: activity request failed with ${res.status}`);
79
+ return [];
80
+ }
81
+ const body = JSON.parse(await res.text());
82
+ return Array.isArray(body.results) ? body.results : [];
83
+ }
84
+ catch (e) {
85
+ log(`todoist-inbox: could not read activity — ${e instanceof Error ? e.message : String(e)}`);
86
+ return [];
87
+ }
88
+ }
89
+ /**
90
+ * Group new comments by the task they landed on.
91
+ *
92
+ * `since` defaults to the stored read marker. Pass it explicitly to look
93
+ * further back without disturbing the marker.
94
+ */
95
+ export async function buildInbox(opts = {}) {
96
+ const since = opts.since ?? lastSeen();
97
+ const records = await fetchCommentActivity();
98
+ const fresh = records.filter((r) => {
99
+ if (!r.event_date || !r.parent_item_id)
100
+ return false;
101
+ return since ? r.event_date > since : true;
102
+ });
103
+ const newest = records.map((r) => r.event_date).filter(Boolean).sort().pop();
104
+ const byTask = new Map();
105
+ for (const r of fresh) {
106
+ const id = r.parent_item_id;
107
+ const entry = byTask.get(id) ?? {
108
+ taskId: id,
109
+ projectId: r.parent_project_id,
110
+ comments: [],
111
+ latest: r.event_date,
112
+ };
113
+ entry.comments.push({
114
+ at: r.event_date,
115
+ content: r.extra_data?.content ?? "",
116
+ client: r.extra_data?.client,
117
+ });
118
+ if (r.event_date > entry.latest)
119
+ entry.latest = r.event_date;
120
+ byTask.set(id, entry);
121
+ }
122
+ const entries = [...byTask.values()].sort((a, b) => b.latest.localeCompare(a.latest));
123
+ const resolve = entries.slice(0, RESOLVE_LIMIT);
124
+ // Titles in parallel — a serial loop over twenty tasks is a visible pause.
125
+ await Promise.all(resolve.map(async (e) => {
126
+ const brief = await fetchTaskBrief(e.taskId);
127
+ e.title = brief.title;
128
+ e.url = brief.url;
129
+ }));
130
+ return {
131
+ entries: resolve,
132
+ truncated: Math.max(0, entries.length - resolve.length),
133
+ since,
134
+ newest,
135
+ };
136
+ }
137
+ /**
138
+ * The digest as text, ready for a phone.
139
+ *
140
+ * A deleted task resolves to no title; it is still listed, because "a comment
141
+ * arrived on something that no longer exists" is information, not noise.
142
+ */
143
+ export function formatInbox(inbox) {
144
+ if (inbox.entries.length === 0) {
145
+ return inbox.since
146
+ ? `No new Todoist comments since ${inbox.since.slice(0, 16).replace("T", " ")}.`
147
+ : "No Todoist comments found.";
148
+ }
149
+ const lines = [
150
+ `📋 ${inbox.entries.length} task${inbox.entries.length === 1 ? "" : "s"} with new comments`,
151
+ "",
152
+ ];
153
+ for (const e of inbox.entries) {
154
+ const when = e.latest.slice(11, 16);
155
+ lines.push(`• ${e.title ?? `(task ${e.taskId})`} — ${when}`);
156
+ const last = e.comments[0];
157
+ if (last?.content)
158
+ lines.push(` ${last.content.replace(/\s+/g, " ").slice(0, 140)}`);
159
+ if (e.comments.length > 1)
160
+ lines.push(` (+${e.comments.length - 1} more)`);
161
+ if (e.url)
162
+ lines.push(` ${e.url}`);
163
+ }
164
+ if (inbox.truncated > 0) {
165
+ lines.push("", `…and ${inbox.truncated} more task${inbox.truncated === 1 ? "" : "s"} not shown.`);
166
+ }
167
+ return lines.join("\n");
168
+ }
169
+ //# sourceMappingURL=todoist-inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todoist-inbox.js","sourceRoot":"","sources":["../../src/daemon/todoist-inbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * daemon/todoist-mirror.ts — new comments, filed where you will actually see them.
3
+ *
4
+ * Todoist never notifies an account about its own activity, and every comment a
5
+ * session writes is written as the account owner. So a reply lands on a task
6
+ * somewhere in a tree of hundreds and is, from the outside, indistinguishable
7
+ * from nothing having happened. `todoist-inbox.ts` answers that on demand. This
8
+ * answers it without being asked: each new comment becomes a task in a mirror
9
+ * project, grouped into a section named after the project the comment came
10
+ * from, linking straight back to the conversation.
11
+ *
12
+ * The sections are managed, not configured. A project that gets commented on
13
+ * gains a section; a section whose items have all been dealt with is removed
14
+ * again on the next run. That keeps the mirror shaped like what is currently
15
+ * outstanding rather than like a history of everything that ever happened —
16
+ * a list nobody prunes is a list nobody reads.
17
+ *
18
+ * Three things this must never do, each of which it would do naturally:
19
+ *
20
+ * - **Feed itself.** A comment on a mirror task is a comment, and mirroring it
21
+ * would create a task, whose comments would create tasks. Anything parented
22
+ * in the mirror project is skipped, and the mirror project is never an
23
+ * ingress project, so its tasks cannot be dispatched as work either.
24
+ * - **Replay.** The read marker moves only after a successful pass, and every
25
+ * mirrored comment id is remembered, because a marker alone loses a race
26
+ * with a comment posted in the same second it was written.
27
+ * - **Delete a section someone else made.** Only sections this module created
28
+ * are pruned, tracked by id, never by name.
29
+ */
30
+ /** The mirror project, or undefined when mirroring is switched off. */
31
+ export declare function mirrorProjectId(): string | undefined;
32
+ export interface MirrorResult {
33
+ mirrored: number;
34
+ sectionsCreated: number;
35
+ sectionsRemoved: number;
36
+ skipped: number;
37
+ }
38
+ /**
39
+ * Mirror everything commented on since the last pass.
40
+ *
41
+ * Safe to call repeatedly: the marker only advances at the end, the seen-set
42
+ * makes a repeated pass a no-op rather than a duplicate, and concurrent callers
43
+ * share one run.
44
+ */
45
+ export declare function syncMirror(fetchImpl?: typeof fetch): Promise<MirrorResult>;
46
+ /**
47
+ * Mirror a comment we just wrote, without going back to Todoist to find it.
48
+ *
49
+ * This is the primary path, and polling is not the fallback for it — retrying
50
+ * is. The reasoning: Todoist stays silent only about the account's OWN
51
+ * activity, and the bridge writes as the account. Everyone else's comments it
52
+ * already notifies about. So the set of comments that need mirroring is exactly
53
+ * the set we produced, and we know each one at the moment we write it. Reading
54
+ * the activity log to rediscover our own writes was a round trip to learn what
55
+ * we already knew, and it put a five-minute delay on the one case somebody is
56
+ * actually watching.
57
+ *
58
+ * Failures are queued rather than dropped and retried on the next write, so
59
+ * removing the poll does not reintroduce silent loss.
60
+ */
61
+ export declare function mirrorComment(comment: {
62
+ taskId: string;
63
+ commentId?: string;
64
+ text: string;
65
+ }, fetchImpl?: typeof fetch): Promise<boolean>;
66
+ /**
67
+ * The source task a mirror entry points at, read from its description link.
68
+ *
69
+ * Stateless on purpose. A stored map would have to survive restarts, stay in
70
+ * step with tasks the user deletes by hand, and be pruned; the link is already
71
+ * in the entry, is the thing the human clicks, and cannot drift out of sync
72
+ * with the entry it belongs to.
73
+ */
74
+ export declare function sourceTaskOf(description: string | undefined): string | undefined;
75
+ /**
76
+ * Carry a comment written on a mirror entry back to the real task.
77
+ *
78
+ * The mirror is where the reader's attention is, so replying there instead of
79
+ * on the source task is not a mistake anyone will stop making — it is the
80
+ * obvious thing to do, and Todoist offers no way to deep-link to an individual
81
+ * comment that would make the source easier to reach than the entry in front of
82
+ * you. So rather than train the human, carry the comment.
83
+ *
84
+ * Loop safety rests on one asymmetry: this writes to the SOURCE task, and the
85
+ * direct mirror path is only ever invoked by `todoist_reply`. Nothing re-reads
86
+ * this write, so there is no cycle to break. Agent-marked comments are ignored
87
+ * regardless, so a session answering inside the mirror cannot bounce either.
88
+ */
89
+ export declare function mirrorBack(ev: {
90
+ taskId: string;
91
+ text: string;
92
+ projectId?: string;
93
+ }, fetchImpl?: typeof fetch): Promise<{
94
+ carried: boolean;
95
+ reason?: string;
96
+ }>;
97
+ //# sourceMappingURL=todoist-mirror.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todoist-mirror.d.ts","sourceRoot":"","sources":["../../src/daemon/todoist-mirror.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAqEH,uEAAuE;AACvE,wBAAgB,eAAe,IAAI,MAAM,GAAG,SAAS,CAGpD;AA2HD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAaD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAIjF;AAgID;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC7D,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,OAAO,CAAC,CA+BlB;AAsED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EACxD,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwBhD"}