clawmoney 0.15.59 → 0.15.61
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/commands/relay.js +52 -9
- package/dist/relay/provider.js +23 -1
- package/dist/relay/types.d.ts +9 -1
- package/package.json +1 -1
package/dist/commands/relay.js
CHANGED
|
@@ -120,11 +120,44 @@ export async function relayRegisterCommand(options) {
|
|
|
120
120
|
// ── relay start ──
|
|
121
121
|
export async function relayStartCommand(options) {
|
|
122
122
|
const config = requireConfig();
|
|
123
|
-
//
|
|
123
|
+
// If an old daemon is still running, auto-stop and replace it instead
|
|
124
|
+
// of bailing with "already running, use stop first". Previously re-
|
|
125
|
+
// running `clawmoney relay setup` (or `relay start`) with a previous
|
|
126
|
+
// daemon alive produced a confusing error at the last step of an
|
|
127
|
+
// otherwise-successful flow. SIGTERM → poll for exit (3s) →
|
|
128
|
+
// SIGKILL on stubborn processes → clean PID file → start fresh.
|
|
124
129
|
const existingPid = readRelayPid();
|
|
125
130
|
if (existingPid && isRelayPidAlive(existingPid)) {
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
const stopSpin = ora(`Replacing existing daemon (PID ${existingPid})...`).start();
|
|
132
|
+
try {
|
|
133
|
+
process.kill(existingPid, "SIGTERM");
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Process may have exited between readPid and kill — ignore.
|
|
137
|
+
}
|
|
138
|
+
const deadline = Date.now() + 3000;
|
|
139
|
+
while (Date.now() < deadline) {
|
|
140
|
+
if (!isRelayPidAlive(existingPid))
|
|
141
|
+
break;
|
|
142
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
143
|
+
}
|
|
144
|
+
if (isRelayPidAlive(existingPid)) {
|
|
145
|
+
try {
|
|
146
|
+
process.kill(existingPid, "SIGKILL");
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// ignore
|
|
150
|
+
}
|
|
151
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
152
|
+
stopSpin.warn(chalk.yellow(`Old daemon (PID ${existingPid}) didn't exit in 3s — forced SIGKILL`));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
stopSpin.succeed(chalk.dim(`Stopped old daemon (PID ${existingPid})`));
|
|
156
|
+
}
|
|
157
|
+
// The graceful-shutdown path in the daemon removes its own PID
|
|
158
|
+
// file, but SIGKILL leaves a stale one. Make sure we start from
|
|
159
|
+
// a clean slate either way.
|
|
160
|
+
removeRelayPid();
|
|
128
161
|
}
|
|
129
162
|
const spinner = ora("Starting Relay Provider...").start();
|
|
130
163
|
try {
|
|
@@ -285,21 +318,31 @@ export async function relayStatusCommand() {
|
|
|
285
318
|
const totalDailySpent = providers.reduce((s, p) => s + (p.daily_spent_usd ?? 0), 0);
|
|
286
319
|
const totalDailyLimit = providers.reduce((s, p) => s + (p.daily_limit_usd ?? 0), 0);
|
|
287
320
|
// Per-provider rows — compact table with status/cli/model/load.
|
|
288
|
-
|
|
321
|
+
// QUARANTINED rows get a follow-up indented line showing the
|
|
322
|
+
// expected/got mismatch reason so the operator knows exactly what
|
|
323
|
+
// to fix — the WS notice also logs this when it first happens,
|
|
324
|
+
// but `relay status` is the after-the-fact checkpoint.
|
|
325
|
+
const header = ` ${"STATUS".padEnd(12)} ${"CLI".padEnd(12)} ${"MODEL".padEnd(30)} ${"LOAD".padEnd(8)} ${"EARNED".padEnd(10)}`;
|
|
289
326
|
console.log(chalk.bold(header));
|
|
290
|
-
console.log(chalk.dim(" " + "─".repeat(
|
|
327
|
+
console.log(chalk.dim(" " + "─".repeat(78)));
|
|
291
328
|
for (const p of providers) {
|
|
292
|
-
const statusRaw = (p.status ?? "-").padEnd(
|
|
329
|
+
const statusRaw = (p.status ?? "-").padEnd(12);
|
|
293
330
|
const statusColored = p.status === "online"
|
|
294
331
|
? chalk.green(statusRaw)
|
|
295
|
-
: p.status === "
|
|
296
|
-
? chalk.
|
|
297
|
-
:
|
|
332
|
+
: p.status === "quarantined"
|
|
333
|
+
? chalk.red(statusRaw)
|
|
334
|
+
: p.status === "offline"
|
|
335
|
+
? chalk.dim(statusRaw)
|
|
336
|
+
: chalk.yellow(statusRaw);
|
|
298
337
|
const cli = (p.cli_type ?? "-").padEnd(12);
|
|
299
338
|
const model = (p.model ?? "-").padEnd(30);
|
|
300
339
|
const load = `${p.current_load ?? 0}/${p.concurrency ?? "-"}`.padEnd(8);
|
|
301
340
|
const earned = `$${(p.total_earned_usd ?? 0).toFixed(2)}`.padEnd(10);
|
|
302
341
|
console.log(` ${statusColored} ${cli} ${model} ${load} ${earned}`);
|
|
342
|
+
if (p.status === "quarantined" && p.quarantine_reason) {
|
|
343
|
+
console.log(chalk.red(` ↳ ${p.quarantine_reason}`) +
|
|
344
|
+
chalk.dim(" (fix: restart daemon after correcting the subscription/registration)"));
|
|
345
|
+
}
|
|
303
346
|
}
|
|
304
347
|
console.log("");
|
|
305
348
|
console.log(` ${chalk.bold("Daily quota:")} $${totalDailySpent.toFixed(2)} / $${totalDailyLimit.toFixed(2)}`);
|
package/dist/relay/provider.js
CHANGED
|
@@ -285,13 +285,23 @@ async function executeRelayRequest(request, config, sendChunk) {
|
|
|
285
285
|
status: snap.sessionWindow.status,
|
|
286
286
|
};
|
|
287
287
|
}
|
|
288
|
+
// CLAWMONEY_FAKE_MODEL_USED — test-only lever. When set, rewrite the
|
|
289
|
+
// reported `model_used` field to the env var's value before returning
|
|
290
|
+
// to the Hub. Used to manually exercise the Hub's model-mismatch guard
|
|
291
|
+
// + quarantine flow without having to juggle real subscription tiers
|
|
292
|
+
// or fake upstream accounts. DO NOT set this in production.
|
|
293
|
+
const fakeModelUsed = process.env.CLAWMONEY_FAKE_MODEL_USED;
|
|
294
|
+
const reportedModel = fakeModelUsed || parsed.model || model;
|
|
295
|
+
if (fakeModelUsed) {
|
|
296
|
+
logger.warn(` ! CLAWMONEY_FAKE_MODEL_USED=${fakeModelUsed} — reporting fake model to Hub (test mode)`);
|
|
297
|
+
}
|
|
288
298
|
return {
|
|
289
299
|
event: "relay_response",
|
|
290
300
|
request_id,
|
|
291
301
|
content: parsed.text,
|
|
292
302
|
cli_session_id: parsed.sessionId || undefined,
|
|
293
303
|
usage: parsed.usage,
|
|
294
|
-
model_used:
|
|
304
|
+
model_used: reportedModel,
|
|
295
305
|
cost_usd: parsed.costUsd || undefined,
|
|
296
306
|
session_window: sessionWindowTelemetry,
|
|
297
307
|
};
|
|
@@ -447,6 +457,18 @@ export function runRelayProvider(cliOverride) {
|
|
|
447
457
|
case "error":
|
|
448
458
|
logger.error(`Server error: ${event.message}`);
|
|
449
459
|
break;
|
|
460
|
+
case "relay_notice":
|
|
461
|
+
// Loud WARN for the human operator. Quarantine notices are the
|
|
462
|
+
// most important kind — they mean buyers are going to stop
|
|
463
|
+
// seeing this provider until the daemon is restarted, so we
|
|
464
|
+
// don't want them buried in the log.
|
|
465
|
+
logger.warn(`[NOTICE ${event.notice_type}] ${event.message}`);
|
|
466
|
+
if (event.notice_type === "model_mismatch_quarantine" &&
|
|
467
|
+
event.expected_model &&
|
|
468
|
+
event.got_model) {
|
|
469
|
+
logger.warn(` expected=${event.expected_model} got=${event.got_model} cli=${event.cli_type ?? "?"}`);
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
450
472
|
default:
|
|
451
473
|
logger.warn("Unknown event:", event);
|
|
452
474
|
}
|
package/dist/relay/types.d.ts
CHANGED
|
@@ -30,7 +30,15 @@ export interface RelayErrorEvent {
|
|
|
30
30
|
event: "error";
|
|
31
31
|
message: string;
|
|
32
32
|
}
|
|
33
|
-
export
|
|
33
|
+
export interface RelayNoticeEvent {
|
|
34
|
+
event: "relay_notice";
|
|
35
|
+
notice_type: string;
|
|
36
|
+
cli_type?: string;
|
|
37
|
+
expected_model?: string;
|
|
38
|
+
got_model?: string;
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
export type RelayIncomingEvent = RelayRequest | RelayConnectedEvent | RelayErrorEvent | RelayNoticeEvent;
|
|
34
42
|
export interface RelayResponseSessionWindow {
|
|
35
43
|
reset_at_ms: number;
|
|
36
44
|
utilization?: number;
|