@alfe.ai/gateway 0.3.2 → 0.5.0
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 +601 -54
- package/dist/src/index.d.ts +68 -1
- package/dist/src/index.js +2 -2
- package/package.json +4 -4
package/dist/bin/gateway.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as installService, c as uninstallService, i as checkExistingDaemon, n as queryDaemonHealth, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport,
|
|
2
|
+
import { a as installService, c as uninstallService, i as checkExistingDaemon, n as queryDaemonHealth, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport, w 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
|
@@ -5266,7 +5266,7 @@ function isIPCResponse(msg) {
|
|
|
5266
5266
|
const PROTOCOL_VERSION = 1;
|
|
5267
5267
|
//#endregion
|
|
5268
5268
|
//#region src/runtime-gate.ts
|
|
5269
|
-
const log$
|
|
5269
|
+
const log$4 = logger$1.child({ component: "RuntimeGate" });
|
|
5270
5270
|
var RuntimeGate = class {
|
|
5271
5271
|
depth = 0;
|
|
5272
5272
|
wasRunning = false;
|
|
@@ -5284,7 +5284,7 @@ var RuntimeGate = class {
|
|
|
5284
5284
|
const rp = this.getRuntime();
|
|
5285
5285
|
this.wasRunning = rp?.isRunning ?? false;
|
|
5286
5286
|
if (rp && this.wasRunning) {
|
|
5287
|
-
log$
|
|
5287
|
+
log$4.info("Suspending runtime for mutating reconcile (avoid concurrent SQLite writers)");
|
|
5288
5288
|
await rp.stop();
|
|
5289
5289
|
}
|
|
5290
5290
|
}
|
|
@@ -5299,7 +5299,7 @@ var RuntimeGate = class {
|
|
|
5299
5299
|
if (this.depth < 0) this.depth = 0;
|
|
5300
5300
|
const rp = this.getRuntime();
|
|
5301
5301
|
if (rp && this.wasRunning) {
|
|
5302
|
-
log$
|
|
5302
|
+
log$4.info("Resuming runtime after mutating reconcile");
|
|
5303
5303
|
rp.resume();
|
|
5304
5304
|
}
|
|
5305
5305
|
this.wasRunning = false;
|
|
@@ -5314,12 +5314,12 @@ var RuntimeGate = class {
|
|
|
5314
5314
|
*/
|
|
5315
5315
|
requestRestart() {
|
|
5316
5316
|
if (this.depth > 0) {
|
|
5317
|
-
log$
|
|
5317
|
+
log$4.info("Runtime restart requested while suspended — deferring to pending resume");
|
|
5318
5318
|
return;
|
|
5319
5319
|
}
|
|
5320
5320
|
const rp = this.getRuntime();
|
|
5321
5321
|
if (rp) rp.restart().catch((err) => {
|
|
5322
|
-
log$
|
|
5322
|
+
log$4.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to restart runtime");
|
|
5323
5323
|
});
|
|
5324
5324
|
}
|
|
5325
5325
|
};
|
|
@@ -5366,14 +5366,40 @@ var NoopRuntimeGate = class {
|
|
|
5366
5366
|
* both places.
|
|
5367
5367
|
*/
|
|
5368
5368
|
const AGENT_DAEMON_SENTRY_DSN = "https://a47f010d8d39fb4350f913492189f283@o4511008239452160.ingest.us.sentry.io/4511679448547328";
|
|
5369
|
+
const CLI_SENTRY_DSN = "https://82dde4631336561f2fcc89d7531623de@o4511008239452160.ingest.us.sentry.io/4511679411191808";
|
|
5370
|
+
/**
|
|
5371
|
+
* Dedicated project for errors emitted BY the agent runtime child process
|
|
5372
|
+
* (OpenClaw/Hermes) — crashes, spawn failures, and error-looking output. Kept
|
|
5373
|
+
* separate from `agent-daemon` so runtime noise never drowns daemon issues.
|
|
5374
|
+
* Mirrors `SENTRY_DSNS.agentRuntime` in `config/config.ts`.
|
|
5375
|
+
*/
|
|
5376
|
+
const AGENT_RUNTIME_SENTRY_DSN = "https://feb7fb2e3b8e723aec518e74715bfa6d@o4511008239452160.ingest.us.sentry.io/4511683667558400";
|
|
5377
|
+
/**
|
|
5378
|
+
* MCP tool/server failures on agent VMs — reported by the daemon-hosted MCP
|
|
5379
|
+
* bundler (tool errors, child crashes, stderr output). This is the SAME
|
|
5380
|
+
* project as the cloud MCP Fly service (`SENTRY_DSNS.mcp`); agent-side events
|
|
5381
|
+
* are distinguished by `source: agent-daemon` + `agentId` tags.
|
|
5382
|
+
*/
|
|
5383
|
+
const AGENT_MCP_SENTRY_DSN = "https://638309eac96146a945f46b578f10c806@o4511008239452160.ingest.us.sentry.io/4511146364174336";
|
|
5369
5384
|
/** Surface → Sentry project: `cli` and `agent-daemon` respectively. */
|
|
5370
5385
|
const SURFACE_DSNS = {
|
|
5371
|
-
cli:
|
|
5386
|
+
cli: CLI_SENTRY_DSN,
|
|
5372
5387
|
daemon: AGENT_DAEMON_SENTRY_DSN
|
|
5373
5388
|
};
|
|
5374
5389
|
/** Held after a successful init so the capture helpers can run synchronously. */
|
|
5375
5390
|
let sentry = null;
|
|
5376
5391
|
/**
|
|
5392
|
+
* Additional bound clients (daemon surface only). The default client keeps the
|
|
5393
|
+
* daemon's own DSN; runtime child-process events and MCP failures are routed
|
|
5394
|
+
* to their own projects via explicitly-bound Scopes — the documented
|
|
5395
|
+
* multi-client pattern. `null` until `initAgentSentry({ surface: "daemon" })`
|
|
5396
|
+
* succeeds.
|
|
5397
|
+
*/
|
|
5398
|
+
let runtimeClient = null;
|
|
5399
|
+
let runtimeScope = null;
|
|
5400
|
+
let mcpClient = null;
|
|
5401
|
+
let mcpScope = null;
|
|
5402
|
+
/**
|
|
5377
5403
|
* Map the configured API URL to a coarse environment tag. Best-effort — returns
|
|
5378
5404
|
* `"unknown"` when no config is present yet (e.g. `alfe login` pre-setup).
|
|
5379
5405
|
*/
|
|
@@ -5404,12 +5430,13 @@ function errorReportingDisabled() {
|
|
|
5404
5430
|
} catch {}
|
|
5405
5431
|
return false;
|
|
5406
5432
|
}
|
|
5407
|
-
const KV_SECRET_RE =
|
|
5433
|
+
const KV_SECRET_RE = /(?<![A-Za-z0-9])([A-Za-z0-9_-]*(?:authorization|api[_-]?key|apikey|token|secret|password|passwd|bearer|credential)s?)(\s*[:=]\s*|\s+)("?)([^\s"']+)\3/gi;
|
|
5434
|
+
const URL_QUERY_SECRET_RE = /([?&](?:[A-Za-z0-9_-]*(?:token|secret|key|signature|credential|password)|sig|code|x-amz-[a-z-]+)=)[^&\s"']+/gi;
|
|
5408
5435
|
const MIN_BARE_SECRET_LEN = 16;
|
|
5409
5436
|
const ALFE_TOKEN_RE = /alfe_(?:dev|test|demo|live)_[A-Za-z0-9._-]+/g;
|
|
5410
5437
|
const BEARER_RE = /\bbearer\s+[A-Za-z0-9._\-+/=]+/gi;
|
|
5411
5438
|
function scrubString(value) {
|
|
5412
|
-
return value.replace(BEARER_RE, "Bearer [REDACTED]").replace(KV_SECRET_RE, (match, label, sep, _q, secret) => {
|
|
5439
|
+
return value.replace(BEARER_RE, "Bearer [REDACTED]").replace(URL_QUERY_SECRET_RE, "$1[REDACTED]").replace(KV_SECRET_RE, (match, label, sep, _q, secret) => {
|
|
5413
5440
|
if (!/[:=]/.test(sep) && secret.length < MIN_BARE_SECRET_LEN) return match;
|
|
5414
5441
|
return `${label}${sep}[REDACTED]`;
|
|
5415
5442
|
}).replace(ALFE_TOKEN_RE, "alfe_[REDACTED]");
|
|
@@ -5453,6 +5480,47 @@ async function initAgentSentry(options) {
|
|
|
5453
5480
|
const dsn = SURFACE_DSNS[options.surface].trim();
|
|
5454
5481
|
if (!dsn) return;
|
|
5455
5482
|
const mod = await import("@sentry/node");
|
|
5483
|
+
if (options.surface === "daemon") {
|
|
5484
|
+
const buildBoundScope = (dsn, extraTags) => {
|
|
5485
|
+
try {
|
|
5486
|
+
if (!dsn.trim()) return null;
|
|
5487
|
+
const client = new mod.NodeClient({
|
|
5488
|
+
dsn: dsn.trim(),
|
|
5489
|
+
environment: deriveEnvironment(),
|
|
5490
|
+
...options.release ? { release: options.release } : {},
|
|
5491
|
+
sampleRate: 1,
|
|
5492
|
+
tracesSampleRate: 0,
|
|
5493
|
+
sendDefaultPii: false,
|
|
5494
|
+
transport: mod.makeNodeTransport,
|
|
5495
|
+
stackParser: mod.defaultStackParser,
|
|
5496
|
+
integrations: [],
|
|
5497
|
+
beforeSend: (event) => scrubEvent(event)
|
|
5498
|
+
});
|
|
5499
|
+
client.init();
|
|
5500
|
+
const scope = new mod.Scope();
|
|
5501
|
+
scope.setClient(client);
|
|
5502
|
+
const runtime = deriveRuntime();
|
|
5503
|
+
if (runtime) scope.setTag("runtime", runtime);
|
|
5504
|
+
for (const [k, v] of Object.entries(extraTags)) scope.setTag(k, v);
|
|
5505
|
+
return {
|
|
5506
|
+
client,
|
|
5507
|
+
scope
|
|
5508
|
+
};
|
|
5509
|
+
} catch {
|
|
5510
|
+
return null;
|
|
5511
|
+
}
|
|
5512
|
+
};
|
|
5513
|
+
if (!runtimeScope) {
|
|
5514
|
+
const bound = buildBoundScope(AGENT_RUNTIME_SENTRY_DSN, {});
|
|
5515
|
+
runtimeClient = bound?.client ?? null;
|
|
5516
|
+
runtimeScope = bound?.scope ?? null;
|
|
5517
|
+
}
|
|
5518
|
+
if (!mcpScope) {
|
|
5519
|
+
const bound = buildBoundScope(AGENT_MCP_SENTRY_DSN, { source: "agent-daemon" });
|
|
5520
|
+
mcpClient = bound?.client ?? null;
|
|
5521
|
+
mcpScope = bound?.scope ?? null;
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5456
5524
|
if (mod.getClient()) {
|
|
5457
5525
|
sentry = mod;
|
|
5458
5526
|
return;
|
|
@@ -5464,6 +5532,7 @@ async function initAgentSentry(options) {
|
|
|
5464
5532
|
sampleRate: 1,
|
|
5465
5533
|
tracesSampleRate: 0,
|
|
5466
5534
|
sendDefaultPii: false,
|
|
5535
|
+
...options.surface === "daemon" ? { integrations: (defaults) => defaults.filter((i) => i.name !== "OnUncaughtException" && i.name !== "OnUnhandledRejection") } : {},
|
|
5467
5536
|
beforeSend: (event) => scrubEvent(event),
|
|
5468
5537
|
beforeBreadcrumb: (breadcrumb) => scrubBreadcrumb(breadcrumb)
|
|
5469
5538
|
});
|
|
@@ -5482,8 +5551,15 @@ async function initAgentSentry(options) {
|
|
|
5482
5551
|
function setAgentContext(context) {
|
|
5483
5552
|
if (!sentry) return;
|
|
5484
5553
|
try {
|
|
5485
|
-
|
|
5486
|
-
|
|
5554
|
+
for (const scope of [
|
|
5555
|
+
sentry,
|
|
5556
|
+
runtimeScope,
|
|
5557
|
+
mcpScope
|
|
5558
|
+
]) {
|
|
5559
|
+
if (!scope) continue;
|
|
5560
|
+
if (context.agentId) scope.setTag("agentId", context.agentId);
|
|
5561
|
+
if (context.runtime) scope.setTag("runtime", context.runtime);
|
|
5562
|
+
}
|
|
5487
5563
|
} catch {}
|
|
5488
5564
|
}
|
|
5489
5565
|
/**
|
|
@@ -5528,8 +5604,105 @@ function captureCliFailure(context, cause) {
|
|
|
5528
5604
|
});
|
|
5529
5605
|
} catch {}
|
|
5530
5606
|
}
|
|
5607
|
+
/**
|
|
5608
|
+
* Capture a runtime child-process crash (non-graceful exit) or spawn failure
|
|
5609
|
+
* into the dedicated `agent-runtime` project. Recent output travels as
|
|
5610
|
+
* breadcrumbs — NEVER `extra`, which `scrubEvent` (the runtime client's
|
|
5611
|
+
* `beforeSend`) deletes. Throttling is the caller's job (`CaptureThrottle` in
|
|
5612
|
+
* `runtime-output-monitor.ts`). No-op when the runtime client is not
|
|
5613
|
+
* initialised. Never throws.
|
|
5614
|
+
*/
|
|
5615
|
+
function captureRuntimeCrash(opts) {
|
|
5616
|
+
if (!runtimeScope) return;
|
|
5617
|
+
try {
|
|
5618
|
+
const scope = runtimeScope.clone();
|
|
5619
|
+
for (const { stream, line } of opts.recentOutput) scope.addBreadcrumb({
|
|
5620
|
+
category: `runtime.${stream}`,
|
|
5621
|
+
level: stream === "stderr" ? "warning" : "info",
|
|
5622
|
+
message: line
|
|
5623
|
+
});
|
|
5624
|
+
const crashKey = opts.spawnError ? `spawn:${opts.spawnError.code ?? "error"}` : String(opts.code ?? opts.signal ?? "unknown");
|
|
5625
|
+
scope.setFingerprint([
|
|
5626
|
+
"runtime-crash",
|
|
5627
|
+
opts.runtime,
|
|
5628
|
+
crashKey
|
|
5629
|
+
]);
|
|
5630
|
+
scope.setTags({
|
|
5631
|
+
runtime: opts.runtime,
|
|
5632
|
+
exitCode: String(opts.code),
|
|
5633
|
+
signal: String(opts.signal),
|
|
5634
|
+
crashesSuppressed: String(opts.crashesSuppressed ?? 0)
|
|
5635
|
+
});
|
|
5636
|
+
if (opts.spawnError) scope.captureException(opts.spawnError);
|
|
5637
|
+
else scope.captureMessage(`Runtime ${opts.runtime} crashed (code=${String(opts.code)}, signal=${String(opts.signal)})`, "error");
|
|
5638
|
+
} catch {}
|
|
5639
|
+
}
|
|
5640
|
+
/**
|
|
5641
|
+
* Capture an error-looking block of runtime output (stack trace, Python
|
|
5642
|
+
* traceback, or ERROR-level log line) detected while the child is running.
|
|
5643
|
+
* The block lines travel as breadcrumbs; the head line is the message.
|
|
5644
|
+
* Throttling/dedupe is the caller's job. No-op when Sentry is not initialised.
|
|
5645
|
+
* Never throws.
|
|
5646
|
+
*/
|
|
5647
|
+
function captureRuntimeErrorOutput(opts) {
|
|
5648
|
+
if (!runtimeScope) return;
|
|
5649
|
+
try {
|
|
5650
|
+
const scope = runtimeScope.clone();
|
|
5651
|
+
for (const line of opts.lines) scope.addBreadcrumb({
|
|
5652
|
+
category: `runtime.${opts.stream}`,
|
|
5653
|
+
level: "error",
|
|
5654
|
+
message: line
|
|
5655
|
+
});
|
|
5656
|
+
scope.setFingerprint([
|
|
5657
|
+
"runtime-output",
|
|
5658
|
+
opts.runtime,
|
|
5659
|
+
opts.kind,
|
|
5660
|
+
opts.fingerprintKey
|
|
5661
|
+
]);
|
|
5662
|
+
scope.setTags({
|
|
5663
|
+
runtime: opts.runtime,
|
|
5664
|
+
stream: opts.stream,
|
|
5665
|
+
kind: opts.kind,
|
|
5666
|
+
suppressedCount: String(opts.suppressedCount ?? 0)
|
|
5667
|
+
});
|
|
5668
|
+
scope.captureMessage(opts.lines[0].slice(0, 300), "error");
|
|
5669
|
+
} catch {}
|
|
5670
|
+
}
|
|
5671
|
+
/**
|
|
5672
|
+
* Capture an MCP failure (tool error, server crash, or error-looking stderr
|
|
5673
|
+
* output) into the `mcp` project via the daemon's bound MCP client. Throttling
|
|
5674
|
+
* is the caller's job (`mcp-error-capture.ts`). No-op when the MCP client is
|
|
5675
|
+
* not initialised. Never throws.
|
|
5676
|
+
*/
|
|
5677
|
+
function captureMcpFailure(opts) {
|
|
5678
|
+
if (!mcpScope) return;
|
|
5679
|
+
try {
|
|
5680
|
+
const scope = mcpScope.clone();
|
|
5681
|
+
for (const line of opts.lines ?? []) scope.addBreadcrumb({
|
|
5682
|
+
category: `mcp.${opts.server}`,
|
|
5683
|
+
level: "error",
|
|
5684
|
+
message: line
|
|
5685
|
+
});
|
|
5686
|
+
scope.setFingerprint([
|
|
5687
|
+
"agent-mcp",
|
|
5688
|
+
opts.server,
|
|
5689
|
+
opts.kind,
|
|
5690
|
+
...opts.tool ? [opts.tool] : []
|
|
5691
|
+
]);
|
|
5692
|
+
scope.setTags({
|
|
5693
|
+
server: opts.server,
|
|
5694
|
+
kind: opts.kind,
|
|
5695
|
+
...opts.tool ? { tool: opts.tool } : {},
|
|
5696
|
+
suppressedCount: String(opts.suppressedCount ?? 0)
|
|
5697
|
+
});
|
|
5698
|
+
scope.captureMessage(`MCP ${opts.server}${opts.tool ? `.${opts.tool}` : ""} ${opts.kind}: ${opts.message.slice(0, 300)}`, "error");
|
|
5699
|
+
} catch {}
|
|
5700
|
+
}
|
|
5531
5701
|
/** Flush queued events (small timeout) so short-lived commands deliver them. */
|
|
5532
5702
|
async function flushSentry(timeoutMs = 2e3) {
|
|
5703
|
+
for (const client of [runtimeClient, mcpClient]) try {
|
|
5704
|
+
if (client) await client.flush(timeoutMs);
|
|
5705
|
+
} catch {}
|
|
5533
5706
|
if (!sentry) return;
|
|
5534
5707
|
try {
|
|
5535
5708
|
await sentry.flush(timeoutMs);
|
|
@@ -5548,7 +5721,7 @@ function captureFatal(cause) {
|
|
|
5548
5721
|
}
|
|
5549
5722
|
//#endregion
|
|
5550
5723
|
//#region src/reconciliation.ts
|
|
5551
|
-
const log$
|
|
5724
|
+
const log$3 = logger$1.child({ component: "Reconciliation" });
|
|
5552
5725
|
/**
|
|
5553
5726
|
* How many times reconcile will re-attempt activation of an intact integration
|
|
5554
5727
|
* stuck in `error` (with no reinstall requested) before giving up and waiting
|
|
@@ -5618,7 +5791,7 @@ var ReconciliationEngine = class {
|
|
|
5618
5791
|
try {
|
|
5619
5792
|
localIntegrations = await this.manager.getInstalledIntegrations();
|
|
5620
5793
|
} catch (err) {
|
|
5621
|
-
log$
|
|
5794
|
+
log$3.error({ err }, "Failed to get local integrations");
|
|
5622
5795
|
localIntegrations = [];
|
|
5623
5796
|
}
|
|
5624
5797
|
const localMap = new Map(localIntegrations.map((i) => [i.id, i]));
|
|
@@ -5637,10 +5810,10 @@ var ReconciliationEngine = class {
|
|
|
5637
5810
|
try {
|
|
5638
5811
|
if (!local) {
|
|
5639
5812
|
await this.ensureSuspended();
|
|
5640
|
-
log$
|
|
5813
|
+
log$3.info(`Installing ${id}@${desired.version}`);
|
|
5641
5814
|
await this.manager.install(id, desired.version, desired.config, desired.customSource);
|
|
5642
5815
|
report.installed.push(id);
|
|
5643
|
-
log$
|
|
5816
|
+
log$3.info(`Activating ${id}`);
|
|
5644
5817
|
if ((await this.manager.activate(id)).configApplied) report.configApplied = true;
|
|
5645
5818
|
report.activated.push(id);
|
|
5646
5819
|
report.results.push({
|
|
@@ -5652,7 +5825,7 @@ var ReconciliationEngine = class {
|
|
|
5652
5825
|
}
|
|
5653
5826
|
if (local.version !== desired.version && desired.version !== "" && local.version !== "unknown") {
|
|
5654
5827
|
await this.ensureSuspended();
|
|
5655
|
-
log$
|
|
5828
|
+
log$3.info(`Upgrading ${id}: ${local.version} → ${desired.version}`);
|
|
5656
5829
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
5657
5830
|
report.installed.push(id);
|
|
5658
5831
|
report.activated.push(id);
|
|
@@ -5666,7 +5839,7 @@ var ReconciliationEngine = class {
|
|
|
5666
5839
|
if (local.status === "error") {
|
|
5667
5840
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5668
5841
|
await this.ensureSuspended();
|
|
5669
|
-
log$
|
|
5842
|
+
log$3.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
5670
5843
|
this.manager.resetReinstallAttempts(id);
|
|
5671
5844
|
this.activateAttempts.delete(id);
|
|
5672
5845
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5682,7 +5855,7 @@ var ReconciliationEngine = class {
|
|
|
5682
5855
|
if (!await this.manager.isInstallIntact(id)) {
|
|
5683
5856
|
const attempts = this.manager.getReinstallAttempts(id);
|
|
5684
5857
|
if (attempts >= 3) {
|
|
5685
|
-
log$
|
|
5858
|
+
log$3.error(`Auto-reinstall blocked for ${id} — ${String(attempts)} consecutive failures. Manual reinstall required.`);
|
|
5686
5859
|
captureIntegrationFailure(id, "reinstall", `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`);
|
|
5687
5860
|
report.errors.push({
|
|
5688
5861
|
integrationId: id,
|
|
@@ -5697,7 +5870,7 @@ var ReconciliationEngine = class {
|
|
|
5697
5870
|
return;
|
|
5698
5871
|
}
|
|
5699
5872
|
await this.ensureSuspended();
|
|
5700
|
-
log$
|
|
5873
|
+
log$3.info(`Auto-reinstalling ${id} — install directory is corrupted or missing (attempt ${String(attempts + 1)}/3)`);
|
|
5701
5874
|
this.manager.incrementReinstallAttempts(id);
|
|
5702
5875
|
try {
|
|
5703
5876
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5712,7 +5885,7 @@ var ReconciliationEngine = class {
|
|
|
5712
5885
|
});
|
|
5713
5886
|
} catch (reinstallErr) {
|
|
5714
5887
|
const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
|
|
5715
|
-
log$
|
|
5888
|
+
log$3.error({ err: reinstallErr }, `Auto-reinstall failed for ${id}`);
|
|
5716
5889
|
captureIntegrationFailure(id, "reinstall", reinstallErr);
|
|
5717
5890
|
report.errors.push({
|
|
5718
5891
|
integrationId: id,
|
|
@@ -5731,7 +5904,7 @@ var ReconciliationEngine = class {
|
|
|
5731
5904
|
if (activateAttempts < MAX_ERROR_ACTIVATE_ATTEMPTS) {
|
|
5732
5905
|
this.activateAttempts.set(id, activateAttempts + 1);
|
|
5733
5906
|
await this.ensureSuspended();
|
|
5734
|
-
log$
|
|
5907
|
+
log$3.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
|
|
5735
5908
|
try {
|
|
5736
5909
|
if ((await this.manager.activate(id)).configApplied) report.configApplied = true;
|
|
5737
5910
|
this.activateAttempts.delete(id);
|
|
@@ -5744,7 +5917,7 @@ var ReconciliationEngine = class {
|
|
|
5744
5917
|
return;
|
|
5745
5918
|
} catch (reactivateErr) {
|
|
5746
5919
|
const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
|
|
5747
|
-
log$
|
|
5920
|
+
log$3.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
|
|
5748
5921
|
captureIntegrationFailure(id, "reactivate", reactivateErr);
|
|
5749
5922
|
report.errors.push({
|
|
5750
5923
|
integrationId: id,
|
|
@@ -5759,7 +5932,7 @@ var ReconciliationEngine = class {
|
|
|
5759
5932
|
return;
|
|
5760
5933
|
}
|
|
5761
5934
|
}
|
|
5762
|
-
log$
|
|
5935
|
+
log$3.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
|
|
5763
5936
|
captureIntegrationFailure(id, "reactivate", `Integration ${id} stuck in error state — re-activation attempts exhausted`);
|
|
5764
5937
|
report.errors.push({
|
|
5765
5938
|
integrationId: id,
|
|
@@ -5775,7 +5948,7 @@ var ReconciliationEngine = class {
|
|
|
5775
5948
|
}
|
|
5776
5949
|
if (local.status !== "active") {
|
|
5777
5950
|
await this.ensureSuspended();
|
|
5778
|
-
log$
|
|
5951
|
+
log$3.info(`Activating ${id}`);
|
|
5779
5952
|
if ((await this.manager.activate(id)).configApplied) report.configApplied = true;
|
|
5780
5953
|
report.activated.push(id);
|
|
5781
5954
|
report.results.push({
|
|
@@ -5787,7 +5960,7 @@ var ReconciliationEngine = class {
|
|
|
5787
5960
|
}
|
|
5788
5961
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5789
5962
|
await this.ensureSuspended();
|
|
5790
|
-
log$
|
|
5963
|
+
log$3.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
5791
5964
|
this.manager.resetReinstallAttempts(id);
|
|
5792
5965
|
this.activateAttempts.delete(id);
|
|
5793
5966
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5808,7 +5981,7 @@ var ReconciliationEngine = class {
|
|
|
5808
5981
|
});
|
|
5809
5982
|
} catch (err) {
|
|
5810
5983
|
const message = err instanceof Error ? err.message : String(err);
|
|
5811
|
-
log$
|
|
5984
|
+
log$3.error({ err }, `Error reconciling ${id}`);
|
|
5812
5985
|
captureIntegrationFailure(id, "reconcile_active", err);
|
|
5813
5986
|
report.errors.push({
|
|
5814
5987
|
integrationId: id,
|
|
@@ -5833,7 +6006,7 @@ var ReconciliationEngine = class {
|
|
|
5833
6006
|
}
|
|
5834
6007
|
try {
|
|
5835
6008
|
await this.ensureSuspended();
|
|
5836
|
-
log$
|
|
6009
|
+
log$3.info(`Removing ${id}`);
|
|
5837
6010
|
if (local.status === "active") {
|
|
5838
6011
|
await this.manager.deactivate(id);
|
|
5839
6012
|
report.deactivated.push(id);
|
|
@@ -5847,7 +6020,7 @@ var ReconciliationEngine = class {
|
|
|
5847
6020
|
});
|
|
5848
6021
|
} catch (err) {
|
|
5849
6022
|
const message = err instanceof Error ? err.message : String(err);
|
|
5850
|
-
log$
|
|
6023
|
+
log$3.error({ err }, `Error removing ${id}`);
|
|
5851
6024
|
captureIntegrationFailure(id, "reconcile_removed", err);
|
|
5852
6025
|
report.errors.push({
|
|
5853
6026
|
integrationId: id,
|
|
@@ -21980,6 +22153,177 @@ function createLogger(component, additionalData) {
|
|
|
21980
22153
|
component
|
|
21981
22154
|
});
|
|
21982
22155
|
}
|
|
22156
|
+
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
22157
|
+
function stripAnsi(line) {
|
|
22158
|
+
return line.replace(ANSI_RE, "");
|
|
22159
|
+
}
|
|
22160
|
+
/** Rolling window of the child's most recent output lines (both streams). */
|
|
22161
|
+
var OutputRingBuffer = class {
|
|
22162
|
+
lines = [];
|
|
22163
|
+
push(line) {
|
|
22164
|
+
this.lines.push({
|
|
22165
|
+
stream: line.stream,
|
|
22166
|
+
line: line.line.length > 500 ? line.line.slice(0, 500) : line.line
|
|
22167
|
+
});
|
|
22168
|
+
if (this.lines.length > 50) this.lines.shift();
|
|
22169
|
+
}
|
|
22170
|
+
/** Oldest → newest copy. */
|
|
22171
|
+
snapshot() {
|
|
22172
|
+
return this.lines.map((l) => ({ ...l }));
|
|
22173
|
+
}
|
|
22174
|
+
};
|
|
22175
|
+
const PY_TRACEBACK_START = "Traceback (most recent call last):";
|
|
22176
|
+
const NODE_ERROR_HEAD = /^\s*(?:Uncaught\s+|Unhandled\w*\s+)?[A-Z][A-Za-z0-9_]*(?:Error|Exception)\b\s*[:(]/;
|
|
22177
|
+
const LOG_ERROR_LEVEL = /^.{0,40}(?:\[(?:ERROR|FATAL|CRITICAL)\]|\b(?:ERROR|FATAL|CRITICAL)\b\s*[:|])/;
|
|
22178
|
+
const JSON_ERROR_LEVEL = /"level"\s*:\s*(?:"(?:error|fatal)"|(?:50|60)\b)/;
|
|
22179
|
+
const NODE_FRAME = /^\s+at\s/;
|
|
22180
|
+
const NODE_CAUSE = /^\s*caused by:?/i;
|
|
22181
|
+
const NODE_CHAINED = /^\s*[A-Z][A-Za-z0-9_]*(?:Error|Exception)\b\s*:/;
|
|
22182
|
+
const PY_FILE = /^\s*File "/;
|
|
22183
|
+
const PY_INDENTED = /^\s{2,}\S/;
|
|
22184
|
+
const PY_TERMINAL = /^[A-Za-z_][\w.]*(?:Error|Exception|Interrupt)?:/;
|
|
22185
|
+
/** Digits/hex-stripped, lowercased head line — stable across timestamps/ids. */
|
|
22186
|
+
function toFingerprintKey(head) {
|
|
22187
|
+
return head.toLowerCase().replace(/\b0x[0-9a-f]+\b/g, "<hex>").replace(/\d+/g, "<n>").trim().slice(0, 120);
|
|
22188
|
+
}
|
|
22189
|
+
function compact(blocks) {
|
|
22190
|
+
return blocks.filter((b) => b !== null);
|
|
22191
|
+
}
|
|
22192
|
+
/**
|
|
22193
|
+
* Stateful multi-line error detector. Feed lines (per stream) as they arrive;
|
|
22194
|
+
* returns the blocks completed by that line (usually zero or one — two when a
|
|
22195
|
+
* line closes a pending block AND is itself a single-line error-log block).
|
|
22196
|
+
* Call `flush()` on quiet periods / process exit to complete a trailing block.
|
|
22197
|
+
*/
|
|
22198
|
+
var ErrorLineDetector = class {
|
|
22199
|
+
pending = null;
|
|
22200
|
+
get isCollecting() {
|
|
22201
|
+
return this.pending !== null;
|
|
22202
|
+
}
|
|
22203
|
+
feed(stream, rawLine) {
|
|
22204
|
+
const line = stripAnsi(rawLine);
|
|
22205
|
+
if (this.pending) {
|
|
22206
|
+
if (stream === this.pending.stream && this.isContinuation(line)) {
|
|
22207
|
+
this.pending.lines.push(line);
|
|
22208
|
+
if (NODE_FRAME.test(line)) this.pending.sawFrame = true;
|
|
22209
|
+
if (this.pending.kind === "python-traceback" && PY_TERMINAL.test(line) && !PY_FILE.test(line) || this.pending.lines.length >= 40) return compact([this.finish()]);
|
|
22210
|
+
return [];
|
|
22211
|
+
}
|
|
22212
|
+
return compact([this.finish(), this.startOrDetect(stream, line)]);
|
|
22213
|
+
}
|
|
22214
|
+
return compact([this.startOrDetect(stream, line)]);
|
|
22215
|
+
}
|
|
22216
|
+
/** Force-complete a pending block (debounce timeout / process exit). */
|
|
22217
|
+
flush() {
|
|
22218
|
+
return this.finish();
|
|
22219
|
+
}
|
|
22220
|
+
startOrDetect(stream, line) {
|
|
22221
|
+
if (line.trim() === "") return null;
|
|
22222
|
+
if (line.startsWith(PY_TRACEBACK_START)) {
|
|
22223
|
+
this.pending = {
|
|
22224
|
+
kind: "python-traceback",
|
|
22225
|
+
stream,
|
|
22226
|
+
lines: [line],
|
|
22227
|
+
sawFrame: true
|
|
22228
|
+
};
|
|
22229
|
+
return null;
|
|
22230
|
+
}
|
|
22231
|
+
if (LOG_ERROR_LEVEL.test(line) || JSON_ERROR_LEVEL.test(line)) return {
|
|
22232
|
+
kind: "error-log",
|
|
22233
|
+
stream,
|
|
22234
|
+
lines: [line.slice(0, 500)],
|
|
22235
|
+
fingerprintKey: toFingerprintKey(line)
|
|
22236
|
+
};
|
|
22237
|
+
if (NODE_ERROR_HEAD.test(line)) {
|
|
22238
|
+
this.pending = {
|
|
22239
|
+
kind: "node-stack",
|
|
22240
|
+
stream,
|
|
22241
|
+
lines: [line],
|
|
22242
|
+
sawFrame: false
|
|
22243
|
+
};
|
|
22244
|
+
return null;
|
|
22245
|
+
}
|
|
22246
|
+
return null;
|
|
22247
|
+
}
|
|
22248
|
+
isContinuation(line) {
|
|
22249
|
+
if (!this.pending) return false;
|
|
22250
|
+
if (this.pending.kind === "python-traceback") return PY_FILE.test(line) || PY_INDENTED.test(line) || PY_TERMINAL.test(line);
|
|
22251
|
+
return NODE_FRAME.test(line) || NODE_CAUSE.test(line) || NODE_CHAINED.test(line);
|
|
22252
|
+
}
|
|
22253
|
+
finish() {
|
|
22254
|
+
const pending = this.pending;
|
|
22255
|
+
this.pending = null;
|
|
22256
|
+
if (!pending) return null;
|
|
22257
|
+
if (pending.kind === "node-stack" && !pending.sawFrame) return null;
|
|
22258
|
+
return {
|
|
22259
|
+
kind: pending.kind,
|
|
22260
|
+
stream: pending.stream,
|
|
22261
|
+
lines: pending.lines.map((l) => l.slice(0, 500)),
|
|
22262
|
+
fingerprintKey: toFingerprintKey(pending.lines[0])
|
|
22263
|
+
};
|
|
22264
|
+
}
|
|
22265
|
+
};
|
|
22266
|
+
/**
|
|
22267
|
+
* Bounds Sentry quota per RuntimeProcess. Worst case per agent:
|
|
22268
|
+
* ≤ `ERROR_CAPTURES_MAX` output events + ≤ 1 crash event per 10-minute window.
|
|
22269
|
+
*/
|
|
22270
|
+
var CaptureThrottle = class {
|
|
22271
|
+
windowStart = 0;
|
|
22272
|
+
windowCount = 0;
|
|
22273
|
+
errorsSuppressed = 0;
|
|
22274
|
+
lastSentByKey = /* @__PURE__ */ new Map();
|
|
22275
|
+
lastCrashCaptureAt = 0;
|
|
22276
|
+
hasCapturedCrash = false;
|
|
22277
|
+
crashesSuppressed = 0;
|
|
22278
|
+
constructor(stableUptimeMs) {
|
|
22279
|
+
this.stableUptimeMs = stableUptimeMs;
|
|
22280
|
+
}
|
|
22281
|
+
allowErrorCapture(fingerprintKey, now = Date.now()) {
|
|
22282
|
+
if (now - this.windowStart >= 6e5) {
|
|
22283
|
+
this.windowStart = now;
|
|
22284
|
+
this.windowCount = 0;
|
|
22285
|
+
}
|
|
22286
|
+
const lastSent = this.lastSentByKey.get(fingerprintKey);
|
|
22287
|
+
if (lastSent !== void 0 && now - lastSent < 6e5 || this.windowCount >= 5) {
|
|
22288
|
+
this.errorsSuppressed++;
|
|
22289
|
+
return {
|
|
22290
|
+
allow: false,
|
|
22291
|
+
suppressedCount: this.errorsSuppressed
|
|
22292
|
+
};
|
|
22293
|
+
}
|
|
22294
|
+
this.windowCount++;
|
|
22295
|
+
this.lastSentByKey.set(fingerprintKey, now);
|
|
22296
|
+
if (this.lastSentByKey.size > 50) {
|
|
22297
|
+
const oldest = this.lastSentByKey.keys().next().value;
|
|
22298
|
+
if (oldest !== void 0) this.lastSentByKey.delete(oldest);
|
|
22299
|
+
}
|
|
22300
|
+
const suppressed = this.errorsSuppressed;
|
|
22301
|
+
this.errorsSuppressed = 0;
|
|
22302
|
+
return {
|
|
22303
|
+
allow: true,
|
|
22304
|
+
suppressedCount: suppressed
|
|
22305
|
+
};
|
|
22306
|
+
}
|
|
22307
|
+
allowCrashCapture(uptimeMs, now = Date.now()) {
|
|
22308
|
+
const stable = uptimeMs >= this.stableUptimeMs;
|
|
22309
|
+
const throttled = this.hasCapturedCrash && now - this.lastCrashCaptureAt < 6e5;
|
|
22310
|
+
if (!stable && throttled) {
|
|
22311
|
+
this.crashesSuppressed++;
|
|
22312
|
+
return {
|
|
22313
|
+
allow: false,
|
|
22314
|
+
suppressedCount: this.crashesSuppressed
|
|
22315
|
+
};
|
|
22316
|
+
}
|
|
22317
|
+
this.hasCapturedCrash = true;
|
|
22318
|
+
this.lastCrashCaptureAt = now;
|
|
22319
|
+
const suppressed = this.crashesSuppressed;
|
|
22320
|
+
this.crashesSuppressed = 0;
|
|
22321
|
+
return {
|
|
22322
|
+
allow: true,
|
|
22323
|
+
suppressedCount: suppressed
|
|
22324
|
+
};
|
|
22325
|
+
}
|
|
22326
|
+
};
|
|
21983
22327
|
//#endregion
|
|
21984
22328
|
//#region src/runtime-process.ts
|
|
21985
22329
|
/**
|
|
@@ -21988,15 +22332,23 @@ function createLogger(component, additionalData) {
|
|
|
21988
22332
|
* Spawns the runtime binary, pipes stdout/stderr to the daemon logger,
|
|
21989
22333
|
* and restarts on crash with exponential backoff.
|
|
21990
22334
|
*/
|
|
21991
|
-
const log$
|
|
22335
|
+
const log$2 = createLogger("RuntimeProcess");
|
|
22336
|
+
/** Quiet-period flush for a pending multi-line error block (e.g. a traceback
|
|
22337
|
+
* followed by silence — no closing line ever arrives to complete it). */
|
|
22338
|
+
const DETECTOR_FLUSH_DEBOUNCE_MS = 1500;
|
|
21992
22339
|
const BACKOFF_INITIAL_MS = 1e3;
|
|
21993
22340
|
const BACKOFF_MAX_MS = 3e4;
|
|
22341
|
+
const STABLE_UPTIME_MS = 6e4;
|
|
21994
22342
|
var RuntimeProcess = class {
|
|
21995
22343
|
child = null;
|
|
21996
22344
|
stopped = false;
|
|
21997
22345
|
backoffMs = BACKOFF_INITIAL_MS;
|
|
21998
22346
|
lastStartTime = 0;
|
|
21999
22347
|
restartTimer = null;
|
|
22348
|
+
ringBuffer = new OutputRingBuffer();
|
|
22349
|
+
detector = new ErrorLineDetector();
|
|
22350
|
+
throttle = new CaptureThrottle(STABLE_UPTIME_MS);
|
|
22351
|
+
detectorFlushTimer = null;
|
|
22000
22352
|
constructor(options) {
|
|
22001
22353
|
this.options = options;
|
|
22002
22354
|
}
|
|
@@ -22027,7 +22379,7 @@ var RuntimeProcess = class {
|
|
|
22027
22379
|
if (this.stopped) return;
|
|
22028
22380
|
const { command, args } = this.resolveCommand();
|
|
22029
22381
|
this.lastStartTime = Date.now();
|
|
22030
|
-
log$
|
|
22382
|
+
log$2.info({
|
|
22031
22383
|
runtime: this.options.runtime,
|
|
22032
22384
|
command,
|
|
22033
22385
|
args,
|
|
@@ -22047,22 +22399,28 @@ var RuntimeProcess = class {
|
|
|
22047
22399
|
});
|
|
22048
22400
|
this.child.stdout?.on("data", (data) => {
|
|
22049
22401
|
const lines = data.toString().trim().split("\n");
|
|
22050
|
-
for (const line of lines)
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
|
|
22402
|
+
for (const line of lines) {
|
|
22403
|
+
log$2.info({
|
|
22404
|
+
runtime: this.options.runtime,
|
|
22405
|
+
stream: "stdout"
|
|
22406
|
+
}, line);
|
|
22407
|
+
this.observeLine("stdout", line);
|
|
22408
|
+
}
|
|
22054
22409
|
});
|
|
22055
22410
|
this.child.stderr?.on("data", (data) => {
|
|
22056
22411
|
const lines = data.toString().trim().split("\n");
|
|
22057
|
-
for (const line of lines)
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22412
|
+
for (const line of lines) {
|
|
22413
|
+
log$2.warn({
|
|
22414
|
+
runtime: this.options.runtime,
|
|
22415
|
+
stream: "stderr"
|
|
22416
|
+
}, line);
|
|
22417
|
+
this.observeLine("stderr", line);
|
|
22418
|
+
}
|
|
22061
22419
|
});
|
|
22062
22420
|
this.child.on("exit", (code, signal) => {
|
|
22063
22421
|
this.child = null;
|
|
22064
22422
|
if (this.stopped) {
|
|
22065
|
-
log$
|
|
22423
|
+
log$2.info({
|
|
22066
22424
|
runtime: this.options.runtime,
|
|
22067
22425
|
code,
|
|
22068
22426
|
signal
|
|
@@ -22070,7 +22428,7 @@ var RuntimeProcess = class {
|
|
|
22070
22428
|
return;
|
|
22071
22429
|
}
|
|
22072
22430
|
if (code === 0 && signal == null) {
|
|
22073
|
-
log$
|
|
22431
|
+
log$2.info({
|
|
22074
22432
|
runtime: this.options.runtime,
|
|
22075
22433
|
code
|
|
22076
22434
|
}, "Runtime exited gracefully — restarting");
|
|
@@ -22080,13 +22438,32 @@ var RuntimeProcess = class {
|
|
|
22080
22438
|
}, 500);
|
|
22081
22439
|
return;
|
|
22082
22440
|
}
|
|
22083
|
-
log$
|
|
22441
|
+
log$2.warn({
|
|
22084
22442
|
runtime: this.options.runtime,
|
|
22085
22443
|
code,
|
|
22086
22444
|
signal,
|
|
22087
22445
|
backoffMs: this.backoffMs
|
|
22088
22446
|
}, "Runtime crashed — scheduling restart with backoff");
|
|
22089
|
-
|
|
22447
|
+
const uptime = Date.now() - this.lastStartTime;
|
|
22448
|
+
if (this.detectorFlushTimer) {
|
|
22449
|
+
clearTimeout(this.detectorFlushTimer);
|
|
22450
|
+
this.detectorFlushTimer = null;
|
|
22451
|
+
}
|
|
22452
|
+
this.emitErrorBlock(this.detector.flush());
|
|
22453
|
+
const crash = this.throttle.allowCrashCapture(uptime);
|
|
22454
|
+
if (crash.allow) captureRuntimeCrash({
|
|
22455
|
+
runtime: this.options.runtime,
|
|
22456
|
+
code,
|
|
22457
|
+
signal,
|
|
22458
|
+
uptimeMs: uptime,
|
|
22459
|
+
recentOutput: this.ringBuffer.snapshot(),
|
|
22460
|
+
crashesSuppressed: crash.suppressedCount
|
|
22461
|
+
});
|
|
22462
|
+
else log$2.debug({
|
|
22463
|
+
runtime: this.options.runtime,
|
|
22464
|
+
suppressed: crash.suppressedCount
|
|
22465
|
+
}, "Crash capture suppressed by throttle");
|
|
22466
|
+
if (uptime >= 6e4) this.backoffMs = BACKOFF_INITIAL_MS;
|
|
22090
22467
|
this.restartTimer = setTimeout(() => {
|
|
22091
22468
|
this.restartTimer = null;
|
|
22092
22469
|
this.start();
|
|
@@ -22094,10 +22471,70 @@ var RuntimeProcess = class {
|
|
|
22094
22471
|
this.backoffMs = Math.min(this.backoffMs * 2, BACKOFF_MAX_MS);
|
|
22095
22472
|
});
|
|
22096
22473
|
this.child.on("error", (err) => {
|
|
22097
|
-
log$
|
|
22474
|
+
log$2.error({
|
|
22098
22475
|
runtime: this.options.runtime,
|
|
22099
22476
|
err: err.message
|
|
22100
22477
|
}, "Runtime process error");
|
|
22478
|
+
if (this.stopped) return;
|
|
22479
|
+
const crash = this.throttle.allowCrashCapture(Date.now() - this.lastStartTime);
|
|
22480
|
+
if (crash.allow) captureRuntimeCrash({
|
|
22481
|
+
runtime: this.options.runtime,
|
|
22482
|
+
code: null,
|
|
22483
|
+
signal: null,
|
|
22484
|
+
uptimeMs: Date.now() - this.lastStartTime,
|
|
22485
|
+
recentOutput: this.ringBuffer.snapshot(),
|
|
22486
|
+
crashesSuppressed: crash.suppressedCount,
|
|
22487
|
+
spawnError: err
|
|
22488
|
+
});
|
|
22489
|
+
});
|
|
22490
|
+
}
|
|
22491
|
+
/**
|
|
22492
|
+
* Track a child output line for error capture: ring-buffer it (crash
|
|
22493
|
+
* context) and run it through the error-block detector. A pending
|
|
22494
|
+
* multi-line block is flushed after a quiet period so a trailing traceback
|
|
22495
|
+
* isn't held forever.
|
|
22496
|
+
*/
|
|
22497
|
+
observeLine(stream, line) {
|
|
22498
|
+
try {
|
|
22499
|
+
this.ringBuffer.push({
|
|
22500
|
+
stream,
|
|
22501
|
+
line
|
|
22502
|
+
});
|
|
22503
|
+
for (const block of this.detector.feed(stream, line)) this.emitErrorBlock(block);
|
|
22504
|
+
if (this.detectorFlushTimer) {
|
|
22505
|
+
clearTimeout(this.detectorFlushTimer);
|
|
22506
|
+
this.detectorFlushTimer = null;
|
|
22507
|
+
}
|
|
22508
|
+
if (this.detector.isCollecting) this.detectorFlushTimer = setTimeout(() => {
|
|
22509
|
+
this.detectorFlushTimer = null;
|
|
22510
|
+
this.emitErrorBlock(this.detector.flush());
|
|
22511
|
+
}, DETECTOR_FLUSH_DEBOUNCE_MS);
|
|
22512
|
+
} catch (err) {
|
|
22513
|
+
log$2.debug({
|
|
22514
|
+
runtime: this.options.runtime,
|
|
22515
|
+
err: err instanceof Error ? err.message : String(err)
|
|
22516
|
+
}, "Runtime output observation failed");
|
|
22517
|
+
}
|
|
22518
|
+
}
|
|
22519
|
+
/** Report a completed error block to Sentry, throttle permitting. */
|
|
22520
|
+
emitErrorBlock(block) {
|
|
22521
|
+
if (!block) return;
|
|
22522
|
+
const { allow, suppressedCount } = this.throttle.allowErrorCapture(block.fingerprintKey);
|
|
22523
|
+
if (!allow) {
|
|
22524
|
+
log$2.debug({
|
|
22525
|
+
runtime: this.options.runtime,
|
|
22526
|
+
kind: block.kind,
|
|
22527
|
+
suppressed: suppressedCount
|
|
22528
|
+
}, "Runtime error-output capture suppressed by throttle");
|
|
22529
|
+
return;
|
|
22530
|
+
}
|
|
22531
|
+
captureRuntimeErrorOutput({
|
|
22532
|
+
runtime: this.options.runtime,
|
|
22533
|
+
stream: block.stream,
|
|
22534
|
+
kind: block.kind,
|
|
22535
|
+
lines: block.lines,
|
|
22536
|
+
fingerprintKey: block.fingerprintKey,
|
|
22537
|
+
suppressedCount
|
|
22101
22538
|
});
|
|
22102
22539
|
}
|
|
22103
22540
|
/**
|
|
@@ -22109,11 +22546,16 @@ var RuntimeProcess = class {
|
|
|
22109
22546
|
clearTimeout(this.restartTimer);
|
|
22110
22547
|
this.restartTimer = null;
|
|
22111
22548
|
}
|
|
22549
|
+
if (this.detectorFlushTimer) {
|
|
22550
|
+
clearTimeout(this.detectorFlushTimer);
|
|
22551
|
+
this.detectorFlushTimer = null;
|
|
22552
|
+
}
|
|
22553
|
+
this.emitErrorBlock(this.detector.flush());
|
|
22112
22554
|
const child = this.child;
|
|
22113
22555
|
if (!child) return;
|
|
22114
22556
|
return new Promise((resolve) => {
|
|
22115
22557
|
const killTimer = setTimeout(() => {
|
|
22116
|
-
log$
|
|
22558
|
+
log$2.warn({ runtime: this.options.runtime }, "Runtime did not exit in time — sending SIGKILL");
|
|
22117
22559
|
child.kill("SIGKILL");
|
|
22118
22560
|
}, 5e3);
|
|
22119
22561
|
child.on("exit", () => {
|
|
@@ -22138,7 +22580,7 @@ var RuntimeProcess = class {
|
|
|
22138
22580
|
* Restart the runtime process (stop then start with fresh backoff).
|
|
22139
22581
|
*/
|
|
22140
22582
|
async restart() {
|
|
22141
|
-
log$
|
|
22583
|
+
log$2.info({ runtime: this.options.runtime }, "Restarting runtime...");
|
|
22142
22584
|
await this.stop();
|
|
22143
22585
|
this.stopped = false;
|
|
22144
22586
|
this.backoffMs = BACKOFF_INITIAL_MS;
|
|
@@ -22160,7 +22602,7 @@ var RuntimeProcess = class {
|
|
|
22160
22602
|
* this registry from active integration manifests. Handler files are loaded
|
|
22161
22603
|
* via ESM dynamic import on first use and cached until the registry is cleared.
|
|
22162
22604
|
*/
|
|
22163
|
-
const log = createLogger("CommandRegistry");
|
|
22605
|
+
const log$1 = createLogger("CommandRegistry");
|
|
22164
22606
|
var CommandRegistry = class {
|
|
22165
22607
|
commands = /* @__PURE__ */ new Map();
|
|
22166
22608
|
version = 0;
|
|
@@ -22171,7 +22613,7 @@ var CommandRegistry = class {
|
|
|
22171
22613
|
register(integrationId, name, handlerPath, method = "handle", timeoutMs = 3e4) {
|
|
22172
22614
|
const existing = this.commands.get(name);
|
|
22173
22615
|
if (existing) {
|
|
22174
|
-
log.warn({
|
|
22616
|
+
log$1.warn({
|
|
22175
22617
|
command: name,
|
|
22176
22618
|
existing: existing.integrationId,
|
|
22177
22619
|
attempted: integrationId
|
|
@@ -22179,7 +22621,7 @@ var CommandRegistry = class {
|
|
|
22179
22621
|
return;
|
|
22180
22622
|
}
|
|
22181
22623
|
if (!existsSync(handlerPath)) {
|
|
22182
|
-
log.warn({
|
|
22624
|
+
log$1.warn({
|
|
22183
22625
|
command: name,
|
|
22184
22626
|
handlerPath,
|
|
22185
22627
|
integrationId
|
|
@@ -22194,7 +22636,7 @@ var CommandRegistry = class {
|
|
|
22194
22636
|
timeoutMs,
|
|
22195
22637
|
handler: null
|
|
22196
22638
|
});
|
|
22197
|
-
log.info({
|
|
22639
|
+
log$1.info({
|
|
22198
22640
|
command: name,
|
|
22199
22641
|
integrationId,
|
|
22200
22642
|
handlerPath,
|
|
@@ -22208,7 +22650,7 @@ var CommandRegistry = class {
|
|
|
22208
22650
|
unregisterAll(integrationId) {
|
|
22209
22651
|
for (const [name, entry] of this.commands) if (entry.integrationId === integrationId) {
|
|
22210
22652
|
this.commands.delete(name);
|
|
22211
|
-
log.info({
|
|
22653
|
+
log$1.info({
|
|
22212
22654
|
command: name,
|
|
22213
22655
|
integrationId
|
|
22214
22656
|
}, "Unregistered command");
|
|
@@ -22246,7 +22688,7 @@ var CommandRegistry = class {
|
|
|
22246
22688
|
entry.handler = fn;
|
|
22247
22689
|
} catch (err) {
|
|
22248
22690
|
const message = err instanceof Error ? err.message : String(err);
|
|
22249
|
-
log.error({
|
|
22691
|
+
log$1.error({
|
|
22250
22692
|
err: message,
|
|
22251
22693
|
command: name,
|
|
22252
22694
|
handlerPath: entry.handlerPath
|
|
@@ -22267,7 +22709,7 @@ var CommandRegistry = class {
|
|
|
22267
22709
|
})]);
|
|
22268
22710
|
} catch (err) {
|
|
22269
22711
|
const message = err instanceof Error ? err.message : String(err);
|
|
22270
|
-
log.error({
|
|
22712
|
+
log$1.error({
|
|
22271
22713
|
err: message,
|
|
22272
22714
|
command: name
|
|
22273
22715
|
}, "Command execution failed");
|
|
@@ -22296,7 +22738,7 @@ var CommandRegistry = class {
|
|
|
22296
22738
|
clear() {
|
|
22297
22739
|
this.commands.clear();
|
|
22298
22740
|
this.version++;
|
|
22299
|
-
log.info({ version: this.version }, "Command registry cleared");
|
|
22741
|
+
log$1.info({ version: this.version }, "Command registry cleared");
|
|
22300
22742
|
}
|
|
22301
22743
|
};
|
|
22302
22744
|
//#endregion
|
|
@@ -22585,6 +23027,93 @@ function writeSentinel(path) {
|
|
|
22585
23027
|
} catch {}
|
|
22586
23028
|
}
|
|
22587
23029
|
//#endregion
|
|
23030
|
+
//#region src/mcp-error-capture.ts
|
|
23031
|
+
const log = createLogger("McpErrorCapture");
|
|
23032
|
+
/** Quiet-period flush for a pending multi-line stderr block. */
|
|
23033
|
+
const MCP_STDERR_FLUSH_DEBOUNCE_MS = 1500;
|
|
23034
|
+
function createMcpErrorHooks() {
|
|
23035
|
+
const resultErrorThrottle = new CaptureThrottle(6e4);
|
|
23036
|
+
const errorThrottle = new CaptureThrottle(6e4);
|
|
23037
|
+
const crashThrottle = new CaptureThrottle(6e4);
|
|
23038
|
+
const throttleFor = (kind) => kind === "tool-result-error" ? resultErrorThrottle : kind === "server-crash" ? crashThrottle : errorThrottle;
|
|
23039
|
+
const detectors = /* @__PURE__ */ new Map();
|
|
23040
|
+
const flushTimers = /* @__PURE__ */ new Map();
|
|
23041
|
+
const capture = (opts) => {
|
|
23042
|
+
const { allow, suppressedCount } = throttleFor(opts.kind).allowErrorCapture(opts.fingerprintKey);
|
|
23043
|
+
if (!allow) {
|
|
23044
|
+
log.debug({
|
|
23045
|
+
server: opts.server,
|
|
23046
|
+
kind: opts.kind,
|
|
23047
|
+
suppressed: suppressedCount
|
|
23048
|
+
}, "MCP failure capture suppressed by throttle");
|
|
23049
|
+
return;
|
|
23050
|
+
}
|
|
23051
|
+
captureMcpFailure({
|
|
23052
|
+
...opts,
|
|
23053
|
+
suppressedCount
|
|
23054
|
+
});
|
|
23055
|
+
};
|
|
23056
|
+
const emitBlock = (server, block) => {
|
|
23057
|
+
if (!block) return;
|
|
23058
|
+
capture({
|
|
23059
|
+
server,
|
|
23060
|
+
kind: "stderr-output",
|
|
23061
|
+
message: block.lines[0],
|
|
23062
|
+
lines: block.lines,
|
|
23063
|
+
fingerprintKey: `stderr:${server}:${block.fingerprintKey}`
|
|
23064
|
+
});
|
|
23065
|
+
};
|
|
23066
|
+
return {
|
|
23067
|
+
onToolError: (info) => {
|
|
23068
|
+
capture({
|
|
23069
|
+
server: info.server,
|
|
23070
|
+
tool: info.tool,
|
|
23071
|
+
kind: info.kind === "thrown" ? "tool-thrown" : "tool-result-error",
|
|
23072
|
+
message: info.message,
|
|
23073
|
+
fingerprintKey: `tool:${info.server}:${info.tool}:${toFingerprintKey(info.message)}`
|
|
23074
|
+
});
|
|
23075
|
+
},
|
|
23076
|
+
onServerCrash: (server) => {
|
|
23077
|
+
const timer = flushTimers.get(server);
|
|
23078
|
+
if (timer) {
|
|
23079
|
+
clearTimeout(timer);
|
|
23080
|
+
flushTimers.delete(server);
|
|
23081
|
+
}
|
|
23082
|
+
emitBlock(server, detectors.get(server)?.flush() ?? null);
|
|
23083
|
+
capture({
|
|
23084
|
+
server,
|
|
23085
|
+
kind: "server-crash",
|
|
23086
|
+
message: "MCP server connection closed unexpectedly",
|
|
23087
|
+
fingerprintKey: `crash:${server}`
|
|
23088
|
+
});
|
|
23089
|
+
},
|
|
23090
|
+
onServerStderr: (server, line) => {
|
|
23091
|
+
let detector = detectors.get(server);
|
|
23092
|
+
if (!detector) {
|
|
23093
|
+
detector = new ErrorLineDetector();
|
|
23094
|
+
detectors.set(server, detector);
|
|
23095
|
+
}
|
|
23096
|
+
for (const block of detector.feed("stderr", line)) emitBlock(server, block);
|
|
23097
|
+
const existing = flushTimers.get(server);
|
|
23098
|
+
if (existing) {
|
|
23099
|
+
clearTimeout(existing);
|
|
23100
|
+
flushTimers.delete(server);
|
|
23101
|
+
}
|
|
23102
|
+
if (detector.isCollecting) {
|
|
23103
|
+
const target = detector;
|
|
23104
|
+
flushTimers.set(server, setTimeout(() => {
|
|
23105
|
+
flushTimers.delete(server);
|
|
23106
|
+
emitBlock(server, target.flush());
|
|
23107
|
+
}, MCP_STDERR_FLUSH_DEBOUNCE_MS));
|
|
23108
|
+
}
|
|
23109
|
+
},
|
|
23110
|
+
dispose: () => {
|
|
23111
|
+
for (const timer of flushTimers.values()) clearTimeout(timer);
|
|
23112
|
+
flushTimers.clear();
|
|
23113
|
+
}
|
|
23114
|
+
};
|
|
23115
|
+
}
|
|
23116
|
+
//#endregion
|
|
22588
23117
|
//#region src/daemon.ts
|
|
22589
23118
|
/**
|
|
22590
23119
|
* Alfe Gateway Daemon — main entry point.
|
|
@@ -22820,6 +23349,21 @@ async function startDaemon() {
|
|
|
22820
23349
|
surface: "daemon",
|
|
22821
23350
|
release: await getCliVersion()
|
|
22822
23351
|
});
|
|
23352
|
+
let fatalExiting = false;
|
|
23353
|
+
process.on("uncaughtException", (err) => {
|
|
23354
|
+
if (fatalExiting) process.exit(1);
|
|
23355
|
+
fatalExiting = true;
|
|
23356
|
+
logger$1.error({
|
|
23357
|
+
err: err.message,
|
|
23358
|
+
stack: err.stack
|
|
23359
|
+
}, "Uncaught exception — daemon exiting");
|
|
23360
|
+
captureFatal(err);
|
|
23361
|
+
flushAndExit(1);
|
|
23362
|
+
});
|
|
23363
|
+
process.on("unhandledRejection", (reason) => {
|
|
23364
|
+
logger$1.error({ reason: reason instanceof Error ? reason.message : String(reason) }, "Unhandled promise rejection");
|
|
23365
|
+
captureFatal(reason instanceof Error ? reason : /* @__PURE__ */ new Error(`Unhandled rejection: ${String(reason)}`));
|
|
23366
|
+
});
|
|
22823
23367
|
if (!managed) {
|
|
22824
23368
|
await mkdir(join(homedir(), ".alfe"), { recursive: true });
|
|
22825
23369
|
const existingPid = await checkExistingDaemon();
|
|
@@ -22954,9 +23498,11 @@ async function startDaemon() {
|
|
|
22954
23498
|
});
|
|
22955
23499
|
const mcpManager = new Manager({ logger: logger$1 });
|
|
22956
23500
|
mcpManagerRef = mcpManager;
|
|
23501
|
+
const mcpErrorHooks = createMcpErrorHooks();
|
|
22957
23502
|
mcpBundler = new McpBundler({
|
|
22958
23503
|
logger: logger$1,
|
|
22959
|
-
idleTtlMs: 0
|
|
23504
|
+
idleTtlMs: 0,
|
|
23505
|
+
...mcpErrorHooks
|
|
22960
23506
|
}, { connect: defaultConnect });
|
|
22961
23507
|
if (config.runtime === "hermes") logger$1.info("Hermes runtime — daemon MCP bundler left idle (store mirrored to config.yaml by HermesMcpSync)");
|
|
22962
23508
|
else {
|
|
@@ -23083,6 +23629,7 @@ async function startDaemon() {
|
|
|
23083
23629
|
mcpBundler = null;
|
|
23084
23630
|
logger$1.debug("MCP bundler stopped");
|
|
23085
23631
|
}
|
|
23632
|
+
mcpErrorHooks.dispose();
|
|
23086
23633
|
logger$1.debug("Stopping MCP bundler manager...");
|
|
23087
23634
|
await mcpManager.dispose();
|
|
23088
23635
|
mcpManagerRef = null;
|
|
@@ -23807,4 +24354,4 @@ function formatDuration(ms) {
|
|
|
23807
24354
|
return `${String(Math.round(seconds / 3600))}h`;
|
|
23808
24355
|
}
|
|
23809
24356
|
//#endregion
|
|
23810
|
-
export {
|
|
24357
|
+
export { PID_PATH as C, resolveAgentIdentity as D, loadDaemonConfig as E, PINNED_OPENCLAW_VERSION as O, ALFE_DIR as S, fetchAgentConfig as T, captureRuntimeErrorOutput as _, installService as a, setAgentContext as b, uninstallService as c, AGENT_RUNTIME_SENTRY_DSN as d, captureCliFailure as f, captureRuntimeCrash as g, captureMcpFailure as h, checkExistingDaemon as i, AGENT_DAEMON_SENTRY_DSN as l, captureIntegrationFailure as m, queryDaemonHealth as n, startService as o, captureFatal as p, startDaemon as r, stopExistingDaemon as s, formatHealthReport as t, AGENT_MCP_SENTRY_DSN as u, flushSentry as v, SOCKET_PATH as w, PROTOCOL_VERSION as x, initAgentSentry as y };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -74,6 +74,20 @@ declare const PINNED_OPENCLAW_VERSION = "2026.6.8";
|
|
|
74
74
|
* both places.
|
|
75
75
|
*/
|
|
76
76
|
declare const AGENT_DAEMON_SENTRY_DSN = "https://a47f010d8d39fb4350f913492189f283@o4511008239452160.ingest.us.sentry.io/4511679448547328";
|
|
77
|
+
/**
|
|
78
|
+
* Dedicated project for errors emitted BY the agent runtime child process
|
|
79
|
+
* (OpenClaw/Hermes) — crashes, spawn failures, and error-looking output. Kept
|
|
80
|
+
* separate from `agent-daemon` so runtime noise never drowns daemon issues.
|
|
81
|
+
* Mirrors `SENTRY_DSNS.agentRuntime` in `config/config.ts`.
|
|
82
|
+
*/
|
|
83
|
+
declare const AGENT_RUNTIME_SENTRY_DSN = "https://feb7fb2e3b8e723aec518e74715bfa6d@o4511008239452160.ingest.us.sentry.io/4511683667558400";
|
|
84
|
+
/**
|
|
85
|
+
* MCP tool/server failures on agent VMs — reported by the daemon-hosted MCP
|
|
86
|
+
* bundler (tool errors, child crashes, stderr output). This is the SAME
|
|
87
|
+
* project as the cloud MCP Fly service (`SENTRY_DSNS.mcp`); agent-side events
|
|
88
|
+
* are distinguished by `source: agent-daemon` + `agentId` tags.
|
|
89
|
+
*/
|
|
90
|
+
declare const AGENT_MCP_SENTRY_DSN = "https://638309eac96146a945f46b578f10c806@o4511008239452160.ingest.us.sentry.io/4511146364174336";
|
|
77
91
|
/** Which surface initialised Sentry — tagged on every event. */
|
|
78
92
|
type SentrySurface = "cli" | "daemon";
|
|
79
93
|
interface InitAgentSentryOptions {
|
|
@@ -112,6 +126,59 @@ declare function captureIntegrationFailure(integration: string, phase: string, c
|
|
|
112
126
|
* Sentry is not initialised. Never throws.
|
|
113
127
|
*/
|
|
114
128
|
declare function captureCliFailure(context: string, cause?: unknown): void;
|
|
129
|
+
/** A line of runtime child-process output attached to a crash event. */
|
|
130
|
+
interface RuntimeOutputLine {
|
|
131
|
+
stream: "stdout" | "stderr";
|
|
132
|
+
line: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Capture a runtime child-process crash (non-graceful exit) or spawn failure
|
|
136
|
+
* into the dedicated `agent-runtime` project. Recent output travels as
|
|
137
|
+
* breadcrumbs — NEVER `extra`, which `scrubEvent` (the runtime client's
|
|
138
|
+
* `beforeSend`) deletes. Throttling is the caller's job (`CaptureThrottle` in
|
|
139
|
+
* `runtime-output-monitor.ts`). No-op when the runtime client is not
|
|
140
|
+
* initialised. Never throws.
|
|
141
|
+
*/
|
|
142
|
+
declare function captureRuntimeCrash(opts: {
|
|
143
|
+
runtime: string;
|
|
144
|
+
code: number | null;
|
|
145
|
+
signal: string | null;
|
|
146
|
+
uptimeMs: number;
|
|
147
|
+
recentOutput: readonly RuntimeOutputLine[];
|
|
148
|
+
crashesSuppressed?: number;
|
|
149
|
+
/** Set for the child `error` event path (e.g. ENOENT — binary missing). */
|
|
150
|
+
spawnError?: Error;
|
|
151
|
+
}): void;
|
|
152
|
+
/**
|
|
153
|
+
* Capture an error-looking block of runtime output (stack trace, Python
|
|
154
|
+
* traceback, or ERROR-level log line) detected while the child is running.
|
|
155
|
+
* The block lines travel as breadcrumbs; the head line is the message.
|
|
156
|
+
* Throttling/dedupe is the caller's job. No-op when Sentry is not initialised.
|
|
157
|
+
* Never throws.
|
|
158
|
+
*/
|
|
159
|
+
declare function captureRuntimeErrorOutput(opts: {
|
|
160
|
+
runtime: string;
|
|
161
|
+
stream: "stdout" | "stderr";
|
|
162
|
+
kind: "node-stack" | "python-traceback" | "error-log";
|
|
163
|
+
lines: readonly string[];
|
|
164
|
+
fingerprintKey: string;
|
|
165
|
+
suppressedCount?: number;
|
|
166
|
+
}): void;
|
|
167
|
+
/**
|
|
168
|
+
* Capture an MCP failure (tool error, server crash, or error-looking stderr
|
|
169
|
+
* output) into the `mcp` project via the daemon's bound MCP client. Throttling
|
|
170
|
+
* is the caller's job (`mcp-error-capture.ts`). No-op when the MCP client is
|
|
171
|
+
* not initialised. Never throws.
|
|
172
|
+
*/
|
|
173
|
+
declare function captureMcpFailure(opts: {
|
|
174
|
+
server: string;
|
|
175
|
+
tool?: string;
|
|
176
|
+
kind: "tool-thrown" | "tool-result-error" | "server-crash" | "stderr-output";
|
|
177
|
+
message: string;
|
|
178
|
+
/** Optional context lines (stderr block) — attached as breadcrumbs. */
|
|
179
|
+
lines?: readonly string[];
|
|
180
|
+
suppressedCount?: number;
|
|
181
|
+
}): void;
|
|
115
182
|
/** Flush queued events (small timeout) so short-lived commands deliver them. */
|
|
116
183
|
declare function flushSentry(timeoutMs?: number): Promise<void>;
|
|
117
184
|
/**
|
|
@@ -293,4 +360,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
|
|
|
293
360
|
*/
|
|
294
361
|
declare function stopExistingDaemon(): Promise<boolean>;
|
|
295
362
|
//#endregion
|
|
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 };
|
|
363
|
+
export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_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, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, 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 { C as PINNED_OPENCLAW_VERSION, S as
|
|
1
|
+
import { C as PID_PATH, D as resolveAgentIdentity, E as loadDaemonConfig, O as PINNED_OPENCLAW_VERSION, S as ALFE_DIR, T as fetchAgentConfig, _ as captureRuntimeErrorOutput, a as installService, b as setAgentContext, c as uninstallService, d as AGENT_RUNTIME_SENTRY_DSN, f as captureCliFailure, g as captureRuntimeCrash, h as captureMcpFailure, i as checkExistingDaemon, l as AGENT_DAEMON_SENTRY_DSN, m as captureIntegrationFailure, n as queryDaemonHealth, o as startService, p as captureFatal, r as startDaemon, s as stopExistingDaemon, t as formatHealthReport, u as AGENT_MCP_SENTRY_DSN, v as flushSentry, w as SOCKET_PATH, x as PROTOCOL_VERSION, y as initAgentSentry } from "../health.js";
|
|
2
2
|
import { n as logger } from "../logger.js";
|
|
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 };
|
|
3
|
+
export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, PID_PATH, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, 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.5.0",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
"pino-roll": "^1.2.0",
|
|
24
24
|
"smol-toml": ">=1.6.1",
|
|
25
25
|
"ws": "^8.18.0",
|
|
26
|
-
"@alfe.ai/agent-api-client": "^0.
|
|
26
|
+
"@alfe.ai/agent-api-client": "^0.8.0",
|
|
27
27
|
"@alfe.ai/ai-proxy-local": "^0.0.13",
|
|
28
28
|
"@alfe.ai/config": "^0.3.0",
|
|
29
29
|
"@alfe.ai/integration-manifest": "^0.3.1",
|
|
30
|
-
"@alfe.ai/integrations": "^0.2.
|
|
31
|
-
"@alfe.ai/mcp-bundler": "^0.
|
|
30
|
+
"@alfe.ai/integrations": "^0.2.10",
|
|
31
|
+
"@alfe.ai/mcp-bundler": "^0.3.0"
|
|
32
32
|
},
|
|
33
33
|
"license": "UNLICENSED",
|
|
34
34
|
"homepage": "https://alfe.ai",
|