sensorium-mcp 3.0.75 โ 3.0.77
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/dashboard/log-parser.d.ts +51 -0
- package/dist/dashboard/log-parser.d.ts.map +1 -0
- package/dist/dashboard/log-parser.js +140 -0
- package/dist/dashboard/log-parser.js.map +1 -0
- package/dist/dashboard/routes/copilot.d.ts +7 -4
- package/dist/dashboard/routes/copilot.d.ts.map +1 -1
- package/dist/dashboard/routes/copilot.js +47 -13
- package/dist/dashboard/routes/copilot.js.map +1 -1
- package/dist/dashboard/routes/threads.d.ts +22 -0
- package/dist/dashboard/routes/threads.d.ts.map +1 -1
- package/dist/dashboard/routes/threads.js +193 -3
- package/dist/dashboard/routes/threads.js.map +1 -1
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +9 -3
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/dashboard/spa.html +7 -7
- package/dist/response-builders.d.ts.map +1 -1
- package/dist/response-builders.js +12 -5
- package/dist/response-builders.js.map +1 -1
- package/dist/services/worker-cleanup.service.d.ts.map +1 -1
- package/dist/services/worker-cleanup.service.js +8 -3
- package/dist/services/worker-cleanup.service.js.map +1 -1
- package/dist/tools/wait/message-delivery.d.ts.map +1 -1
- package/dist/tools/wait/message-delivery.js +7 -2
- package/dist/tools/wait/message-delivery.js.map +1 -1
- package/dist/tools/wait/poll-loop.d.ts.map +1 -1
- package/dist/tools/wait/poll-loop.js +5 -4
- package/dist/tools/wait/poll-loop.js.map +1 -1
- package/package.json +2 -2
- package/templates/playwright.default.md +30 -0
- package/templates/sensorium-repair.default.md +99 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for Claude CLI stream-json thread logs (newline-delimited JSON).
|
|
3
|
+
*
|
|
4
|
+
* The agent's stdout is captured as one JSON event per line. This turns that
|
|
5
|
+
* raw event stream into readable, condensed "turns" for display over Telegram.
|
|
6
|
+
* A turn is one assistant message (its text + any tool calls) together with the
|
|
7
|
+
* tool results that immediately follow it.
|
|
8
|
+
*
|
|
9
|
+
* Assistant text is shown in full; tool inputs and tool results are previewed
|
|
10
|
+
* (collapsed to a single line and capped) because they are machine output, not
|
|
11
|
+
* the agent's reasoning, and would otherwise drown the useful signal.
|
|
12
|
+
*/
|
|
13
|
+
export interface LogTurn {
|
|
14
|
+
/** Rendered lines for this turn, in display order. */
|
|
15
|
+
lines: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_PREVIEW_CHARS = 300;
|
|
18
|
+
/**
|
|
19
|
+
* Parse a raw stream-json log into chronological turns. Malformed lines and
|
|
20
|
+
* noise events (system, rate_limit_event) are skipped.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseClaudeStreamLog(raw: string, previewChars?: number): LogTurn[];
|
|
23
|
+
/**
|
|
24
|
+
* Render turns to a single string. With tailFirst (default) the most recent
|
|
25
|
+
* turn appears first, so a reader paging from the top sees latest activity.
|
|
26
|
+
*/
|
|
27
|
+
export declare function renderThreadLog(turns: LogTurn[], tailFirst?: boolean): string;
|
|
28
|
+
/** Default character window per page โ matches Telegram's ~4096-char message cap. */
|
|
29
|
+
export declare const DEFAULT_LOG_PAGE_CHARS = 4000;
|
|
30
|
+
export interface LogPage {
|
|
31
|
+
/** The character window for this page. */
|
|
32
|
+
text: string;
|
|
33
|
+
/** Start index of this page within the rendered log. */
|
|
34
|
+
offset: number;
|
|
35
|
+
/** Start index of the next page (feeds a "next chunk" button). */
|
|
36
|
+
nextOffset: number;
|
|
37
|
+
/** Whether more text remains after this page. */
|
|
38
|
+
hasMore: boolean;
|
|
39
|
+
/** Total length of the rendered log. */
|
|
40
|
+
totalLength: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extract a single character window from a rendered log for paginated display.
|
|
44
|
+
*
|
|
45
|
+
* The digest uses astral emoji markers (๐ค๐งโ
โฆ), so a naive .slice() could cut
|
|
46
|
+
* between the two UTF-16 code units of a surrogate pair and emit a lone
|
|
47
|
+
* surrogate that renders as ๏ฟฝ. When the window would end mid-pair we snap the
|
|
48
|
+
* end back one unit; that code unit becomes the start of the next page.
|
|
49
|
+
*/
|
|
50
|
+
export declare function sliceLogWindow(rendered: string, offsetRaw: number, limitRaw: number): LogPage;
|
|
51
|
+
//# sourceMappingURL=log-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-parser.d.ts","sourceRoot":"","sources":["../../src/dashboard/log-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkBH,MAAM,WAAW,OAAO;IACpB,sDAAsD;IACtD,KAAK,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,eAAO,MAAM,qBAAqB,MAAM,CAAC;AA+BzC;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAA8B,GAAG,OAAO,EAAE,CAoCzG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,SAAS,GAAE,OAAc,GAAG,MAAM,CAGnF;AAED,qFAAqF;AACrF,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,MAAM,WAAW,OAAO;IACpB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;CACvB;AAcD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAY7F"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for Claude CLI stream-json thread logs (newline-delimited JSON).
|
|
3
|
+
*
|
|
4
|
+
* The agent's stdout is captured as one JSON event per line. This turns that
|
|
5
|
+
* raw event stream into readable, condensed "turns" for display over Telegram.
|
|
6
|
+
* A turn is one assistant message (its text + any tool calls) together with the
|
|
7
|
+
* tool results that immediately follow it.
|
|
8
|
+
*
|
|
9
|
+
* Assistant text is shown in full; tool inputs and tool results are previewed
|
|
10
|
+
* (collapsed to a single line and capped) because they are machine output, not
|
|
11
|
+
* the agent's reasoning, and would otherwise drown the useful signal.
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_PREVIEW_CHARS = 300;
|
|
14
|
+
/** Collapse whitespace and cap length, appending an ellipsis when truncated. */
|
|
15
|
+
function preview(s, max) {
|
|
16
|
+
const oneLine = s.replace(/\s+/g, " ").trim();
|
|
17
|
+
return oneLine.length > max ? oneLine.slice(0, max) + "โฆ" : oneLine;
|
|
18
|
+
}
|
|
19
|
+
function compactArgs(input, max) {
|
|
20
|
+
let s;
|
|
21
|
+
try {
|
|
22
|
+
s = typeof input === "string" ? input : JSON.stringify(input ?? {});
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
s = String(input);
|
|
26
|
+
}
|
|
27
|
+
return preview(s, max);
|
|
28
|
+
}
|
|
29
|
+
/** Extract readable text from a tool_result's content (string, or array of text blocks). */
|
|
30
|
+
function resultText(content) {
|
|
31
|
+
if (typeof content === "string")
|
|
32
|
+
return content;
|
|
33
|
+
if (Array.isArray(content)) {
|
|
34
|
+
return content
|
|
35
|
+
.map((c) => {
|
|
36
|
+
if (c && typeof c === "object" && typeof c.text === "string") {
|
|
37
|
+
return c.text;
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(c);
|
|
40
|
+
})
|
|
41
|
+
.join(" ");
|
|
42
|
+
}
|
|
43
|
+
return JSON.stringify(content ?? "");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parse a raw stream-json log into chronological turns. Malformed lines and
|
|
47
|
+
* noise events (system, rate_limit_event) are skipped.
|
|
48
|
+
*/
|
|
49
|
+
export function parseClaudeStreamLog(raw, previewChars = DEFAULT_PREVIEW_CHARS) {
|
|
50
|
+
const turns = [];
|
|
51
|
+
let current = null;
|
|
52
|
+
for (const line of raw.split("\n")) {
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
if (!trimmed)
|
|
55
|
+
continue;
|
|
56
|
+
let ev;
|
|
57
|
+
try {
|
|
58
|
+
ev = JSON.parse(trimmed);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (ev.type === "assistant" && ev.message?.content) {
|
|
64
|
+
current = { lines: [] };
|
|
65
|
+
turns.push(current);
|
|
66
|
+
for (const c of ev.message.content) {
|
|
67
|
+
if (c.type === "text" && typeof c.text === "string") {
|
|
68
|
+
const t = c.text.trim();
|
|
69
|
+
if (t)
|
|
70
|
+
current.lines.push(`๐ค ${t}`);
|
|
71
|
+
}
|
|
72
|
+
else if (c.type === "tool_use") {
|
|
73
|
+
const name = typeof c.name === "string" ? c.name : "tool";
|
|
74
|
+
current.lines.push(`๐ง ${name}(${compactArgs(c.input, previewChars)})`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (ev.type === "user" && ev.message?.content) {
|
|
79
|
+
for (const c of ev.message.content) {
|
|
80
|
+
if (c.type !== "tool_result")
|
|
81
|
+
continue;
|
|
82
|
+
if (!current) {
|
|
83
|
+
current = { lines: [] };
|
|
84
|
+
turns.push(current);
|
|
85
|
+
}
|
|
86
|
+
const marker = c.is_error ? "โ ๏ธ" : "โณ";
|
|
87
|
+
current.lines.push(` ${marker} ${preview(resultText(c.content), previewChars)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (ev.type === "result") {
|
|
91
|
+
const marker = ev.is_error ? "โ" : "โ
";
|
|
92
|
+
turns.push({ lines: [`${marker} ${preview(typeof ev.result === "string" ? ev.result : "", previewChars)}`] });
|
|
93
|
+
current = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return turns;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Render turns to a single string. With tailFirst (default) the most recent
|
|
100
|
+
* turn appears first, so a reader paging from the top sees latest activity.
|
|
101
|
+
*/
|
|
102
|
+
export function renderThreadLog(turns, tailFirst = true) {
|
|
103
|
+
const ordered = tailFirst ? [...turns].reverse() : turns;
|
|
104
|
+
return ordered.map((t) => t.lines.join("\n")).join("\n\n");
|
|
105
|
+
}
|
|
106
|
+
/** Default character window per page โ matches Telegram's ~4096-char message cap. */
|
|
107
|
+
export const DEFAULT_LOG_PAGE_CHARS = 4000;
|
|
108
|
+
/** Clamp an offset to [0, max]; non-finite or non-positive values become 0. */
|
|
109
|
+
function clampOffset(n, max) {
|
|
110
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
111
|
+
return 0;
|
|
112
|
+
return Math.min(Math.floor(n), max);
|
|
113
|
+
}
|
|
114
|
+
/** Clamp a page size to (0, DEFAULT_LOG_PAGE_CHARS]; invalid values default to the cap. */
|
|
115
|
+
function clampLimit(n) {
|
|
116
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
117
|
+
return DEFAULT_LOG_PAGE_CHARS;
|
|
118
|
+
return Math.min(Math.floor(n), DEFAULT_LOG_PAGE_CHARS);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Extract a single character window from a rendered log for paginated display.
|
|
122
|
+
*
|
|
123
|
+
* The digest uses astral emoji markers (๐ค๐งโ
โฆ), so a naive .slice() could cut
|
|
124
|
+
* between the two UTF-16 code units of a surrogate pair and emit a lone
|
|
125
|
+
* surrogate that renders as ๏ฟฝ. When the window would end mid-pair we snap the
|
|
126
|
+
* end back one unit; that code unit becomes the start of the next page.
|
|
127
|
+
*/
|
|
128
|
+
export function sliceLogWindow(rendered, offsetRaw, limitRaw) {
|
|
129
|
+
const totalLength = rendered.length;
|
|
130
|
+
const offset = clampOffset(offsetRaw, totalLength);
|
|
131
|
+
const limit = clampLimit(limitRaw);
|
|
132
|
+
let end = Math.min(offset + limit, totalLength);
|
|
133
|
+
if (end < totalLength && end - 1 > offset) {
|
|
134
|
+
const lastCode = rendered.charCodeAt(end - 1);
|
|
135
|
+
if (lastCode >= 0xd800 && lastCode <= 0xdbff)
|
|
136
|
+
end -= 1;
|
|
137
|
+
}
|
|
138
|
+
return { text: rendered.slice(offset, end), offset, nextOffset: end, hasMore: end < totalLength, totalLength };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=log-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-parser.js","sourceRoot":"","sources":["../../src/dashboard/log-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,9 +9,12 @@ import { type RouteHandler } from "./types.js";
|
|
|
9
9
|
export declare const handleGetCopilot: RouteHandler;
|
|
10
10
|
export declare const handlePostCopilot: RouteHandler;
|
|
11
11
|
/**
|
|
12
|
-
* Verify the BYOK connection by listing models from the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Verify the BYOK connection by listing models from the provider, using the
|
|
13
|
+
* values POSTed from the form (baseUrl, providerType, apiKey) so the user can
|
|
14
|
+
* test BEFORE saving. Any field omitted or blank falls back to the stored
|
|
15
|
+
* config โ an empty apiKey keeps the previously saved key so it isn't echoed to
|
|
16
|
+
* the browser. Request shape and headers are provider-aware (openai/azure/
|
|
17
|
+
* anthropic). Returns model ids on success, a descriptive error otherwise.
|
|
15
18
|
*/
|
|
16
|
-
export declare const
|
|
19
|
+
export declare const handlePostCopilotModels: RouteHandler;
|
|
17
20
|
//# sourceMappingURL=copilot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../../src/dashboard/routes/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../../src/dashboard/routes/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAkDxE,eAAO,MAAM,gBAAgB,EAAE,YAG9B,CAAC;AAIF,eAAO,MAAM,iBAAiB,EAAE,YAiF/B,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,EAAE,YA2CrC,CAAC"}
|
|
@@ -9,6 +9,28 @@ import { getCopilotModel, setCopilotModel, getCopilotByokConfig, setCopilotByokC
|
|
|
9
9
|
import { readBody, safeParseJSON } from "./types.js";
|
|
10
10
|
import { errorMessage } from "../../utils.js";
|
|
11
11
|
const MODELS_FETCH_TIMEOUT_MS = 10_000;
|
|
12
|
+
/**
|
|
13
|
+
* Build the provider-specific "list models" request. Each provider type speaks a
|
|
14
|
+
* different wire protocol, so the models endpoint path and auth header differ:
|
|
15
|
+
* - openai: GET {base}/models Authorization: Bearer <key>
|
|
16
|
+
* - anthropic: GET {base}/v1/models x-api-key + anthropic-version
|
|
17
|
+
* - azure: GET {base}/models?api-version=โฆ api-key: <key>
|
|
18
|
+
* All three return an OpenAI-style { data: [{ id }] } body.
|
|
19
|
+
*/
|
|
20
|
+
function buildModelsRequest(providerType, baseUrl, apiKey) {
|
|
21
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
22
|
+
switch (providerType) {
|
|
23
|
+
case "anthropic": {
|
|
24
|
+
const url = /\/v1$/.test(base) ? `${base}/models` : `${base}/v1/models`;
|
|
25
|
+
return { url, headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" } };
|
|
26
|
+
}
|
|
27
|
+
case "azure":
|
|
28
|
+
return { url: `${base}/models?api-version=2024-10-21`, headers: { "api-key": apiKey } };
|
|
29
|
+
case "openai":
|
|
30
|
+
default:
|
|
31
|
+
return { url: `${base}/models`, headers: { Authorization: `Bearer ${apiKey}` } };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
12
34
|
/** Public (key-masked) view of the Copilot settings for the dashboard. */
|
|
13
35
|
function publicCopilotSettings() {
|
|
14
36
|
const byok = getCopilotByokConfig();
|
|
@@ -109,27 +131,39 @@ export const handlePostCopilot = ({ req, json }) => {
|
|
|
109
131
|
})();
|
|
110
132
|
return true;
|
|
111
133
|
};
|
|
112
|
-
// โโโ
|
|
134
|
+
// โโโ POST /api/settings/copilot/models โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
113
135
|
/**
|
|
114
|
-
* Verify the BYOK connection by listing models from the
|
|
115
|
-
*
|
|
116
|
-
*
|
|
136
|
+
* Verify the BYOK connection by listing models from the provider, using the
|
|
137
|
+
* values POSTed from the form (baseUrl, providerType, apiKey) so the user can
|
|
138
|
+
* test BEFORE saving. Any field omitted or blank falls back to the stored
|
|
139
|
+
* config โ an empty apiKey keeps the previously saved key so it isn't echoed to
|
|
140
|
+
* the browser. Request shape and headers are provider-aware (openai/azure/
|
|
141
|
+
* anthropic). Returns model ids on success, a descriptive error otherwise.
|
|
117
142
|
*/
|
|
118
|
-
export const
|
|
143
|
+
export const handlePostCopilotModels = ({ req, json }) => {
|
|
119
144
|
void (async () => {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
const stored = getCopilotByokConfig();
|
|
146
|
+
let body = {};
|
|
147
|
+
try {
|
|
148
|
+
const raw = await readBody(req);
|
|
149
|
+
const parsed = raw ? safeParseJSON(raw) : null;
|
|
150
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
151
|
+
body = parsed;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch { /* no/invalid body โ fall back entirely to stored config */ }
|
|
155
|
+
const baseUrl = (typeof body.baseUrl === "string" && body.baseUrl.trim() ? body.baseUrl.trim() : stored.baseUrl);
|
|
156
|
+
const apiKey = (typeof body.apiKey === "string" && body.apiKey.length > 0 ? body.apiKey : stored.apiKey);
|
|
157
|
+
const providerType = isValidCopilotProviderType(body.providerType) ? body.providerType : stored.providerType;
|
|
158
|
+
if (!baseUrl || !apiKey) {
|
|
159
|
+
json({ ok: false, error: "Enter a base URL and API key first (or save them)." }, 400);
|
|
123
160
|
return;
|
|
124
161
|
}
|
|
125
|
-
const url =
|
|
162
|
+
const { url, headers } = buildModelsRequest(providerType, baseUrl, apiKey);
|
|
126
163
|
const controller = new AbortController();
|
|
127
164
|
const timer = setTimeout(() => controller.abort(), MODELS_FETCH_TIMEOUT_MS);
|
|
128
165
|
try {
|
|
129
|
-
const resp = await fetch(url, {
|
|
130
|
-
headers: { Authorization: `Bearer ${byok.apiKey}` },
|
|
131
|
-
signal: controller.signal,
|
|
132
|
-
});
|
|
166
|
+
const resp = await fetch(url, { headers, signal: controller.signal });
|
|
133
167
|
if (!resp.ok) {
|
|
134
168
|
json({ ok: false, error: `Provider returned HTTP ${resp.status} ${resp.statusText}` }, 502);
|
|
135
169
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../../src/dashboard/routes/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACH,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,
|
|
1
|
+
{"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../../src/dashboard/routes/copilot.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,12 +10,34 @@ export declare const handleGetArchivedThreads: RouteHandler;
|
|
|
10
10
|
export declare function handleUnarchiveThread(args: RouteArgs, threadId: number): boolean;
|
|
11
11
|
export declare function handleGenerateSummary(args: RouteArgs, threadId: number): boolean;
|
|
12
12
|
export declare const handleCreateThread: RouteHandler;
|
|
13
|
+
/**
|
|
14
|
+
* Create a brand-new root thread: opens a Telegram forum topic and registers
|
|
15
|
+
* the thread. Unlike POST /api/threads (which only registers a caller-supplied
|
|
16
|
+
* topic id), this owns topic creation so callers without Telegram access (e.g.
|
|
17
|
+
* the supervisor) can spin up threads. keepAlive + dailyRotation default on for
|
|
18
|
+
* root threads via registerThread.
|
|
19
|
+
*/
|
|
20
|
+
export declare const handleCreateRoot: RouteHandler;
|
|
21
|
+
/**
|
|
22
|
+
* Create a branch thread under an existing root: opens a Telegram forum topic
|
|
23
|
+
* and registers it with type=branch. Working directory and agent type are
|
|
24
|
+
* inherited from the parent root unless overridden.
|
|
25
|
+
*/
|
|
26
|
+
export declare const handleCreateBranch: RouteHandler;
|
|
13
27
|
/** GET /api/threads/:threadId โ get single thread */
|
|
14
28
|
export declare function handleGetThread(args: RouteArgs, threadId: number): boolean;
|
|
15
29
|
/** GET /api/threads/:threadId/running โ check if an agent process is alive for this thread */
|
|
16
30
|
export declare function handleGetThreadRunning(args: RouteArgs, threadId: number): boolean;
|
|
17
31
|
/** GET /api/threads/:threadId/heartbeat โ last MCP activity timestamp for stuck-process detection */
|
|
18
32
|
export declare function handleGetThreadHeartbeat(args: RouteArgs, threadId: number): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* GET /api/threads/:threadId/logs โ a readable digest of the agent's raw stdout.
|
|
35
|
+
*
|
|
36
|
+
* Parses the newest stream-json log file into tail-first turns, then returns a
|
|
37
|
+
* single character window (default 4000, Telegram-sized). Callers page deeper
|
|
38
|
+
* into history with ?offset=; nextOffset/hasMore drive a "next chunk" button.
|
|
39
|
+
*/
|
|
40
|
+
export declare function handleGetThreadLogs(args: RouteArgs, threadId: number): boolean;
|
|
19
41
|
/** GET /api/threads/:threadId/children โ get children of a root */
|
|
20
42
|
export declare function handleGetThreadChildren(args: RouteArgs, rootThreadId: number): boolean;
|
|
21
43
|
/** PATCH /api/threads/:threadId โ update a thread */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"threads.d.ts","sourceRoot":"","sources":["../../../src/dashboard/routes/threads.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"threads.d.ts","sourceRoot":"","sources":["../../../src/dashboard/routes/threads.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAmCH,OAAO,EAA2B,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AA0FxF,eAAO,MAAM,gBAAgB,EAAE,YAI9B,CAAC;AAIF,eAAO,MAAM,oBAAoB,EAAE,YAGlC,CAAC;AAIF,eAAO,MAAM,yBAAyB,EAAE,YAGvC,CAAC;AAIF,eAAO,MAAM,wBAAwB,EAAE,YAItC,CAAC;AAIF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAchF;AAID,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CA2ChF;AAID,eAAO,MAAM,kBAAkB,EAAE,YAmEhC,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAuC9B,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,YAiDhC,CAAC;AAcF,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ1E;AAED,8FAA8F;AAC9F,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIjF;AAED,qGAAqG;AACrG,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAInF;AAqBD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAkB9E;AAED,mEAAmE;AACnE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAItF;AAED,qDAAqD;AACrD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAwC7E;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAsC7E;AAYD,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CA8B5E;AAID,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CA6B9E"}
|
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
* Covers: listing, creating, updating, archiving, and deleting threads.
|
|
4
4
|
*/
|
|
5
5
|
import { registerThread, getThread, getRootThreads, getKeepAliveThreads, getActiveThreads, getArchivedThreads, getDashboardThreads, getThreadsByRoot, updateThread, archiveThread, unarchiveThread, deleteThread, getExplicitTelegramTopicId, resolveTelegramTopicId, } from "../../data/memory/thread-registry.js";
|
|
6
|
-
import { config, setKeepAliveEnabled, setKeepAliveThreadId, setKeepAliveClient, setKeepAliveMaxRetries, setKeepAliveCooldownMs, setThreadKeepAlive, removeThreadKeepAlive, isValidKeeperClient, } from "../../config.js";
|
|
6
|
+
import { config, getAgentType, setKeepAliveEnabled, setKeepAliveThreadId, setKeepAliveClient, setKeepAliveMaxRetries, setKeepAliveCooldownMs, setThreadKeepAlive, removeThreadKeepAlive, isValidKeeperClient, } from "../../config.js";
|
|
7
7
|
import { readBody, safeParseJSON } from "./types.js";
|
|
8
8
|
import { errorMessage } from "../../utils.js";
|
|
9
9
|
import { log } from "../../logger.js";
|
|
10
|
-
import { isThreadRunning } from "../../services/process.service.js";
|
|
10
|
+
import { isThreadRunning, THREAD_LOGS_DIR } from "../../services/process.service.js";
|
|
11
11
|
import { readThreadHeartbeat } from "../../data/file-storage.js";
|
|
12
|
-
import {
|
|
12
|
+
import { parseClaudeStreamLog, renderThreadLog, sliceLogWindow } from "../log-parser.js";
|
|
13
|
+
import { readdirSync, statSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { createManagedTopic, deleteTelegramTopicByBotApi } from "../../services/topic.service.js";
|
|
13
16
|
import { dispatchSpawn } from "../../services/agent-spawn.service.js";
|
|
17
|
+
import { TelegramClient } from "../../telegram.js";
|
|
14
18
|
// โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
15
19
|
const VALID_STATUSES = ["active", "archived", "expired", "exited"];
|
|
16
20
|
const VALID_CLIENTS = ["claude", "copilot", "codex", "openai_codex"];
|
|
@@ -233,6 +237,141 @@ export const handleCreateThread = ({ req, json, db }) => {
|
|
|
233
237
|
})();
|
|
234
238
|
return true;
|
|
235
239
|
};
|
|
240
|
+
// โโโ POST /api/threads/create-root โ create a fresh topic + root thread โโโโโ
|
|
241
|
+
/**
|
|
242
|
+
* Create a brand-new root thread: opens a Telegram forum topic and registers
|
|
243
|
+
* the thread. Unlike POST /api/threads (which only registers a caller-supplied
|
|
244
|
+
* topic id), this owns topic creation so callers without Telegram access (e.g.
|
|
245
|
+
* the supervisor) can spin up threads. keepAlive + dailyRotation default on for
|
|
246
|
+
* root threads via registerThread.
|
|
247
|
+
*/
|
|
248
|
+
export const handleCreateRoot = ({ req, json, db, ctx }) => {
|
|
249
|
+
void (async () => {
|
|
250
|
+
try {
|
|
251
|
+
const raw = await readBody(req);
|
|
252
|
+
const body = safeParseJSON(raw);
|
|
253
|
+
if (!body || typeof body !== "object") {
|
|
254
|
+
json({ error: "Invalid request body" }, 400);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
258
|
+
if (!name) {
|
|
259
|
+
json({ error: "name is required" }, 400);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const agentType = resolveAgentType(body.agentType);
|
|
263
|
+
if (agentType === INVALID_AGENT) {
|
|
264
|
+
json({ error: `agentType must be one of: ${VALID_CLIENTS.join(", ")}` }, 400);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const workingDirectory = typeof body.workingDirectory === "string" ? body.workingDirectory.trim() : "";
|
|
268
|
+
if (!workingDirectory) {
|
|
269
|
+
json({ error: "workingDirectory is required" }, 400);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const { TELEGRAM_TOKEN, TELEGRAM_CHAT_ID } = config;
|
|
273
|
+
if (!TELEGRAM_TOKEN || !TELEGRAM_CHAT_ID) {
|
|
274
|
+
json({ error: "Telegram is not configured" }, 500);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const telegram = new TelegramClient(TELEGRAM_TOKEN);
|
|
278
|
+
const threadId = await createManagedTopic(telegram, TELEGRAM_CHAT_ID, name);
|
|
279
|
+
const thread = ctx.threadLifecycle.registerThread(db, {
|
|
280
|
+
threadId,
|
|
281
|
+
name,
|
|
282
|
+
type: "root",
|
|
283
|
+
client: agentType ?? getAgentType(),
|
|
284
|
+
workingDirectory,
|
|
285
|
+
chatId: TELEGRAM_CHAT_ID,
|
|
286
|
+
telegramTopicId: threadId,
|
|
287
|
+
});
|
|
288
|
+
json({ ok: true, threadId, thread }, 201);
|
|
289
|
+
syncKeepAliveToSettings(db);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
json({ error: errorMessage(err) }, 500);
|
|
293
|
+
}
|
|
294
|
+
})();
|
|
295
|
+
return true;
|
|
296
|
+
};
|
|
297
|
+
// โโโ POST /api/threads/create-branch โ create a fresh topic + branch thread โโ
|
|
298
|
+
/**
|
|
299
|
+
* Create a branch thread under an existing root: opens a Telegram forum topic
|
|
300
|
+
* and registers it with type=branch. Working directory and agent type are
|
|
301
|
+
* inherited from the parent root unless overridden.
|
|
302
|
+
*/
|
|
303
|
+
export const handleCreateBranch = ({ req, json, db, ctx }) => {
|
|
304
|
+
void (async () => {
|
|
305
|
+
try {
|
|
306
|
+
const raw = await readBody(req);
|
|
307
|
+
const body = safeParseJSON(raw);
|
|
308
|
+
if (!body || typeof body !== "object") {
|
|
309
|
+
json({ error: "Invalid request body" }, 400);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const rootThreadId = body.rootThreadId;
|
|
313
|
+
if (typeof rootThreadId !== "number" || !Number.isInteger(rootThreadId) || rootThreadId <= 0) {
|
|
314
|
+
json({ error: "rootThreadId must be a positive integer" }, 400);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
318
|
+
if (!name) {
|
|
319
|
+
json({ error: "name is required" }, 400);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const parent = getThread(db, rootThreadId);
|
|
323
|
+
if (!parent) {
|
|
324
|
+
json({ error: `Root thread ${rootThreadId} not found` }, 404);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (parent.type === "worker") {
|
|
328
|
+
json({ error: "Cannot branch from a worker thread" }, 400);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const agentType = resolveAgentType(body.agentType);
|
|
332
|
+
if (agentType === INVALID_AGENT) {
|
|
333
|
+
json({ error: `agentType must be one of: ${VALID_CLIENTS.join(", ")}` }, 400);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const workingDirectoryOverride = typeof body.workingDirectory === "string" && body.workingDirectory.trim()
|
|
337
|
+
? body.workingDirectory.trim()
|
|
338
|
+
: undefined;
|
|
339
|
+
const { TELEGRAM_TOKEN, TELEGRAM_CHAT_ID } = config;
|
|
340
|
+
if (!TELEGRAM_TOKEN || !TELEGRAM_CHAT_ID) {
|
|
341
|
+
json({ error: "Telegram is not configured" }, 500);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const telegram = new TelegramClient(TELEGRAM_TOKEN);
|
|
345
|
+
const threadId = await createManagedTopic(telegram, TELEGRAM_CHAT_ID, name);
|
|
346
|
+
const thread = ctx.threadLifecycle.registerThread(db, {
|
|
347
|
+
threadId,
|
|
348
|
+
name,
|
|
349
|
+
type: "branch",
|
|
350
|
+
rootThreadId,
|
|
351
|
+
client: agentType ?? parent.client ?? getAgentType(),
|
|
352
|
+
workingDirectory: workingDirectoryOverride ?? parent.workingDirectory ?? undefined,
|
|
353
|
+
chatId: TELEGRAM_CHAT_ID,
|
|
354
|
+
telegramTopicId: threadId,
|
|
355
|
+
});
|
|
356
|
+
json({ ok: true, threadId, thread }, 201);
|
|
357
|
+
syncKeepAliveToSettings(db);
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
json({ error: errorMessage(err) }, 500);
|
|
361
|
+
}
|
|
362
|
+
})();
|
|
363
|
+
return true;
|
|
364
|
+
};
|
|
365
|
+
/** Sentinel returned by resolveAgentType when an explicit agentType is invalid. */
|
|
366
|
+
const INVALID_AGENT = Symbol("invalid-agent");
|
|
367
|
+
/** Validate an optional agentType field: undefined โ undefined, valid โ the value, invalid โ INVALID_AGENT. */
|
|
368
|
+
function resolveAgentType(value) {
|
|
369
|
+
if (value === undefined)
|
|
370
|
+
return undefined;
|
|
371
|
+
if (typeof value === "string" && VALID_CLIENTS.includes(value))
|
|
372
|
+
return value;
|
|
373
|
+
return INVALID_AGENT;
|
|
374
|
+
}
|
|
236
375
|
// โโโ Dynamic handlers (threadId extracted by caller) โโโโโโโโโโโโโโโโโโโโโโโโ
|
|
237
376
|
/** GET /api/threads/:threadId โ get single thread */
|
|
238
377
|
export function handleGetThread(args, threadId) {
|
|
@@ -257,6 +396,57 @@ export function handleGetThreadHeartbeat(args, threadId) {
|
|
|
257
396
|
args.json({ threadId, lastActivityMs });
|
|
258
397
|
return true;
|
|
259
398
|
}
|
|
399
|
+
/** Locate the most-recently-modified log file for a thread, or null if none exist. */
|
|
400
|
+
function findLatestThreadLogFile(threadId) {
|
|
401
|
+
let files;
|
|
402
|
+
try {
|
|
403
|
+
files = readdirSync(THREAD_LOGS_DIR);
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const suffixRe = new RegExp(`_${threadId}_\\d{4}-\\d{2}-\\d{2}\\.jsonl?$`);
|
|
409
|
+
let best = null;
|
|
410
|
+
for (const file of files) {
|
|
411
|
+
if (!suffixRe.test(file))
|
|
412
|
+
continue;
|
|
413
|
+
const full = join(THREAD_LOGS_DIR, file);
|
|
414
|
+
try {
|
|
415
|
+
const mtimeMs = statSync(full).mtimeMs;
|
|
416
|
+
if (!best || mtimeMs > best.mtimeMs)
|
|
417
|
+
best = { path: full, mtimeMs };
|
|
418
|
+
}
|
|
419
|
+
catch { /* file vanished between readdir and stat */ }
|
|
420
|
+
}
|
|
421
|
+
return best?.path ?? null;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* GET /api/threads/:threadId/logs โ a readable digest of the agent's raw stdout.
|
|
425
|
+
*
|
|
426
|
+
* Parses the newest stream-json log file into tail-first turns, then returns a
|
|
427
|
+
* single character window (default 4000, Telegram-sized). Callers page deeper
|
|
428
|
+
* into history with ?offset=; nextOffset/hasMore drive a "next chunk" button.
|
|
429
|
+
*/
|
|
430
|
+
export function handleGetThreadLogs(args, threadId) {
|
|
431
|
+
const { json, url } = args;
|
|
432
|
+
const logFile = findLatestThreadLogFile(threadId);
|
|
433
|
+
if (!logFile) {
|
|
434
|
+
json({ threadId, text: "", offset: 0, nextOffset: 0, hasMore: false, totalLength: 0 });
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
let raw;
|
|
438
|
+
try {
|
|
439
|
+
raw = readFileSync(logFile, "utf-8");
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
json({ error: errorMessage(err) }, 500);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
const rendered = renderThreadLog(parseClaudeStreamLog(raw), true);
|
|
446
|
+
const page = sliceLogWindow(rendered, Number(url.searchParams.get("offset")), Number(url.searchParams.get("limit")));
|
|
447
|
+
json({ threadId, ...page });
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
260
450
|
/** GET /api/threads/:threadId/children โ get children of a root */
|
|
261
451
|
export function handleGetThreadChildren(args, rootThreadId) {
|
|
262
452
|
const children = getThreadsByRoot(args.db, rootThreadId);
|