feat(pi): expire recent subagent statuses
This commit was merged in pull request #38.
This commit is contained in:
@@ -24,6 +24,7 @@ interface RunningChild {
|
||||
interface SupervisorOptions {
|
||||
maxConcurrent?: number;
|
||||
recentTerminalLimit?: number;
|
||||
recentTerminalTtlMs?: number;
|
||||
timeouts?: {
|
||||
startMs?: number;
|
||||
runMs?: number;
|
||||
@@ -47,6 +48,7 @@ export class Supervisor {
|
||||
private readonly children = new Map<string, RunningChild>();
|
||||
private readonly queue: RunningChild[] = [];
|
||||
private readonly waiters = new Set<() => void>();
|
||||
private recentTerminalTimer?: ReturnType<typeof setTimeout>;
|
||||
|
||||
constructor(
|
||||
private readonly runner: ChildRunner,
|
||||
@@ -76,6 +78,7 @@ export class Supervisor {
|
||||
const active = statuses.filter((status) => !isTerminal(status.state));
|
||||
const terminal = statuses
|
||||
.filter((status) => isTerminal(status.state))
|
||||
.filter((status) => this.isRecentTerminal(status))
|
||||
.sort((a, b) => Date.parse(b.completedAt ?? b.startedAt) - Date.parse(a.completedAt ?? a.startedAt))
|
||||
.slice(0, this.options.recentTerminalLimit ?? 10);
|
||||
return [...active, ...terminal];
|
||||
@@ -134,6 +137,7 @@ export class Supervisor {
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
this.clearRecentTerminalTimer();
|
||||
await Promise.allSettled(
|
||||
[...this.children.values()].map(async (child) => {
|
||||
if (!isTerminal(child.record.status.state)) {
|
||||
@@ -142,6 +146,7 @@ export class Supervisor {
|
||||
}
|
||||
}),
|
||||
);
|
||||
this.clearRecentTerminalTimer();
|
||||
}
|
||||
|
||||
private createChild(request: SpawnRequest): SpawnAccepted {
|
||||
@@ -340,6 +345,39 @@ export class Supervisor {
|
||||
private emitChange() {
|
||||
this.options.onChange?.(this.list());
|
||||
for (const waiter of this.waiters) waiter();
|
||||
this.scheduleRecentTerminalExpiry();
|
||||
}
|
||||
|
||||
private scheduleRecentTerminalExpiry() {
|
||||
this.clearRecentTerminalTimer();
|
||||
const ttl = this.options.recentTerminalTtlMs;
|
||||
if (ttl === undefined || ttl <= 0) return;
|
||||
const now = Date.now();
|
||||
const nextExpiryMs = [...this.children.values()]
|
||||
.map((child) => child.record.status)
|
||||
.filter((status) => isTerminal(status.state))
|
||||
.map((status) => Date.parse(status.completedAt ?? status.startedAt))
|
||||
.filter((completed) => Number.isFinite(completed))
|
||||
.map((completed) => completed + ttl - now)
|
||||
.filter((remaining) => remaining > 0)
|
||||
.sort((a, b) => a - b)[0];
|
||||
if (nextExpiryMs === undefined) return;
|
||||
this.recentTerminalTimer = setTimeout(() => this.emitChange(), nextExpiryMs + 1);
|
||||
}
|
||||
|
||||
private clearRecentTerminalTimer() {
|
||||
if (!this.recentTerminalTimer) return;
|
||||
clearTimeout(this.recentTerminalTimer);
|
||||
this.recentTerminalTimer = undefined;
|
||||
}
|
||||
|
||||
private isRecentTerminal(status: SubagentStatus): boolean {
|
||||
const ttl = this.options.recentTerminalTtlMs;
|
||||
if (ttl === undefined) return true;
|
||||
if (ttl <= 0) return false;
|
||||
const completed = Date.parse(status.completedAt ?? status.startedAt);
|
||||
if (!Number.isFinite(completed)) return true;
|
||||
return Date.now() - completed <= ttl;
|
||||
}
|
||||
|
||||
private waitReady(ids: string[], mode: SubagentWaitMode): boolean {
|
||||
|
||||
Reference in New Issue
Block a user