peerllm-host-cli 2.9.0 → 2.10.1
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/CHANGELOG.md +72 -0
- package/dist/core/auth.d.ts +26 -0
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +47 -0
- package/dist/core/auth.js.map +1 -1
- package/dist/core/backend-client.d.ts +8 -0
- package/dist/core/backend-client.d.ts.map +1 -1
- package/dist/core/backend-client.js +13 -1
- package/dist/core/backend-client.js.map +1 -1
- package/dist/core/error-detail.d.ts +12 -0
- package/dist/core/error-detail.d.ts.map +1 -0
- package/dist/core/error-detail.js +78 -0
- package/dist/core/error-detail.js.map +1 -0
- package/dist/core/host-stats.d.ts +32 -0
- package/dist/core/host-stats.d.ts.map +1 -1
- package/dist/core/host-stats.js +55 -1
- package/dist/core/host-stats.js.map +1 -1
- package/dist/core/llmWorker.js +67 -6
- package/dist/core/llmWorker.js.map +1 -1
- package/dist/core/openai-tools.d.ts +111 -0
- package/dist/core/openai-tools.d.ts.map +1 -0
- package/dist/core/openai-tools.js +387 -0
- package/dist/core/openai-tools.js.map +1 -0
- package/dist/core/orchestrator.d.ts +8 -0
- package/dist/core/orchestrator.d.ts.map +1 -1
- package/dist/core/orchestrator.js +21 -0
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/shared-runner.d.ts +23 -5
- package/dist/core/shared-runner.d.ts.map +1 -1
- package/dist/core/shared-runner.js +29 -6
- package/dist/core/shared-runner.js.map +1 -1
- package/dist/daemon/rest-server.d.ts +9 -0
- package/dist/daemon/rest-server.d.ts.map +1 -1
- package/dist/daemon/rest-server.js +110 -9
- package/dist/daemon/rest-server.js.map +1 -1
- package/dist/daemon/run.d.ts.map +1 -1
- package/dist/daemon/run.js +3 -0
- package/dist/daemon/run.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,78 @@
|
|
|
3
3
|
All notable changes to the PeerLLM host CLI are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [2.10.1]
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Failures say what actually went wrong.** Every transport error surfaced as
|
|
11
|
+
`network error: fetch failed`, discarding the cause underneath. DNS failure, connection
|
|
12
|
+
refused, connection reset and connect timeout are unrelated problems that all read
|
|
13
|
+
identically; the real code is now reported.
|
|
14
|
+
- **Requests are bounded.** Backend calls had no whole-request timeout — only the connect
|
|
15
|
+
phase was limited — so a request that connected and then stalled hung indefinitely.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Stats polling moved from every 10 seconds to every 30.** Each tick is several requests
|
|
20
|
+
and the server marks that data cacheable for 30 seconds, so two of every three polls were
|
|
21
|
+
re-fetching a value the server still considered fresh. Part of reducing the connection
|
|
22
|
+
churn that saturates home-router connection tables.
|
|
23
|
+
|
|
24
|
+
- **Dashboard data now arrives over the connection the host already holds.** When the
|
|
25
|
+
orchestrator supports it, stats are pushed over the existing network connection instead of
|
|
26
|
+
being fetched on a timer. That removes the last frequent poll — and with it the name
|
|
27
|
+
lookup, handshake and router connection-table entry each poll needed, which is the exact
|
|
28
|
+
thing that fails on a busy home network. Polling resumes automatically if pushes stop, so
|
|
29
|
+
nothing depends on the server supporting it.
|
|
30
|
+
|
|
31
|
+
## [2.10.0]
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Tool calling on the local REST server.** `POST /v1/chat/completions` now honors the
|
|
36
|
+
OpenAI `tools` array: the schemas reach the model through its own chat template, and a
|
|
37
|
+
tool call comes back as `message.tool_calls` with `finish_reason: "tool_calls"`, in both
|
|
38
|
+
streaming and non-streaming mode. `tool_choice` is honored for `"auto"` and `"none"`.
|
|
39
|
+
Previously the field was parsed off the request and silently discarded — the caller got
|
|
40
|
+
HTTP 200 and a plausible prose answer with no signal that its tools had never been shown
|
|
41
|
+
to the model, which downstream JSON parsing then failed on.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **Chat messages now reach the model as real turns.** `/v1/chat/completions` used to
|
|
46
|
+
concatenate the whole conversation into a single `"role: content"` string and hand it over
|
|
47
|
+
as one user message, so a system prompt arrived as literal `"system: ..."` text inside the
|
|
48
|
+
user turn, and `role: "tool"` results could not be fed back at all. The request is now
|
|
49
|
+
rendered through the model's own chat template with system, user, assistant and tool turns
|
|
50
|
+
intact. Expect better instruction-following on any request that uses a system prompt or
|
|
51
|
+
multi-turn history.
|
|
52
|
+
- **`usage.prompt_tokens` was under-reported on every request.** It counted the raw prompt
|
|
53
|
+
string and excluded every token the chat template adds. It now reports what llama.cpp
|
|
54
|
+
actually evaluated.
|
|
55
|
+
- **Unsupported request fields fail loudly instead of being dropped.** A tool schema using
|
|
56
|
+
`allOf`/`not`, a `tool_choice` of `"required"` or a named function, an unknown message
|
|
57
|
+
role, or `tools` on `/v1/completions` now return HTTP 400 naming the offending field,
|
|
58
|
+
rather than a 200 whose answer quietly ignored them.
|
|
59
|
+
- **A daemon holding a dead refresh token no longer hammers the network.** When the stored
|
|
60
|
+
credential went stale, the daemon retried `/auth/refresh` every few seconds indefinitely —
|
|
61
|
+
the token is deliberately never deleted (a headless daemon has no password to
|
|
62
|
+
re-authenticate with), and the server answered a status the daemon correctly read as
|
|
63
|
+
"try again shortly", so nothing ever ended the loop. A single host in that state generated
|
|
64
|
+
681 calls an hour, enough to saturate the orchestrator's database and make *other* hosts
|
|
65
|
+
read as disconnected while they were in fact serving normally. Failed refreshes now back
|
|
66
|
+
off 15s, 30s, 1m, 2m, 4m, 8m, then 15m: four attempts an hour instead of 720. The
|
|
67
|
+
credential is still never deleted, and `doctor`/`status` still report a present-but-
|
|
68
|
+
rejected token.
|
|
69
|
+
|
|
70
|
+
### Notes
|
|
71
|
+
|
|
72
|
+
- Tool support is local-only in the sense that it needs a tool-capable model; `agentic on`
|
|
73
|
+
gates what this host advertises to the orchestrator and has never gated the local REST API.
|
|
74
|
+
- node-llama-cpp's grammar layer treats every declared property as required. Optional
|
|
75
|
+
parameters are encoded as "or null" and the nulls are stripped from the emitted arguments,
|
|
76
|
+
so `required` behaves as callers expect.
|
|
77
|
+
|
|
6
78
|
## [2.9.0]
|
|
7
79
|
|
|
8
80
|
### Added
|
package/dist/core/auth.d.ts
CHANGED
|
@@ -8,6 +8,24 @@ import type { SecretsService } from "./secrets.js";
|
|
|
8
8
|
* null when the token isn't a decodable JWT or carries no numeric exp.
|
|
9
9
|
*/
|
|
10
10
|
export declare function decodeJwtExpiryMs(token: string): number | null;
|
|
11
|
+
/** First retry delay after a failed refresh; doubles per consecutive failure. */
|
|
12
|
+
export declare const REFRESH_BACKOFF_BASE_MS = 15000;
|
|
13
|
+
/** Ceiling on the retry delay. A recovering daemon still rejoins within this. */
|
|
14
|
+
export declare const REFRESH_BACKOFF_MAX_MS: number;
|
|
15
|
+
/**
|
|
16
|
+
* Delay before the next refresh attempt after `failures` consecutive failures:
|
|
17
|
+
* 15s, 30s, 1m, 2m, 4m, 8m, then 15m forever.
|
|
18
|
+
*
|
|
19
|
+
* Why this is needed at all: we deliberately never delete the refresh token (a
|
|
20
|
+
* headless daemon has no password, so clearing it turns a transient rejection
|
|
21
|
+
* into a permanent lockout). That leaves nothing to end a doomed retry loop.
|
|
22
|
+
* Callers ask for a token far more often than a refresh is needed, so a daemon
|
|
23
|
+
* holding a dead credential used to re-hit /auth/refresh every few seconds
|
|
24
|
+
* indefinitely — one such host pinned the orchestrator's database for eight
|
|
25
|
+
* hours on 2026-08-19. Kept identical to the Electron host's schedule
|
|
26
|
+
* (src/main/refreshTokenPolicy.ts) so both clients behave the same.
|
|
27
|
+
*/
|
|
28
|
+
export declare function refreshBackoffMs(failures: number): number;
|
|
11
29
|
export declare class AuthService {
|
|
12
30
|
private readonly client;
|
|
13
31
|
private readonly secrets;
|
|
@@ -16,6 +34,14 @@ export declare class AuthService {
|
|
|
16
34
|
private tokenExpiresAt;
|
|
17
35
|
private refreshInFlight;
|
|
18
36
|
private refreshRejections;
|
|
37
|
+
/**
|
|
38
|
+
* Consecutive FAILED refreshes of any kind (401, other statuses, network) and
|
|
39
|
+
* the earliest time we may try again. Kept separate from refreshRejections,
|
|
40
|
+
* which counts only genuine 401s — a slow backend must pace us without being
|
|
41
|
+
* mistaken for a credential rejection.
|
|
42
|
+
*/
|
|
43
|
+
private refreshFailures;
|
|
44
|
+
private refreshBackoffUntil;
|
|
19
45
|
/**
|
|
20
46
|
* How many times in a row the backend has *rejected* the refresh token (HTTP
|
|
21
47
|
* 401) — reset to 0 on the next success. Network errors and other transient
|
package/dist/core/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAyB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAChG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAUnD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa9D;AAoBD,qBAAa,WAAW;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAyB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAChG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAUnD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa9D;AAoBD,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,QAAiB,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,qBAAa,WAAW;IAyBpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IA1BzB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,cAAc,CAAmB;IACzC,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,iBAAiB,CAAK;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,mBAAmB,CAAK;IAEhC;;;;;OAKG;IACH,IAAI,4BAA4B,IAAI,MAAM,CAEzC;gBAGkB,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,aAAa;IAGlC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAsB/E,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAMvB,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa7C,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;YAK3B,OAAO;IAsErB;;;;OAIG;IACH,qBAAqB,IAAI,IAAI;IAK7B;;;;;OAKG;IACG,oBAAoB,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAe7F,OAAO,CAAC,cAAc;YAWR,iBAAiB;CAYhC"}
|
package/dist/core/auth.js
CHANGED
|
@@ -27,6 +27,29 @@ export function decodeJwtExpiryMs(token) {
|
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
/** First retry delay after a failed refresh; doubles per consecutive failure. */
|
|
31
|
+
export const REFRESH_BACKOFF_BASE_MS = 15_000;
|
|
32
|
+
/** Ceiling on the retry delay. A recovering daemon still rejoins within this. */
|
|
33
|
+
export const REFRESH_BACKOFF_MAX_MS = 15 * 60 * 1000;
|
|
34
|
+
/**
|
|
35
|
+
* Delay before the next refresh attempt after `failures` consecutive failures:
|
|
36
|
+
* 15s, 30s, 1m, 2m, 4m, 8m, then 15m forever.
|
|
37
|
+
*
|
|
38
|
+
* Why this is needed at all: we deliberately never delete the refresh token (a
|
|
39
|
+
* headless daemon has no password, so clearing it turns a transient rejection
|
|
40
|
+
* into a permanent lockout). That leaves nothing to end a doomed retry loop.
|
|
41
|
+
* Callers ask for a token far more often than a refresh is needed, so a daemon
|
|
42
|
+
* holding a dead credential used to re-hit /auth/refresh every few seconds
|
|
43
|
+
* indefinitely — one such host pinned the orchestrator's database for eight
|
|
44
|
+
* hours on 2026-08-19. Kept identical to the Electron host's schedule
|
|
45
|
+
* (src/main/refreshTokenPolicy.ts) so both clients behave the same.
|
|
46
|
+
*/
|
|
47
|
+
export function refreshBackoffMs(failures) {
|
|
48
|
+
if (failures <= 0)
|
|
49
|
+
return 0;
|
|
50
|
+
const raw = REFRESH_BACKOFF_BASE_MS * Math.pow(2, failures - 1);
|
|
51
|
+
return Math.min(raw, REFRESH_BACKOFF_MAX_MS);
|
|
52
|
+
}
|
|
30
53
|
export class AuthService {
|
|
31
54
|
client;
|
|
32
55
|
secrets;
|
|
@@ -35,6 +58,14 @@ export class AuthService {
|
|
|
35
58
|
tokenExpiresAt;
|
|
36
59
|
refreshInFlight;
|
|
37
60
|
refreshRejections = 0;
|
|
61
|
+
/**
|
|
62
|
+
* Consecutive FAILED refreshes of any kind (401, other statuses, network) and
|
|
63
|
+
* the earliest time we may try again. Kept separate from refreshRejections,
|
|
64
|
+
* which counts only genuine 401s — a slow backend must pace us without being
|
|
65
|
+
* mistaken for a credential rejection.
|
|
66
|
+
*/
|
|
67
|
+
refreshFailures = 0;
|
|
68
|
+
refreshBackoffUntil = 0;
|
|
38
69
|
/**
|
|
39
70
|
* How many times in a row the backend has *rejected* the refresh token (HTTP
|
|
40
71
|
* 401) — reset to 0 on the next success. Network errors and other transient
|
|
@@ -97,6 +128,14 @@ export class AuthService {
|
|
|
97
128
|
const refreshToken = await this.secrets.get("refreshToken");
|
|
98
129
|
if (!refreshToken)
|
|
99
130
|
return null;
|
|
131
|
+
// Honour the backoff from the last failure. Returning null is the same answer
|
|
132
|
+
// the caller would get from a failed refresh, minus the request — the point is
|
|
133
|
+
// that a doomed credential stops generating traffic. Cleared on first success.
|
|
134
|
+
if (this.refreshBackoffUntil > Date.now()) {
|
|
135
|
+
const waitSec = Math.ceil((this.refreshBackoffUntil - Date.now()) / 1000);
|
|
136
|
+
getLogger().warn(`skipping refresh — backing off for another ${waitSec}s`);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
100
139
|
try {
|
|
101
140
|
const result = await this.client.request({
|
|
102
141
|
method: "POST",
|
|
@@ -117,6 +156,8 @@ export class AuthService {
|
|
|
117
156
|
}
|
|
118
157
|
this.setAccessToken(result.accessToken, result.expiresIn);
|
|
119
158
|
this.refreshRejections = 0;
|
|
159
|
+
this.refreshFailures = 0;
|
|
160
|
+
this.refreshBackoffUntil = 0;
|
|
120
161
|
return result.accessToken;
|
|
121
162
|
}
|
|
122
163
|
catch (err) {
|
|
@@ -128,6 +169,11 @@ export class AuthService {
|
|
|
128
169
|
// transient/racy rejection into a permanent lockout that only a manual
|
|
129
170
|
// `peerllm-host login` can undo. Keep it on disk and let the caller retry
|
|
130
171
|
// — a server-side hiccup then self-heals with no human intervention.
|
|
172
|
+
// Every failure paces the next attempt, whatever caused it — see
|
|
173
|
+
// refreshBackoffMs for why nothing else can end the loop.
|
|
174
|
+
this.refreshFailures++;
|
|
175
|
+
const retryAfterMs = refreshBackoffMs(this.refreshFailures);
|
|
176
|
+
this.refreshBackoffUntil = Date.now() + retryAfterMs;
|
|
131
177
|
if (err instanceof NotAuthenticatedError) {
|
|
132
178
|
this.refreshRejections++;
|
|
133
179
|
getLogger().warn(`refresh token rejected (${this.refreshRejections} in a row); keeping it for retry — run 'peerllm-host login' if this persists`);
|
|
@@ -135,6 +181,7 @@ export class AuthService {
|
|
|
135
181
|
else {
|
|
136
182
|
getLogger().error("token refresh failed", err.message);
|
|
137
183
|
}
|
|
184
|
+
getLogger().warn(`backing off refresh for ${Math.round(retryAfterMs / 1000)}s (${this.refreshFailures} consecutive failures)`);
|
|
138
185
|
return null;
|
|
139
186
|
}
|
|
140
187
|
}
|
package/dist/core/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,qBAAqB,EAAuB,MAAM,qBAAqB,CAAC;AAEhG,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,0EAA0E;AAC1E,6EAA6E;AAC7E,6DAA6D;AAC7D,MAAM,8BAA8B,GAAG,EAAE,GAAG,EAAE,CAAC;AAC/C,6EAA6E;AAC7E,8DAA8D;AAC9D,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAE5E,CAAC;QACF,OAAO,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC;YACpE,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI;YACpB,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAoBD,MAAM,OAAO,WAAW;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,7 +19,15 @@ export interface BackendRequest {
|
|
|
19
19
|
body?: unknown;
|
|
20
20
|
bearerToken?: string;
|
|
21
21
|
headers?: Record<string, string>;
|
|
22
|
+
/** Whole-request budget. Defaults to DEFAULT_REQUEST_TIMEOUT_MS. */
|
|
23
|
+
timeoutMs?: number;
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Whole-request timeout. undici's built-in default bounds the CONNECT phase
|
|
27
|
+
* only, so a request that connected and then stalled would hang forever — and
|
|
28
|
+
* on a polling path that means ticks piling up behind a dead socket.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
23
31
|
export interface BackendClientOptions {
|
|
24
32
|
baseUrl?: string;
|
|
25
33
|
userAgent?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend-client.d.ts","sourceRoot":"","sources":["../../src/core/backend-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"backend-client.d.ts","sourceRoot":"","sources":["../../src/core/backend-client.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,4BAA4B,CAAC;AAElE,qBAAa,YAAa,SAAQ,KAAK;aAGnB,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,OAAO;aACb,IAAI,CAAC,EAAE,MAAM;gBAH7B,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE,MAAM,YAAA;CAIhC;AAED,qBAAa,sBAAuB,SAAQ,YAAY;aAEpC,aAAa,EAAE,MAAM,GAAG,SAAS;aACjC,WAAW,EAAE,MAAM,GAAG,SAAS;gBAD/B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/C,OAAO,EAAE,MAAM;CAIlB;AAED,qBAAa,qBAAsB,SAAQ,YAAY;gBACzC,OAAO,SAAsB;CAG1C;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,IAAI,GAAE,oBAAyB;IAKrC,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAwD5D"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { describeError } from "./error-detail.js";
|
|
1
2
|
export const DEFAULT_ORCHESTRATOR_URL = "https://api.peerllm.com";
|
|
2
3
|
export class BackendError extends Error {
|
|
3
4
|
status;
|
|
@@ -24,6 +25,12 @@ export class NotAuthenticatedError extends BackendError {
|
|
|
24
25
|
super(message, 401, undefined, "NotAuthenticated");
|
|
25
26
|
}
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Whole-request timeout. undici's built-in default bounds the CONNECT phase
|
|
30
|
+
* only, so a request that connected and then stalled would hang forever — and
|
|
31
|
+
* on a polling path that means ticks piling up behind a dead socket.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
27
34
|
export class BackendClient {
|
|
28
35
|
baseUrl;
|
|
29
36
|
userAgent;
|
|
@@ -48,10 +55,15 @@ export class BackendClient {
|
|
|
48
55
|
method: req.method,
|
|
49
56
|
headers,
|
|
50
57
|
body: req.body === undefined ? undefined : JSON.stringify(req.body),
|
|
58
|
+
signal: AbortSignal.timeout(req.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS),
|
|
51
59
|
});
|
|
52
60
|
}
|
|
53
61
|
catch (err) {
|
|
54
|
-
|
|
62
|
+
// describeError, not err.message: undici reports every transport fault as the
|
|
63
|
+
// same "fetch failed" string and hides the real code (EAI_AGAIN, ECONNRESET,
|
|
64
|
+
// ECONNREFUSED, UND_ERR_CONNECT_TIMEOUT) on err.cause. Without this the daemon
|
|
65
|
+
// logs cannot tell a DNS failure from an unreachable host.
|
|
66
|
+
throw new BackendError(`network error: ${describeError(err)}`, 0, undefined);
|
|
55
67
|
}
|
|
56
68
|
if (response.ok) {
|
|
57
69
|
if (response.status === 204)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend-client.js","sourceRoot":"","sources":["../../src/core/backend-client.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,wBAAwB,GAAG,yBAAyB,CAAC;AAElE,MAAM,OAAO,YAAa,SAAQ,KAAK;IAGnB;IACA;IACA;IAJlB,YACE,OAAe,EACC,MAAc,EACd,IAAa,EACb,IAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAS;QACb,SAAI,GAAJ,IAAI,CAAS;IAG/B,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,YAAY;IAEpC;IACA;IAFlB,YACkB,aAAiC,EACjC,WAA+B,EAC/C,OAAe;QAEf,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,aAAa,EAAE,WAAW,EAAE,EAAE,mBAAmB,CAAC,CAAC;QAJzD,kBAAa,GAAb,aAAa,CAAoB;QACjC,gBAAW,GAAX,WAAW,CAAoB;IAIjD,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,YAAY;IACrD,YAAY,OAAO,GAAG,mBAAmB;QACvC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;IACrD,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"backend-client.js","sourceRoot":"","sources":["../../src/core/backend-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a thrown value plus its full `cause` chain on one line.
|
|
3
|
+
*
|
|
4
|
+
* "TypeError: fetch failed"
|
|
5
|
+
* becomes
|
|
6
|
+
* "TypeError: fetch failed <- Error: getaddrinfo EAI_AGAIN api.peerllm.com
|
|
7
|
+
* code=EAI_AGAIN errno=-3001 syscall=getaddrinfo hostname=api.peerllm.com"
|
|
8
|
+
*
|
|
9
|
+
* Safe on anything: strings, null, objects with a circular `cause`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function describeError(err: unknown): string;
|
|
12
|
+
//# sourceMappingURL=error-detail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-detail.d.ts","sourceRoot":"","sources":["../../src/core/error-detail.ts"],"names":[],"mappings":"AAuDA;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CA6BlD"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/core/error-detail.ts
|
|
2
|
+
//
|
|
3
|
+
// PURE error formatter. No I/O — a function of an unknown thrown value, so it can
|
|
4
|
+
// be unit-tested. Kept in lock-step with the Electron host's src/main/errorDetail.ts.
|
|
5
|
+
//
|
|
6
|
+
// Why this exists:
|
|
7
|
+
// `${err}` on a fetch rejection prints exactly "TypeError: fetch failed" and
|
|
8
|
+
// throws away everything that identifies the fault. undici puts the real reason
|
|
9
|
+
// on `err.cause` — ENOTFOUND / EAI_AGAIN (name resolution), ECONNREFUSED,
|
|
10
|
+
// ECONNRESET, UND_ERR_CONNECT_TIMEOUT (the SYN never landed), EMFILE (out of
|
|
11
|
+
// descriptors). Those are four completely different problems that all read the
|
|
12
|
+
// same in our logs today, which has repeatedly forced diagnosis by timing
|
|
13
|
+
// forensics instead of by reading a log line.
|
|
14
|
+
//
|
|
15
|
+
// The cause chain is walked, because undici nests: TypeError -> AggregateError
|
|
16
|
+
// -> the individual socket errors.
|
|
17
|
+
/** Longest cause chain we will walk; guards against a self-referential cause. */
|
|
18
|
+
const MAX_DEPTH = 5;
|
|
19
|
+
function isObject(value) {
|
|
20
|
+
return typeof value === "object" && value !== null;
|
|
21
|
+
}
|
|
22
|
+
/** `code`/`syscall`/`hostname` etc., in a compact `key=value` form. */
|
|
23
|
+
function describeOne(err) {
|
|
24
|
+
const bits = [];
|
|
25
|
+
const name = typeof err.name === "string" ? err.name : null;
|
|
26
|
+
const message = typeof err.message === "string" ? err.message : null;
|
|
27
|
+
if (name && message)
|
|
28
|
+
bits.push(`${name}: ${message}`);
|
|
29
|
+
else if (message)
|
|
30
|
+
bits.push(message);
|
|
31
|
+
else if (name)
|
|
32
|
+
bits.push(name);
|
|
33
|
+
for (const key of ["code", "errno", "syscall", "hostname", "address", "port"]) {
|
|
34
|
+
const v = err[key];
|
|
35
|
+
if (v !== undefined && v !== null && v !== "")
|
|
36
|
+
bits.push(`${key}=${String(v)}`);
|
|
37
|
+
}
|
|
38
|
+
return bits.join(" ");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Renders a thrown value plus its full `cause` chain on one line.
|
|
42
|
+
*
|
|
43
|
+
* "TypeError: fetch failed"
|
|
44
|
+
* becomes
|
|
45
|
+
* "TypeError: fetch failed <- Error: getaddrinfo EAI_AGAIN api.peerllm.com
|
|
46
|
+
* code=EAI_AGAIN errno=-3001 syscall=getaddrinfo hostname=api.peerllm.com"
|
|
47
|
+
*
|
|
48
|
+
* Safe on anything: strings, null, objects with a circular `cause`.
|
|
49
|
+
*/
|
|
50
|
+
export function describeError(err) {
|
|
51
|
+
if (!isObject(err))
|
|
52
|
+
return String(err);
|
|
53
|
+
const parts = [];
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
let current = err;
|
|
56
|
+
for (let depth = 0; depth < MAX_DEPTH && isObject(current); depth++) {
|
|
57
|
+
if (seen.has(current))
|
|
58
|
+
break;
|
|
59
|
+
seen.add(current);
|
|
60
|
+
const described = describeOne(current);
|
|
61
|
+
if (described)
|
|
62
|
+
parts.push(described);
|
|
63
|
+
// AggregateError (undici raises one when every resolved address fails):
|
|
64
|
+
// the individual socket errors are the interesting part, not the wrapper.
|
|
65
|
+
const inner = current.errors;
|
|
66
|
+
if (Array.isArray(inner) && inner.length > 0) {
|
|
67
|
+
const each = inner
|
|
68
|
+
.slice(0, 3)
|
|
69
|
+
.map(e => (isObject(e) ? describeOne(e) : String(e)))
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
if (each.length)
|
|
72
|
+
parts.push(`[${each.join(" | ")}]`);
|
|
73
|
+
}
|
|
74
|
+
current = current.cause;
|
|
75
|
+
}
|
|
76
|
+
return parts.length ? parts.join(" <- ") : String(err);
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=error-detail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-detail.js","sourceRoot":"","sources":["../../src/core/error-detail.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import type { AuthService } from "./auth.js";
|
|
2
2
|
import { BackendClient } from "./backend-client.js";
|
|
3
3
|
import type { ConfigService } from "./config.js";
|
|
4
|
+
/** Wire shape of the orchestrator's "HostStats" push. Field names are the server's
|
|
5
|
+
* contract — see HostStatsSnapshot.cs — and deliberately not this service's names. */
|
|
6
|
+
export interface PushedStatsPayload {
|
|
7
|
+
totalTokensServed?: number;
|
|
8
|
+
incomingTokensDb?: number;
|
|
9
|
+
conversations?: number;
|
|
10
|
+
hostSpeed?: number;
|
|
11
|
+
currentUptime?: string;
|
|
12
|
+
totalUptime?: string;
|
|
13
|
+
isApproved?: boolean;
|
|
14
|
+
activePeers?: number;
|
|
15
|
+
approvedPeers?: number;
|
|
16
|
+
totalPeers?: number;
|
|
17
|
+
totalTokensNetwork?: number;
|
|
18
|
+
networkUptimePct?: number;
|
|
19
|
+
}
|
|
4
20
|
export interface HostSnapshot {
|
|
5
21
|
isApproved: boolean;
|
|
6
22
|
conversations: number;
|
|
@@ -47,6 +63,8 @@ export declare class HostStatsService {
|
|
|
47
63
|
private lastError;
|
|
48
64
|
private tickCount;
|
|
49
65
|
private tickInFlight;
|
|
66
|
+
/** When the orchestrator last pushed a snapshot. 0 = never (older orchestrator). */
|
|
67
|
+
private lastPushAt;
|
|
50
68
|
private readonly lastFailureBySource;
|
|
51
69
|
constructor(backend: BackendClient, auth: AuthService, config: ConfigService);
|
|
52
70
|
start(intervalMs?: number): void;
|
|
@@ -55,6 +73,20 @@ export declare class HostStatsService {
|
|
|
55
73
|
getNetworkSnapshot(): NetworkSnapshot | null;
|
|
56
74
|
getEarningsSnapshot(): EarningsSnapshot | null;
|
|
57
75
|
getHostEarningsSnapshot(): HostEarningsSnapshot | null;
|
|
76
|
+
/**
|
|
77
|
+
* Accept a dashboard snapshot pushed over the hub instead of polled.
|
|
78
|
+
*
|
|
79
|
+
* The orchestrator sends this on register and whenever the underlying figures move; see
|
|
80
|
+
* Host-Stats-Push-Design.md in the orchestrator repo. Taking the dashboard off the poll
|
|
81
|
+
* removes the DNS lookup, TCP handshake and router NAT entry each poll needed — which is
|
|
82
|
+
* the failure this whole change exists to remove, since a saturated home router drops new
|
|
83
|
+
* connections while the hub socket keeps working fine.
|
|
84
|
+
*
|
|
85
|
+
* The wire payload uses the Electron renderer's field names (it forwards them verbatim),
|
|
86
|
+
* so the mapping to this service's shape lives here — one place, rather than teaching the
|
|
87
|
+
* server two vocabularies.
|
|
88
|
+
*/
|
|
89
|
+
applyPushedSnapshot(payload: PushedStatsPayload | null | undefined): void;
|
|
58
90
|
getLastError(): StatsFetchError | null;
|
|
59
91
|
tick(): Promise<void>;
|
|
60
92
|
private runTick;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-stats.d.ts","sourceRoot":"","sources":["../../src/core/host-stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAuC,MAAM,qBAAqB,CAAC;AACzF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"host-stats.d.ts","sourceRoot":"","sources":["../../src/core/host-stats.ts"],"names":[],"mappings":"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"}
|
package/dist/core/host-stats.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { BackendError, NotAuthenticatedError } from "./backend-client.js";
|
|
2
2
|
import { getLogger } from "./logger.js";
|
|
3
|
-
|
|
3
|
+
// Matches the Electron host's stats loop cadence. Widened from 10s: each tick is
|
|
4
|
+
// several requests, and the server answers /api/networks/stats with
|
|
5
|
+
// Cache-Control: max-age=30, so two of every three polls re-fetched a value the
|
|
6
|
+
// server still considered fresh. Every request is a fresh connection today, and
|
|
7
|
+
// that churn is what saturates home-router NAT tables.
|
|
8
|
+
const DEFAULT_POLL_MS = 30_000;
|
|
4
9
|
const EARNINGS_POLL_EVERY = 6; // every 6 ticks ≈ 60s — payouts move slowly
|
|
10
|
+
/** How long a push keeps us from polling. See applyPushedSnapshot. */
|
|
11
|
+
const PUSH_SAFETY_POLL_MS = 5 * 60_000;
|
|
5
12
|
export class HostStatsService {
|
|
6
13
|
backend;
|
|
7
14
|
auth;
|
|
@@ -14,6 +21,8 @@ export class HostStatsService {
|
|
|
14
21
|
lastError = null;
|
|
15
22
|
tickCount = 0;
|
|
16
23
|
tickInFlight = false;
|
|
24
|
+
/** When the orchestrator last pushed a snapshot. 0 = never (older orchestrator). */
|
|
25
|
+
lastPushAt = 0;
|
|
17
26
|
// Last failure message per source, used to log only on state changes
|
|
18
27
|
// (working→broken, message change, broken→recovered) instead of every tick.
|
|
19
28
|
lastFailureBySource = new Map();
|
|
@@ -46,6 +55,44 @@ export class HostStatsService {
|
|
|
46
55
|
getHostEarningsSnapshot() {
|
|
47
56
|
return this.hostEarnings;
|
|
48
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Accept a dashboard snapshot pushed over the hub instead of polled.
|
|
60
|
+
*
|
|
61
|
+
* The orchestrator sends this on register and whenever the underlying figures move; see
|
|
62
|
+
* Host-Stats-Push-Design.md in the orchestrator repo. Taking the dashboard off the poll
|
|
63
|
+
* removes the DNS lookup, TCP handshake and router NAT entry each poll needed — which is
|
|
64
|
+
* the failure this whole change exists to remove, since a saturated home router drops new
|
|
65
|
+
* connections while the hub socket keeps working fine.
|
|
66
|
+
*
|
|
67
|
+
* The wire payload uses the Electron renderer's field names (it forwards them verbatim),
|
|
68
|
+
* so the mapping to this service's shape lives here — one place, rather than teaching the
|
|
69
|
+
* server two vocabularies.
|
|
70
|
+
*/
|
|
71
|
+
applyPushedSnapshot(payload) {
|
|
72
|
+
if (!payload)
|
|
73
|
+
return;
|
|
74
|
+
this.host = {
|
|
75
|
+
isApproved: payload.isApproved ?? this.host?.isApproved ?? false,
|
|
76
|
+
conversations: payload.conversations ?? 0,
|
|
77
|
+
servedTokens: payload.totalTokensServed ?? 0,
|
|
78
|
+
incomingTokens: payload.incomingTokensDb ?? 0,
|
|
79
|
+
averageResponseMs: payload.hostSpeed ?? 0,
|
|
80
|
+
currentUptime: payload.currentUptime ?? "unknown",
|
|
81
|
+
totalUptime: payload.totalUptime ?? "unknown",
|
|
82
|
+
fetchedAt: Date.now(),
|
|
83
|
+
};
|
|
84
|
+
this.network = {
|
|
85
|
+
activeHosts: payload.activePeers ?? 0,
|
|
86
|
+
approvedHosts: payload.approvedPeers ?? 0,
|
|
87
|
+
totalHosts: payload.totalPeers ?? 0,
|
|
88
|
+
totalServedTokens: payload.totalTokensNetwork ?? 0,
|
|
89
|
+
networkUptimePct: payload.networkUptimePct ?? 0,
|
|
90
|
+
fetchedAt: Date.now(),
|
|
91
|
+
};
|
|
92
|
+
// Earnings are not in the payload yet, so the payout poll stays. Do not clear it.
|
|
93
|
+
this.lastError = null;
|
|
94
|
+
this.lastPushAt = Date.now();
|
|
95
|
+
}
|
|
49
96
|
getLastError() {
|
|
50
97
|
return this.lastError;
|
|
51
98
|
}
|
|
@@ -55,6 +102,13 @@ export class HostStatsService {
|
|
|
55
102
|
// failures all land in the log at once when the requests finally settle.
|
|
56
103
|
if (this.tickInFlight)
|
|
57
104
|
return;
|
|
105
|
+
// Push is carrying the dashboard — skip the request entirely. Because push is
|
|
106
|
+
// event-driven, silence is normal on an idle network and cannot be read as "the server
|
|
107
|
+
// stopped", so this falls back to polling on its own after PUSH_SAFETY_POLL_MS. That
|
|
108
|
+
// covers the server flag being switched off mid-session, a missed event, or an older
|
|
109
|
+
// orchestrator, at ~12 requests/hour instead of ~120.
|
|
110
|
+
if (this.lastPushAt > 0 && Date.now() - this.lastPushAt < PUSH_SAFETY_POLL_MS)
|
|
111
|
+
return;
|
|
58
112
|
this.tickInFlight = true;
|
|
59
113
|
try {
|
|
60
114
|
await this.runTick();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-stats.js","sourceRoot":"","sources":["../../src/core/host-stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEzF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,eAAe,GAAG,MAAM,CAAC
|
|
1
|
+
{"version":3,"file":"host-stats.js","sourceRoot":"","sources":["../../src/core/host-stats.ts"],"names":[],"mappings":"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"}
|