@ai-setting/roy-plugin-task-show 0.8.9 → 0.9.5

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 (47) hide show
  1. package/dist/cli-tasks-adapter.d.ts +2 -17
  2. package/dist/cli-tasks-adapter.d.ts.map +1 -1
  3. package/dist/cli-tasks-adapter.js +7 -57
  4. package/dist/cli-tasks-adapter.js.map +1 -1
  5. package/dist/index.d.ts +0 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +0 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/operations-cache.d.ts +21 -0
  10. package/dist/operations-cache.d.ts.map +1 -1
  11. package/dist/operations-cache.js +45 -0
  12. package/dist/operations-cache.js.map +1 -1
  13. package/dist/plugin.d.ts +22 -74
  14. package/dist/plugin.d.ts.map +1 -1
  15. package/dist/plugin.js +174 -173
  16. package/dist/plugin.js.map +1 -1
  17. package/dist/server.d.ts +15 -31
  18. package/dist/server.d.ts.map +1 -1
  19. package/dist/server.js +42 -206
  20. package/dist/server.js.map +1 -1
  21. package/dist/task-detail-mermaid.d.ts +9 -0
  22. package/dist/task-detail-mermaid.d.ts.map +1 -1
  23. package/dist/task-detail-mermaid.js +43 -3
  24. package/dist/task-detail-mermaid.js.map +1 -1
  25. package/dist/task-session-store.d.ts +54 -94
  26. package/dist/task-session-store.d.ts.map +1 -1
  27. package/dist/task-session-store.js +137 -177
  28. package/dist/task-session-store.js.map +1 -1
  29. package/dist/tasks-tree-cache.d.ts +17 -0
  30. package/dist/tasks-tree-cache.d.ts.map +1 -1
  31. package/dist/tasks-tree-cache.js +39 -0
  32. package/dist/tasks-tree-cache.js.map +1 -1
  33. package/dist/tracing/decorator.d.ts +48 -0
  34. package/dist/tracing/decorator.d.ts.map +1 -0
  35. package/dist/tracing/decorator.js +310 -0
  36. package/dist/tracing/decorator.js.map +1 -0
  37. package/dist/types.d.ts +28 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/types.js +9 -0
  40. package/dist/types.js.map +1 -1
  41. package/package.json +4 -2
  42. package/plugin.json +2 -2
  43. package/public/app.js +45 -1
  44. package/public/index.html +3 -0
  45. package/public/session-forest.js +97 -0
  46. package/public/style.css +0 -45
  47. package/public/task-operations.js +7 -0
@@ -1,193 +1,153 @@
1
- /**
2
- * @fileoverview TaskSessionStore process-scoped registry of tasks
3
- * observed through the `task:after.create` hook.
4
- *
5
- * v0.8.7 introduced this store so the home page can render an
6
- * *ancestor chain* (the latest "leaf" task plus the chain of
7
- * `parent_task_id` links up to the root) instead of the full
8
- * cross-host task tree. Tracking only this-process tasks also keeps
9
- * the home page quiet until the plugin actually does work, which
10
- * makes the visualization a more accurate reflection of what the
11
- * agent is doing right now.
12
- *
13
- * Design notes:
14
- * - Pure in-memory; no I/O, no globals. The plugin owns a single
15
- * instance and disposes it via {@link TaskSessionStore.clear} when
16
- * the plugin is torn down.
17
- * - The fetcher for parent tasks is **injected** so tests can mock
18
- * the CLI call without spawning a real subprocess.
19
- * - The store is tolerant: a broken parent link, a cycle, or a
20
- * missing fetcher all degrade gracefully (the chain stops at the
21
- * first unverifiable link rather than throwing).
22
- * - `buildAncestorChain` always returns tasks in **root-first** order
23
- * (`[root, …, leaf]`) so the UI can render the chain linearly
24
- * from top to bottom without having to reverse it.
25
- */
26
- import { execFile } from "node:child_process";
27
- import { promisify } from "node:util";
28
- const execFileAsync = promisify(execFile);
29
- // ---------------------------------------------------------------------------
30
- // Store
31
- // ---------------------------------------------------------------------------
32
- /**
33
- * Process-scoped registry of tasks observed via the
34
- * `task:after.create` hook.
35
- */
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { TracedAs, withTraceSync } from "./tracing/decorator.js";
36
8
  export class TaskSessionStore {
37
- /** In-memory metadata, keyed by task id. */
38
- tasks = new Map();
39
- /** Insertion order (Map preserves it). We keep a snapshot for tests. */
40
- insertionOrder = [];
9
+ opts;
10
+ latestLeafId = null;
11
+ constructor(opts) {
12
+ this.opts = {
13
+ sessionStartedAt: opts.sessionStartedAt,
14
+ maxAncestors: opts.maxAncestors ?? 32,
15
+ listTasks: opts.listTasks,
16
+ taskFetcher: opts.taskFetcher ?? (async () => null),
17
+ };
18
+ }
41
19
  /**
42
- * Record a task. Re-recording an existing id overwrites the cached
43
- * metadata (this is the contract: the latest hook payload wins).
20
+ * Returns the id of the most recently recorded leaf task in this session,
21
+ * or undefined if no task has been recorded yet. The v0.9.0 home page
22
+ * highlights this task with a `data-leaf="1"` attribute.
44
23
  */
45
- record(task) {
46
- if (!Number.isFinite(task.id) || task.id <= 0)
47
- return;
48
- const isNew = !this.tasks.has(task.id);
49
- this.tasks.set(task.id, { ...task });
50
- if (isNew)
51
- this.insertionOrder.push(task.id);
52
- }
53
- /** Return the most recently recorded task id, or `null` if empty. */
54
24
  getLatestLeafId() {
55
- if (this.insertionOrder.length === 0)
56
- return null;
57
- return this.insertionOrder[this.insertionOrder.length - 1] ?? null;
58
- }
59
- /** Return the cached metadata for `id`, or `null` if unknown. */
60
- getRecordedTask(id) {
61
- return this.tasks.get(id) ?? null;
62
- }
63
- /** Return the recorded task ids in insertion order. */
64
- getRecordedTaskIds() {
65
- return [...this.insertionOrder];
66
- }
67
- /** Drop everything. Used by tests + on plugin disposal. */
68
- clear() {
69
- this.tasks.clear();
70
- this.insertionOrder.length = 0;
25
+ return this.latestLeafId ?? undefined;
71
26
  }
72
27
  /**
73
- * Walk `parent_task_id` from `leafId` up to the root, using
74
- * `fetcher` to resolve any ancestors not already in the in-memory
75
- * cache. Returns the chain in **root-first** order
76
- * (`[root, …, leaf]`).
77
- *
78
- * Guarantees:
79
- * - Always returns at least the leaf task if the leaf is known
80
- * (from cache or via fetcher).
81
- * - Cycle-safe: a task already in the chain stops the walk.
82
- * - Depth-bounded by `options.maxDepth` (default 32).
83
- * - Never throws: a broken parent link just ends the chain early.
28
+ * Record a leaf task id (called by the host plugin as new tasks appear).
29
+ * Idempotent only the most recent call wins.
84
30
  */
85
- async buildAncestorChain(leafId, fetcher, options = {}) {
86
- if (!Number.isFinite(leafId) || leafId <= 0)
87
- return [];
88
- const maxDepth = Math.max(1, options.maxDepth ?? 32);
89
- // Walk leaf → root, then reverse at the end. We collect into an
90
- // array in walk order so cycles / missing ancestors are easy to
91
- // detect.
92
- const walked = [];
93
- const seen = new Set();
94
- let currentId = leafId;
95
- for (let depth = 0; depth < maxDepth; depth++) {
96
- if (seen.has(currentId))
97
- break;
98
- seen.add(currentId);
99
- const cached = this.tasks.get(currentId);
100
- const task = cached ?? await safeFetch(fetcher, currentId);
101
- if (!task)
102
- break;
103
- walked.push(task);
104
- const parentId = task.parent_task_id;
105
- if (parentId === null || parentId === undefined)
106
- break;
107
- if (!Number.isFinite(parentId) || parentId <= 0)
108
- break;
109
- currentId = parentId;
110
- }
111
- return walked.reverse();
31
+ recordLeaf(taskId) {
32
+ this.latestLeafId = taskId;
112
33
  }
113
- }
114
- /**
115
- * Run `fetcher(id)` and swallow any rejection. A failed fetch
116
- * (network / parse / CLI error) should not crash the chain walk —
117
- * we just stop at the last known task.
118
- */
119
- async function safeFetch(fetcher, id) {
120
- try {
121
- return await fetcher(id);
122
- }
123
- catch {
124
- return null;
125
- }
126
- }
127
- // ---------------------------------------------------------------------------
128
- // Default CLI-backed fetcher
129
- // ---------------------------------------------------------------------------
130
- /**
131
- * Build a {@link TaskMetaFetcher} that shells out to the host's
132
- * `roy-agent` CLI. Intended for production use; tests should pass a
133
- * stub.
134
- *
135
- * args: [cliPath, "tasks", "get", "<id>", "--json"]
136
- * stdout: { task: { id, title, status, priority, type,
137
- * parent_task_id, createdAt, updatedAt, tags,
138
- * project_path }, ... }
139
- *
140
- * The returned fetcher:
141
- * - Spawns the CLI as a discrete arg array (no shell string).
142
- * - Enforces a 5-second wall-clock timeout.
143
- * - Locates the first `{` in stdout (the CLI prints INFO lines
144
- * before the JSON envelope).
145
- * - Returns `null` on any failure (non-zero exit, timeout, parse
146
- * error, schema mismatch).
147
- */
148
- export function makeCliTaskMetaFetcher(cliPath) {
149
- return async (id) => {
150
- if (!Number.isInteger(id) || id <= 0)
151
- return null;
152
- const args = [cliPath, "tasks", "get", String(id), "--json"];
153
- let stdout = "";
154
- try {
155
- const result = await execFileAsync(cliPath, args.slice(1), {
156
- timeout: 5000,
157
- maxBuffer: 4 * 1024 * 1024,
158
- windowsHide: true,
159
- });
160
- stdout = result.stdout ?? "";
34
+ async buildSessionForest() {
35
+ const cutoff = Date.parse(this.opts.sessionStartedAt);
36
+ const raw = await this.opts.listTasks({ sinceIso: this.opts.sessionStartedAt });
37
+ const tasks = raw.filter((t) => {
38
+ const ts = Date.parse(t.createdAt);
39
+ return Number.isFinite(ts) && ts >= cutoff;
40
+ });
41
+ const byId = new Map();
42
+ for (const t of tasks)
43
+ byId.set(t.id, t);
44
+ const children = new Map();
45
+ for (const t of tasks) {
46
+ if (t.parent_task_id === undefined || t.parent_task_id === null)
47
+ continue;
48
+ const arr = children.get(t.parent_task_id) ?? [];
49
+ arr.push(t);
50
+ children.set(t.parent_task_id, arr);
161
51
  }
162
- catch {
163
- return null;
52
+ // v0.9.0: bounded BFS for external ancestors. For every in-session
53
+ // task whose parent is not in `byId`, BFS up the parent chain,
54
+ // fetching each ancestor through `taskFetcher` and inserting it
55
+ // (deduplicated) into `byId` and `children` until we run out of
56
+ // ancestors or hit `maxAncestors`.
57
+ const maxAncestors = this.opts.maxAncestors;
58
+ let fetchedAncestors = 0;
59
+ // FIFO queue of parent_ids we still need to look up.
60
+ const queue = [];
61
+ const queued = new Set();
62
+ for (const t of tasks) {
63
+ if (t.parent_task_id === undefined || t.parent_task_id === null)
64
+ continue;
65
+ if (byId.has(t.parent_task_id))
66
+ continue;
67
+ if (queued.has(t.parent_task_id))
68
+ continue;
69
+ queue.push(t.parent_task_id);
70
+ queued.add(t.parent_task_id);
164
71
  }
165
- const jsonStart = stdout.indexOf("{");
166
- if (jsonStart < 0)
167
- return null;
168
- const envelopeText = stdout.slice(jsonStart);
169
- let envelope;
170
- try {
171
- envelope = JSON.parse(envelopeText);
72
+ while (queue.length > 0 && fetchedAncestors < maxAncestors) {
73
+ const id = queue.shift();
74
+ const fetched = await this.opts.taskFetcher(id);
75
+ if (!fetched)
76
+ continue;
77
+ byId.set(fetched.id, fetched);
78
+ fetchedAncestors += 1;
79
+ // Index as a child of its own parent (so the tree walks correctly).
80
+ if (fetched.parent_task_id !== undefined && fetched.parent_task_id !== null) {
81
+ const arr = children.get(fetched.parent_task_id) ?? [];
82
+ arr.push(fetched);
83
+ children.set(fetched.parent_task_id, arr);
84
+ if (!byId.has(fetched.parent_task_id) && !queued.has(fetched.parent_task_id) && fetchedAncestors + queue.length < maxAncestors) {
85
+ queue.push(fetched.parent_task_id);
86
+ queued.add(fetched.parent_task_id);
87
+ }
88
+ }
172
89
  }
173
- catch {
174
- return null;
90
+ // Assemble roots: any task (in-session or fetched) whose parent is
91
+ // not present in byId becomes a root.
92
+ const roots = [];
93
+ for (const t of byId.values()) {
94
+ if (t.parent_task_id === undefined || t.parent_task_id === null || !byId.has(t.parent_task_id)) {
95
+ roots.push(t);
96
+ }
175
97
  }
176
- const t = envelope?.task;
177
- if (!t || typeof t.id !== "number")
178
- return null;
179
- return {
180
- id: t.id,
181
- title: typeof t.title === "string" ? t.title : "",
182
- status: typeof t.status === "string" ? t.status : undefined,
183
- priority: typeof t.priority === "string" ? t.priority : undefined,
184
- type: typeof t.type === "string" ? t.type : undefined,
185
- parent_task_id: typeof t.parent_task_id === "number" ? t.parent_task_id : null,
186
- createdAt: typeof t.createdAt === "string" ? t.createdAt : undefined,
187
- updatedAt: typeof t.updatedAt === "string" ? t.updatedAt : undefined,
188
- tags: Array.isArray(t.tags) ? t.tags.filter((x) => typeof x === "string") : undefined,
189
- projectPath: typeof t.project_path === "string" ? t.project_path : undefined,
98
+ const buildChildren = (parentId, depth = 0) => {
99
+ const arr = children.get(parentId) ?? [];
100
+ const out = [];
101
+ for (const t of arr) {
102
+ if (depth > maxAncestors + 4)
103
+ break; // hard ceiling for defensive recursion
104
+ out.push({ task: t, children: buildChildren(t.id, depth + 1) });
105
+ }
106
+ out.sort((a, b) => a.task.id - b.task.id);
107
+ return out;
190
108
  };
109
+ return roots
110
+ .sort((a, b) => a.id - b.id)
111
+ .map((t) => ({ task: t, children: buildChildren(t.id, 0) }));
112
+ }
113
+ }
114
+ __decorate([
115
+ TracedAs("session.forest.build", { recordParams: false, recordResult: false })
116
+ ], TaskSessionStore.prototype, "buildSessionForest", null);
117
+ function esc(s) {
118
+ const str = s === undefined || s === null ? "" : String(s);
119
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
120
+ }
121
+ export function renderSessionForestPage(args) {
122
+ return withTraceSync("html.session-forest.render", () => _renderSessionForestPageImpl(args));
123
+ }
124
+ function _renderSessionForestPageImpl(args) {
125
+ const rows = [];
126
+ const walk = (node, depth) => {
127
+ const t = node.task;
128
+ const checked = t.id === args.leafTaskId ? ' data-leaf="1"' : "";
129
+ rows.push(`<li class="session-row" data-depth="${depth}"${checked}>
130
+ <a class="session-title" href="/task/${t.id}">#${t.id} ${esc(t.title)}</a>
131
+ <button type="button" class="session-toggle" data-toggle="${t.id}">显示全部栏位</button>
132
+ <details class="session-details" data-details="${t.id}">
133
+ <summary>metadata</summary>
134
+ <dl>
135
+ <dt>project_path</dt><dd>${esc(t.project_path)}</dd>
136
+ <dt>context</dt><dd><pre>${esc(t.context)}</pre></dd>
137
+ <dt>description</dt><dd>${esc(t.description)}</dd>
138
+ <dt>goals</dt><dd>${esc(t.goals_and_expected_deliverables)}</dd>
139
+ <dt>tags</dt><dd>${esc((t.tags ?? []).join(", "))}</dd>
140
+ <dt>createdAt</dt><dd>${esc(t.createdAt)}</dd>
141
+ <dt>updatedAt</dt><dd>${esc(t.updatedAt)}</dd>
142
+ </dl>
143
+ </details>
144
+ <ul class="session-operations" data-task-operations="${t.id}"></ul>
145
+ </li>`);
146
+ for (const child of node.children)
147
+ walk(child, depth + 1);
191
148
  };
149
+ for (const node of args.forest)
150
+ walk(node, 0);
151
+ return `<section class="session-forest" data-session-forest="1"><h2>本会话任务</h2><ul>${rows.join("")}</ul></section>`;
192
152
  }
193
153
  //# sourceMappingURL=task-session-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"task-session-store.js","sourceRoot":"","sources":["../src/task-session-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task-session-store.js","sourceRoot":"","sources":["../src/task-session-store.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,11 @@
10
10
  * Cache keys are derived from the `TasksTreeFilter` object via a stable
11
11
  * JSON.stringify so functionally-equal filters (e.g. `{ status: undefined,
12
12
  * priority: "high" }` vs `{ priority: "high" }`) hit the same entry.
13
+ *
14
+ * v0.8.10+: the cache enforces a hard `maxEntries` cap. Without this,
15
+ * long-lived hosts that page through every filter combination (the
16
+ * frontend polls with shifting depth/status/priority/tags) leak
17
+ * entries forever — see Task #2537 (roy-agent 4 GiB OOM).
13
18
  */
14
19
  import type { TasksTreeEnvelope, TasksTreeFilter, TasksTreeSource } from "./cli-tasks-tree-adapter.js";
15
20
  type SourceFn = (filter: TasksTreeFilter) => Promise<TasksTreeEnvelope>;
@@ -17,6 +22,12 @@ export interface TasksTreeCacheOptions {
17
22
  source: SourceFn | TasksTreeSource;
18
23
  /** Time-to-live in milliseconds. Default 5000. */
19
24
  ttlMs?: number;
25
+ /**
26
+ * v0.8.10+: hard cap on the number of cached entries (one per unique
27
+ * filter combination). Past this limit, the cache sweeps the oldest
28
+ * stale entries before inserting a new one. Default 64.
29
+ */
30
+ maxEntries?: number;
20
31
  /** Optional: clock for tests. */
21
32
  now?: () => number;
22
33
  }
@@ -25,6 +36,7 @@ export declare class TasksTreeCache {
25
36
  private readonly ttlMs;
26
37
  private readonly source;
27
38
  private readonly now;
39
+ private readonly maxEntries;
28
40
  constructor(opts: TasksTreeCacheOptions);
29
41
  /**
30
42
  * Fetch (or read from cache). Concurrent calls for the same filter
@@ -38,6 +50,11 @@ export declare class TasksTreeCache {
38
50
  clear(): void;
39
51
  /** Number of cached entries (test introspection). */
40
52
  size(): number;
53
+ /**
54
+ * Sweep entries that are past their TTL once we exceed `maxEntries`.
55
+ * See OperationsCache.evictStale for the same rationale.
56
+ */
57
+ private evictStale;
41
58
  private refresh;
42
59
  }
43
60
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"tasks-tree-cache.d.ts","sourceRoot":"","sources":["../src/tasks-tree-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,MAAM,6BAA6B,CAAC;AAErC,KAAK,QAAQ,GAAG,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAQxE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAAC;IACnC,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE,qBAAqB;IAavC;;;;OAIG;IACG,GAAG,CACP,MAAM,EAAE,eAAe,EACvB,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAClC,OAAO,CAAC,iBAAiB,CAAC;IAyC7B,wBAAwB;IACxB,KAAK,IAAI,IAAI;IAIb,qDAAqD;IACrD,IAAI,IAAI,MAAM;YAIA,OAAO;CActB"}
1
+ {"version":3,"file":"tasks-tree-cache.d.ts","sourceRoot":"","sources":["../src/tasks-tree-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,MAAM,6BAA6B,CAAC;AAErC,KAAK,QAAQ,GAAG,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAQxE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAAC;IACnC,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,IAAI,EAAE,qBAAqB;IAmBvC;;;;OAIG;IACG,GAAG,CACP,MAAM,EAAE,eAAe,EACvB,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAClC,OAAO,CAAC,iBAAiB,CAAC;IA4C7B,wBAAwB;IACxB,KAAK,IAAI,IAAI;IAIb,qDAAqD;IACrD,IAAI,IAAI,MAAM;IAId;;;OAGG;IACH,OAAO,CAAC,UAAU;YAoBJ,OAAO;CActB"}
@@ -10,15 +10,25 @@
10
10
  * Cache keys are derived from the `TasksTreeFilter` object via a stable
11
11
  * JSON.stringify so functionally-equal filters (e.g. `{ status: undefined,
12
12
  * priority: "high" }` vs `{ priority: "high" }`) hit the same entry.
13
+ *
14
+ * v0.8.10+: the cache enforces a hard `maxEntries` cap. Without this,
15
+ * long-lived hosts that page through every filter combination (the
16
+ * frontend polls with shifting depth/status/priority/tags) leak
17
+ * entries forever — see Task #2537 (roy-agent 4 GiB OOM).
13
18
  */
14
19
  export class TasksTreeCache {
15
20
  entries = new Map();
16
21
  ttlMs;
17
22
  source;
18
23
  now;
24
+ maxEntries;
19
25
  constructor(opts) {
20
26
  this.ttlMs = opts.ttlMs ?? 5000;
21
27
  this.now = opts.now ?? (() => Date.now());
28
+ this.maxEntries = opts.maxEntries ?? 64;
29
+ if (!Number.isFinite(this.maxEntries) || this.maxEntries < 1) {
30
+ throw new RangeError(`TasksTreeCache: maxEntries must be a positive finite number, got ${this.maxEntries}`);
31
+ }
22
32
  const src = opts.source;
23
33
  if (typeof src === "function") {
24
34
  this.source = src;
@@ -41,6 +51,7 @@ export class TasksTreeCache {
41
51
  const now = this.now();
42
52
  if (existing && existing.fetchedAtMs > 0 && now - existing.fetchedAtMs < this.ttlMs) {
43
53
  existing.envelope.stale = false;
54
+ this.evictStale(now);
44
55
  return existing.envelope;
45
56
  }
46
57
  if (existing && existing.fetchedAtMs > 0 && opts.allowStale) {
@@ -51,11 +62,13 @@ export class TasksTreeCache {
51
62
  });
52
63
  existing.inflight.catch(() => { });
53
64
  }
65
+ this.evictStale(now);
54
66
  return existing.envelope;
55
67
  }
56
68
  if (existing?.inflight) {
57
69
  return existing.inflight;
58
70
  }
71
+ this.evictStale(now);
59
72
  const entry = existing ?? {
60
73
  envelope: {
61
74
  total: 0,
@@ -80,6 +93,32 @@ export class TasksTreeCache {
80
93
  size() {
81
94
  return this.entries.size;
82
95
  }
96
+ /**
97
+ * Sweep entries that are past their TTL once we exceed `maxEntries`.
98
+ * See OperationsCache.evictStale for the same rationale.
99
+ */
100
+ evictStale(now) {
101
+ if (this.entries.size <= this.maxEntries)
102
+ return;
103
+ let overflow = this.entries.size - this.maxEntries;
104
+ for (const [key, entry] of this.entries) {
105
+ if (overflow <= 0)
106
+ break;
107
+ if (entry.fetchedAtMs > 0 && now - entry.fetchedAtMs >= this.ttlMs) {
108
+ this.entries.delete(key);
109
+ overflow -= 1;
110
+ }
111
+ }
112
+ if (this.entries.size > this.maxEntries) {
113
+ overflow = this.entries.size - this.maxEntries;
114
+ for (const key of this.entries.keys()) {
115
+ if (overflow <= 0)
116
+ break;
117
+ this.entries.delete(key);
118
+ overflow -= 1;
119
+ }
120
+ }
121
+ }
83
122
  async refresh(filter) {
84
123
  const envelope = await this.source(filter);
85
124
  const now = this.now();
@@ -1 +1 @@
1
- {"version":3,"file":"tasks-tree-cache.js","sourceRoot":"","sources":["../src/tasks-tree-cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tasks-tree-cache.js","sourceRoot":"","sources":["../src/tasks-tree-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ export interface TracedAsOptions {
2
+ recordParams?: boolean;
3
+ recordResult?: boolean;
4
+ recordError?: boolean;
5
+ }
6
+ /**
7
+ * @TracedAs(name, options) decorator factory.
8
+ *
9
+ * Usage:
10
+ * @TracedAs("server.home.render", { recordParams: false, recordResult: false })
11
+ * private async sendIndex(res: http.ServerResponse): Promise<void> { ... }
12
+ *
13
+ * If the spans.db is unreachable (no host, no env, schema mismatch),
14
+ * the decorator becomes a transparent passthrough — the wrapped
15
+ * function is called with its original `this` and arguments.
16
+ */
17
+ export declare function TracedAs(name: string, _options?: TracedAsOptions): <T extends (...args: any[]) => any>(_target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T>;
18
+ /**
19
+ * For tests: reset the module-level db cache so test fixtures
20
+ * can simulate a fresh host install. Not used in production.
21
+ */
22
+ export declare function __resetTracedAsCacheForTests(): void;
23
+ /**
24
+ * For tests: returns the resolved spans.db path or null. Allows
25
+ * tests to assert the decorator located the host DB.
26
+ */
27
+ export declare function __getResolvedSpansDbPathForTests(): string | null;
28
+ /**
29
+ * Ensure the parent dir of a candidate spans.db path exists
30
+ * (used by tests that point at a temp file). Best-effort.
31
+ */
32
+ export declare function __ensureSpansDbDirForTests(path: string): void;
33
+ /**
34
+ * `withTrace(name, fn)` — manual instrumentation helper for free
35
+ * functions (which TS decorators cannot decorate). Runs `fn` and
36
+ * records a span around the invocation. Mirrors the @TracedAs
37
+ * contract: never throws, no-ops when spans.db is unavailable.
38
+ *
39
+ * Used by `renderSessionForestPage` and `encodeMermaidLabelText`
40
+ * (top-level exports that cannot carry a class-method decorator).
41
+ */
42
+ export declare function withTrace<T>(name: string, fn: () => Promise<T> | T): Promise<T>;
43
+ /**
44
+ * Synchronous variant of `withTrace`. Used for top-level
45
+ * non-async exports.
46
+ */
47
+ export declare function withTraceSync<T>(name: string, fn: () => T): T;
48
+ //# sourceMappingURL=decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorator.d.ts","sourceRoot":"","sources":["../../src/tracing/decorator.ts"],"names":[],"mappings":"AAqDA,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA8HD;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,eAAe,IAC9C,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAChD,SAAS,GAAG,EACZ,aAAa,MAAM,EACnB,YAAY,uBAAuB,CAAC,CAAC,CAAC,KACrC,uBAAuB,CAAC,CAAC,CAAC,CAyD9B;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CAUnD;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,IAAI,MAAM,GAAG,IAAI,CAEhE;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM7D;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAgB7D"}