@askexenow/exe-os 0.8.83 → 0.8.86
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/bin/backfill-conversations.js +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +154 -21
- package/dist/bin/cli.js +14678 -12676
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +100 -91
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1420 -485
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +572 -271
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +102 -3
- package/dist/bin/exe-gateway.js +796 -292
- package/dist/bin/exe-healthcheck.js +134 -1
- package/dist/bin/exe-heartbeat.js +172 -36
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +927 -82
- package/dist/bin/exe-new-employee.js +60 -8
- package/dist/bin/exe-pending-messages.js +151 -19
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +155 -22
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +995 -228
- package/dist/bin/exe-session-cleanup.js +4930 -1664
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +154 -21
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +1180 -363
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +60 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +1185 -367
- package/dist/bin/setup.js +914 -270
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +792 -285
- package/dist/hooks/bug-report-worker.js +445 -135
- package/dist/hooks/commit-complete.js +1178 -361
- package/dist/hooks/error-recall.js +994 -228
- package/dist/hooks/ingest-worker.js +1799 -1234
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +757 -109
- package/dist/hooks/pre-compact.js +1061 -244
- package/dist/hooks/pre-tool-use.js +787 -130
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +1121 -299
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +4063 -397
- package/dist/hooks/session-start.js +1071 -254
- package/dist/hooks/stop.js +768 -120
- package/dist/hooks/subagent-stop.js +757 -109
- package/dist/hooks/summary-worker.js +1706 -1011
- package/dist/index.js +1821 -1098
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +932 -88
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +2733 -1575
- package/dist/lib/hybrid-search.js +995 -228
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +103 -40
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +348 -134
- package/dist/lib/tmux-routing.js +422 -208
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5742 -696
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +375 -152
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +99 -31
- package/dist/mcp/tools/send-message.js +108 -45
- package/dist/mcp/tools/update-task.js +162 -77
- package/dist/runtime/index.js +1075 -258
- package/dist/tui/App.js +1333 -506
- package/package.json +6 -1
- package/src/commands/exe/agent-config.md +27 -0
- package/src/commands/exe/cc-doctor.md +10 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// src/lib/config.ts
|
|
7
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
8
|
+
import { readFileSync, existsSync, renameSync } from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import os from "os";
|
|
11
|
+
function resolveDataDir() {
|
|
12
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
13
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
14
|
+
const newDir = path.join(os.homedir(), ".exe-os");
|
|
15
|
+
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
16
|
+
if (!existsSync(newDir) && existsSync(legacyDir)) {
|
|
17
|
+
try {
|
|
18
|
+
renameSync(legacyDir, newDir);
|
|
19
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
20
|
+
`);
|
|
21
|
+
} catch {
|
|
22
|
+
return legacyDir;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return newDir;
|
|
26
|
+
}
|
|
27
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG;
|
|
28
|
+
var init_config = __esm({
|
|
29
|
+
"src/lib/config.ts"() {
|
|
30
|
+
"use strict";
|
|
31
|
+
EXE_AI_DIR = resolveDataDir();
|
|
32
|
+
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
33
|
+
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
34
|
+
CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
35
|
+
LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
36
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
37
|
+
DEFAULT_CONFIG = {
|
|
38
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
39
|
+
dbPath: DB_PATH,
|
|
40
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
41
|
+
embeddingDim: 1024,
|
|
42
|
+
batchSize: 20,
|
|
43
|
+
flushIntervalMs: 1e4,
|
|
44
|
+
autoIngestion: true,
|
|
45
|
+
autoRetrieval: true,
|
|
46
|
+
searchMode: "hybrid",
|
|
47
|
+
hookSearchMode: "hybrid",
|
|
48
|
+
fileGrepEnabled: true,
|
|
49
|
+
splashEffect: true,
|
|
50
|
+
consolidationEnabled: true,
|
|
51
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
52
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
53
|
+
consolidationMaxCallsPerRun: 20,
|
|
54
|
+
selfQueryRouter: true,
|
|
55
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
56
|
+
rerankerEnabled: true,
|
|
57
|
+
scalingRoadmap: {
|
|
58
|
+
rerankerAutoTrigger: {
|
|
59
|
+
enabled: true,
|
|
60
|
+
broadQueryMinCardinality: 5e4,
|
|
61
|
+
fetchTopK: 150,
|
|
62
|
+
returnTopK: 5
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
graphRagEnabled: true,
|
|
66
|
+
wikiEnabled: false,
|
|
67
|
+
wikiUrl: "",
|
|
68
|
+
wikiApiKey: "",
|
|
69
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
70
|
+
wikiWorkspaceMapping: {},
|
|
71
|
+
wikiAutoUpdate: true,
|
|
72
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
73
|
+
wikiAutoUpdateCreateNew: true,
|
|
74
|
+
skillLearning: true,
|
|
75
|
+
skillThreshold: 3,
|
|
76
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
77
|
+
exeHeartbeat: {
|
|
78
|
+
enabled: true,
|
|
79
|
+
intervalSeconds: 60,
|
|
80
|
+
staleInProgressThresholdHours: 2
|
|
81
|
+
},
|
|
82
|
+
sessionLifecycle: {
|
|
83
|
+
idleKillEnabled: true,
|
|
84
|
+
idleKillTicksRequired: 3,
|
|
85
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
86
|
+
maxAutoInstances: 10
|
|
87
|
+
},
|
|
88
|
+
autoUpdate: {
|
|
89
|
+
checkOnBoot: true,
|
|
90
|
+
autoInstall: false,
|
|
91
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// src/lib/token-spend.ts
|
|
98
|
+
import { readdir } from "fs/promises";
|
|
99
|
+
import { createReadStream } from "fs";
|
|
100
|
+
import { createInterface } from "readline";
|
|
101
|
+
import path3 from "path";
|
|
102
|
+
import os3 from "os";
|
|
103
|
+
|
|
104
|
+
// src/lib/database.ts
|
|
105
|
+
import { createClient } from "@libsql/client";
|
|
106
|
+
|
|
107
|
+
// src/lib/employees.ts
|
|
108
|
+
init_config();
|
|
109
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
110
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
111
|
+
import { execSync } from "child_process";
|
|
112
|
+
import path2 from "path";
|
|
113
|
+
import os2 from "os";
|
|
114
|
+
var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
115
|
+
|
|
116
|
+
// src/lib/database.ts
|
|
117
|
+
var _resilientClient = null;
|
|
118
|
+
var _daemonClient = null;
|
|
119
|
+
function getClient() {
|
|
120
|
+
if (!_resilientClient) {
|
|
121
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
122
|
+
}
|
|
123
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
124
|
+
return _resilientClient;
|
|
125
|
+
}
|
|
126
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
127
|
+
return _daemonClient;
|
|
128
|
+
}
|
|
129
|
+
return _resilientClient;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/lib/token-spend.ts
|
|
133
|
+
var MODEL_PRICING = {
|
|
134
|
+
// Opus 4.5+ ($5/$25 — Anthropic price drop from original Opus 4)
|
|
135
|
+
"claude-opus-4-7": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
|
|
136
|
+
"claude-opus-4-6": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
|
|
137
|
+
"claude-opus-4-5": { input: 5 / 1e6, output: 25 / 1e6, cacheRead: 0.5 / 1e6, cacheWrite: 6.25 / 1e6 },
|
|
138
|
+
// Opus 4.0/4.1 (legacy $15/$75)
|
|
139
|
+
"claude-opus-4-1": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
|
|
140
|
+
"claude-opus-4": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
|
|
141
|
+
// Sonnet 4.x
|
|
142
|
+
"claude-sonnet-4": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
|
|
143
|
+
// Sonnet 3.7/3.5
|
|
144
|
+
"claude-3-7-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
|
|
145
|
+
"claude-3-5-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
|
|
146
|
+
// Haiku 4.5
|
|
147
|
+
"claude-haiku-4-5": { input: 1 / 1e6, output: 5 / 1e6, cacheRead: 0.1 / 1e6, cacheWrite: 1.25 / 1e6 },
|
|
148
|
+
// Haiku 3.5
|
|
149
|
+
"claude-3-5-haiku": { input: 0.8 / 1e6, output: 4 / 1e6, cacheRead: 0.08 / 1e6, cacheWrite: 1 / 1e6 },
|
|
150
|
+
// Opus 3
|
|
151
|
+
"claude-3-opus": { input: 15 / 1e6, output: 75 / 1e6, cacheRead: 1.5 / 1e6, cacheWrite: 18.75 / 1e6 },
|
|
152
|
+
// Sonnet 3
|
|
153
|
+
"claude-3-sonnet": { input: 3 / 1e6, output: 15 / 1e6, cacheRead: 0.3 / 1e6, cacheWrite: 3.75 / 1e6 },
|
|
154
|
+
// Haiku 3
|
|
155
|
+
"claude-3-haiku": { input: 0.25 / 1e6, output: 1.25 / 1e6, cacheRead: 0.03 / 1e6, cacheWrite: 0.3 / 1e6 }
|
|
156
|
+
};
|
|
157
|
+
var DEFAULT_PRICING = MODEL_PRICING["claude-sonnet-4"];
|
|
158
|
+
function getPricing(model) {
|
|
159
|
+
if (MODEL_PRICING[model]) return MODEL_PRICING[model];
|
|
160
|
+
const stripped = model.replace(/-\d{8}$/, "");
|
|
161
|
+
if (MODEL_PRICING[stripped]) return MODEL_PRICING[stripped];
|
|
162
|
+
const sortedKeys = Object.keys(MODEL_PRICING).sort((a, b) => b.length - a.length);
|
|
163
|
+
for (const key of sortedKeys) {
|
|
164
|
+
if (model.includes(key)) return MODEL_PRICING[key];
|
|
165
|
+
}
|
|
166
|
+
return DEFAULT_PRICING;
|
|
167
|
+
}
|
|
168
|
+
async function getAgentSpend(period = "7d") {
|
|
169
|
+
const cutoff = periodToCutoff(period);
|
|
170
|
+
const client = getClient();
|
|
171
|
+
const result = await client.execute({
|
|
172
|
+
sql: `SELECT session_uuid, agent_id FROM session_agent_map WHERE started_at >= ?`,
|
|
173
|
+
args: [cutoff]
|
|
174
|
+
});
|
|
175
|
+
if (result.rows.length === 0) return [];
|
|
176
|
+
const sessionAgent = /* @__PURE__ */ new Map();
|
|
177
|
+
for (const row of result.rows) {
|
|
178
|
+
sessionAgent.set(row.session_uuid, row.agent_id);
|
|
179
|
+
}
|
|
180
|
+
const claudeDir = path3.join(os3.homedir(), ".claude", "projects");
|
|
181
|
+
let projectDirs = [];
|
|
182
|
+
try {
|
|
183
|
+
const entries = await readdir(claudeDir);
|
|
184
|
+
projectDirs = entries.map((e) => path3.join(claudeDir, e));
|
|
185
|
+
} catch {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const agentTotals = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const [sessionUuid, agentId] of sessionAgent) {
|
|
190
|
+
for (const dir of projectDirs) {
|
|
191
|
+
const jsonlPath = path3.join(dir, `${sessionUuid}.jsonl`);
|
|
192
|
+
try {
|
|
193
|
+
const usage = await extractSessionUsage(jsonlPath);
|
|
194
|
+
if (usage.input === 0 && usage.output === 0) continue;
|
|
195
|
+
const totals = agentTotals.get(agentId) ?? {
|
|
196
|
+
input: 0,
|
|
197
|
+
output: 0,
|
|
198
|
+
cacheRead: 0,
|
|
199
|
+
cacheCreate: 0,
|
|
200
|
+
costUSD: 0,
|
|
201
|
+
sessions: /* @__PURE__ */ new Set()
|
|
202
|
+
};
|
|
203
|
+
totals.input += usage.input;
|
|
204
|
+
totals.output += usage.output;
|
|
205
|
+
totals.cacheRead += usage.cacheRead;
|
|
206
|
+
totals.cacheCreate += usage.cacheCreate;
|
|
207
|
+
totals.costUSD += usage.costUSD;
|
|
208
|
+
totals.sessions.add(sessionUuid);
|
|
209
|
+
agentTotals.set(agentId, totals);
|
|
210
|
+
break;
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return Array.from(agentTotals.entries()).map(([agentId, t]) => ({
|
|
216
|
+
agentId,
|
|
217
|
+
inputTokens: t.input,
|
|
218
|
+
outputTokens: t.output,
|
|
219
|
+
cacheReadTokens: t.cacheRead,
|
|
220
|
+
cacheCreationTokens: t.cacheCreate,
|
|
221
|
+
costUSD: t.costUSD,
|
|
222
|
+
sessions: t.sessions.size,
|
|
223
|
+
period
|
|
224
|
+
})).sort((a, b) => b.costUSD - a.costUSD);
|
|
225
|
+
}
|
|
226
|
+
async function extractSessionUsage(jsonlPath) {
|
|
227
|
+
let input = 0;
|
|
228
|
+
let output = 0;
|
|
229
|
+
let cacheRead = 0;
|
|
230
|
+
let cacheCreate = 0;
|
|
231
|
+
let costUSD = 0;
|
|
232
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
233
|
+
const rl = createInterface({
|
|
234
|
+
input: createReadStream(jsonlPath, { encoding: "utf8" }),
|
|
235
|
+
crlfDelay: Infinity
|
|
236
|
+
});
|
|
237
|
+
for await (const line of rl) {
|
|
238
|
+
if (!line.includes('"type":"assistant"')) continue;
|
|
239
|
+
try {
|
|
240
|
+
const record = JSON.parse(line);
|
|
241
|
+
if (record.type !== "assistant") continue;
|
|
242
|
+
const messageId = record.message?.id;
|
|
243
|
+
if (messageId) {
|
|
244
|
+
if (seenMessageIds.has(messageId)) continue;
|
|
245
|
+
seenMessageIds.add(messageId);
|
|
246
|
+
}
|
|
247
|
+
const usage = record.message?.usage;
|
|
248
|
+
if (!usage) continue;
|
|
249
|
+
const model = record.message?.model ?? "";
|
|
250
|
+
const pricing = getPricing(model);
|
|
251
|
+
const inp = usage.input_tokens ?? 0;
|
|
252
|
+
const out = usage.output_tokens ?? 0;
|
|
253
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
254
|
+
const cc = usage.cache_creation_input_tokens ?? 0;
|
|
255
|
+
input += inp;
|
|
256
|
+
output += out;
|
|
257
|
+
cacheRead += cr;
|
|
258
|
+
cacheCreate += cc;
|
|
259
|
+
if (pricing) {
|
|
260
|
+
costUSD += inp * pricing.input + out * pricing.output + cr * pricing.cacheRead + cc * pricing.cacheWrite;
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return { input, output, cacheRead, cacheCreate, costUSD };
|
|
266
|
+
}
|
|
267
|
+
function periodToCutoff(period) {
|
|
268
|
+
const ms = { "24h": 864e5, "7d": 6048e5, "30d": 2592e6 }[period];
|
|
269
|
+
return new Date(Date.now() - ms).toISOString();
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
getAgentSpend
|
|
273
|
+
};
|
package/dist/lib/ws-client.js
CHANGED
|
@@ -25,12 +25,14 @@ function assertSecureWsUrl(url) {
|
|
|
25
25
|
var MIN_RECONNECT_MS = 1e3;
|
|
26
26
|
var MAX_RECONNECT_MS = 3e4;
|
|
27
27
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
28
|
+
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
28
29
|
function createWsClient(config, handlers) {
|
|
29
30
|
assertSecureWsUrl(config.endpoint);
|
|
30
31
|
let ws = null;
|
|
31
32
|
let connected = false;
|
|
32
33
|
let closed = false;
|
|
33
34
|
let reconnectDelay = MIN_RECONNECT_MS;
|
|
35
|
+
let reconnectAttempts = 0;
|
|
34
36
|
let reconnectTimer = null;
|
|
35
37
|
let heartbeatTimer = null;
|
|
36
38
|
let currentAgents = [];
|
|
@@ -47,6 +49,7 @@ function createWsClient(config, handlers) {
|
|
|
47
49
|
ws.onopen = () => {
|
|
48
50
|
connected = true;
|
|
49
51
|
reconnectDelay = MIN_RECONNECT_MS;
|
|
52
|
+
reconnectAttempts = 0;
|
|
50
53
|
handlers.onConnect();
|
|
51
54
|
sendRaw({
|
|
52
55
|
type: "register",
|
|
@@ -113,6 +116,14 @@ function createWsClient(config, handlers) {
|
|
|
113
116
|
}
|
|
114
117
|
function scheduleReconnect() {
|
|
115
118
|
if (closed || reconnectTimer) return;
|
|
119
|
+
reconnectAttempts++;
|
|
120
|
+
if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {
|
|
121
|
+
process.stderr.write(
|
|
122
|
+
`[ws-client] Cloud sync not available \u2014 gave up after ${MAX_RECONNECT_ATTEMPTS} attempts. Check your network or endpoint config.
|
|
123
|
+
`
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
116
127
|
const jitter = reconnectDelay * (0.75 + Math.random() * 0.5);
|
|
117
128
|
reconnectTimer = setTimeout(() => {
|
|
118
129
|
reconnectTimer = null;
|