@alfe.ai/gateway 0.2.5 → 0.3.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/dist/bin/gateway.js +1 -1
- package/dist/health.js +226 -2
- package/dist/src/index.d.ts +76 -1
- package/dist/src/index.js +2 -2
- package/package.json +3 -2
package/dist/bin/gateway.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as installService, c as uninstallService,
|
|
2
|
+
import { a as installService, c as uninstallService, i as checkExistingDaemon, n as queryDaemonHealth, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport, y as SOCKET_PATH } from "../health.js";
|
|
3
3
|
import { t as LOG_FILE } from "../logger.js";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
//#region bin/gateway.ts
|
package/dist/health.js
CHANGED
|
@@ -7,7 +7,7 @@ import { dirname, join } from "node:path";
|
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import pino from "pino";
|
|
9
9
|
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
-
import { getEndpointFromToken, readConfig } from "@alfe.ai/config";
|
|
10
|
+
import { getEndpointFromToken, readConfig, resolveConfig } from "@alfe.ai/config";
|
|
11
11
|
import crypto from "crypto";
|
|
12
12
|
import { parse } from "smol-toml";
|
|
13
13
|
import WebSocket from "ws";
|
|
@@ -5330,6 +5330,215 @@ var NoopRuntimeGate = class {
|
|
|
5330
5330
|
requestRestart() {}
|
|
5331
5331
|
};
|
|
5332
5332
|
//#endregion
|
|
5333
|
+
//#region src/sentry.ts
|
|
5334
|
+
/**
|
|
5335
|
+
* Agent-side error reporting (Sentry) for the Alfe CLI + gateway daemon.
|
|
5336
|
+
*
|
|
5337
|
+
* This module is the single Sentry bootstrap shared by both published
|
|
5338
|
+
* agent-side packages — `@alfe.ai/gateway` (the daemon) and `@alfe.ai/cli`
|
|
5339
|
+
* (which re-imports these helpers from `@alfe.ai/gateway`). It exists so
|
|
5340
|
+
* integration-installation failures on customer/managed VMs surface centrally
|
|
5341
|
+
* instead of silently rotting until someone SSHes into the box.
|
|
5342
|
+
*
|
|
5343
|
+
* Design constraints (see packages/gateway/DEVELOPING.md → "Error reporting"):
|
|
5344
|
+
* - Errors only. No tracing/profiling (`tracesSampleRate: 0`, `sampleRate: 1`).
|
|
5345
|
+
* - `@sentry/node` is lazy-imported so `alfe --version` doesn't pay for it.
|
|
5346
|
+
* - Sentry can NEVER break the CLI/daemon — every entry point is try/catch'd.
|
|
5347
|
+
* - The DSNs are baked as constants: published tarballs run on agent VMs and
|
|
5348
|
+
* cannot read the repo's `config/config.ts`. That file's `SENTRY_DSNS`
|
|
5349
|
+
* registry (keys `agentDaemon` + `cli`) mirrors them as the human reference.
|
|
5350
|
+
* - Secrets are scrubbed in `beforeSend`; request/env payloads are dropped.
|
|
5351
|
+
* - Opt-out via `ALFE_ERROR_REPORTING=0|false` or `errorReporting = false`
|
|
5352
|
+
* in `~/.alfe/config.toml`. Default ON.
|
|
5353
|
+
*/
|
|
5354
|
+
/**
|
|
5355
|
+
* Baked per-surface Sentry DSNs (org `alfe-ai`). DSNs are public by design
|
|
5356
|
+
* (they only permit event ingestion). Mirrors of `config/config.ts` →
|
|
5357
|
+
* `SENTRY_DSNS.agentDaemon` / `SENTRY_DSNS.cli`. If you rotate a DSN, update
|
|
5358
|
+
* both places.
|
|
5359
|
+
*/
|
|
5360
|
+
const AGENT_DAEMON_SENTRY_DSN = "https://a47f010d8d39fb4350f913492189f283@o4511008239452160.ingest.us.sentry.io/4511679448547328";
|
|
5361
|
+
/** Surface → Sentry project: `cli` and `agent-daemon` respectively. */
|
|
5362
|
+
const SURFACE_DSNS = {
|
|
5363
|
+
cli: "https://82dde4631336561f2fcc89d7531623de@o4511008239452160.ingest.us.sentry.io/4511679411191808",
|
|
5364
|
+
daemon: AGENT_DAEMON_SENTRY_DSN
|
|
5365
|
+
};
|
|
5366
|
+
/** Held after a successful init so the capture helpers can run synchronously. */
|
|
5367
|
+
let sentry = null;
|
|
5368
|
+
/**
|
|
5369
|
+
* Map the configured API URL to a coarse environment tag. Best-effort — returns
|
|
5370
|
+
* `"unknown"` when no config is present yet (e.g. `alfe login` pre-setup).
|
|
5371
|
+
*/
|
|
5372
|
+
function deriveEnvironment() {
|
|
5373
|
+
try {
|
|
5374
|
+
const { apiUrl } = resolveConfig();
|
|
5375
|
+
if (apiUrl.includes("api.dev.alfe.ai") || apiUrl.includes("dev.alfe.ai")) return "dev";
|
|
5376
|
+
if (apiUrl.includes("api.test.alfe.ai")) return "test";
|
|
5377
|
+
if (apiUrl.includes("api.demo.alfe.ai")) return "demo";
|
|
5378
|
+
if (apiUrl.includes("api.alfe.ai")) return "prod";
|
|
5379
|
+
} catch {}
|
|
5380
|
+
return "unknown";
|
|
5381
|
+
}
|
|
5382
|
+
/** Active runtime (`openclaw`/`hermes`) from config, or undefined if unknown. */
|
|
5383
|
+
function deriveRuntime() {
|
|
5384
|
+
try {
|
|
5385
|
+
return resolveConfig().runtime;
|
|
5386
|
+
} catch {
|
|
5387
|
+
return;
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
/** True when the operator has opted out of error reporting. */
|
|
5391
|
+
function errorReportingDisabled() {
|
|
5392
|
+
const env = process.env.ALFE_ERROR_REPORTING?.trim().toLowerCase();
|
|
5393
|
+
if (env === "0" || env === "false") return true;
|
|
5394
|
+
try {
|
|
5395
|
+
if (readConfig().errorReporting === false) return true;
|
|
5396
|
+
} catch {}
|
|
5397
|
+
return false;
|
|
5398
|
+
}
|
|
5399
|
+
const KV_SECRET_RE = /\b(authorization|api[_-]?key|apikey|token|secret|password|passwd|bearer)\b(\s*[:=]\s*|\s+)("?)([^\s"']+)\3/gi;
|
|
5400
|
+
const MIN_BARE_SECRET_LEN = 16;
|
|
5401
|
+
const ALFE_TOKEN_RE = /alfe_(?:dev|test|demo|live)_[A-Za-z0-9._-]+/g;
|
|
5402
|
+
const BEARER_RE = /\bbearer\s+[A-Za-z0-9._\-+/=]+/gi;
|
|
5403
|
+
function scrubString(value) {
|
|
5404
|
+
return value.replace(BEARER_RE, "Bearer [REDACTED]").replace(KV_SECRET_RE, (match, label, sep, _q, secret) => {
|
|
5405
|
+
if (!/[:=]/.test(sep) && secret.length < MIN_BARE_SECRET_LEN) return match;
|
|
5406
|
+
return `${label}${sep}[REDACTED]`;
|
|
5407
|
+
}).replace(ALFE_TOKEN_RE, "alfe_[REDACTED]");
|
|
5408
|
+
}
|
|
5409
|
+
/** Scrub secrets from a breadcrumb's message + string data values in place. */
|
|
5410
|
+
function scrubBreadcrumb(b) {
|
|
5411
|
+
if (typeof b.message === "string") b.message = scrubString(b.message);
|
|
5412
|
+
const data = b.data;
|
|
5413
|
+
if (data) for (const k of Object.keys(data)) {
|
|
5414
|
+
const dv = data[k];
|
|
5415
|
+
if (typeof dv === "string") data[k] = scrubString(dv);
|
|
5416
|
+
}
|
|
5417
|
+
return b;
|
|
5418
|
+
}
|
|
5419
|
+
/**
|
|
5420
|
+
* Scrub secrets and drop request/env payloads before an event is sent.
|
|
5421
|
+
* Generic so it preserves the caller's exact event subtype (`beforeSend`
|
|
5422
|
+
* receives — and must return — an `ErrorEvent`, not a widened `Event`).
|
|
5423
|
+
*/
|
|
5424
|
+
function scrubEvent(event) {
|
|
5425
|
+
delete event.request;
|
|
5426
|
+
if (event.contexts) delete event.contexts.runtime;
|
|
5427
|
+
delete event.extra;
|
|
5428
|
+
delete event.server_name;
|
|
5429
|
+
if (typeof event.message === "string") event.message = scrubString(event.message);
|
|
5430
|
+
const values = event.exception?.values;
|
|
5431
|
+
if (values) {
|
|
5432
|
+
for (const v of values) if (typeof v.value === "string") v.value = scrubString(v.value);
|
|
5433
|
+
}
|
|
5434
|
+
if (event.breadcrumbs) for (const b of event.breadcrumbs) scrubBreadcrumb(b);
|
|
5435
|
+
return event;
|
|
5436
|
+
}
|
|
5437
|
+
/**
|
|
5438
|
+
* Initialise Sentry for the agent-side CLI/daemon. Idempotent, best-effort, and
|
|
5439
|
+
* a no-op when opted out or when the baked DSN is empty. Never throws.
|
|
5440
|
+
*/
|
|
5441
|
+
async function initAgentSentry(options) {
|
|
5442
|
+
try {
|
|
5443
|
+
if (sentry) return;
|
|
5444
|
+
if (errorReportingDisabled()) return;
|
|
5445
|
+
const dsn = SURFACE_DSNS[options.surface].trim();
|
|
5446
|
+
if (!dsn) return;
|
|
5447
|
+
const mod = await import("@sentry/node");
|
|
5448
|
+
if (mod.getClient()) {
|
|
5449
|
+
sentry = mod;
|
|
5450
|
+
return;
|
|
5451
|
+
}
|
|
5452
|
+
mod.init({
|
|
5453
|
+
dsn,
|
|
5454
|
+
environment: deriveEnvironment(),
|
|
5455
|
+
...options.release ? { release: options.release } : {},
|
|
5456
|
+
sampleRate: 1,
|
|
5457
|
+
tracesSampleRate: 0,
|
|
5458
|
+
sendDefaultPii: false,
|
|
5459
|
+
beforeSend: (event) => scrubEvent(event),
|
|
5460
|
+
beforeBreadcrumb: (breadcrumb) => scrubBreadcrumb(breadcrumb)
|
|
5461
|
+
});
|
|
5462
|
+
sentry = mod;
|
|
5463
|
+
mod.setTag("surface", options.surface);
|
|
5464
|
+
const runtime = deriveRuntime();
|
|
5465
|
+
if (runtime) mod.setTag("runtime", runtime);
|
|
5466
|
+
} catch {
|
|
5467
|
+
sentry = null;
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
/**
|
|
5471
|
+
* Attach the resolved agent identity to all subsequent events. Called by the
|
|
5472
|
+
* daemon once `loadDaemonConfig()` has resolved `agentId`/`runtime`.
|
|
5473
|
+
*/
|
|
5474
|
+
function setAgentContext(context) {
|
|
5475
|
+
if (!sentry) return;
|
|
5476
|
+
try {
|
|
5477
|
+
if (context.agentId) sentry.setTag("agentId", context.agentId);
|
|
5478
|
+
if (context.runtime) sentry.setTag("runtime", context.runtime);
|
|
5479
|
+
} catch {}
|
|
5480
|
+
}
|
|
5481
|
+
/**
|
|
5482
|
+
* Capture an integration lifecycle failure (install/activate/reinstall/remove)
|
|
5483
|
+
* with `{ integration, phase }` tags. Accepts an `Error` (captured with stack)
|
|
5484
|
+
* or a plain message string (captured as an error-level message). No-op when
|
|
5485
|
+
* Sentry is not initialised.
|
|
5486
|
+
*/
|
|
5487
|
+
function captureIntegrationFailure(integration, phase, cause) {
|
|
5488
|
+
if (!sentry) return;
|
|
5489
|
+
try {
|
|
5490
|
+
if (cause instanceof Error) sentry.captureException(cause, { tags: {
|
|
5491
|
+
integration,
|
|
5492
|
+
phase
|
|
5493
|
+
} });
|
|
5494
|
+
else sentry.captureMessage(String(cause), {
|
|
5495
|
+
level: "error",
|
|
5496
|
+
tags: {
|
|
5497
|
+
integration,
|
|
5498
|
+
phase
|
|
5499
|
+
}
|
|
5500
|
+
});
|
|
5501
|
+
} catch {}
|
|
5502
|
+
}
|
|
5503
|
+
/**
|
|
5504
|
+
* Capture a generic CLI failure with a `{ context }` tag. The CLI counterpart
|
|
5505
|
+
* to `captureIntegrationFailure` — used by `exitWithError` at the handled-error
|
|
5506
|
+
* `process.exit()` sites in `@alfe.ai/cli`'s commands, which otherwise report
|
|
5507
|
+
* nothing (the error was caught, never thrown). Accepts an `Error` (captured
|
|
5508
|
+
* with stack), a defined non-Error value (captured as an error-level message),
|
|
5509
|
+
* or nothing (the `context` string itself becomes the message). No-op when
|
|
5510
|
+
* Sentry is not initialised. Never throws.
|
|
5511
|
+
*/
|
|
5512
|
+
function captureCliFailure(context, cause) {
|
|
5513
|
+
if (!sentry) return;
|
|
5514
|
+
try {
|
|
5515
|
+
if (cause instanceof Error) sentry.captureException(cause, { tags: { context } });
|
|
5516
|
+
else if (cause === void 0) sentry.captureMessage(context, { level: "error" });
|
|
5517
|
+
else sentry.captureMessage(String(cause), {
|
|
5518
|
+
level: "error",
|
|
5519
|
+
tags: { context }
|
|
5520
|
+
});
|
|
5521
|
+
} catch {}
|
|
5522
|
+
}
|
|
5523
|
+
/** Flush queued events (small timeout) so short-lived commands deliver them. */
|
|
5524
|
+
async function flushSentry(timeoutMs = 2e3) {
|
|
5525
|
+
if (!sentry) return;
|
|
5526
|
+
try {
|
|
5527
|
+
await sentry.flush(timeoutMs);
|
|
5528
|
+
} catch {}
|
|
5529
|
+
}
|
|
5530
|
+
/**
|
|
5531
|
+
* Capture a fatal error — for the CLI's top-level catch. Does NOT flush: the
|
|
5532
|
+
* caller's `finally { await flushSentry() }` delivers it (flushing here too
|
|
5533
|
+
* doubled the worst-case exit delay on a failing command).
|
|
5534
|
+
*/
|
|
5535
|
+
function captureFatal(cause) {
|
|
5536
|
+
if (!sentry) return;
|
|
5537
|
+
try {
|
|
5538
|
+
sentry.captureException(cause);
|
|
5539
|
+
} catch {}
|
|
5540
|
+
}
|
|
5541
|
+
//#endregion
|
|
5333
5542
|
//#region src/reconciliation.ts
|
|
5334
5543
|
const log$2 = logger$1.child({ component: "Reconciliation" });
|
|
5335
5544
|
/**
|
|
@@ -5466,6 +5675,7 @@ var ReconciliationEngine = class {
|
|
|
5466
5675
|
const attempts = this.manager.getReinstallAttempts(id);
|
|
5467
5676
|
if (attempts >= 3) {
|
|
5468
5677
|
log$2.error(`Auto-reinstall blocked for ${id} — ${String(attempts)} consecutive failures. Manual reinstall required.`);
|
|
5678
|
+
captureIntegrationFailure(id, "reinstall", `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`);
|
|
5469
5679
|
report.errors.push({
|
|
5470
5680
|
integrationId: id,
|
|
5471
5681
|
error: `Max auto-reinstall attempts (${String(attempts)}) reached`
|
|
@@ -5495,6 +5705,7 @@ var ReconciliationEngine = class {
|
|
|
5495
5705
|
} catch (reinstallErr) {
|
|
5496
5706
|
const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
|
|
5497
5707
|
log$2.error({ err: reinstallErr }, `Auto-reinstall failed for ${id}`);
|
|
5708
|
+
captureIntegrationFailure(id, "reinstall", reinstallErr);
|
|
5498
5709
|
report.errors.push({
|
|
5499
5710
|
integrationId: id,
|
|
5500
5711
|
error: reinstallMsg
|
|
@@ -5526,6 +5737,7 @@ var ReconciliationEngine = class {
|
|
|
5526
5737
|
} catch (reactivateErr) {
|
|
5527
5738
|
const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
|
|
5528
5739
|
log$2.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
|
|
5740
|
+
captureIntegrationFailure(id, "reactivate", reactivateErr);
|
|
5529
5741
|
report.errors.push({
|
|
5530
5742
|
integrationId: id,
|
|
5531
5743
|
error: reactivateMsg
|
|
@@ -5540,6 +5752,7 @@ var ReconciliationEngine = class {
|
|
|
5540
5752
|
}
|
|
5541
5753
|
}
|
|
5542
5754
|
log$2.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
|
|
5755
|
+
captureIntegrationFailure(id, "reactivate", `Integration ${id} stuck in error state — re-activation attempts exhausted`);
|
|
5543
5756
|
report.errors.push({
|
|
5544
5757
|
integrationId: id,
|
|
5545
5758
|
error: "Integration is in error state"
|
|
@@ -5588,6 +5801,7 @@ var ReconciliationEngine = class {
|
|
|
5588
5801
|
} catch (err) {
|
|
5589
5802
|
const message = err instanceof Error ? err.message : String(err);
|
|
5590
5803
|
log$2.error({ err }, `Error reconciling ${id}`);
|
|
5804
|
+
captureIntegrationFailure(id, "reconcile_active", err);
|
|
5591
5805
|
report.errors.push({
|
|
5592
5806
|
integrationId: id,
|
|
5593
5807
|
error: message
|
|
@@ -5626,6 +5840,7 @@ var ReconciliationEngine = class {
|
|
|
5626
5840
|
} catch (err) {
|
|
5627
5841
|
const message = err instanceof Error ? err.message : String(err);
|
|
5628
5842
|
log$2.error({ err }, `Error removing ${id}`);
|
|
5843
|
+
captureIntegrationFailure(id, "reconcile_removed", err);
|
|
5629
5844
|
report.errors.push({
|
|
5630
5845
|
integrationId: id,
|
|
5631
5846
|
error: message
|
|
@@ -22530,6 +22745,7 @@ async function getRuntimeVersion(runtime) {
|
|
|
22530
22745
|
* process.exit() can drop buffered log lines — this ensures they're written first.
|
|
22531
22746
|
*/
|
|
22532
22747
|
async function flushAndExit(code) {
|
|
22748
|
+
await flushSentry();
|
|
22533
22749
|
await new Promise((resolve) => {
|
|
22534
22750
|
logger$1.flush();
|
|
22535
22751
|
setTimeout(resolve, 500);
|
|
@@ -22592,6 +22808,10 @@ async function startDaemon() {
|
|
|
22592
22808
|
managed,
|
|
22593
22809
|
pid: process.pid
|
|
22594
22810
|
}, "Starting Alfe Gateway Daemon...");
|
|
22811
|
+
await initAgentSentry({
|
|
22812
|
+
surface: "daemon",
|
|
22813
|
+
release: await getCliVersion()
|
|
22814
|
+
});
|
|
22595
22815
|
if (!managed) {
|
|
22596
22816
|
await mkdir(join(homedir(), ".alfe"), { recursive: true });
|
|
22597
22817
|
const existingPid = await checkExistingDaemon();
|
|
@@ -22608,6 +22828,10 @@ async function startDaemon() {
|
|
|
22608
22828
|
orgId: config.orgId,
|
|
22609
22829
|
wsUrl: config.gatewayWsUrl
|
|
22610
22830
|
}, "Config loaded, identity resolved");
|
|
22831
|
+
setAgentContext({
|
|
22832
|
+
agentId: config.agentId,
|
|
22833
|
+
runtime: config.runtime
|
|
22834
|
+
});
|
|
22611
22835
|
} catch (err) {
|
|
22612
22836
|
const message = err instanceof Error ? err.message : String(err);
|
|
22613
22837
|
const stack = err instanceof Error ? err.stack : void 0;
|
|
@@ -23575,4 +23799,4 @@ function formatDuration(ms) {
|
|
|
23575
23799
|
return `${String(Math.round(seconds / 3600))}h`;
|
|
23576
23800
|
}
|
|
23577
23801
|
//#endregion
|
|
23578
|
-
export { installService as a, uninstallService as c,
|
|
23802
|
+
export { PINNED_OPENCLAW_VERSION as C, resolveAgentIdentity as S, ALFE_DIR as _, installService as a, fetchAgentConfig as b, uninstallService as c, captureFatal as d, captureIntegrationFailure as f, PROTOCOL_VERSION as g, setAgentContext as h, checkExistingDaemon as i, AGENT_DAEMON_SENTRY_DSN as l, initAgentSentry as m, queryDaemonHealth as n, startService as o, flushSentry as p, startDaemon as r, stopExistingDaemon as s, formatHealthReport as t, captureCliFailure as u, PID_PATH as v, loadDaemonConfig as x, SOCKET_PATH as y };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -46,6 +46,81 @@ declare function startDaemon(): Promise<void>;
|
|
|
46
46
|
*/
|
|
47
47
|
declare const PINNED_OPENCLAW_VERSION = "2026.6.8";
|
|
48
48
|
//#endregion
|
|
49
|
+
//#region src/sentry.d.ts
|
|
50
|
+
/**
|
|
51
|
+
* Agent-side error reporting (Sentry) for the Alfe CLI + gateway daemon.
|
|
52
|
+
*
|
|
53
|
+
* This module is the single Sentry bootstrap shared by both published
|
|
54
|
+
* agent-side packages — `@alfe.ai/gateway` (the daemon) and `@alfe.ai/cli`
|
|
55
|
+
* (which re-imports these helpers from `@alfe.ai/gateway`). It exists so
|
|
56
|
+
* integration-installation failures on customer/managed VMs surface centrally
|
|
57
|
+
* instead of silently rotting until someone SSHes into the box.
|
|
58
|
+
*
|
|
59
|
+
* Design constraints (see packages/gateway/DEVELOPING.md → "Error reporting"):
|
|
60
|
+
* - Errors only. No tracing/profiling (`tracesSampleRate: 0`, `sampleRate: 1`).
|
|
61
|
+
* - `@sentry/node` is lazy-imported so `alfe --version` doesn't pay for it.
|
|
62
|
+
* - Sentry can NEVER break the CLI/daemon — every entry point is try/catch'd.
|
|
63
|
+
* - The DSNs are baked as constants: published tarballs run on agent VMs and
|
|
64
|
+
* cannot read the repo's `config/config.ts`. That file's `SENTRY_DSNS`
|
|
65
|
+
* registry (keys `agentDaemon` + `cli`) mirrors them as the human reference.
|
|
66
|
+
* - Secrets are scrubbed in `beforeSend`; request/env payloads are dropped.
|
|
67
|
+
* - Opt-out via `ALFE_ERROR_REPORTING=0|false` or `errorReporting = false`
|
|
68
|
+
* in `~/.alfe/config.toml`. Default ON.
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* Baked per-surface Sentry DSNs (org `alfe-ai`). DSNs are public by design
|
|
72
|
+
* (they only permit event ingestion). Mirrors of `config/config.ts` →
|
|
73
|
+
* `SENTRY_DSNS.agentDaemon` / `SENTRY_DSNS.cli`. If you rotate a DSN, update
|
|
74
|
+
* both places.
|
|
75
|
+
*/
|
|
76
|
+
declare const AGENT_DAEMON_SENTRY_DSN = "https://a47f010d8d39fb4350f913492189f283@o4511008239452160.ingest.us.sentry.io/4511679448547328";
|
|
77
|
+
/** Which surface initialised Sentry — tagged on every event. */
|
|
78
|
+
type SentrySurface = "cli" | "daemon";
|
|
79
|
+
interface InitAgentSentryOptions {
|
|
80
|
+
/** `"cli"` or `"daemon"` — tagged as `surface`. */
|
|
81
|
+
surface: SentrySurface;
|
|
82
|
+
/** Release identifier — the package version. */
|
|
83
|
+
release?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Initialise Sentry for the agent-side CLI/daemon. Idempotent, best-effort, and
|
|
87
|
+
* a no-op when opted out or when the baked DSN is empty. Never throws.
|
|
88
|
+
*/
|
|
89
|
+
declare function initAgentSentry(options: InitAgentSentryOptions): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Attach the resolved agent identity to all subsequent events. Called by the
|
|
92
|
+
* daemon once `loadDaemonConfig()` has resolved `agentId`/`runtime`.
|
|
93
|
+
*/
|
|
94
|
+
declare function setAgentContext(context: {
|
|
95
|
+
agentId?: string;
|
|
96
|
+
runtime?: string;
|
|
97
|
+
}): void;
|
|
98
|
+
/**
|
|
99
|
+
* Capture an integration lifecycle failure (install/activate/reinstall/remove)
|
|
100
|
+
* with `{ integration, phase }` tags. Accepts an `Error` (captured with stack)
|
|
101
|
+
* or a plain message string (captured as an error-level message). No-op when
|
|
102
|
+
* Sentry is not initialised.
|
|
103
|
+
*/
|
|
104
|
+
declare function captureIntegrationFailure(integration: string, phase: string, cause: unknown): void;
|
|
105
|
+
/**
|
|
106
|
+
* Capture a generic CLI failure with a `{ context }` tag. The CLI counterpart
|
|
107
|
+
* to `captureIntegrationFailure` — used by `exitWithError` at the handled-error
|
|
108
|
+
* `process.exit()` sites in `@alfe.ai/cli`'s commands, which otherwise report
|
|
109
|
+
* nothing (the error was caught, never thrown). Accepts an `Error` (captured
|
|
110
|
+
* with stack), a defined non-Error value (captured as an error-level message),
|
|
111
|
+
* or nothing (the `context` string itself becomes the message). No-op when
|
|
112
|
+
* Sentry is not initialised. Never throws.
|
|
113
|
+
*/
|
|
114
|
+
declare function captureCliFailure(context: string, cause?: unknown): void;
|
|
115
|
+
/** Flush queued events (small timeout) so short-lived commands deliver them. */
|
|
116
|
+
declare function flushSentry(timeoutMs?: number): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Capture a fatal error — for the CLI's top-level catch. Does NOT flush: the
|
|
119
|
+
* caller's `finally { await flushSentry() }` delivers it (flushing here too
|
|
120
|
+
* doubled the worst-case exit delay on a failing command).
|
|
121
|
+
*/
|
|
122
|
+
declare function captureFatal(cause: unknown): void;
|
|
123
|
+
//#endregion
|
|
49
124
|
//#region src/config.d.ts
|
|
50
125
|
/**
|
|
51
126
|
* Daemon configuration — reads ~/.alfe/config.toml and resolves agent identity.
|
|
@@ -218,4 +293,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
|
|
|
218
293
|
*/
|
|
219
294
|
declare function stopExistingDaemon(): Promise<boolean>;
|
|
220
295
|
//#endregion
|
|
221
|
-
export { ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, checkExistingDaemon, fetchAgentConfig, formatHealthReport, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, startDaemon, startService, stopExistingDaemon, uninstallService };
|
|
296
|
+
export { AGENT_DAEMON_SENTRY_DSN, ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, type InitAgentSentryOptions, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, setAgentContext, startDaemon, startService, stopExistingDaemon, uninstallService };
|
package/dist/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as installService, c as uninstallService, d as
|
|
1
|
+
import { C as PINNED_OPENCLAW_VERSION, S as resolveAgentIdentity, _ as ALFE_DIR, a as installService, b as fetchAgentConfig, c as uninstallService, d as captureFatal, f as captureIntegrationFailure, g as PROTOCOL_VERSION, h as setAgentContext, i as checkExistingDaemon, l as AGENT_DAEMON_SENTRY_DSN, m as initAgentSentry, n as queryDaemonHealth, o as startService, p as flushSentry, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport, u as captureCliFailure, v as PID_PATH, x as loadDaemonConfig, y as SOCKET_PATH } from "../health.js";
|
|
2
2
|
import { n as logger } from "../logger.js";
|
|
3
|
-
export { ALFE_DIR, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, checkExistingDaemon, fetchAgentConfig, formatHealthReport, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, startDaemon, startService, stopExistingDaemon, uninstallService };
|
|
3
|
+
export { AGENT_DAEMON_SENTRY_DSN, ALFE_DIR, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, captureCliFailure, captureFatal, captureIntegrationFailure, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, setAgentContext, startDaemon, startService, stopExistingDaemon, uninstallService };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
+
"@sentry/node": "^10.42.0",
|
|
21
22
|
"pino": "^9.6.0",
|
|
22
23
|
"pino-roll": "^1.2.0",
|
|
23
24
|
"smol-toml": ">=1.6.1",
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
"@alfe.ai/ai-proxy-local": "^0.0.13",
|
|
27
28
|
"@alfe.ai/config": "^0.3.0",
|
|
28
29
|
"@alfe.ai/integration-manifest": "^0.3.1",
|
|
29
|
-
"@alfe.ai/integrations": "^0.2.
|
|
30
|
+
"@alfe.ai/integrations": "^0.2.7",
|
|
30
31
|
"@alfe.ai/mcp-bundler": "^0.2.2"
|
|
31
32
|
},
|
|
32
33
|
"license": "UNLICENSED",
|