chat-recall 0.5.22 → 0.5.24
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.js +347 -197
- package/dist/cli.js.map +1 -1
- package/dist/conformant-server.d.ts +17 -0
- package/dist/conformant-server.d.ts.map +1 -0
- package/dist/conformant-server.js +116 -0
- package/dist/conformant-server.js.map +1 -0
- package/dist/device-auth.d.ts +0 -4
- package/dist/device-auth.d.ts.map +1 -1
- package/dist/device-auth.js +43 -2
- package/dist/device-auth.js.map +1 -1
- package/dist/hostile-server.d.ts +47 -0
- package/dist/hostile-server.d.ts.map +1 -0
- package/dist/hostile-server.js +95 -0
- package/dist/hostile-server.js.map +1 -0
- package/dist/mcp.js +160 -8
- package/dist/server-probe.d.ts +16 -2
- package/dist/server-probe.d.ts.map +1 -1
- package/dist/server-probe.js +59 -4
- package/dist/server-probe.js.map +1 -1
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +47 -2
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +263 -117
- package/package.json +2 -2
package/dist/mcp.js
CHANGED
|
@@ -4808,8 +4808,11 @@ function isInside(child, parent) {
|
|
|
4808
4808
|
return c.startsWith(p);
|
|
4809
4809
|
}
|
|
4810
4810
|
function globMatch(pattern, path) {
|
|
4811
|
-
const re = new RegExp("^" + globToRegex(pattern) + "$");
|
|
4812
|
-
return re.test(path);
|
|
4811
|
+
const re = new RegExp("^" + globToRegex(toPosix(pattern)) + "$");
|
|
4812
|
+
return re.test(toPosix(path));
|
|
4813
|
+
}
|
|
4814
|
+
function toPosix(p) {
|
|
4815
|
+
return p.replace(/\\/g, "/");
|
|
4813
4816
|
}
|
|
4814
4817
|
function globToRegex(glob) {
|
|
4815
4818
|
let out = "";
|
|
@@ -20630,7 +20633,7 @@ var init_http = __esm({
|
|
|
20630
20633
|
CLI_VERSION = JSON.parse(readFileSync37(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
20631
20634
|
} catch {
|
|
20632
20635
|
try {
|
|
20633
|
-
if (true) CLI_VERSION = "0.5.
|
|
20636
|
+
if (true) CLI_VERSION = "0.5.24";
|
|
20634
20637
|
} catch {
|
|
20635
20638
|
}
|
|
20636
20639
|
}
|
|
@@ -21142,7 +21145,7 @@ var init_telemetry = __esm({
|
|
|
21142
21145
|
init_sync_client();
|
|
21143
21146
|
cliVersion2 = () => {
|
|
21144
21147
|
try {
|
|
21145
|
-
return true ? "0.5.
|
|
21148
|
+
return true ? "0.5.24" : "dev";
|
|
21146
21149
|
} catch {
|
|
21147
21150
|
return "dev";
|
|
21148
21151
|
}
|
|
@@ -21152,6 +21155,143 @@ var init_telemetry = __esm({
|
|
|
21152
21155
|
}
|
|
21153
21156
|
});
|
|
21154
21157
|
|
|
21158
|
+
// src/server-probe.ts
|
|
21159
|
+
var server_probe_exports = {};
|
|
21160
|
+
__export(server_probe_exports, {
|
|
21161
|
+
classifyUnreachable: () => classifyUnreachable,
|
|
21162
|
+
describeCause: () => describeCause,
|
|
21163
|
+
describeUnreachable: () => describeUnreachable,
|
|
21164
|
+
probeAdvice: () => probeAdvice,
|
|
21165
|
+
probeOk: () => probeOk,
|
|
21166
|
+
probeServer: () => probeServer
|
|
21167
|
+
});
|
|
21168
|
+
function probeOk(r) {
|
|
21169
|
+
return r.kind === "ok" && r.apiVersion >= 2;
|
|
21170
|
+
}
|
|
21171
|
+
async function probeServer(base, opts = {}) {
|
|
21172
|
+
const f = opts.fetchImpl || fetch;
|
|
21173
|
+
let res;
|
|
21174
|
+
try {
|
|
21175
|
+
res = await f(`${base.replace(/\/+$/, "")}/api/capabilities`, {
|
|
21176
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
|
|
21177
|
+
});
|
|
21178
|
+
} catch (err) {
|
|
21179
|
+
return {
|
|
21180
|
+
kind: "unreachable",
|
|
21181
|
+
error: describeCause(err),
|
|
21182
|
+
reason: classifyUnreachable(err)
|
|
21183
|
+
};
|
|
21184
|
+
}
|
|
21185
|
+
if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
|
|
21186
|
+
if (res.status === 404 || res.status === 410) return { kind: "moved", status: res.status };
|
|
21187
|
+
if (res.status >= 500) return { kind: "server_error", status: res.status };
|
|
21188
|
+
if (!res.ok) return { kind: "moved", status: res.status };
|
|
21189
|
+
let body;
|
|
21190
|
+
try {
|
|
21191
|
+
body = await res.text();
|
|
21192
|
+
} catch (err) {
|
|
21193
|
+
return { kind: "unreachable", error: describeCause(err), reason: classifyUnreachable(err) };
|
|
21194
|
+
}
|
|
21195
|
+
let parsed;
|
|
21196
|
+
try {
|
|
21197
|
+
parsed = JSON.parse(body);
|
|
21198
|
+
} catch {
|
|
21199
|
+
return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
|
|
21200
|
+
}
|
|
21201
|
+
if (typeof parsed?.apiVersion !== "number") {
|
|
21202
|
+
return { kind: "not_api", status: res.status, snippet: body.trim().slice(0, 60) };
|
|
21203
|
+
}
|
|
21204
|
+
return {
|
|
21205
|
+
kind: "ok",
|
|
21206
|
+
apiVersion: parsed.apiVersion,
|
|
21207
|
+
edition: parsed.edition,
|
|
21208
|
+
cliVersion: parsed.cli?.version,
|
|
21209
|
+
raw: parsed
|
|
21210
|
+
};
|
|
21211
|
+
}
|
|
21212
|
+
function probeAdvice(r, base) {
|
|
21213
|
+
switch (r.kind) {
|
|
21214
|
+
case "ok":
|
|
21215
|
+
return r.apiVersion >= 2 ? `apiVersion ${r.apiVersion}${r.edition ? `, edition ${r.edition}` : ""}` : `apiVersion ${r.apiVersion} is too old for this CLI \u2014 upgrade the server`;
|
|
21216
|
+
case "moved":
|
|
21217
|
+
return `HTTP ${r.status} \u2014 nothing is served at ${base}. If the host moved, re-point it: \`chat-recall login <new-server-url>\``;
|
|
21218
|
+
case "unauthorized":
|
|
21219
|
+
return `HTTP ${r.status} \u2014 ${base} rejected the credential. Re-login: \`chat-recall login ` + base + "`";
|
|
21220
|
+
case "server_error":
|
|
21221
|
+
return `HTTP ${r.status} \u2014 ${base} is up but failing. Nothing to fix locally; check the server.`;
|
|
21222
|
+
case "not_api":
|
|
21223
|
+
return `${base} answered ${r.status} with something that is not the chat-recall API (${JSON.stringify(r.snippet)}) \u2014 check the URL, a proxy or a captive portal`;
|
|
21224
|
+
case "unreachable":
|
|
21225
|
+
switch (r.reason) {
|
|
21226
|
+
case "dns":
|
|
21227
|
+
return `cannot resolve the host in ${base} (${r.error}) \u2014 check the URL for a typo, and your DNS or VPN`;
|
|
21228
|
+
case "refused":
|
|
21229
|
+
return `${base} resolves but refused the connection (${r.error}) \u2014 the server is not running, or the port is wrong`;
|
|
21230
|
+
case "tls":
|
|
21231
|
+
return `the TLS certificate at ${base} was rejected (${r.error}) \u2014 expired, self-signed, or issued for another host. A corporate proxy that re-signs traffic does this.`;
|
|
21232
|
+
case "timeout":
|
|
21233
|
+
return `${base} did not answer in time (${r.error}) \u2014 a firewall or proxy that drops traffic silently looks exactly like this`;
|
|
21234
|
+
case "reset":
|
|
21235
|
+
return `the connection to ${base} was cut (${r.error}) \u2014 often a proxy or a captive portal`;
|
|
21236
|
+
case "bad_port":
|
|
21237
|
+
return `${base} names a port that Node and every browser refuse to open (the WHATWG bad-port list) \u2014 use the server's real port`;
|
|
21238
|
+
default:
|
|
21239
|
+
return `no HTTP response from ${base} (${r.error}) \u2014 check the URL, DNS, or that the server is running`;
|
|
21240
|
+
}
|
|
21241
|
+
}
|
|
21242
|
+
}
|
|
21243
|
+
function classifyUnreachable(err) {
|
|
21244
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) return "timeout";
|
|
21245
|
+
for (const e of walkErrorChain(err)) {
|
|
21246
|
+
const code = String(e.code ?? "");
|
|
21247
|
+
const msg = String(e.message ?? "");
|
|
21248
|
+
if (code === "ENOTFOUND" || code === "EAI_AGAIN") return "dns";
|
|
21249
|
+
if (code === "ECONNREFUSED") return "refused";
|
|
21250
|
+
if (code === "ECONNRESET" || code === "EPIPE") return "reset";
|
|
21251
|
+
if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT" || code === "UND_ERR_HEADERS_TIMEOUT") return "timeout";
|
|
21252
|
+
if (code.startsWith("CERT_") || code.startsWith("ERR_TLS") || code === "DEPTH_ZERO_SELF_SIGNED_CERT" || code === "SELF_SIGNED_CERT_IN_CHAIN" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE" || /certificate/i.test(msg)) return "tls";
|
|
21253
|
+
if (/^bad port$/i.test(msg)) return "bad_port";
|
|
21254
|
+
}
|
|
21255
|
+
return "unknown";
|
|
21256
|
+
}
|
|
21257
|
+
function* walkErrorChain(err, depth = 0) {
|
|
21258
|
+
if (!err || typeof err !== "object" || depth > 5) return;
|
|
21259
|
+
yield err;
|
|
21260
|
+
const members = err.errors;
|
|
21261
|
+
if (Array.isArray(members)) {
|
|
21262
|
+
for (const m of members) yield* walkErrorChain(m, depth + 1);
|
|
21263
|
+
}
|
|
21264
|
+
yield* walkErrorChain(err.cause, depth + 1);
|
|
21265
|
+
}
|
|
21266
|
+
function describeCause(err) {
|
|
21267
|
+
const parts = [];
|
|
21268
|
+
for (const e of walkErrorChain(err)) {
|
|
21269
|
+
const code = e.code;
|
|
21270
|
+
if (typeof code === "string" && code && !parts.includes(code)) parts.push(code);
|
|
21271
|
+
}
|
|
21272
|
+
const base = err instanceof Error ? err.message : String(err);
|
|
21273
|
+
return parts.length ? `${base}: ${parts.join(" \u2192 ")}` : base;
|
|
21274
|
+
}
|
|
21275
|
+
function describeUnreachable(err) {
|
|
21276
|
+
const reason = classifyUnreachable(err);
|
|
21277
|
+
const detail = describeCause(err);
|
|
21278
|
+
const phrase = {
|
|
21279
|
+
dns: "the host name does not resolve",
|
|
21280
|
+
refused: "the connection was refused \u2014 nothing is listening there",
|
|
21281
|
+
tls: "the TLS certificate was rejected",
|
|
21282
|
+
timeout: "no answer before the timeout",
|
|
21283
|
+
reset: "the connection was cut mid-request",
|
|
21284
|
+
bad_port: "that port is one Node and every browser refuse to open",
|
|
21285
|
+
unknown: "no HTTP response"
|
|
21286
|
+
};
|
|
21287
|
+
return `${phrase[reason]} (${detail})`;
|
|
21288
|
+
}
|
|
21289
|
+
var init_server_probe = __esm({
|
|
21290
|
+
"src/server-probe.ts"() {
|
|
21291
|
+
"use strict";
|
|
21292
|
+
}
|
|
21293
|
+
});
|
|
21294
|
+
|
|
21155
21295
|
// src/auto-update.ts
|
|
21156
21296
|
var auto_update_exports = {};
|
|
21157
21297
|
__export(auto_update_exports, {
|
|
@@ -21494,6 +21634,7 @@ async function fetchTenantSyncConfig(cred) {
|
|
|
21494
21634
|
persistSyncConfig(base, config2);
|
|
21495
21635
|
return config2;
|
|
21496
21636
|
} catch (err) {
|
|
21637
|
+
if (isSyncRulesRefusal(err)) throw err;
|
|
21497
21638
|
return lastKnownSyncConfig(base, err instanceof Error ? err.message : "request failed");
|
|
21498
21639
|
}
|
|
21499
21640
|
}
|
|
@@ -21503,9 +21644,14 @@ function lastKnownSyncConfig(base, reason) {
|
|
|
21503
21644
|
console.error(`[sync] could not read server-side sync rules (${reason}) \u2014 applying the last known set from ${new Date(remembered.fetchedAt).toISOString()}`);
|
|
21504
21645
|
return remembered.config;
|
|
21505
21646
|
}
|
|
21506
|
-
|
|
21647
|
+
const refusal = new Error(
|
|
21507
21648
|
`Cannot read the server-side sync rules from ${base} (${reason}), and this machine has never successfully read them. Refusing to sync: uploading without knowing your exclusions could ship a project you told the dashboard to hold back. Fix the server or retry.`
|
|
21508
21649
|
);
|
|
21650
|
+
refusal[SYNC_RULES_REFUSAL] = true;
|
|
21651
|
+
throw refusal;
|
|
21652
|
+
}
|
|
21653
|
+
function isSyncRulesRefusal(err) {
|
|
21654
|
+
return !!err && typeof err === "object" && err[SYNC_RULES_REFUSAL] === true;
|
|
21509
21655
|
}
|
|
21510
21656
|
function deviceLabel() {
|
|
21511
21657
|
try {
|
|
@@ -21766,7 +21912,12 @@ async function verifyServerApi(serverUrl, minApi) {
|
|
|
21766
21912
|
const base = serverUrl.replace(/\/$/, "");
|
|
21767
21913
|
const seen = capsOk.get(base);
|
|
21768
21914
|
if (seen !== void 0 && Date.now() - seen < CAPS_TTL_MS) return true;
|
|
21769
|
-
const
|
|
21915
|
+
const { probeServer: probeServer2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
21916
|
+
const probe = await probeServer2(base, {
|
|
21917
|
+
fetchImpl: ((url, init) => fetchWithTimeout(String(url), init ?? {}))
|
|
21918
|
+
});
|
|
21919
|
+
if (probe.kind !== "ok") throw new Error(probeAdvice2(probe, base));
|
|
21920
|
+
const caps = probe.raw;
|
|
21770
21921
|
if ((caps.apiVersion ?? 0) < minApi) {
|
|
21771
21922
|
throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${minApi} \u2014 update the server image before syncing`);
|
|
21772
21923
|
}
|
|
@@ -22756,7 +22907,7 @@ async function syncIncremental(opts = {}) {
|
|
|
22756
22907
|
lock.release();
|
|
22757
22908
|
}
|
|
22758
22909
|
}
|
|
22759
|
-
var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
22910
|
+
var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
22760
22911
|
var init_sync_client = __esm({
|
|
22761
22912
|
"src/sync-client.ts"() {
|
|
22762
22913
|
"use strict";
|
|
@@ -22802,7 +22953,7 @@ var init_sync_client = __esm({
|
|
|
22802
22953
|
ownVersion = JSON.parse(readFileSync39(join49(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
22803
22954
|
} catch {
|
|
22804
22955
|
try {
|
|
22805
|
-
if (true) ownVersion = "0.5.
|
|
22956
|
+
if (true) ownVersion = "0.5.24";
|
|
22806
22957
|
} catch {
|
|
22807
22958
|
}
|
|
22808
22959
|
}
|
|
@@ -22814,6 +22965,7 @@ var init_sync_client = __esm({
|
|
|
22814
22965
|
_syncConfigCache = /* @__PURE__ */ new Map();
|
|
22815
22966
|
EMPTY_SYNC_CONFIG = { excludeTools: [], excludeProjects: [], excludeSources: [], approveSources: [] };
|
|
22816
22967
|
_fetchTenantSyncConfigForTests = fetchTenantSyncConfig;
|
|
22968
|
+
SYNC_RULES_REFUSAL = /* @__PURE__ */ Symbol.for("chat-recall.syncRulesRefusal");
|
|
22817
22969
|
hashPath = (p) => p ? "p_" + createHash14("sha256").update(p).digest("hex").slice(0, 12) : "";
|
|
22818
22970
|
MAX_DERIVED_ROW_BYTES = 2 * 1024 * 1024;
|
|
22819
22971
|
isLocalHost = isPrivateHost;
|
package/dist/server-probe.d.ts
CHANGED
|
@@ -21,12 +21,17 @@
|
|
|
21
21
|
* fixes it.
|
|
22
22
|
*/
|
|
23
23
|
export type ProbeResult =
|
|
24
|
-
/** Spoke to a chat-recall server and understood the answer.
|
|
24
|
+
/** Spoke to a chat-recall server and understood the answer. `raw` is the
|
|
25
|
+
* parsed document, so a caller needing more fields off /api/capabilities
|
|
26
|
+
* (the sync client wants `limits`) reads them from THIS probe instead of
|
|
27
|
+
* issuing a second, unclassified request — which is how the sync client
|
|
28
|
+
* ended up with its own `.json()` and its own JSON-parse offsets. */
|
|
25
29
|
{
|
|
26
30
|
kind: 'ok';
|
|
27
31
|
apiVersion: number;
|
|
28
32
|
edition?: string;
|
|
29
33
|
cliVersion?: string;
|
|
34
|
+
raw: Record<string, unknown>;
|
|
30
35
|
}
|
|
31
36
|
/** Reached an HTTP server, but nothing is mounted here. Usually a moved host. */
|
|
32
37
|
| {
|
|
@@ -62,7 +67,7 @@ export type ProbeResult =
|
|
|
62
67
|
* `err.cause`. A user pasting that string leaves nothing to diagnose — which is
|
|
63
68
|
* exactly what happened on a macOS install: the message named SSO, the cause
|
|
64
69
|
* was the network, and nobody could tell which. */
|
|
65
|
-
export type UnreachableReason = 'dns' | 'refused' | 'tls' | 'timeout' | 'reset' | 'unknown';
|
|
70
|
+
export type UnreachableReason = 'dns' | 'refused' | 'tls' | 'timeout' | 'reset' | 'bad_port' | 'unknown';
|
|
66
71
|
/** True when the probe proves a usable chat-recall server. */
|
|
67
72
|
export declare function probeOk(r: ProbeResult): r is Extract<ProbeResult, {
|
|
68
73
|
kind: 'ok';
|
|
@@ -93,4 +98,13 @@ export declare function classifyUnreachable(err: unknown): UnreachableReason;
|
|
|
93
98
|
/** The most specific string available: the underlying code where there is one,
|
|
94
99
|
* because "fetch failed" is worth nothing in a bug report. */
|
|
95
100
|
export declare function describeCause(err: unknown): string;
|
|
101
|
+
/**
|
|
102
|
+
* One sentence naming WHY a request never got an answer — for the callers that
|
|
103
|
+
* have no URL to advise about and only need to say what happened.
|
|
104
|
+
*
|
|
105
|
+
* `describeCause` alone degrades to "fetch failed" whenever no error in the
|
|
106
|
+
* chain carries a `code`, and `init` printed exactly that: "Not connected. fetch
|
|
107
|
+
* failed". The reason is the part a person can act on, so it leads.
|
|
108
|
+
*/
|
|
109
|
+
export declare function describeUnreachable(err: unknown): string;
|
|
96
110
|
//# sourceMappingURL=server-probe.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-probe.d.ts","sourceRoot":"","sources":["../src/server-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,WAAW;AACrB
|
|
1
|
+
{"version":3,"file":"server-probe.d.ts","sourceRoot":"","sources":["../src/server-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,WAAW;AACrB;;;;sEAIsE;AACpE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE;AACzG,iFAAiF;GAC/E;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACnC,sDAAsD;GACpD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC1C,sCAAsC;GACpC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC1C,mFAAmF;GACjF;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACtD,wEAAwE;AACxE;2EAC2E;GACzE;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAEtE;;;;oDAIoD;AACpD,MAAM,MAAM,iBAAiB,GACzB,KAAK,GACL,SAAS,GACT,KAAK,GACL,SAAS,GACT,OAAO,GACP,UAAU,GACV,SAAS,CAAC;AAEd,8DAA8D;AAC9D,wBAAgB,OAAO,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,IAAI,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAEjF;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAAO,GAC1D,OAAO,CAAC,WAAW,CAAC,CA0CtB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA2ChE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAoBnE;AA0BD;+DAC+D;AAC/D,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQlD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAaxD"}
|
package/dist/server-probe.js
CHANGED
|
@@ -80,6 +80,7 @@ export async function probeServer(base, opts = {}) {
|
|
|
80
80
|
apiVersion: parsed.apiVersion,
|
|
81
81
|
edition: parsed.edition,
|
|
82
82
|
cliVersion: parsed.cli?.version,
|
|
83
|
+
raw: parsed,
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
86
|
/**
|
|
@@ -120,6 +121,9 @@ export function probeAdvice(r, base) {
|
|
|
120
121
|
+ 'traffic silently looks exactly like this';
|
|
121
122
|
case 'reset':
|
|
122
123
|
return `the connection to ${base} was cut (${r.error}) — often a proxy or a captive portal`;
|
|
124
|
+
case 'bad_port':
|
|
125
|
+
return `${base} names a port that Node and every browser refuse to open (the WHATWG `
|
|
126
|
+
+ "bad-port list) — use the server's real port";
|
|
123
127
|
default:
|
|
124
128
|
return `no HTTP response from ${base} (${r.error}) — check the URL, DNS, or that the `
|
|
125
129
|
+ 'server is running';
|
|
@@ -136,7 +140,7 @@ export function probeAdvice(r, base) {
|
|
|
136
140
|
export function classifyUnreachable(err) {
|
|
137
141
|
if (err instanceof Error && (err.name === 'TimeoutError' || err.name === 'AbortError'))
|
|
138
142
|
return 'timeout';
|
|
139
|
-
for (
|
|
143
|
+
for (const e of walkErrorChain(err)) {
|
|
140
144
|
const code = String(e.code ?? '');
|
|
141
145
|
const msg = String(e.message ?? '');
|
|
142
146
|
if (code === 'ENOTFOUND' || code === 'EAI_AGAIN')
|
|
@@ -152,21 +156,72 @@ export function classifyUnreachable(err) {
|
|
|
152
156
|
|| code === 'DEPTH_ZERO_SELF_SIGNED_CERT' || code === 'SELF_SIGNED_CERT_IN_CHAIN'
|
|
153
157
|
|| code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' || /certificate/i.test(msg))
|
|
154
158
|
return 'tls';
|
|
155
|
-
|
|
159
|
+
// undici refuses a fetch to a port on the WHATWG "bad port" list (1, 7, 9,
|
|
160
|
+
// 11, 13, … 6697) before it opens a socket, and reports it as a bare
|
|
161
|
+
// `Error: bad port` with NO code — so every code-based branch above misses
|
|
162
|
+
// it and the user gets the five useless characters "fetch failed".
|
|
163
|
+
if (/^bad port$/i.test(msg))
|
|
164
|
+
return 'bad_port';
|
|
156
165
|
}
|
|
157
166
|
return 'unknown';
|
|
158
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Every error reachable from a failed fetch, in order: the error itself, the
|
|
170
|
+
* members of any AggregateError on the way, and the `cause` chain.
|
|
171
|
+
*
|
|
172
|
+
* THE CHAIN IS NOT A LINE. A `cause` walk alone was enough on Linux CI and wrong
|
|
173
|
+
* on the two platforms this product also ships to: when a host name resolves to
|
|
174
|
+
* both an A and an AAAA record — which `localhost` does on a default macOS and
|
|
175
|
+
* Windows install — undici tries both addresses and reports the pair as an
|
|
176
|
+
* `AggregateError`, whose members hold the real codes and whose own `code` is
|
|
177
|
+
* undefined. So a refused connection on a Mac classified as 'unknown' and
|
|
178
|
+
* printed "fetch failed", while the identical failure on Linux printed
|
|
179
|
+
* "connection refused". A cross-platform product cannot diagnose itself with a
|
|
180
|
+
* classifier that only works on one platform.
|
|
181
|
+
*/
|
|
182
|
+
function* walkErrorChain(err, depth = 0) {
|
|
183
|
+
if (!err || typeof err !== 'object' || depth > 5)
|
|
184
|
+
return;
|
|
185
|
+
yield err;
|
|
186
|
+
const members = err.errors;
|
|
187
|
+
if (Array.isArray(members)) {
|
|
188
|
+
for (const m of members)
|
|
189
|
+
yield* walkErrorChain(m, depth + 1);
|
|
190
|
+
}
|
|
191
|
+
yield* walkErrorChain(err.cause, depth + 1);
|
|
192
|
+
}
|
|
159
193
|
/** The most specific string available: the underlying code where there is one,
|
|
160
194
|
* because "fetch failed" is worth nothing in a bug report. */
|
|
161
195
|
export function describeCause(err) {
|
|
162
196
|
const parts = [];
|
|
163
|
-
for (
|
|
197
|
+
for (const e of walkErrorChain(err)) {
|
|
164
198
|
const code = e.code;
|
|
165
199
|
if (typeof code === 'string' && code && !parts.includes(code))
|
|
166
200
|
parts.push(code);
|
|
167
|
-
e = e.cause;
|
|
168
201
|
}
|
|
169
202
|
const base = err instanceof Error ? err.message : String(err);
|
|
170
203
|
return parts.length ? `${base}: ${parts.join(' → ')}` : base;
|
|
171
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* One sentence naming WHY a request never got an answer — for the callers that
|
|
207
|
+
* have no URL to advise about and only need to say what happened.
|
|
208
|
+
*
|
|
209
|
+
* `describeCause` alone degrades to "fetch failed" whenever no error in the
|
|
210
|
+
* chain carries a `code`, and `init` printed exactly that: "Not connected. fetch
|
|
211
|
+
* failed". The reason is the part a person can act on, so it leads.
|
|
212
|
+
*/
|
|
213
|
+
export function describeUnreachable(err) {
|
|
214
|
+
const reason = classifyUnreachable(err);
|
|
215
|
+
const detail = describeCause(err);
|
|
216
|
+
const phrase = {
|
|
217
|
+
dns: 'the host name does not resolve',
|
|
218
|
+
refused: 'the connection was refused — nothing is listening there',
|
|
219
|
+
tls: 'the TLS certificate was rejected',
|
|
220
|
+
timeout: 'no answer before the timeout',
|
|
221
|
+
reset: 'the connection was cut mid-request',
|
|
222
|
+
bad_port: 'that port is one Node and every browser refuse to open',
|
|
223
|
+
unknown: 'no HTTP response',
|
|
224
|
+
};
|
|
225
|
+
return `${phrase[reason]} (${detail})`;
|
|
226
|
+
}
|
|
172
227
|
//# sourceMappingURL=server-probe.js.map
|
package/dist/server-probe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-probe.js","sourceRoot":"","sources":["../src/server-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;
|
|
1
|
+
{"version":3,"file":"server-probe.js","sourceRoot":"","sources":["../src/server-probe.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../src/sync-client.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAoF,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAWrL,OAAO,EAA+B,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAC;AA4ExG,OAAO,4CAA4C,CAAC;AAMpD,MAAM,WAAW,WAAW;IAAG,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;CAAE;AAElE;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,CAoBpD;AAED,wBAAgB,kBAAkB,IAAI,WAAW,EAAE,CAelD;AAwGD,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAuBD,UAAU,gBAAgB;IAAG,YAAY,EAAE,MAAM,EAAE,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE;AAkCpI,iBAAe,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../src/sync-client.ts"],"names":[],"mappings":"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"}
|
package/dist/sync-client.js
CHANGED
|
@@ -371,6 +371,14 @@ async function fetchTenantSyncConfig(cred) {
|
|
|
371
371
|
return config;
|
|
372
372
|
}
|
|
373
373
|
catch (err) {
|
|
374
|
+
// DO NOT RE-WRAP OUR OWN REFUSAL. `lastKnownSyncConfig` is called inside
|
|
375
|
+
// this try (the !res.ok branch) and throws when there is nothing to fall
|
|
376
|
+
// back on — so this catch caught that throw and fed the whole sentence back
|
|
377
|
+
// in as the `reason`, printing it nested inside itself:
|
|
378
|
+
// "Cannot read the sync rules from X (Cannot read the sync rules from X
|
|
379
|
+
// (HTTP 502), and this machine has never…), and this machine has never…"
|
|
380
|
+
if (isSyncRulesRefusal(err))
|
|
381
|
+
throw err;
|
|
374
382
|
return lastKnownSyncConfig(base, err instanceof Error ? err.message : 'request failed');
|
|
375
383
|
}
|
|
376
384
|
}
|
|
@@ -392,9 +400,18 @@ function lastKnownSyncConfig(base, reason) {
|
|
|
392
400
|
console.error(`[sync] could not read server-side sync rules (${reason}) — applying the last known set from ${new Date(remembered.fetchedAt).toISOString()}`);
|
|
393
401
|
return remembered.config;
|
|
394
402
|
}
|
|
395
|
-
|
|
403
|
+
const refusal = new Error(`Cannot read the server-side sync rules from ${base} (${reason}), and this machine has never `
|
|
396
404
|
+ 'successfully read them. Refusing to sync: uploading without knowing your exclusions could '
|
|
397
405
|
+ 'ship a project you told the dashboard to hold back. Fix the server or retry.');
|
|
406
|
+
refusal[SYNC_RULES_REFUSAL] = true;
|
|
407
|
+
throw refusal;
|
|
408
|
+
}
|
|
409
|
+
/** Marks the refusal above as ours, so the caller's catch can rethrow it
|
|
410
|
+
* untouched instead of describing it a second time. A symbol rather than a
|
|
411
|
+
* message match: the text is user-facing and will be reworded. */
|
|
412
|
+
const SYNC_RULES_REFUSAL = Symbol.for('chat-recall.syncRulesRefusal');
|
|
413
|
+
function isSyncRulesRefusal(err) {
|
|
414
|
+
return !!err && typeof err === 'object' && err[SYNC_RULES_REFUSAL] === true;
|
|
398
415
|
}
|
|
399
416
|
/** Stable-ish machine label for the dashboard's per-device grouping. */
|
|
400
417
|
function deviceLabel() {
|
|
@@ -802,12 +819,40 @@ export function serverAcceptsGzipBody(serverUrl) {
|
|
|
802
819
|
export function serverIngestConcurrency(serverUrl) {
|
|
803
820
|
return advertisedIngestConc.get(serverUrl.replace(/\/$/, '')) ?? null;
|
|
804
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* Is this a chat-recall server new enough to sync to?
|
|
824
|
+
*
|
|
825
|
+
* THE BUG THIS LINE HELD FOR MONTHS, and the one a user hit on macOS:
|
|
826
|
+
*
|
|
827
|
+
* fetchWithTimeout(`${base}/api/capabilities`).then((r) => r.json())
|
|
828
|
+
*
|
|
829
|
+
* No `r.ok`, no content-type check, one hop from the response to JSON.parse. A
|
|
830
|
+
* host that had stopped serving chat-recall answered Traefik's `404 page not
|
|
831
|
+
* found` in text/plain; JSON.parse read `404` as a number, choked on the space,
|
|
832
|
+
* and every sync on that machine failed with
|
|
833
|
+
*
|
|
834
|
+
* Unexpected non-whitespace character after JSON at position 4
|
|
835
|
+
*
|
|
836
|
+
* — a message that names no host, no status and no fix, for a problem whose fix
|
|
837
|
+
* is one `chat-recall login <new-url>`. `doctor` had classified this correctly
|
|
838
|
+
* since server-probe.ts was written; sync just never used it. It does now: ONE
|
|
839
|
+
* probe, and the `limits` this function needs come off the same document rather
|
|
840
|
+
* than a second unclassified request.
|
|
841
|
+
*/
|
|
805
842
|
async function verifyServerApi(serverUrl, minApi) {
|
|
806
843
|
const base = serverUrl.replace(/\/$/, '');
|
|
807
844
|
const seen = capsOk.get(base);
|
|
808
845
|
if (seen !== undefined && Date.now() - seen < CAPS_TTL_MS)
|
|
809
846
|
return true;
|
|
810
|
-
const
|
|
847
|
+
const { probeServer, probeAdvice } = await import('./server-probe.js');
|
|
848
|
+
// Our own fetch, so the CLI-version and OS headers still reach the server's
|
|
849
|
+
// device heartbeat; probeServer supplies the timeout signal.
|
|
850
|
+
const probe = await probeServer(base, {
|
|
851
|
+
fetchImpl: ((url, init) => fetchWithTimeout(String(url), init ?? {})),
|
|
852
|
+
});
|
|
853
|
+
if (probe.kind !== 'ok')
|
|
854
|
+
throw new Error(probeAdvice(probe, base));
|
|
855
|
+
const caps = probe.raw;
|
|
811
856
|
if ((caps.apiVersion ?? 0) < minApi) {
|
|
812
857
|
throw new Error(`server too old: apiVersion ${caps.apiVersion ?? 'none'} < required ${minApi} — update the server image before syncing`);
|
|
813
858
|
}
|