64 lines
2.9 KiB
TypeScript
64 lines
2.9 KiB
TypeScript
import type { PiTeamsConfig } from "../config/config.ts";
|
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
import { appendTaskAttentionEvent } from "./attention-events.ts";
|
|
import type { CrewAgentRecord } from "./crew-agent-runtime.ts";
|
|
import { upsertCrewAgent } from "./crew-agent-records.ts";
|
|
|
|
export interface CrewControlConfig {
|
|
enabled: boolean;
|
|
needsAttentionAfterMs: number;
|
|
}
|
|
|
|
const DEFAULT_NEEDS_ATTENTION_MS = 60_000;
|
|
|
|
function positiveInt(value: unknown): number | undefined {
|
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
}
|
|
|
|
export function resolveCrewControlConfig(config: PiTeamsConfig | undefined): CrewControlConfig {
|
|
const raw = config as PiTeamsConfig & { control?: { enabled?: unknown; needsAttentionAfterMs?: unknown } } | undefined;
|
|
return {
|
|
enabled: raw?.control?.enabled === false ? false : true,
|
|
needsAttentionAfterMs: positiveInt(raw?.control?.needsAttentionAfterMs) ?? DEFAULT_NEEDS_ATTENTION_MS,
|
|
};
|
|
}
|
|
|
|
export function activityAgeMs(agent: CrewAgentRecord, now = Date.now()): number | undefined {
|
|
const timestamp = agent.progress?.lastActivityAt ?? agent.startedAt;
|
|
if (!timestamp) return undefined;
|
|
const ms = now - new Date(timestamp).getTime();
|
|
return Number.isFinite(ms) ? Math.max(0, ms) : undefined;
|
|
}
|
|
|
|
export function formatActivityAge(agent: CrewAgentRecord, now = Date.now()): string | undefined {
|
|
const age = activityAgeMs(agent, now);
|
|
if (age === undefined) return undefined;
|
|
if (age < 1000) return "active now";
|
|
const seconds = Math.floor(age / 1000);
|
|
if (seconds < 60) return agent.progress?.activityState === "needs_attention" ? `no activity for ${seconds}s` : `active ${seconds}s ago`;
|
|
const minutes = Math.floor(seconds / 60);
|
|
return agent.progress?.activityState === "needs_attention" ? `no activity for ${minutes}m` : `active ${minutes}m ago`;
|
|
}
|
|
|
|
export function applyAttentionState(manifest: TeamRunManifest, agent: CrewAgentRecord, config: CrewControlConfig, now = Date.now()): CrewAgentRecord {
|
|
if (!config.enabled || agent.status !== "running") return agent;
|
|
const age = activityAgeMs(agent, now);
|
|
if (age === undefined || age <= config.needsAttentionAfterMs) return agent;
|
|
if (agent.progress?.activityState === "needs_attention") return agent;
|
|
const updated: CrewAgentRecord = {
|
|
...agent,
|
|
progress: {
|
|
...(agent.progress ?? { recentTools: [], recentOutput: [], toolCount: agent.toolUses ?? 0 }),
|
|
activityState: "needs_attention",
|
|
},
|
|
};
|
|
upsertCrewAgent(manifest, updated);
|
|
appendTaskAttentionEvent({
|
|
manifest,
|
|
taskId: agent.taskId,
|
|
message: `${agent.agent} needs attention (no observed activity for ${Math.floor(age / 1000)}s).`,
|
|
data: { activityState: "needs_attention", reason: "idle", elapsedMs: age, taskId: agent.taskId, agentName: agent.agent, suggestedAction: "Check worker status, wait, steer, or cancel if needed." },
|
|
});
|
|
return updated;
|
|
}
|