fix(FN-834): fix branch prefix drift, add merger branch guard, and fix test OOM
- Fix resolveBaseBranch to use stored branch name and consistent fusion/ prefix for both explicit deps and blockedBy paths (was using kb/ for blockedBy) - Add main branch checkout verification in merger before squash merge to prevent feature code from landing on wrong branch lineage - Align all branch prefix references from stale kb/ to fusion/ across executor, merger, store, and routes - Fix executor test OOM by mocking merger fully, adding fake timers to retry tests, and switching vitest pool to vmThreads - Update all test assertions to use fusion/ branch prefix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ import type {
|
||||
AgentHeartbeatEvent,
|
||||
AgentHeartbeatRun,
|
||||
AgentDetail,
|
||||
AgentTaskSession,
|
||||
} from "./types.js";
|
||||
import { AGENT_VALID_TRANSITIONS } from "./types.js";
|
||||
|
||||
@@ -66,6 +67,15 @@ interface AgentData {
|
||||
updatedAt: string;
|
||||
lastHeartbeatAt?: string;
|
||||
metadata: Record<string, unknown>;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
reportsTo?: string;
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
pauseReason?: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
totalInputTokens?: number;
|
||||
totalOutputTokens?: number;
|
||||
lastError?: string;
|
||||
}
|
||||
|
||||
/** Per-agent write lock for serialization */
|
||||
@@ -121,6 +131,11 @@ export class AgentStore extends EventEmitter {
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
metadata: input.metadata ?? {},
|
||||
...(input.title && { title: input.title }),
|
||||
...(input.icon && { icon: input.icon }),
|
||||
...(input.reportsTo && { reportsTo: input.reportsTo }),
|
||||
...(input.runtimeConfig && { runtimeConfig: input.runtimeConfig }),
|
||||
...(input.permissions && { permissions: input.permissions }),
|
||||
};
|
||||
|
||||
await this.writeAgent(agent);
|
||||
@@ -190,6 +205,15 @@ export class AgentStore extends EventEmitter {
|
||||
role: updates.role ?? agent.role,
|
||||
metadata: updates.metadata !== undefined ? updates.metadata : agent.metadata,
|
||||
updatedAt: new Date().toISOString(),
|
||||
...(updates.title !== undefined && { title: updates.title }),
|
||||
...(updates.icon !== undefined && { icon: updates.icon }),
|
||||
...(updates.reportsTo !== undefined && { reportsTo: updates.reportsTo }),
|
||||
...(updates.runtimeConfig !== undefined && { runtimeConfig: updates.runtimeConfig }),
|
||||
...(updates.pauseReason !== undefined && { pauseReason: updates.pauseReason }),
|
||||
...(updates.permissions !== undefined && { permissions: updates.permissions }),
|
||||
...(updates.lastError !== undefined && { lastError: updates.lastError }),
|
||||
...(updates.totalInputTokens !== undefined && { totalInputTokens: updates.totalInputTokens }),
|
||||
...(updates.totalOutputTokens !== undefined && { totalOutputTokens: updates.totalOutputTokens }),
|
||||
};
|
||||
|
||||
await this.writeAgent(updated);
|
||||
@@ -290,7 +314,7 @@ export class AgentStore extends EventEmitter {
|
||||
*/
|
||||
async listAgents(filter?: { state?: AgentState; role?: AgentCapability }): Promise<Agent[]> {
|
||||
const files = await readdir(this.agentsDir).catch(() => [] as string[]);
|
||||
const agentFiles = files.filter((f) => f.endsWith(".json") && !f.includes("-heartbeats"));
|
||||
const agentFiles = files.filter((f) => f.endsWith(".json") && !f.includes("-heartbeats") && !f.includes("-sessions") && !f.includes("-runs"));
|
||||
|
||||
const agents: Agent[] = [];
|
||||
for (const file of agentFiles) {
|
||||
@@ -332,6 +356,13 @@ export class AgentStore extends EventEmitter {
|
||||
await unlink(agentPath).catch(() => {});
|
||||
await unlink(heartbeatPath).catch(() => {});
|
||||
|
||||
// Clean up sessions and runs directories
|
||||
const { rm } = await import("node:fs/promises");
|
||||
const sessionsDir = join(this.agentsDir, `${agentId}-sessions`);
|
||||
const runsDir = join(this.agentsDir, `${agentId}-runs`);
|
||||
await rm(sessionsDir, { recursive: true, force: true }).catch(() => {});
|
||||
await rm(runsDir, { recursive: true, force: true }).catch(() => {});
|
||||
|
||||
this.emit("agent:deleted", agentId);
|
||||
});
|
||||
}
|
||||
@@ -534,6 +565,146 @@ export class AgentStore extends EventEmitter {
|
||||
return Array.from(runs.values()).filter((r) => r.status !== "active");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Task Session Management
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get a task session for an agent.
|
||||
* @param agentId - The agent ID
|
||||
* @param taskId - The task ID
|
||||
* @returns The session, or null if not found
|
||||
*/
|
||||
async getTaskSession(agentId: string, taskId: string): Promise<AgentTaskSession | null> {
|
||||
const sessionsDir = join(this.agentsDir, `${agentId}-sessions`);
|
||||
const sessionPath = join(sessionsDir, `${taskId}.json`);
|
||||
|
||||
try {
|
||||
const content = await readFile(sessionPath, "utf-8");
|
||||
return JSON.parse(content) as AgentTaskSession;
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a task session for an agent.
|
||||
* @param session - The session data
|
||||
* @returns The saved session
|
||||
*/
|
||||
async upsertTaskSession(session: AgentTaskSession): Promise<AgentTaskSession> {
|
||||
const sessionsDir = join(this.agentsDir, `${session.agentId}-sessions`);
|
||||
await mkdir(sessionsDir, { recursive: true });
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const existing = await this.getTaskSession(session.agentId, session.taskId);
|
||||
|
||||
const saved: AgentTaskSession = {
|
||||
...session,
|
||||
createdAt: existing?.createdAt ?? now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
const sessionPath = join(sessionsDir, `${session.taskId}.json`);
|
||||
await writeFile(sessionPath, JSON.stringify(saved, null, 2));
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a task session.
|
||||
* @param agentId - The agent ID
|
||||
* @param taskId - The task ID
|
||||
*/
|
||||
async deleteTaskSession(agentId: string, taskId: string): Promise<void> {
|
||||
const sessionPath = join(this.agentsDir, `${agentId}-sessions`, `${taskId}.json`);
|
||||
await unlink(sessionPath).catch(() => {});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Org Hierarchy
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get agents that report to a specific agent.
|
||||
* @param agentId - The parent agent ID
|
||||
* @returns Array of agents that report to this agent
|
||||
*/
|
||||
async getAgentsByReportsTo(agentId: string): Promise<Agent[]> {
|
||||
const all = await this.listAgents();
|
||||
return all.filter((a) => a.reportsTo === agentId);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Rich Run Storage
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Save a rich heartbeat run record (structured JSON, not JSONL events).
|
||||
* @param run - The heartbeat run data
|
||||
*/
|
||||
async saveRun(run: AgentHeartbeatRun): Promise<void> {
|
||||
const runsDir = join(this.agentsDir, `${run.agentId}-runs`);
|
||||
await mkdir(runsDir, { recursive: true });
|
||||
const runPath = join(runsDir, `${run.id}.json`);
|
||||
await writeFile(runPath, JSON.stringify(run, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific run by ID.
|
||||
* @param agentId - The agent ID
|
||||
* @param runId - The run ID
|
||||
* @returns The run detail, or null if not found
|
||||
*/
|
||||
async getRunDetail(agentId: string, runId: string): Promise<AgentHeartbeatRun | null> {
|
||||
const runPath = join(this.agentsDir, `${agentId}-runs`, `${runId}.json`);
|
||||
try {
|
||||
const content = await readFile(runPath, "utf-8");
|
||||
return JSON.parse(content) as AgentHeartbeatRun;
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent runs for an agent from structured run storage.
|
||||
* @param agentId - The agent ID
|
||||
* @param limit - Max number of runs to return (default: 20)
|
||||
* @returns Array of runs (newest first)
|
||||
*/
|
||||
async getRecentRuns(agentId: string, limit = 20): Promise<AgentHeartbeatRun[]> {
|
||||
const runsDir = join(this.agentsDir, `${agentId}-runs`);
|
||||
let files: string[];
|
||||
try {
|
||||
files = await readdir(runsDir);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
const runFiles = files.filter((f) => f.endsWith(".json"));
|
||||
const runs: AgentHeartbeatRun[] = [];
|
||||
|
||||
for (const file of runFiles) {
|
||||
try {
|
||||
const content = await readFile(join(runsDir, file), "utf-8");
|
||||
runs.push(JSON.parse(content) as AgentHeartbeatRun);
|
||||
} catch {
|
||||
// Skip corrupted files
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by startedAt desc and limit
|
||||
return runs
|
||||
.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Private helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
@@ -555,6 +726,15 @@ export class AgentStore extends EventEmitter {
|
||||
updatedAt: data.updatedAt,
|
||||
lastHeartbeatAt: data.lastHeartbeatAt,
|
||||
metadata: data.metadata ?? {},
|
||||
title: data.title,
|
||||
icon: data.icon,
|
||||
reportsTo: data.reportsTo,
|
||||
runtimeConfig: data.runtimeConfig,
|
||||
pauseReason: data.pauseReason,
|
||||
permissions: data.permissions,
|
||||
totalInputTokens: data.totalInputTokens,
|
||||
totalOutputTokens: data.totalOutputTokens,
|
||||
lastError: data.lastError,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -570,6 +750,15 @@ export class AgentStore extends EventEmitter {
|
||||
updatedAt: agent.updatedAt,
|
||||
lastHeartbeatAt: agent.lastHeartbeatAt,
|
||||
metadata: agent.metadata,
|
||||
title: agent.title,
|
||||
icon: agent.icon,
|
||||
reportsTo: agent.reportsTo,
|
||||
runtimeConfig: agent.runtimeConfig,
|
||||
pauseReason: agent.pauseReason,
|
||||
permissions: agent.permissions,
|
||||
totalInputTokens: agent.totalInputTokens,
|
||||
totalOutputTokens: agent.totalOutputTokens,
|
||||
lastError: agent.lastError,
|
||||
};
|
||||
|
||||
// Write atomically using temp file
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { COLUMNS, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES } from "./types.js";
|
||||
export type { Column, IssueInfo, IssueState, PrInfo, PrStatus, Task, TaskAttachment, TaskComment, TaskCommentInput, TaskCreateInput, TaskDetail, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, MergeDetails, MergeResult, Settings, GlobalSettings, ProjectSettings, SettingsScope, TaskStep, StepStatus, TaskLogEntry, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentCapability, AgentHeartbeatEvent, AgentHeartbeatRun, NtfyNotificationEvent, SteeringComment } from "./types.js";
|
||||
export type { Column, IssueInfo, IssueState, PrInfo, PrStatus, Task, TaskAttachment, TaskComment, TaskCommentInput, TaskCreateInput, TaskDetail, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, MergeDetails, MergeResult, Settings, GlobalSettings, ProjectSettings, SettingsScope, TaskStep, StepStatus, TaskLogEntry, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentCapability, AgentHeartbeatEvent, AgentHeartbeatRun, HeartbeatInvocationSource, AgentTaskSession, AgentStats, NtfyNotificationEvent, SteeringComment } from "./types.js";
|
||||
export { AgentStore } from "./agent-store.js";
|
||||
export type { AgentStoreEvents } from "./agent-store.js";
|
||||
export { TaskStore } from "./store.js";
|
||||
|
||||
@@ -4301,7 +4301,7 @@ Task with acceptance criteria
|
||||
|
||||
const { execSync } = await import("node:child_process");
|
||||
try {
|
||||
execSync(`git checkout -b kb/${task.id.toLowerCase()}`, { cwd: rootDir, stdio: "pipe" });
|
||||
execSync(`git checkout -b fusion/${task.id.toLowerCase()}`, { cwd: rootDir, stdio: "pipe" });
|
||||
execSync('git commit --allow-empty -m "test commit"', { cwd: rootDir, stdio: "pipe" });
|
||||
execSync("git checkout main || git checkout master", { cwd: rootDir, stdio: "pipe" });
|
||||
} catch {
|
||||
@@ -4333,7 +4333,7 @@ Task with acceptance criteria
|
||||
// Create branch for merge
|
||||
const { execSync } = await import("node:child_process");
|
||||
try {
|
||||
execSync(`git checkout -b kb/${task.id.toLowerCase()}`, { cwd: rootDir, stdio: "pipe" });
|
||||
execSync(`git checkout -b fusion/${task.id.toLowerCase()}`, { cwd: rootDir, stdio: "pipe" });
|
||||
execSync('git commit --allow-empty -m "test commit"', { cwd: rootDir, stdio: "pipe" });
|
||||
execSync("git checkout main || git checkout master", { cwd: rootDir, stdio: "pipe" });
|
||||
} catch {
|
||||
|
||||
@@ -1016,7 +1016,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
|
||||
async updateTask(
|
||||
id: string,
|
||||
updates: { title?: string; description?: string; prompt?: string; worktree?: string; status?: string | null; dependencies?: string[]; blockedBy?: string | null; paused?: boolean; baseBranch?: string; branch?: string; baseCommitSha?: string; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; stuckKillCount?: number | null; recoveryRetryCount?: number | null; nextRecoveryAt?: string | null; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; error?: string | null; summary?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
|
||||
updates: { title?: string; description?: string; prompt?: string; worktree?: string; status?: string | null; dependencies?: string[]; blockedBy?: string | null; paused?: boolean; baseBranch?: string; branch?: string; baseCommitSha?: string; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; stuckKillCount?: number | null; recoveryRetryCount?: number | null; nextRecoveryAt?: string | null; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
|
||||
): Promise<Task> {
|
||||
return this.withTaskLock(id, async () => {
|
||||
// Validate that task doesn't depend on itself
|
||||
@@ -1115,6 +1115,11 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
} else if (updates.summary !== undefined) {
|
||||
task.summary = updates.summary;
|
||||
}
|
||||
if (updates.sessionFile === null) {
|
||||
task.sessionFile = undefined;
|
||||
} else if (updates.sessionFile !== undefined) {
|
||||
task.sessionFile = updates.sessionFile;
|
||||
}
|
||||
if (updates.workflowStepResults === null) {
|
||||
task.workflowStepResults = undefined;
|
||||
} else if (updates.workflowStepResults !== undefined) {
|
||||
@@ -1475,7 +1480,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
throw new Error(`Cannot merge ${id}: ${mergeBlocker}`);
|
||||
}
|
||||
|
||||
const branch = `kb/${id.toLowerCase()}`;
|
||||
const branch = `fusion/${id.toLowerCase()}`;
|
||||
const worktreePath = task.worktree;
|
||||
const result: MergeResult = {
|
||||
task,
|
||||
|
||||
@@ -524,6 +524,10 @@ export interface Task {
|
||||
nextRecoveryAt?: string;
|
||||
/** Thinking level for AI agent sessions — controls reasoning effort (off/minimal/low/medium/high) */
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
/** Path to the persisted agent session file, enabling pause/resume without
|
||||
* losing conversation context. Set when execution starts; cleared on
|
||||
* completion or terminal failure. */
|
||||
sessionFile?: string;
|
||||
/** Error message from the last failure, if the task failed during execution */
|
||||
error?: string;
|
||||
/** Optional summary of what was changed/fixed when task is completed */
|
||||
@@ -1243,14 +1247,16 @@ export interface PlanningSession {
|
||||
// ── Agent Types ────────────────────────────────────────────────────────────
|
||||
|
||||
/** Agent lifecycle states */
|
||||
export const AGENT_STATES = ["idle", "active", "paused", "terminated"] as const;
|
||||
export const AGENT_STATES = ["idle", "active", "running", "paused", "error", "terminated"] as const;
|
||||
export type AgentState = (typeof AGENT_STATES)[number];
|
||||
|
||||
/** Valid state transitions for agents */
|
||||
export const AGENT_VALID_TRANSITIONS: Record<AgentState, AgentState[]> = {
|
||||
idle: ["active"],
|
||||
active: ["paused", "terminated"],
|
||||
active: ["running", "paused", "terminated"],
|
||||
running: ["active", "paused", "error", "terminated"],
|
||||
paused: ["active", "terminated"],
|
||||
error: ["active", "terminated"],
|
||||
terminated: [], // Terminal state - no exits
|
||||
};
|
||||
|
||||
@@ -1264,6 +1270,9 @@ export interface AgentHeartbeatEvent {
|
||||
runId: string;
|
||||
}
|
||||
|
||||
/** What triggered a heartbeat run */
|
||||
export type HeartbeatInvocationSource = "on_demand" | "timer" | "assignment" | "automation";
|
||||
|
||||
/** A continuous heartbeat session/run for an agent */
|
||||
export interface AgentHeartbeatRun {
|
||||
/** Unique identifier for this run */
|
||||
@@ -1275,11 +1284,33 @@ export interface AgentHeartbeatRun {
|
||||
/** ISO-8601 timestamp when the run ended (null if active) */
|
||||
endedAt: string | null;
|
||||
/** Status of the run */
|
||||
status: "active" | "completed" | "terminated";
|
||||
status: "active" | "completed" | "terminated" | "failed";
|
||||
/** What triggered this run */
|
||||
invocationSource?: HeartbeatInvocationSource;
|
||||
/** Trigger detail (manual, ping, scheduler, system) */
|
||||
triggerDetail?: string;
|
||||
/** PID of the agent process */
|
||||
processPid?: number;
|
||||
/** Exit code of the agent process */
|
||||
exitCode?: number;
|
||||
/** Session ID before execution (for continuity tracking) */
|
||||
sessionIdBefore?: string;
|
||||
/** Session ID after execution */
|
||||
sessionIdAfter?: string;
|
||||
/** Token usage for this run */
|
||||
usageJson?: { inputTokens: number; outputTokens: number; cachedTokens: number };
|
||||
/** Structured result from the run */
|
||||
resultJson?: Record<string, unknown>;
|
||||
/** Snapshot of context at run start (taskId, projectId, etc.) */
|
||||
contextSnapshot?: Record<string, unknown>;
|
||||
/** Excerpt of stdout output */
|
||||
stdoutExcerpt?: string;
|
||||
/** Excerpt of stderr output */
|
||||
stderrExcerpt?: string;
|
||||
}
|
||||
|
||||
/** Capabilities/roles an agent can have */
|
||||
export type AgentCapability = "triage" | "executor" | "reviewer" | "merger" | "scheduler" | "custom";
|
||||
export type AgentCapability = "triage" | "executor" | "reviewer" | "merger" | "scheduler" | "engineer" | "custom";
|
||||
|
||||
/** Agent record stored in the system */
|
||||
export interface Agent {
|
||||
@@ -1301,6 +1332,24 @@ export interface Agent {
|
||||
lastHeartbeatAt?: string;
|
||||
/** Optional metadata */
|
||||
metadata: Record<string, unknown>;
|
||||
/** Job title / description for the agent */
|
||||
title?: string;
|
||||
/** Custom icon identifier */
|
||||
icon?: string;
|
||||
/** Agent ID this agent reports to (org hierarchy) */
|
||||
reportsTo?: string;
|
||||
/** Runtime configuration (maxTurns, thinkingLevel, etc.) */
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
/** Why the agent was paused (error, manual, etc.) */
|
||||
pauseReason?: string;
|
||||
/** Capability permission flags */
|
||||
permissions?: Record<string, boolean>;
|
||||
/** Cumulative input tokens across all runs */
|
||||
totalInputTokens?: number;
|
||||
/** Cumulative output tokens across all runs */
|
||||
totalOutputTokens?: number;
|
||||
/** Last error message */
|
||||
lastError?: string;
|
||||
}
|
||||
|
||||
/** Extended agent information including heartbeat history */
|
||||
@@ -1318,6 +1367,11 @@ export interface AgentCreateInput {
|
||||
name: string;
|
||||
role: AgentCapability;
|
||||
metadata?: Record<string, unknown>;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
reportsTo?: string;
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
permissions?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
/** Input for updating an existing agent */
|
||||
@@ -1325,6 +1379,45 @@ export interface AgentUpdateInput {
|
||||
name?: string;
|
||||
role?: AgentCapability;
|
||||
metadata?: Record<string, unknown>;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
reportsTo?: string;
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
pauseReason?: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
lastError?: string;
|
||||
totalInputTokens?: number;
|
||||
totalOutputTokens?: number;
|
||||
}
|
||||
|
||||
/** Per-task session persistence for an agent */
|
||||
export interface AgentTaskSession {
|
||||
/** Agent ID */
|
||||
agentId: string;
|
||||
/** Task ID */
|
||||
taskId: string;
|
||||
/** Session state for resuming context across runs */
|
||||
sessionParams: Record<string, unknown>;
|
||||
/** Human-readable session identifier */
|
||||
sessionDisplayId?: string;
|
||||
/** ISO-8601 timestamp when session was created */
|
||||
createdAt: string;
|
||||
/** ISO-8601 timestamp of last update */
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/** Aggregate statistics for agents */
|
||||
export interface AgentStats {
|
||||
/** Number of agents in active/running state */
|
||||
activeCount: number;
|
||||
/** Number of tasks assigned to agents */
|
||||
assignedTaskCount: number;
|
||||
/** Total completed runs */
|
||||
completedRuns: number;
|
||||
/** Total failed runs */
|
||||
failedRuns: number;
|
||||
/** Success rate (0-1) */
|
||||
successRate: number;
|
||||
}
|
||||
|
||||
// ── Multi-Project First-Run & Migration Types ───────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user