Merge branch 'main' into dev
Resolve 38 conflicts preserving both sides' work: - Server: DB-based registries (dev) + WorkspaceGitService/DaemonConfigStore (main) - App: script proxy/voice MCP (dev) + pin/hide tabs/bootstrap boundary (main) - Shared: WorkspaceScriptPayload (dev) + git/GitHub runtime schemas (main) - Deleted workspace-registry-bootstrap.ts (dev's DB approach supersedes)
This commit is contained in:
@@ -45,7 +45,7 @@ import type {
|
||||
AgentTimelineRow,
|
||||
AgentTimelineStore,
|
||||
} from "./agent-timeline-store-types.js";
|
||||
import { AGENT_PROVIDER_IDS } from "./provider-manifest.js";
|
||||
import { AGENT_PROVIDER_IDS, getAgentProviderDefinition } from "./provider-manifest.js";
|
||||
|
||||
export { AGENT_LIFECYCLE_STATUSES, type AgentLifecycleStatus };
|
||||
export type {
|
||||
@@ -96,6 +96,7 @@ export type AgentManagerOptions = {
|
||||
onAgentAttention?: AgentAttentionCallback;
|
||||
durableTimelineStore?: AgentTimelineStore;
|
||||
terminalManager?: TerminalManager | null;
|
||||
mcpBaseUrl?: string;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
@@ -309,6 +310,7 @@ export class AgentManager {
|
||||
private readonly durableTimelineStore?: AgentTimelineStore;
|
||||
private readonly previousStatuses = new Map<string, AgentLifecycleStatus>();
|
||||
private readonly backgroundTasks = new Set<Promise<void>>();
|
||||
private mcpBaseUrl: string | null;
|
||||
private onAgentAttention?: AgentAttentionCallback;
|
||||
private logger: Logger;
|
||||
|
||||
@@ -317,6 +319,7 @@ export class AgentManager {
|
||||
this.registry = options?.registry;
|
||||
this.durableTimelineStore = options?.durableTimelineStore;
|
||||
this.onAgentAttention = options?.onAgentAttention;
|
||||
this.mcpBaseUrl = options?.mcpBaseUrl ?? null;
|
||||
this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
|
||||
if (options?.clients) {
|
||||
for (const [provider, client] of Object.entries(options.clients)) {
|
||||
@@ -335,6 +338,10 @@ export class AgentManager {
|
||||
this.onAgentAttention = callback;
|
||||
}
|
||||
|
||||
setMcpBaseUrl(url: string | null): void {
|
||||
this.mcpBaseUrl = url;
|
||||
}
|
||||
|
||||
public getMetricsSnapshot(): AgentMetricsSnapshot {
|
||||
const byLifecycle: Record<string, number> = {};
|
||||
let withActiveForegroundTurn = 0;
|
||||
@@ -599,9 +606,21 @@ export class AgentManager {
|
||||
initialPrompt?: string;
|
||||
},
|
||||
): Promise<ManagedAgent> {
|
||||
// Generate agent ID early so we can use it in MCP config
|
||||
const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "createAgent");
|
||||
const normalizedConfig = await this.normalizeConfig(config);
|
||||
const injectedConfig =
|
||||
this.mcpBaseUrl == null
|
||||
? config
|
||||
: {
|
||||
...config,
|
||||
mcpServers: {
|
||||
paseo: {
|
||||
type: "http" as const,
|
||||
url: `${this.mcpBaseUrl}?callerAgentId=${resolvedAgentId}`,
|
||||
},
|
||||
...(config.mcpServers ?? {}),
|
||||
},
|
||||
};
|
||||
const normalizedConfig = await this.normalizeConfig(injectedConfig);
|
||||
const launchContext = this.buildLaunchContext(resolvedAgentId);
|
||||
const client = this.requireClient(normalizedConfig.provider);
|
||||
const available = await client.isAvailable();
|
||||
@@ -1273,10 +1292,7 @@ export class AgentManager {
|
||||
}
|
||||
|
||||
const pendingRun = this.getPendingForegroundRun(agentId);
|
||||
if (
|
||||
(snapshot.lifecycle === "running" || pendingRun?.started) &&
|
||||
!snapshot.pendingReplacement
|
||||
) {
|
||||
if ((snapshot.lifecycle === "running" || pendingRun?.started) && !snapshot.pendingReplacement) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1353,11 +1369,7 @@ export class AgentManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!currentPendingRun &&
|
||||
!current.activeForegroundTurnId &&
|
||||
!current.pendingReplacement
|
||||
) {
|
||||
if (!currentPendingRun && !current.activeForegroundTurnId && !current.pendingReplacement) {
|
||||
finishErr(new Error(`Agent ${agentId} run finished before starting`));
|
||||
return true;
|
||||
}
|
||||
@@ -1419,8 +1431,7 @@ export class AgentManager {
|
||||
const pendingRun = this.getPendingForegroundRun(agentId);
|
||||
const foregroundTurnId = agent.activeForegroundTurnId;
|
||||
const hasForegroundTurn = Boolean(foregroundTurnId);
|
||||
const isAutonomousRunning =
|
||||
agent.lifecycle === "running" && !hasForegroundTurn && !pendingRun;
|
||||
const isAutonomousRunning = agent.lifecycle === "running" && !hasForegroundTurn && !pendingRun;
|
||||
|
||||
if (!hasForegroundTurn && !isAutonomousRunning && !pendingRun) {
|
||||
return false;
|
||||
@@ -2186,9 +2197,7 @@ export class AgentManager {
|
||||
},
|
||||
): Promise<void> {
|
||||
const eventTurnId = (event as { turnId?: string }).turnId;
|
||||
const isForegroundEvent = Boolean(
|
||||
eventTurnId && agent.activeForegroundTurnId === eventTurnId,
|
||||
);
|
||||
const isForegroundEvent = Boolean(eventTurnId && agent.activeForegroundTurnId === eventTurnId);
|
||||
|
||||
// Only update timestamp for live events, not history replay
|
||||
if (!options?.fromHistory) {
|
||||
@@ -2229,11 +2238,7 @@ export class AgentManager {
|
||||
}
|
||||
// Suppress user_message echoes for the active foreground turn —
|
||||
// these are already recorded by recordUserMessage().
|
||||
if (
|
||||
!options?.fromHistory &&
|
||||
event.item.type === "user_message" &&
|
||||
isForegroundEvent
|
||||
) {
|
||||
if (!options?.fromHistory && event.item.type === "user_message" && isForegroundEvent) {
|
||||
const eventMessageId = normalizeMessageId(event.item.messageId);
|
||||
const eventText = event.item.text;
|
||||
if (eventMessageId) {
|
||||
@@ -2274,7 +2279,7 @@ export class AgentManager {
|
||||
void this.refreshRuntimeInfo(agent);
|
||||
break;
|
||||
case "turn_failed":
|
||||
this.logger.trace(
|
||||
this.logger.warn(
|
||||
{
|
||||
agentId: agent.id,
|
||||
lifecycle: agent.lifecycle,
|
||||
@@ -2631,9 +2636,7 @@ export class AgentManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async normalizeConfig(
|
||||
config: AgentSessionConfig,
|
||||
): Promise<AgentSessionConfig> {
|
||||
private async normalizeConfig(config: AgentSessionConfig): Promise<AgentSessionConfig> {
|
||||
const normalized: AgentSessionConfig = { ...config };
|
||||
|
||||
// Always resolve cwd to absolute path for consistent history file lookup
|
||||
@@ -2661,7 +2664,31 @@ export class AgentManager {
|
||||
|
||||
if (typeof normalized.model === "string") {
|
||||
const trimmed = normalized.model.trim();
|
||||
normalized.model = trimmed.length > 0 ? trimmed : undefined;
|
||||
normalized.model = trimmed.length > 0 && trimmed !== "default" ? trimmed : undefined;
|
||||
}
|
||||
|
||||
if (!normalized.model) {
|
||||
const client = this.clients.get(normalized.provider);
|
||||
if (client) {
|
||||
try {
|
||||
const models = await client.listModels();
|
||||
const defaultModel = models.find((model) => model.isDefault) ?? models[0];
|
||||
if (defaultModel) {
|
||||
normalized.model = defaultModel.id;
|
||||
}
|
||||
} catch {
|
||||
// Provider may not support model listing — leave model undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!normalized.modeId) {
|
||||
try {
|
||||
normalized.modeId =
|
||||
getAgentProviderDefinition(normalized.provider).defaultModeId ?? undefined;
|
||||
} catch {
|
||||
// Unknown provider
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
|
||||
Reference in New Issue
Block a user