Add provider/model selection for schedules and workspace fetch debug logging
- Unify resolveProviderAndModel across CLI and server, supporting provider/model syntax (e.g. codex/gpt-5.4) for agent runs and schedules - Add --provider flag to schedule create CLI and both MCP schedule tools - Add structured debug logging to workspace fetch hydrate, sidebar refresh, and real-time update paths - Update orchestrate skill references
This commit is contained in:
@@ -53,6 +53,7 @@ import { buildDraftStoreKey } from "@/stores/draft-keys";
|
||||
import type { AttachmentMetadata } from "@/attachments/types";
|
||||
import { reconcilePreviousAgentStatuses } from "@/contexts/session-status-tracking";
|
||||
import { isNative } from "@/constants/platform";
|
||||
import { summarizeWorkspaceCollection } from "@/utils/workspace-fetch-debug";
|
||||
|
||||
// Re-export types from session-store and draft-store for backward compatibility
|
||||
export type { DraftInput } from "@/stores/draft-store";
|
||||
@@ -333,11 +334,24 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
let includeSubscribe = options?.subscribe ?? false;
|
||||
|
||||
while (true) {
|
||||
console.log("[WorkspaceFetch][hydrate] request", {
|
||||
serverId,
|
||||
cursor,
|
||||
includeSubscribe,
|
||||
existingWorkspaces: summarizeWorkspaceCollection(existingWorkspaces?.values()),
|
||||
});
|
||||
const payload = await client.fetchWorkspaces({
|
||||
sort: [{ key: "activity_at", direction: "desc" }],
|
||||
...(includeSubscribe ? { subscribe: {} } : {}),
|
||||
page: cursor ? { limit: 200, cursor } : { limit: 200 },
|
||||
});
|
||||
console.log("[WorkspaceFetch][hydrate] response", {
|
||||
serverId,
|
||||
cursor,
|
||||
includeSubscribe,
|
||||
pageInfo: payload.pageInfo,
|
||||
payload: summarizeWorkspaceCollection(payload.entries),
|
||||
});
|
||||
if (options?.isCancelled?.()) {
|
||||
return;
|
||||
}
|
||||
@@ -366,6 +380,11 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
|
||||
setWorkspaces(serverId, workspaces);
|
||||
setHasHydratedWorkspaces(serverId, true);
|
||||
console.log("[WorkspaceFetch][hydrate] applied", {
|
||||
serverId,
|
||||
subscribed: options?.subscribe ?? false,
|
||||
nextWorkspaces: summarizeWorkspaceCollection(workspaces.values()),
|
||||
});
|
||||
},
|
||||
[client, isConnected, serverId, setHasHydratedWorkspaces, setWorkspaces],
|
||||
);
|
||||
@@ -1153,10 +1172,23 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
const unsubWorkspaceUpdate = client.on("workspace_update", (message) => {
|
||||
if (message.type !== "workspace_update") return;
|
||||
if (message.payload.kind === "remove") {
|
||||
console.log("[WorkspaceFetch][update] remove", {
|
||||
serverId,
|
||||
workspaceId: message.payload.id,
|
||||
});
|
||||
removeWorkspace(serverId, message.payload.id);
|
||||
return;
|
||||
}
|
||||
mergeWorkspaces(serverId, [normalizeWorkspaceDescriptor(message.payload.workspace)]);
|
||||
const workspace = normalizeWorkspaceDescriptor(message.payload.workspace);
|
||||
const existingWorkspace = useSessionStore
|
||||
.getState()
|
||||
.sessions[serverId]?.workspaces.get(workspace.id);
|
||||
console.log("[WorkspaceFetch][update] upsert", {
|
||||
serverId,
|
||||
existedInStore: Boolean(existingWorkspace),
|
||||
workspace: summarizeWorkspaceCollection([workspace]),
|
||||
});
|
||||
mergeWorkspaces(serverId, [workspace]);
|
||||
});
|
||||
|
||||
const unsubStatus = client.on("status", (message) => {
|
||||
|
||||
@@ -9,6 +9,10 @@ import { useSidebarOrderStore } from "@/stores/sidebar-order-store";
|
||||
import type { WorkspaceDescriptor } from "@/stores/session-store";
|
||||
import type { WorkspaceDescriptorPayload } from "@server/shared/messages";
|
||||
import { projectDisplayNameFromProjectId } from "@/utils/project-display-name";
|
||||
import {
|
||||
summarizeSidebarProjects,
|
||||
summarizeWorkspaceCollection,
|
||||
} from "@/utils/workspace-fetch-debug";
|
||||
|
||||
const EMPTY_ORDER: string[] = [];
|
||||
const EMPTY_PROJECTS: SidebarProjectEntry[] = [];
|
||||
@@ -284,6 +288,20 @@ export function useSidebarWorkspacesList(options?: {
|
||||
});
|
||||
}, [persistedProjectOrder, persistedWorkspaceOrderByScope, serverId, sessionWorkspaces]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!serverId) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[WorkspaceFetch][sidebar] model", {
|
||||
serverId,
|
||||
connectionStatus,
|
||||
hasHydratedWorkspaces,
|
||||
sessionWorkspaces: summarizeWorkspaceCollection(sessionWorkspaces?.values()),
|
||||
projects: summarizeSidebarProjects(projects),
|
||||
});
|
||||
}, [connectionStatus, hasHydratedWorkspaces, projects, serverId, sessionWorkspaces]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!serverId || projects.length === 0) {
|
||||
return;
|
||||
@@ -327,10 +345,21 @@ export function useSidebarWorkspacesList(options?: {
|
||||
let cursor: string | null = null;
|
||||
try {
|
||||
while (true) {
|
||||
console.log("[WorkspaceFetch][sidebar-refresh] request", {
|
||||
serverId,
|
||||
cursor,
|
||||
existingWorkspaces: summarizeWorkspaceCollection(existingWorkspaces?.values()),
|
||||
});
|
||||
const payload = await client.fetchWorkspaces({
|
||||
sort: [{ key: "activity_at", direction: "desc" }],
|
||||
page: cursor ? { limit: 200, cursor } : { limit: 200 },
|
||||
});
|
||||
console.log("[WorkspaceFetch][sidebar-refresh] response", {
|
||||
serverId,
|
||||
cursor,
|
||||
pageInfo: payload.pageInfo,
|
||||
payload: summarizeWorkspaceCollection(payload.entries),
|
||||
});
|
||||
for (const entry of payload.entries) {
|
||||
const workspace = toWorkspaceDescriptor(entry);
|
||||
next.set(
|
||||
@@ -349,7 +378,16 @@ export function useSidebarWorkspacesList(options?: {
|
||||
const store = useSessionStore.getState();
|
||||
store.setWorkspaces(serverId, next);
|
||||
store.setHasHydratedWorkspaces(serverId, true);
|
||||
} catch {
|
||||
console.log("[WorkspaceFetch][sidebar-refresh] applied", {
|
||||
serverId,
|
||||
nextWorkspaces: summarizeWorkspaceCollection(next.values()),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[WorkspaceFetch][sidebar-refresh] failed", {
|
||||
serverId,
|
||||
cursor,
|
||||
error,
|
||||
});
|
||||
// ignore explicit refresh failures; hook keeps existing data
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
type WorkspaceLike = {
|
||||
id: string;
|
||||
projectId: string;
|
||||
projectDisplayName?: string;
|
||||
name: string;
|
||||
status: string;
|
||||
workspaceKind: string;
|
||||
};
|
||||
|
||||
type SidebarWorkspaceLike = {
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
statusBucket: string;
|
||||
workspaceKind: string;
|
||||
};
|
||||
|
||||
type SidebarProjectLike = {
|
||||
projectKey: string;
|
||||
projectName: string;
|
||||
statusBucket: string;
|
||||
activeCount: number;
|
||||
totalWorkspaces: number;
|
||||
workspaces: SidebarWorkspaceLike[];
|
||||
};
|
||||
|
||||
function countByValue(values: Iterable<string>): Record<string, number> {
|
||||
const counts = new Map<string, number>();
|
||||
for (const value of values) {
|
||||
counts.set(value, (counts.get(value) ?? 0) + 1);
|
||||
}
|
||||
return Object.fromEntries(counts);
|
||||
}
|
||||
|
||||
export function summarizeWorkspaceCollection(
|
||||
workspaces: Iterable<WorkspaceLike> | null | undefined,
|
||||
): {
|
||||
count: number;
|
||||
projectIds: string[];
|
||||
statusCounts: Record<string, number>;
|
||||
workspaces: Array<{
|
||||
id: string;
|
||||
projectId: string;
|
||||
projectDisplayName: string | null;
|
||||
name: string;
|
||||
status: string;
|
||||
workspaceKind: string;
|
||||
}>;
|
||||
} {
|
||||
const entries = Array.from(workspaces ?? [], (workspace) => ({
|
||||
id: workspace.id,
|
||||
projectId: workspace.projectId,
|
||||
projectDisplayName: workspace.projectDisplayName?.trim() || null,
|
||||
name: workspace.name,
|
||||
status: workspace.status,
|
||||
workspaceKind: workspace.workspaceKind,
|
||||
}));
|
||||
|
||||
return {
|
||||
count: entries.length,
|
||||
projectIds: [...new Set(entries.map((workspace) => workspace.projectId))],
|
||||
statusCounts: countByValue(entries.map((workspace) => workspace.status)),
|
||||
workspaces: entries,
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeSidebarProjects(
|
||||
projects: Iterable<SidebarProjectLike> | null | undefined,
|
||||
): {
|
||||
count: number;
|
||||
projectKeys: string[];
|
||||
projects: Array<{
|
||||
projectKey: string;
|
||||
projectName: string;
|
||||
statusBucket: string;
|
||||
activeCount: number;
|
||||
totalWorkspaces: number;
|
||||
workspaces: Array<{
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
statusBucket: string;
|
||||
workspaceKind: string;
|
||||
}>;
|
||||
}>;
|
||||
} {
|
||||
const entries = Array.from(projects ?? [], (project) => ({
|
||||
projectKey: project.projectKey,
|
||||
projectName: project.projectName,
|
||||
statusBucket: project.statusBucket,
|
||||
activeCount: project.activeCount,
|
||||
totalWorkspaces: project.totalWorkspaces,
|
||||
workspaces: project.workspaces.map((workspace) => ({
|
||||
workspaceId: workspace.workspaceId,
|
||||
name: workspace.name,
|
||||
statusBucket: workspace.statusBucket,
|
||||
workspaceKind: workspace.workspaceKind,
|
||||
})),
|
||||
}));
|
||||
|
||||
return {
|
||||
count: entries.length,
|
||||
projectKeys: entries.map((project) => project.projectKey),
|
||||
projects: entries,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user