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:
Mohamed Boudra
2026-04-14 03:05:45 +07:00
333 changed files with 13490 additions and 6317 deletions
@@ -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;