@evomap/evolver 1.89.17 → 1.89.19
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/README.ja-JP.md +1 -1
- package/README.ko-KR.md +1 -1
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/index.js +272 -18
- package/package.json +7 -2
- package/src/adapters/claudeCode.js +16 -18
- package/src/adapters/hookAdapter.js +92 -5
- package/src/adapters/scripts/evolver-session-start.js +98 -0
- package/src/atp/atpExecute.js +3 -1
- package/src/atp/hubClient.js +5 -3
- package/src/atp/serviceHelper.js +3 -2
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/antiAbuseTelemetry.js +1 -1
- package/src/gep/autoDistillConv.js +1 -1
- package/src/gep/autoDistillLlm.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/cliContracts.js +37 -1
- package/src/gep/contentHash.js +1 -1
- package/src/gep/conversationDistiller.js +1 -1
- package/src/gep/conversationSniffer.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/directoryClient.js +7 -3
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -1
- package/src/gep/execBridge.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -1
- package/src/gep/hubFetch.js +1 -1
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/oauthLogin.js +3 -5
- package/src/gep/openPRRegistry.js +1 -1
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/privacyClient.js +28 -10
- package/src/gep/prompt.js +1 -1
- package/src/gep/recallInject.js +1 -1
- package/src/gep/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/savingsCore.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/skill2gep.js +329 -43
- package/src/gep/skill2gepAudit.js +303 -0
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/skillPublisher.js +8 -3
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/taskReceiver.js +3 -2
- package/src/gep/tokenSavings.js +1 -1
- package/src/gep/trajectoryExport.js +1 -0
- package/src/gep/validator/index.js +7 -2
- package/src/gep/validator/reporter.js +7 -2
- package/src/gep/validator/stakeBootstrap.js +7 -2
- package/src/gep/workspaceKeychain.js +1 -1
- package/src/proxy/clientSettings.js +405 -0
- package/src/proxy/extensions/traceControl.js +1 -1
- package/src/proxy/index.js +13 -5
- package/src/proxy/inject.js +1 -1
- package/src/proxy/lifecycle/manager.js +82 -16
- package/src/proxy/mailbox/state.js +207 -0
- package/src/proxy/mailbox/store.js +164 -64
- package/src/proxy/router/messages_route.js +56 -4
- package/src/proxy/server/http.js +40 -6
- package/src/proxy/sync/inbound.js +31 -12
- package/src/proxy/sync/outbound.js +164 -24
- package/src/proxy/trace/extractor.js +1 -1
- package/src/proxy/trace/usage.js +1 -1
package/src/proxy/server/http.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const http = require('http');
|
|
5
5
|
const { writeSettings, readSettings, clearSettings, clearIfStale } = require('./settings');
|
|
6
|
+
const {
|
|
7
|
+
isValidReusableProxyToken,
|
|
8
|
+
readReusableClientProxyToken,
|
|
9
|
+
syncClaudeProxySettings,
|
|
10
|
+
} = require('../clientSettings');
|
|
6
11
|
|
|
7
12
|
const MAX_PORT_ATTEMPTS = 100;
|
|
8
13
|
const DEFAULT_PORT = 19820;
|
|
@@ -93,13 +98,14 @@ function tryListen(server, port) {
|
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
class ProxyHttpServer {
|
|
96
|
-
constructor(routes, { port, logger } = {}) {
|
|
101
|
+
constructor(routes, { port, logger, clientSettings } = {}) {
|
|
97
102
|
this.routes = routes;
|
|
98
103
|
this.basePort = port || Number(process.env.EVOMAP_PROXY_PORT) || DEFAULT_PORT;
|
|
99
104
|
this.actualPort = null;
|
|
100
105
|
this.logger = logger || console;
|
|
101
106
|
this.server = null;
|
|
102
107
|
this.token = null;
|
|
108
|
+
this.clientSettings = clientSettings || null;
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
async start() {
|
|
@@ -109,16 +115,22 @@ class ProxyHttpServer {
|
|
|
109
115
|
// already exported into long-lived shells (the .bashrc auto-source only
|
|
110
116
|
// runs once per terminal).
|
|
111
117
|
const priorProxy = readSettings().proxy || {};
|
|
112
|
-
const previous =
|
|
118
|
+
const previous = isValidReusableProxyToken(priorProxy.token) ? priorProxy.token.trim() : null;
|
|
113
119
|
// settings.json is operator-edited; previous_tokens may contain non-strings
|
|
114
120
|
// (numbers, booleans, objects) that would later crash Buffer.from(cand, 'utf8')
|
|
115
121
|
// in _handleRequest as ERR_INVALID_ARG_TYPE — an unhandled rejection that
|
|
116
122
|
// takes the daemon down under default --unhandled-rejections=throw.
|
|
117
123
|
const priorPreviousTokens = Array.isArray(priorProxy.previous_tokens)
|
|
118
|
-
? priorProxy.previous_tokens
|
|
124
|
+
? priorProxy.previous_tokens
|
|
125
|
+
.map((t) => (typeof t === 'string' ? t.trim() : ''))
|
|
126
|
+
.filter((t) => isValidReusableProxyToken(t))
|
|
119
127
|
: [];
|
|
120
128
|
clearIfStale();
|
|
121
|
-
|
|
129
|
+
const clientSettingsOpts = this.clientSettings || {};
|
|
130
|
+
const clientToken = this.clientSettings
|
|
131
|
+
? readReusableClientProxyToken({ ...clientSettingsOpts, port: this.basePort })
|
|
132
|
+
: null;
|
|
133
|
+
this.token = previous || clientToken || crypto.randomBytes(32).toString('hex');
|
|
122
134
|
this._priorPreviousTokens = priorPreviousTokens;
|
|
123
135
|
this.server = http.createServer((req, res) => this._handleRequest(req, res));
|
|
124
136
|
|
|
@@ -138,6 +150,24 @@ class ProxyHttpServer {
|
|
|
138
150
|
proxyBlock.previous_tokens = this._priorPreviousTokens;
|
|
139
151
|
}
|
|
140
152
|
writeSettings({ proxy: proxyBlock });
|
|
153
|
+
if (this.clientSettings) {
|
|
154
|
+
try {
|
|
155
|
+
const syncResult = syncClaudeProxySettings({
|
|
156
|
+
...clientSettingsOpts,
|
|
157
|
+
url,
|
|
158
|
+
port: this.basePort,
|
|
159
|
+
token: this.token,
|
|
160
|
+
runtimeEnv: process.env,
|
|
161
|
+
});
|
|
162
|
+
if (syncResult.synced && syncResult.changed) {
|
|
163
|
+
this.logger.log(`[proxy] Synced Claude client settings at ${syncResult.file}`);
|
|
164
|
+
} else if (syncResult.reason === 'invalid_settings_json') {
|
|
165
|
+
this.logger.warn?.(`[proxy] Skipped Claude client settings sync because ${syncResult.file} is not valid JSON`);
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
this.logger.warn?.('[proxy] Claude client settings sync failed: ' + (err && err.message ? err.message : err));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
141
171
|
this.logger.log(`[proxy] HTTP server listening on ${url}`);
|
|
142
172
|
return { port, url, token: this.token };
|
|
143
173
|
}
|
|
@@ -171,9 +201,13 @@ class ProxyHttpServer {
|
|
|
171
201
|
// and unhandled-reject through the auth path.
|
|
172
202
|
const previous = readSettings().proxy?.previous_tokens;
|
|
173
203
|
const extras = Array.isArray(previous)
|
|
174
|
-
? previous
|
|
204
|
+
? previous
|
|
205
|
+
.map((t) => (typeof t === 'string' ? t.trim() : ''))
|
|
206
|
+
.filter((t) => isValidReusableProxyToken(t))
|
|
175
207
|
: [];
|
|
176
|
-
const candidates = [this.token, ...extras]
|
|
208
|
+
const candidates = [this.token, ...extras]
|
|
209
|
+
.filter((t) => isValidReusableProxyToken(t))
|
|
210
|
+
.map((t) => t.trim());
|
|
177
211
|
let valid = false;
|
|
178
212
|
for (const cand of candidates) {
|
|
179
213
|
const expBuf = Buffer.from(cand, 'utf8');
|
|
@@ -16,6 +16,18 @@ const {
|
|
|
16
16
|
const DEFAULT_POLL_INTERVAL_ACTIVE = 10_000;
|
|
17
17
|
const DEFAULT_POLL_INTERVAL_IDLE = 60_000;
|
|
18
18
|
|
|
19
|
+
function toInboundStoreMessage(m, channel) {
|
|
20
|
+
return {
|
|
21
|
+
id: m.id,
|
|
22
|
+
type: m.type,
|
|
23
|
+
payload: m.payload,
|
|
24
|
+
channel,
|
|
25
|
+
priority: m.priority || 'normal',
|
|
26
|
+
refId: m.ref_id,
|
|
27
|
+
expiresAt: m.expires_at,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
class InboundSync {
|
|
20
32
|
constructor({ store, hubUrl, getHeaders, logger }) {
|
|
21
33
|
this.store = store;
|
|
@@ -86,26 +98,33 @@ class InboundSync {
|
|
|
86
98
|
|
|
87
99
|
const data = await readHubResponseJson(res);
|
|
88
100
|
const messages = data.messages || [];
|
|
101
|
+
let stored = 0;
|
|
102
|
+
let dropped = 0;
|
|
89
103
|
|
|
90
104
|
if (messages.length > 0) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
for (const m of messages) {
|
|
106
|
+
try {
|
|
107
|
+
this.store.writeInbound(toInboundStoreMessage(m, channel));
|
|
108
|
+
stored++;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (err && err.code === 'MAILBOX_JSONL_LINE_TOO_LARGE') {
|
|
111
|
+
dropped++;
|
|
112
|
+
const msgId = m && m.id ? String(m.id) : '(missing id)';
|
|
113
|
+
this.logger.warn?.(`[inbound] dropped oversized inbound message ${msgId}: ${err.message}`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
102
119
|
}
|
|
103
120
|
|
|
104
121
|
if (data.next_cursor) {
|
|
105
122
|
this.store.setCursor(cursorKey, data.next_cursor);
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
|
|
125
|
+
const result = { received: stored, cursor: data.next_cursor || cursor };
|
|
126
|
+
if (dropped > 0) result.dropped = dropped;
|
|
127
|
+
return result;
|
|
109
128
|
} catch (err) {
|
|
110
129
|
if (err instanceof AuthError) throw err;
|
|
111
130
|
if (isHubUnreachableError(err)) {
|
|
@@ -12,9 +12,113 @@ const {
|
|
|
12
12
|
sanitizeHubResponseForLog,
|
|
13
13
|
throwIfHubUnreachableResponse,
|
|
14
14
|
} = require('../../gep/hubFetch');
|
|
15
|
+
const { redactString } = require('../../gep/sanitize');
|
|
15
16
|
|
|
16
17
|
const MAX_BATCH = 50;
|
|
17
18
|
const MAX_RETRIES = 10;
|
|
19
|
+
const DEFAULT_MAX_OUTBOUND_BODY_BYTES = 4 * 1024 * 1024;
|
|
20
|
+
const MIN_ADAPTIVE_OUTBOUND_BODY_BYTES = 16 * 1024;
|
|
21
|
+
const OUTBOUND_BODY_BYTES_STATE_KEY = 'outbound_sync_max_body_bytes';
|
|
22
|
+
|
|
23
|
+
function sanitizeHubErrorMessage(value) {
|
|
24
|
+
if (typeof sanitizeHubResponseForLog === 'function') {
|
|
25
|
+
return sanitizeHubResponseForLog(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let text;
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
text = value;
|
|
31
|
+
} else if (value && typeof value.message === 'string') {
|
|
32
|
+
text = value.message;
|
|
33
|
+
} else {
|
|
34
|
+
try { text = JSON.stringify(value); } catch { text = String(value || ''); }
|
|
35
|
+
}
|
|
36
|
+
return redactString(String(text || ''));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveHardOutboundBodyBytes(env = process.env) {
|
|
40
|
+
const raw = Number(env.EVOMAP_OUTBOUND_SYNC_MAX_BODY_BYTES || env.EVOMAP_MAILBOX_OUTBOUND_MAX_BODY_BYTES);
|
|
41
|
+
const max = Number.isFinite(raw) && raw > 0
|
|
42
|
+
? Math.floor(raw)
|
|
43
|
+
: DEFAULT_MAX_OUTBOUND_BODY_BYTES;
|
|
44
|
+
return Math.max(1, max);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveMaxOutboundBodyBytes(env = process.env, store = null) {
|
|
48
|
+
let max = resolveHardOutboundBodyBytes(env);
|
|
49
|
+
try {
|
|
50
|
+
const adaptive = Number(store && typeof store.getState === 'function'
|
|
51
|
+
? store.getState(OUTBOUND_BODY_BYTES_STATE_KEY)
|
|
52
|
+
: null);
|
|
53
|
+
if (Number.isFinite(adaptive) && adaptive > 0) max = Math.min(max, Math.floor(adaptive));
|
|
54
|
+
} catch { /* adaptive state is best-effort */ }
|
|
55
|
+
return Math.max(1, max);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hubMessage(m) {
|
|
59
|
+
return {
|
|
60
|
+
id: m.id,
|
|
61
|
+
type: m.type,
|
|
62
|
+
payload: m.payload,
|
|
63
|
+
priority: m.priority,
|
|
64
|
+
ref_id: m.ref_id,
|
|
65
|
+
created_at: m.created_at,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function serializeOutboundBody(senderId, messages) {
|
|
70
|
+
return JSON.stringify({
|
|
71
|
+
sender_id: senderId,
|
|
72
|
+
proxy_protocol_version: PROXY_PROTOCOL_VERSION,
|
|
73
|
+
messages: messages.map(hubMessage),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function outboundEndpoint(hubUrl, senderId) {
|
|
78
|
+
const endpoint = new URL(`${String(hubUrl || '').replace(/\/+$/, '')}/a2a/mailbox/outbound`);
|
|
79
|
+
if (senderId) endpoint.searchParams.set('sender_id', senderId);
|
|
80
|
+
return endpoint.toString();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function outboundBodyBytes(senderId, messages) {
|
|
84
|
+
return Buffer.byteLength(serializeOutboundBody(senderId, messages), 'utf8');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildSizedOutboundBatch(senderId, messages, maxBodyBytes, hardMaxBodyBytes = maxBodyBytes) {
|
|
88
|
+
const selected = [];
|
|
89
|
+
const rejected = [];
|
|
90
|
+
let body = serializeOutboundBody(senderId, selected);
|
|
91
|
+
let bodyBytes = Buffer.byteLength(body, 'utf8');
|
|
92
|
+
|
|
93
|
+
for (const m of messages) {
|
|
94
|
+
const singleBytes = outboundBodyBytes(senderId, [m]);
|
|
95
|
+
if (singleBytes > hardMaxBodyBytes) {
|
|
96
|
+
rejected.push({
|
|
97
|
+
id: m.id,
|
|
98
|
+
status: 'rejected',
|
|
99
|
+
error: `outbound message exceeds max body bytes (${singleBytes} > ${hardMaxBodyBytes})`,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (selected.length === 0 && singleBytes > maxBodyBytes) {
|
|
104
|
+
selected.push(m);
|
|
105
|
+
body = serializeOutboundBody(senderId, selected);
|
|
106
|
+
bodyBytes = Buffer.byteLength(body, 'utf8');
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const candidate = selected.concat(m);
|
|
111
|
+
const candidateBody = serializeOutboundBody(senderId, candidate);
|
|
112
|
+
const candidateBytes = Buffer.byteLength(candidateBody, 'utf8');
|
|
113
|
+
if (candidateBytes > maxBodyBytes) break;
|
|
114
|
+
|
|
115
|
+
selected.push(m);
|
|
116
|
+
body = candidateBody;
|
|
117
|
+
bodyBytes = candidateBytes;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { selected, rejected, body, bodyBytes, maxBodyBytes };
|
|
121
|
+
}
|
|
18
122
|
|
|
19
123
|
class OutboundSync {
|
|
20
124
|
constructor({ store, hubUrl, getHeaders, logger }) {
|
|
@@ -81,41 +185,75 @@ class OutboundSync {
|
|
|
81
185
|
pending = pendingBatch.filter(m => !rejectedIds.has(m.id));
|
|
82
186
|
if (pending.length === 0) return { sent: 0, dropped: rejectedTraceUploads.length };
|
|
83
187
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const endpoint = `${this.hubUrl}/a2a/mailbox/outbound`;
|
|
188
|
+
let dropped = rejectedTraceUploads.length;
|
|
87
189
|
|
|
88
190
|
try {
|
|
89
191
|
const senderId = this.store.getState('node_id');
|
|
192
|
+
const endpoint = outboundEndpoint(this.hubUrl, senderId);
|
|
193
|
+
const prepared = buildSizedOutboundBatch(
|
|
194
|
+
senderId,
|
|
195
|
+
pending,
|
|
196
|
+
resolveMaxOutboundBodyBytes(process.env, this.store),
|
|
197
|
+
resolveHardOutboundBodyBytes(process.env)
|
|
198
|
+
);
|
|
199
|
+
if (prepared.rejected.length > 0) {
|
|
200
|
+
this.store.updateStatusBatch(prepared.rejected);
|
|
201
|
+
dropped += prepared.rejected.length;
|
|
202
|
+
}
|
|
203
|
+
pending = prepared.selected;
|
|
204
|
+
if (pending.length === 0) return { sent: 0, dropped };
|
|
205
|
+
|
|
90
206
|
const res = await hubFetch(endpoint, {
|
|
91
207
|
method: 'POST',
|
|
92
208
|
headers: this.getHeaders(),
|
|
93
|
-
body:
|
|
94
|
-
sender_id: senderId,
|
|
95
|
-
proxy_protocol_version: PROXY_PROTOCOL_VERSION,
|
|
96
|
-
messages: pending.map(m => ({
|
|
97
|
-
id: m.id,
|
|
98
|
-
type: m.type,
|
|
99
|
-
payload: m.payload,
|
|
100
|
-
priority: m.priority,
|
|
101
|
-
ref_id: m.ref_id,
|
|
102
|
-
created_at: m.created_at,
|
|
103
|
-
})),
|
|
104
|
-
}),
|
|
209
|
+
body: prepared.body,
|
|
105
210
|
signal: AbortSignal.timeout(30_000),
|
|
106
211
|
});
|
|
107
212
|
|
|
213
|
+
if (res.status === 413) {
|
|
214
|
+
const errText = sanitizeHubErrorMessage(
|
|
215
|
+
await readHubResponseText(res).catch(() => 'request entity too large')
|
|
216
|
+
);
|
|
217
|
+
const error = `Hub 413 outbound payload too large: ${errText}`;
|
|
218
|
+
if (pending.length <= 1) {
|
|
219
|
+
this.store.updateStatusBatch(pending.map(m => ({
|
|
220
|
+
id: m.id,
|
|
221
|
+
status: 'rejected',
|
|
222
|
+
error,
|
|
223
|
+
})));
|
|
224
|
+
const result = {
|
|
225
|
+
sent: 0,
|
|
226
|
+
error: 'hub_payload_too_large',
|
|
227
|
+
payloadTooLarge: true,
|
|
228
|
+
dropped: dropped + pending.length,
|
|
229
|
+
};
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
const currentCeiling = Math.min(prepared.maxBodyBytes, Math.max(1, prepared.bodyBytes - 1));
|
|
233
|
+
const nextMax = currentCeiling > MIN_ADAPTIVE_OUTBOUND_BODY_BYTES
|
|
234
|
+
? Math.max(MIN_ADAPTIVE_OUTBOUND_BODY_BYTES, Math.floor(currentCeiling / 2))
|
|
235
|
+
: Math.max(1, Math.floor(currentCeiling / 2));
|
|
236
|
+
try { this.store.setState(OUTBOUND_BODY_BYTES_STATE_KEY, nextMax); } catch { /* best effort */ }
|
|
237
|
+
this.logger.warn?.(
|
|
238
|
+
`[outbound] Hub 413 for ${pending.length} messages (${prepared.bodyBytes} bytes); ` +
|
|
239
|
+
`reducing outbound batch budget to ${nextMax} bytes`
|
|
240
|
+
);
|
|
241
|
+
const result = { sent: 0, error: 'hub_payload_too_large', payloadTooLarge: true };
|
|
242
|
+
if (dropped > 0) result.dropped = dropped;
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
|
|
108
246
|
await throwIfHubUnreachableResponse(res, 'outbound flush');
|
|
109
247
|
this._recordHubReachable();
|
|
110
248
|
|
|
111
249
|
if (res.status === 403 || res.status === 401) {
|
|
112
|
-
const errText = await readHubResponseText(res).catch(() => 'unknown');
|
|
113
|
-
throw new AuthError(`Hub ${res.status}: ${
|
|
250
|
+
const errText = sanitizeHubErrorMessage(await readHubResponseText(res).catch(() => 'unknown'));
|
|
251
|
+
throw new AuthError(`Hub ${res.status}: ${errText}`, res.status);
|
|
114
252
|
}
|
|
115
253
|
|
|
116
254
|
if (!res.ok) {
|
|
117
|
-
const errText = await readHubResponseText(res).catch(() => 'unknown');
|
|
118
|
-
throw new Error(`Hub returned ${res.status}: ${
|
|
255
|
+
const errText = sanitizeHubErrorMessage(await readHubResponseText(res).catch(() => 'unknown'));
|
|
256
|
+
throw new Error(`Hub returned ${res.status}: ${errText}`);
|
|
119
257
|
}
|
|
120
258
|
|
|
121
259
|
const data = await readHubResponseJson(res);
|
|
@@ -129,10 +267,11 @@ class OutboundSync {
|
|
|
129
267
|
updates.push({ id: r.id, status: 'synced' });
|
|
130
268
|
} else if (r.status === 'failed' || r.status === 'rejected') {
|
|
131
269
|
const msg = pending.find(m => m.id === r.id);
|
|
270
|
+
const error = sanitizeHubErrorMessage(r.error || 'rejected by hub');
|
|
132
271
|
if (msg && msg.retry_count < MAX_RETRIES) {
|
|
133
|
-
this.store.incrementRetry(r.id,
|
|
272
|
+
this.store.incrementRetry(r.id, error);
|
|
134
273
|
} else {
|
|
135
|
-
updates.push({ id: r.id, status: 'failed', error: r.error || 'max retries' });
|
|
274
|
+
updates.push({ id: r.id, status: 'failed', error: sanitizeHubErrorMessage(r.error || 'max retries') });
|
|
136
275
|
}
|
|
137
276
|
}
|
|
138
277
|
|
|
@@ -166,11 +305,12 @@ class OutboundSync {
|
|
|
166
305
|
if (dropped > 0) result.dropped = dropped;
|
|
167
306
|
return result;
|
|
168
307
|
}
|
|
169
|
-
|
|
308
|
+
const errMessage = sanitizeHubErrorMessage(err);
|
|
309
|
+
this.logger.error(`[outbound] flush failed: ${errMessage}`);
|
|
170
310
|
for (const m of pending) {
|
|
171
|
-
this.store.incrementRetry(m.id,
|
|
311
|
+
this.store.incrementRetry(m.id, errMessage);
|
|
172
312
|
}
|
|
173
|
-
const result = { sent: 0, error:
|
|
313
|
+
const result = { sent: 0, error: errMessage };
|
|
174
314
|
if (dropped > 0) result.dropped = dropped;
|
|
175
315
|
return result;
|
|
176
316
|
}
|