feat: add privacy-safe server diagnostics

This commit is contained in:
inman committed 2026-08-30 16:01:42 +08:00
1 parent 2360506607
commit a3963ad0f6
27 files changed
+1305 -112

No files matched your search

+88 -7
View File
@@ -8,6 +8,7 @@ import {
type AgentBusSocketFactory
} from './agentbus.js';
import { TaskError, type TaskContext } from './task-service.js';
import { diagnosticError, diagnosticMetadataKeys } from './diagnostics.js';
export interface PublicAgentBusChannel {
id: string;
@@ -120,7 +121,22 @@ export function mergeRuntimeChannelStatuses(
export class AgentBusChannelService {
private readonly runtimeStatusWrites = new Map<string, Promise<void>>();
constructor(private readonly config: AppConfig) {}
constructor(
private readonly config: AppConfig,
private readonly logger?: AgentBusLogger
) {}
private log(
level: 'info' | 'warn' | 'error',
metadata: Record<string, unknown>,
message: string
): void {
try {
this.logger?.[level](metadata, message);
} catch {
// Channel persistence and encryption never depend on operational logs.
}
}
private legacyEnvironmentManaged(): boolean {
return this.config.agentBusEnabled
@@ -178,6 +194,13 @@ export class AgentBusChannelService {
try {
const wsToken = decryptText(this.config, text(row.agentbus_ws_token_ciphertext));
if (!wsUrl || !wsToken || !botAddress) {
this.log('warn', {
agentbus_event: 'channel_configuration_incomplete',
channel_id: text(row.id),
ws_url_present: Boolean(wsUrl),
ws_token_present: Boolean(wsToken),
bot_address_present: Boolean(botAddress)
}, 'AgentBus channel configuration is incomplete');
await this.setRuntimeStatus(
organizationId,
text(row.id),
@@ -192,7 +215,12 @@ export class AgentBusChannelService {
ws_token: wsToken,
bot_address: botAddress
});
} catch {
} catch (error) {
this.log('error', {
agentbus_event: 'channel_key_decryption_failed',
channel_id: text(row.id),
...diagnosticError(error, 'agentbus_channel_key_decryption_failed')
}, 'AgentBus channel key decryption failed');
await this.setRuntimeStatus(organizationId, text(row.id), 'error', '渠道 key 无法解密。');
}
}
@@ -471,6 +499,15 @@ export class AgentBusChannelService {
VALUES ($1, $2, $3, 'user_channel', $4, $5, $6)`,
[context.organizationId, context.userId || null, eventType, entityId, context.requestId, metadata]
);
this.log('info', {
agentbus_event: 'channel_audit_staged',
request_id: context.requestId,
domain_event: eventType,
entity_type: 'user_channel',
entity_id: entityId,
actor_present: Boolean(context.userId),
metadata_keys: diagnosticMetadataKeys(metadata)
}, 'AgentBus channel audit event staged');
}
}
@@ -493,7 +530,19 @@ export class AgentBusManager {
constructor(options: AgentBusManagerOptions) {
this.options = options;
this.channels = new AgentBusChannelService(options.config);
this.channels = new AgentBusChannelService(options.config, options.logger);
}
private log(
level: 'info' | 'warn' | 'error',
metadata: Record<string, unknown>,
message: string
): void {
try {
this.options.logger?.[level](metadata, message);
} catch {
// Runtime channel management must not depend on log delivery.
}
}
get channelService(): AgentBusChannelService {
@@ -504,12 +553,18 @@ export class AgentBusManager {
if (!this.options.config.agentBusEnabled) return;
if (this.started) return;
this.started = true;
this.log('info', { agentbus_event: 'manager_starting' }, 'AgentBus manager starting');
await this.reload();
}
async reload(): Promise<void> {
if (!this.started) return;
this.reloadRequested = true;
this.log('info', {
agentbus_event: 'manager_reload_requested',
reload_in_flight: Boolean(this.reloadInFlight),
current_listeners: this.listeners.size
}, 'AgentBus manager reload requested');
if (this.reloadInFlight) return this.reloadInFlight;
this.reloadInFlight = (async () => {
while (this.started && this.reloadRequested) {
@@ -518,6 +573,10 @@ export class AgentBusManager {
for (const listener of this.listeners.values()) listener.stop(false);
this.listeners.clear();
const secrets = await this.channels.listEnabledSecrets(this.options.organizationId);
this.log('info', {
agentbus_event: 'manager_channels_loaded',
enabled_channels: secrets.length
}, 'AgentBus enabled channels loaded');
for (const channel of secrets) {
const listener = new AgentBusListener({
config: this.options.config,
@@ -539,15 +598,36 @@ export class AgentBusManager {
status,
error,
epoch
).catch(() => undefined)
).catch((statusError) => {
this.log('warn', {
agentbus_event: 'channel_status_persist_failed',
channel_id: channel.id,
runtime_status: status,
session_epoch: epoch,
...diagnosticError(statusError, 'agentbus_channel_status_persist_failed')
}, 'AgentBus channel status persistence failed');
})
});
this.listeners.set(channel.id, listener);
listener.start();
}
this.log('info', {
agentbus_event: 'manager_reload_completed',
active_listeners: this.listeners.size,
reload_requested_again: this.reloadRequested
}, 'AgentBus manager reload completed');
}
})().finally(() => {
this.reloadInFlight = null;
});
})()
.catch((error) => {
this.log('error', {
agentbus_event: 'manager_reload_failed',
...diagnosticError(error, 'agentbus_manager_reload_failed')
}, 'AgentBus manager reload failed');
throw error;
})
.finally(() => {
this.reloadInFlight = null;
});
return this.reloadInFlight;
}
@@ -559,6 +639,7 @@ export class AgentBusManager {
// an older process write `disabled` after a newer process has connected.
for (const listener of this.listeners.values()) listener.stop(false);
this.listeners.clear();
this.log('info', { agentbus_event: 'manager_stopped' }, 'AgentBus manager stopped');
}
status(): {