feat(heartbeat): add heartbeat core runner and profile integration

This commit is contained in:
Jiang Bohan 2026-02-06 16:41:56 +08:00
parent 91aa433f34
commit ffa5355a95
19 changed files with 934 additions and 2 deletions

View file

@ -15,6 +15,7 @@ export class AsyncAgent {
private readonly channel = new Channel<ChannelItem>();
private _closed = false;
private queue: Promise<void> = Promise.resolve();
private pendingWrites = 0;
private closeCallbacks: Array<() => void> = [];
readonly sessionId: string;
@ -38,6 +39,7 @@ export class AsyncAgent {
/** Write message to agent (non-blocking, serialized queue) */
write(content: string): void {
if (this._closed) throw new Error("Agent is closed");
this.pendingWrites += 1;
this.queue = this.queue
.then(async () => {
@ -54,6 +56,9 @@ export class AsyncAgent {
.catch((err) => {
const message = err instanceof Error ? err.message : String(err);
this.channel.send({ id: uuidv7(), content: `[error] ${message}` });
})
.finally(() => {
this.pendingWrites = Math.max(0, this.pendingWrites - 1);
});
}
@ -170,6 +175,34 @@ export class AsyncAgent {
return this.agent.getProfileId();
}
/**
* Get profile directory path, if profile is enabled.
*/
getProfileDir(): string | undefined {
return this.agent.getProfileDir();
}
/**
* Get heartbeat configuration from profile config.
*/
getHeartbeatConfig():
| {
enabled?: boolean | undefined;
every?: string | undefined;
prompt?: string | undefined;
ackMaxChars?: number | undefined;
}
| undefined {
return this.agent.getHeartbeatConfig();
}
/**
* Number of queued/in-flight writes.
*/
getPendingWrites(): number {
return this.pendingWrites;
}
/**
* Get agent display name from profile config.
*/