@iletai/nzb 1.7.0 → 1.7.4
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/api/server.js +37 -6
- package/dist/cli.js +1 -0
- package/dist/config.js +12 -3
- package/dist/copilot/client.js +17 -16
- package/dist/copilot/mcp-config.js +2 -0
- package/dist/copilot/orchestrator.js +289 -125
- package/dist/copilot/skills.js +4 -2
- package/dist/copilot/tools.js +48 -11
- package/dist/copilot/types.js +2 -0
- package/dist/daemon.js +11 -10
- package/dist/setup.js +3 -2
- package/dist/store/conversation.js +96 -0
- package/dist/store/db.js +7 -206
- package/dist/store/memory.js +90 -0
- package/dist/store/team-store.js +51 -0
- package/dist/telegram/bot.js +85 -8
- package/dist/telegram/handlers/commands.js +1 -1
- package/dist/telegram/handlers/media.js +63 -6
- package/dist/telegram/handlers/streaming.js +223 -188
- package/dist/telegram/handlers/suggestions.js +22 -1
- package/dist/telegram/log-channel.js +2 -2
- package/dist/telegram/menus.js +243 -99
- package/dist/tui/ansi.js +19 -0
- package/dist/tui/api-client.js +158 -0
- package/dist/tui/debug.js +27 -0
- package/dist/tui/renderer.js +59 -0
- package/dist/tui/stream.js +163 -0
- package/dist/update.js +2 -0
- package/dist/utils.js +102 -0
- package/package.json +1 -1
package/dist/api/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { cancelCurrentMessage, getWorkers, sendToOrchestrator } from "../copilot
|
|
|
6
6
|
import { listSkills, removeSkill } from "../copilot/skills.js";
|
|
7
7
|
import { restartDaemon } from "../daemon.js";
|
|
8
8
|
import { API_TOKEN_PATH, ensureNZBHome } from "../paths.js";
|
|
9
|
-
import { searchMemories } from "../store/
|
|
9
|
+
import { searchMemories } from "../store/memory.js";
|
|
10
10
|
import { sendPhoto } from "../telegram/bot.js";
|
|
11
11
|
// Ensure token file exists (generate on first run)
|
|
12
12
|
let apiToken = null;
|
|
@@ -26,9 +26,9 @@ catch (err) {
|
|
|
26
26
|
}
|
|
27
27
|
const app = express();
|
|
28
28
|
app.use(express.json());
|
|
29
|
-
// Bearer token authentication middleware (skip /
|
|
29
|
+
// Bearer token authentication middleware (skip /ping health check only)
|
|
30
30
|
app.use((req, res, next) => {
|
|
31
|
-
if (!apiToken || req.path === "/
|
|
31
|
+
if (!apiToken || req.path === "/ping")
|
|
32
32
|
return next();
|
|
33
33
|
const auth = req.headers.authorization;
|
|
34
34
|
if (!auth || auth !== `Bearer ${apiToken}`) {
|
|
@@ -40,7 +40,11 @@ app.use((req, res, next) => {
|
|
|
40
40
|
// Active SSE connections
|
|
41
41
|
const sseClients = new Map();
|
|
42
42
|
let connectionCounter = 0;
|
|
43
|
-
//
|
|
43
|
+
// Minimal unauthenticated health check — no internal details
|
|
44
|
+
app.get("/ping", (_req, res) => {
|
|
45
|
+
res.json({ status: "ok" });
|
|
46
|
+
});
|
|
47
|
+
// Authenticated status with worker details
|
|
44
48
|
app.get("/status", (_req, res) => {
|
|
45
49
|
res.json({
|
|
46
50
|
status: "ok",
|
|
@@ -73,8 +77,23 @@ app.get("/stream", (req, res) => {
|
|
|
73
77
|
sseClients.set(connectionId, res);
|
|
74
78
|
// Heartbeat to keep connection alive
|
|
75
79
|
const heartbeat = setInterval(() => {
|
|
76
|
-
res.
|
|
80
|
+
if (res.writableEnded || res.closed) {
|
|
81
|
+
clearInterval(heartbeat);
|
|
82
|
+
sseClients.delete(connectionId);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
res.write(`:ping\n\n`);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
clearInterval(heartbeat);
|
|
90
|
+
sseClients.delete(connectionId);
|
|
91
|
+
}
|
|
77
92
|
}, 20_000);
|
|
93
|
+
res.on("error", () => {
|
|
94
|
+
clearInterval(heartbeat);
|
|
95
|
+
sseClients.delete(connectionId);
|
|
96
|
+
});
|
|
78
97
|
req.on("close", () => {
|
|
79
98
|
clearInterval(heartbeat);
|
|
80
99
|
sseClients.delete(connectionId);
|
|
@@ -175,7 +194,12 @@ app.post("/restart", (_req, res) => {
|
|
|
175
194
|
app.post("/send-photo", async (req, res) => {
|
|
176
195
|
const { photo, caption } = req.body;
|
|
177
196
|
if (!photo || typeof photo !== "string") {
|
|
178
|
-
res.status(400).json({ error: "Missing 'photo' (file path or URL) in request body" });
|
|
197
|
+
res.status(400).json({ error: "Missing 'photo' (file path or HTTPS URL) in request body" });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// Basic input validation before passing to sendPhoto
|
|
201
|
+
if (photo.startsWith("http://")) {
|
|
202
|
+
res.status(400).json({ error: "Only HTTPS URLs are allowed for photos" });
|
|
179
203
|
return;
|
|
180
204
|
}
|
|
181
205
|
try {
|
|
@@ -187,6 +211,13 @@ app.post("/send-photo", async (req, res) => {
|
|
|
187
211
|
res.status(500).json({ error: msg });
|
|
188
212
|
}
|
|
189
213
|
});
|
|
214
|
+
// Global error handler — catch unhandled Express errors
|
|
215
|
+
app.use((err, _req, res, _next) => {
|
|
216
|
+
console.error("[nzb] Express error:", err.message);
|
|
217
|
+
if (!res.headersSent) {
|
|
218
|
+
res.status(500).json({ error: "Internal server error" });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
190
221
|
export function startApiServer() {
|
|
191
222
|
return new Promise((resolve, reject) => {
|
|
192
223
|
const server = app.listen(config.apiPort, "127.0.0.1", () => {
|
package/dist/cli.js
CHANGED
package/dist/config.js
CHANGED
|
@@ -38,6 +38,14 @@ if (!Number.isInteger(parsedWorkerTimeout) || parsedWorkerTimeout <= 0) {
|
|
|
38
38
|
}
|
|
39
39
|
const parsedLogChannelId = raw.LOG_CHANNEL_ID ? raw.LOG_CHANNEL_ID.trim() : undefined;
|
|
40
40
|
export const DEFAULT_MODEL = "claude-sonnet-4.6";
|
|
41
|
+
function validateEnum(value, validValues, defaultValue, name) {
|
|
42
|
+
if (!value)
|
|
43
|
+
return defaultValue;
|
|
44
|
+
if (validValues.includes(value))
|
|
45
|
+
return value;
|
|
46
|
+
console.log(`[nzb] Invalid ${name} value "${value}", using default "${defaultValue}"`);
|
|
47
|
+
return defaultValue;
|
|
48
|
+
}
|
|
41
49
|
let _copilotModel = raw.COPILOT_MODEL || DEFAULT_MODEL;
|
|
42
50
|
export const config = {
|
|
43
51
|
telegramBotToken: raw.TELEGRAM_BOT_TOKEN,
|
|
@@ -65,15 +73,15 @@ export const config = {
|
|
|
65
73
|
process.env.SHOW_REASONING = value ? "true" : "false";
|
|
66
74
|
},
|
|
67
75
|
/** Usage display mode: off | tokens | full */
|
|
68
|
-
usageMode: (process.env.USAGE_MODE
|
|
76
|
+
usageMode: validateEnum(process.env.USAGE_MODE, ["off", "tokens", "full"], "off", "USAGE_MODE"),
|
|
69
77
|
/** Verbose mode: when on, instructs the AI to be more detailed */
|
|
70
78
|
verboseMode: process.env.VERBOSE_MODE === "true",
|
|
71
79
|
/** Thinking level: off | low | medium | high */
|
|
72
|
-
thinkingLevel: (process.env.THINKING_LEVEL
|
|
80
|
+
thinkingLevel: validateEnum(process.env.THINKING_LEVEL, ["off", "low", "medium", "high"], "off", "THINKING_LEVEL"),
|
|
73
81
|
/** Group chat: when true, bot only responds when mentioned in groups */
|
|
74
82
|
groupMentionOnly: process.env.GROUP_MENTION_ONLY !== "false",
|
|
75
83
|
/** Reasoning effort: low | medium | high */
|
|
76
|
-
reasoningEffort: (process.env.REASONING_EFFORT
|
|
84
|
+
reasoningEffort: validateEnum(process.env.REASONING_EFFORT, ["low", "medium", "high"], "medium", "REASONING_EFFORT"),
|
|
77
85
|
};
|
|
78
86
|
/** Persist an env variable to ~/.nzb/.env */
|
|
79
87
|
export function persistEnvVar(key, value) {
|
|
@@ -94,6 +102,7 @@ export function persistEnvVar(key, value) {
|
|
|
94
102
|
writeFileSync(ENV_PATH, updated.join("\n"));
|
|
95
103
|
}
|
|
96
104
|
catch {
|
|
105
|
+
// Expected: .env file may not exist yet on first run
|
|
97
106
|
writeFileSync(ENV_PATH, `${key}=${value}\n`);
|
|
98
107
|
}
|
|
99
108
|
}
|
package/dist/copilot/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CopilotClient } from "@github/copilot-sdk";
|
|
2
|
+
import { withTimeout } from "../utils.js";
|
|
2
3
|
let client;
|
|
3
4
|
/** Coalesces concurrent resetClient() calls into a single reset operation. */
|
|
4
5
|
let pendingResetPromise;
|
|
@@ -7,7 +8,7 @@ export async function getClient() {
|
|
|
7
8
|
client = new CopilotClient({
|
|
8
9
|
autoStart: true,
|
|
9
10
|
});
|
|
10
|
-
await client.start();
|
|
11
|
+
await withTimeout(client.start(), 30_000, "client.start()");
|
|
11
12
|
}
|
|
12
13
|
return client;
|
|
13
14
|
}
|
|
@@ -16,27 +17,27 @@ export async function resetClient() {
|
|
|
16
17
|
if (pendingResetPromise)
|
|
17
18
|
return pendingResetPromise;
|
|
18
19
|
pendingResetPromise = (async () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
try {
|
|
21
|
+
if (client) {
|
|
22
|
+
try {
|
|
23
|
+
await withTimeout(client.stop(), 10_000, "client.stop()");
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
console.error("[nzb] Error stopping client during reset:", err);
|
|
27
|
+
}
|
|
28
|
+
client = undefined;
|
|
22
29
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
return await getClient();
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
pendingResetPromise = undefined;
|
|
27
34
|
}
|
|
28
|
-
return getClient();
|
|
29
35
|
})();
|
|
30
|
-
|
|
31
|
-
return await pendingResetPromise;
|
|
32
|
-
}
|
|
33
|
-
finally {
|
|
34
|
-
pendingResetPromise = undefined;
|
|
35
|
-
}
|
|
36
|
+
return pendingResetPromise;
|
|
36
37
|
}
|
|
37
38
|
export async function stopClient() {
|
|
38
39
|
if (client) {
|
|
39
|
-
await client.stop();
|
|
40
|
+
await withTimeout(client.stop(), 10_000, "client.stop()");
|
|
40
41
|
client = undefined;
|
|
41
42
|
}
|
|
42
43
|
}
|
|
@@ -10,6 +10,7 @@ const isWSL = (() => {
|
|
|
10
10
|
return readFileSync("/proc/version", "utf-8").toLowerCase().includes("microsoft");
|
|
11
11
|
}
|
|
12
12
|
catch {
|
|
13
|
+
// Expected: /proc/version may not exist on non-Linux systems
|
|
13
14
|
return false;
|
|
14
15
|
}
|
|
15
16
|
})();
|
|
@@ -86,6 +87,7 @@ export function loadMcpConfig() {
|
|
|
86
87
|
return cachedConfig;
|
|
87
88
|
}
|
|
88
89
|
catch {
|
|
90
|
+
// Expected: config file may not exist or be malformed
|
|
89
91
|
cachedConfig = {};
|
|
90
92
|
return cachedConfig;
|
|
91
93
|
}
|