@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.
- package/dist/cli-tasks-adapter.d.ts +2 -17
- package/dist/cli-tasks-adapter.d.ts.map +1 -1
- package/dist/cli-tasks-adapter.js +7 -57
- package/dist/cli-tasks-adapter.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/operations-cache.d.ts +21 -0
- package/dist/operations-cache.d.ts.map +1 -1
- package/dist/operations-cache.js +45 -0
- package/dist/operations-cache.js.map +1 -1
- package/dist/plugin.d.ts +22 -74
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +174 -173
- package/dist/plugin.js.map +1 -1
- package/dist/server.d.ts +15 -31
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +42 -206
- package/dist/server.js.map +1 -1
- package/dist/task-detail-mermaid.d.ts +9 -0
- package/dist/task-detail-mermaid.d.ts.map +1 -1
- package/dist/task-detail-mermaid.js +43 -3
- package/dist/task-detail-mermaid.js.map +1 -1
- package/dist/task-session-store.d.ts +54 -94
- package/dist/task-session-store.d.ts.map +1 -1
- package/dist/task-session-store.js +137 -177
- package/dist/task-session-store.js.map +1 -1
- package/dist/tasks-tree-cache.d.ts +17 -0
- package/dist/tasks-tree-cache.d.ts.map +1 -1
- package/dist/tasks-tree-cache.js +39 -0
- package/dist/tasks-tree-cache.js.map +1 -1
- package/dist/tracing/decorator.d.ts +48 -0
- package/dist/tracing/decorator.d.ts.map +1 -0
- package/dist/tracing/decorator.js +310 -0
- package/dist/tracing/decorator.js.map +1 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
- package/plugin.json +2 -2
- package/public/app.js +45 -1
- package/public/index.html +3 -0
- package/public/session-forest.js +97 -0
- package/public/style.css +0 -45
- package/public/task-operations.js +7 -0
|
@@ -1,193 +1,153 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
*
|
|
43
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
74
|
-
*
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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":"
|
|
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
|
|
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"}
|
package/dist/tasks-tree-cache.js
CHANGED
|
@@ -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":"AAAA
|
|
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"}
|