Unverified 提交 beeaef57 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Replace ChatLogsData with comprehensive SessionDebugBundle schema (#2488)

## Summary - Replaces the old `ChatLogsDataSchema` with a new `SessionDebugBundleSchema` (schema version 1) that captures all non-sensitive data needed for debugging chat sessions - Each message now includes AI SDK JSON (with base64 images stripped), model name, token usage, timestamps, commit hashes, request ID, and approval state - Adds non-sensitive user settings snapshot, app metadata, custom provider/model definitions, and MCP server configurations to the debug bundle - Updates HelpDialog to use the new schema and adds `Session Schema: v2.0` marker to GitHub issue templates ## Test plan - [ ] Verify `npm run ts` passes (type-check) - [ ] Verify `npm run lint` passes - [ ] Verify `npm test` passes (all 661 unit tests) - [ ] Manual: Open Help dialog → Upload Chat Session → verify the review modal shows messages, codebase, logs, and system info correctly - [ ] Manual: Complete upload and verify the GitHub issue template includes `Session Schema: v2.0` - [ ] Manual: Download uploaded JSON and verify it contains: `schemaVersion`, `system`, `settings`, `app`, `chat` (with `aiMessagesJson` per message), `providers`, `mcpServers`, `codebase`, `logs` - [ ] Manual: Verify no API keys, OAuth tokens, or MCP env vars appear in uploaded data 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2488"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Expands what gets captured and uploaded for support sessions (settings/app metadata/providers/MCP and richer message data), so any sanitization or schema mismatch could leak data or break the support upload flow. > > **Overview** > Switches chat-session uploads from `ChatLogsData` to a new versioned `SessionDebugBundle` (`SESSION_DEBUG_SCHEMA_VERSION = 2`) and updates the IPC contract/exports accordingly. > > The main-process handler now assembles a comprehensive bundle (system/runtime info, sanitized settings snapshot, app metadata, full chat messages with stripped image/file blobs, custom provider/model summaries, MCP server configs without env/header secrets, codebase snapshot, and last ~1000 log lines), and `HelpDialog` is updated to fetch/review/upload this bundle and annotate created GitHub issues with `Session Schema: v2.0` (plus adds a settings section to the bug report template). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7b94199939aa6963787e8fe69716e20cd6570b7d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 fd917781
...@@ -21,7 +21,7 @@ import { ipc } from "@/ipc/types"; ...@@ -21,7 +21,7 @@ import { ipc } from "@/ipc/types";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { ChatLogsData } from "@/ipc/types"; import { SessionDebugBundle } from "@/ipc/types";
import { showError } from "@/lib/toast"; import { showError } from "@/lib/toast";
import { HelpBotDialog } from "./HelpBotDialog"; import { HelpBotDialog } from "./HelpBotDialog";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
...@@ -37,7 +37,9 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) { ...@@ -37,7 +37,9 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const [reviewMode, setReviewMode] = useState(false); const [reviewMode, setReviewMode] = useState(false);
const [chatLogsData, setChatLogsData] = useState<ChatLogsData | null>(null); const [debugBundle, setDebugBundle] = useState<SessionDebugBundle | null>(
null,
);
const [uploadComplete, setUploadComplete] = useState(false); const [uploadComplete, setUploadComplete] = useState(false);
const [sessionId, setSessionId] = useState(""); const [sessionId, setSessionId] = useState("");
const [isHelpBotOpen, setIsHelpBotOpen] = useState(false); const [isHelpBotOpen, setIsHelpBotOpen] = useState(false);
...@@ -52,7 +54,7 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) { ...@@ -52,7 +54,7 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
setIsLoading(false); setIsLoading(false);
setIsUploading(false); setIsUploading(false);
setReviewMode(false); setReviewMode(false);
setChatLogsData(null); setDebugBundle(null);
setUploadComplete(false); setUploadComplete(false);
setSessionId(""); setSessionId("");
}; };
...@@ -76,6 +78,20 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) { ...@@ -76,6 +78,20 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
const debugInfo = await ipc.system.getSystemDebugInfo(); const debugInfo = await ipc.system.getSystemDebugInfo();
// Create a formatted issue body with the debug info // Create a formatted issue body with the debug info
const settingsLines = settings
? [
`- Selected Model: ${settings.selectedModel?.provider}:${settings.selectedModel?.name}`,
`- Chat Mode: ${settings.selectedChatMode ?? "default"}`,
`- Auto Approve Changes: ${settings.autoApproveChanges ?? "n/a"}`,
`- Dyad Pro Enabled: ${settings.enableDyadPro ?? "n/a"}`,
`- Thinking Budget: ${settings.thinkingBudget ?? "n/a"}`,
`- Runtime Mode: ${settings.runtimeMode2 ?? "n/a"}`,
`- Release Channel: ${settings.releaseChannel ?? "n/a"}`,
`- Auto Fix Problems: ${settings.enableAutoFixProblems ?? "n/a"}`,
`- Native Git: ${settings.enableNativeGit ?? "n/a"}`,
].join("\n")
: "Settings not available";
const issueBody = ` const issueBody = `
<!-- Please fill in all fields in English --> <!-- Please fill in all fields in English -->
...@@ -96,6 +112,9 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) { ...@@ -96,6 +112,9 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
- Telemetry ID: ${debugInfo.telemetryId || "n/a"} - Telemetry ID: ${debugInfo.telemetryId || "n/a"}
- Model: ${debugInfo.selectedLanguageModel || "n/a"} - Model: ${debugInfo.selectedLanguageModel || "n/a"}
## Settings
${settingsLines}
## Logs ## Logs
\`\`\` \`\`\`
${debugInfo.logs.slice(-3_500) || "No logs available"} ${debugInfo.logs.slice(-3_500) || "No logs available"}
...@@ -131,10 +150,10 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"} ...@@ -131,10 +150,10 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
setIsUploading(true); setIsUploading(true);
try { try {
// Get chat logs (includes debug info, chat data, and codebase) // Get chat logs (includes debug info, chat data, and codebase)
const chatLogs = await ipc.misc.getChatLogs(selectedChatId); const debugBundle = await ipc.misc.getSessionDebugBundle(selectedChatId);
// Store data for review and switch to review mode // Store data for review and switch to review mode
setChatLogsData(chatLogs); setDebugBundle(debugBundle);
setReviewMode(true); setReviewMode(true);
} catch (error) { } catch (error) {
console.error("Failed to upload chat session:", error); console.error("Failed to upload chat session:", error);
...@@ -147,17 +166,10 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"} ...@@ -147,17 +166,10 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
}; };
const handleSubmitChatLogs = async () => { const handleSubmitChatLogs = async () => {
if (!chatLogsData) return; if (!debugBundle) return;
setIsUploading(true); setIsUploading(true);
try { try {
// Prepare data for upload
const chatLogsJson = {
systemInfo: chatLogsData.debugInfo,
chat: chatLogsData.chat,
codebaseSnippet: chatLogsData.codebase,
};
// Get signed URL // Get signed URL
const response = await fetch( const response = await fetch(
"https://upload-logs.dyad.sh/generate-upload-url", "https://upload-logs.dyad.sh/generate-upload-url",
...@@ -180,10 +192,11 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"} ...@@ -180,10 +192,11 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
const { uploadUrl, filename } = await response.json(); const { uploadUrl, filename } = await response.json();
// Upload the full debug bundle directly
await ipc.system.uploadToSignedUrl({ await ipc.system.uploadToSignedUrl({
url: uploadUrl, url: uploadUrl,
contentType: "application/json", contentType: "application/json",
data: chatLogsJson, data: debugBundle,
}); });
// Extract session ID (filename without extension) // Extract session ID (filename without extension)
...@@ -201,7 +214,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"} ...@@ -201,7 +214,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
const handleCancelReview = () => { const handleCancelReview = () => {
setReviewMode(false); setReviewMode(false);
setChatLogsData(null); setDebugBundle(null);
}; };
const handleOpenGitHubIssue = () => { const handleOpenGitHubIssue = () => {
...@@ -210,6 +223,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"} ...@@ -210,6 +223,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
<!-- Please fill in all fields in English --> <!-- Please fill in all fields in English -->
Session ID: ${sessionId} Session ID: ${sessionId}
Session Schema: v2.0
Pro User ID: ${userBudget?.redactedUserId || "n/a"} Pro User ID: ${userBudget?.redactedUserId || "n/a"}
## Issue Description (required) ## Issue Description (required)
...@@ -276,7 +290,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"} ...@@ -276,7 +290,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"}
); );
} }
if (reviewMode && chatLogsData) { if (reviewMode && debugBundle) {
return ( return (
<Dialog open={isOpen} onOpenChange={handleClose}> <Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col"> <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col">
...@@ -302,7 +316,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"} ...@@ -302,7 +316,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"}
<div className="border rounded-md p-3"> <div className="border rounded-md p-3">
<h3 className="font-medium mb-2">Chat Messages</h3> <h3 className="font-medium mb-2">Chat Messages</h3>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto"> <div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto">
{chatLogsData.chat.messages.map((msg) => ( {debugBundle.chat.messages.map((msg) => (
<div key={msg.id} className="mb-2"> <div key={msg.id} className="mb-2">
<span className="font-semibold"> <span className="font-semibold">
{msg.role === "user" ? "You" : "Assistant"}:{" "} {msg.role === "user" ? "You" : "Assistant"}:{" "}
...@@ -316,29 +330,63 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"} ...@@ -316,29 +330,63 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"}
<div className="border rounded-md p-3"> <div className="border rounded-md p-3">
<h3 className="font-medium mb-2">Codebase Snapshot</h3> <h3 className="font-medium mb-2">Codebase Snapshot</h3>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto font-mono"> <div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto font-mono">
{chatLogsData.codebase} {debugBundle.codebase}
</div> </div>
</div> </div>
<div className="border rounded-md p-3"> <div className="border rounded-md p-3">
<h3 className="font-medium mb-2">Logs</h3> <h3 className="font-medium mb-2">Logs</h3>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto font-mono"> <div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto font-mono">
{chatLogsData.debugInfo.logs} {debugBundle.logs}
</div> </div>
</div> </div>
<div className="border rounded-md p-3"> <div className="border rounded-md p-3">
<h3 className="font-medium mb-2">System Information</h3> <h3 className="font-medium mb-2">System Information</h3>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-32 overflow-y-auto"> <div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-32 overflow-y-auto">
<p>Dyad Version: {chatLogsData.debugInfo.dyadVersion}</p> <p>Dyad Version: {debugBundle.system.dyadVersion}</p>
<p>Platform: {chatLogsData.debugInfo.platform}</p> <p>Platform: {debugBundle.system.platform}</p>
<p>Architecture: {chatLogsData.debugInfo.architecture}</p> <p>Architecture: {debugBundle.system.architecture}</p>
<p> <p>
Node Version:{" "} Node Version:{" "}
{chatLogsData.debugInfo.nodeVersion || "Not available"} {debugBundle.system.nodeVersion || "Not available"}
</p> </p>
</div> </div>
</div> </div>
<details className="border rounded-md p-3">
<summary className="font-medium cursor-pointer">Settings</summary>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto mt-2 font-mono whitespace-pre-wrap">
{JSON.stringify(debugBundle.settings, null, 2)}
</div>
</details>
<details className="border rounded-md p-3">
<summary className="font-medium cursor-pointer">
App Metadata
</summary>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto mt-2 font-mono whitespace-pre-wrap">
{JSON.stringify(debugBundle.app, null, 2)}
</div>
</details>
<details className="border rounded-md p-3">
<summary className="font-medium cursor-pointer">
Custom Providers & Models
</summary>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto mt-2 font-mono whitespace-pre-wrap">
{JSON.stringify(debugBundle.providers, null, 2)}
</div>
</details>
<details className="border rounded-md p-3">
<summary className="font-medium cursor-pointer">
MCP Servers
</summary>
<div className="text-sm bg-slate-50 dark:bg-slate-900 rounded p-2 max-h-40 overflow-y-auto mt-2 font-mono whitespace-pre-wrap">
{JSON.stringify(debugBundle.mcpServers, null, 2)}
</div>
</details>
</div> </div>
<div className="flex justify-between mt-4 pt-2 sticky bottom-0 bg-background"> <div className="flex justify-between mt-4 pt-2 sticky bottom-0 bg-background">
......
...@@ -280,7 +280,12 @@ export type { ...@@ -280,7 +280,12 @@ export type {
export type { SecurityReviewResult } from "./security"; export type { SecurityReviewResult } from "./security";
// Misc types // Misc types
export type { ChatLogsData, DeepLinkData, AppOutput, EnvVar } from "./misc"; export type {
SessionDebugBundle,
DeepLinkData,
AppOutput,
EnvVar,
} from "./misc";
// Free agent quota types // Free agent quota types
export type { FreeAgentQuotaStatus } from "./free_agent_quota"; export type { FreeAgentQuotaStatus } from "./free_agent_quota";
......
...@@ -41,39 +41,241 @@ export const SetAppEnvVarsParamsSchema = z.object({ ...@@ -41,39 +41,241 @@ export const SetAppEnvVarsParamsSchema = z.object({
}); });
// ============================================================================= // =============================================================================
// Chat Logs Schemas // Session Debug Bundle Schemas
// ============================================================================= // =============================================================================
export const ChatLogsDataSchema = z.object({ /**
debugInfo: z.object({ * Schema version for the session debug bundle format.
nodeVersion: z.string().nullable(), * Bump this when making breaking changes to the schema.
pnpmVersion: z.string().nullable(), */
nodePath: z.string().nullable(), export const SESSION_DEBUG_SCHEMA_VERSION = 2;
telemetryId: z.string(),
telemetryConsent: z.string(), // -- System info --
telemetryUrl: z.string(),
const DebugSystemInfoSchema = z.object({
/** Dyad application version (from package.json) */
dyadVersion: z.string(), dyadVersion: z.string(),
/** OS platform: "darwin", "win32", "linux" */
platform: z.string(), platform: z.string(),
/** CPU architecture: "x64", "arm64" */
architecture: z.string(), architecture: z.string(),
logs: z.string(), /** Node.js version, or null if not found */
selectedLanguageModel: z.string(), nodeVersion: z.string().nullable(),
/** pnpm version, or null if not found */
pnpmVersion: z.string().nullable(),
/** Resolved path to the node binary, or null */
nodePath: z.string().nullable(),
/** Electron version */
electronVersion: z.string(),
/** Telemetry ID for cross-referencing server-side logs. Null if user opted out. */
telemetryId: z.string().nullable(),
});
// -- Non-sensitive settings snapshot --
const DebugSettingsSchema = z.object({
/** Currently selected language model */
selectedModel: z.object({
name: z.string(),
provider: z.string(),
customModelId: z.number().optional(),
}), }),
chat: z.object({ /** Active chat mode for the session */
selectedChatMode: z.string().nullable(),
/** Default chat mode preference */
defaultChatMode: z.string().nullable(),
/** Whether changes are auto-approved without review */
autoApproveChanges: z.boolean().nullable(),
/** Whether Dyad Pro is enabled */
enableDyadPro: z.boolean().nullable(),
/** Thinking budget level: "low" | "medium" | "high" */
thinkingBudget: z.string().nullable(),
/** Max chat turns kept in context window */
maxChatTurnsInContext: z.number().nullable(),
/** Whether auto-fix problems is enabled */
enableAutoFixProblems: z.boolean().nullable(),
/** Whether native git is enabled */
enableNativeGit: z.boolean().nullable(),
/** Whether auto-update is enabled */
enableAutoUpdate: z.boolean(),
/** Release channel: "stable" | "beta" */
releaseChannel: z.string(),
/** Runtime mode: "host" | "docker" */
runtimeMode2: z.string().nullable(),
/** UI zoom level */
zoomLevel: z.string().nullable(),
/** Preview device mode: "desktop" | "tablet" | "mobile" */
previewDeviceMode: z.string().nullable(),
/** Whether turbo edits mode is enabled */
enableProLazyEditsMode: z.boolean().nullable(),
/** Turbo edits mode variant: "off" | "v1" | "v2" */
proLazyEditsMode: z.string().nullable(),
/** Whether smart files context mode is enabled (Pro) */
enableProSmartFilesContextMode: z.boolean().nullable(),
/** Whether web search is enabled (Pro) */
enableProWebSearch: z.boolean().nullable(),
/** Smart context option: "balanced" | "conservative" | "deep" */
proSmartContextOption: z.string().nullable(),
/** Whether Supabase write SQL migration is enabled */
enableSupabaseWriteSqlMigration: z.boolean().nullable(),
/** Agent tool consent settings per tool */
agentToolConsents: z.record(z.string(), z.string()).nullable(),
/** Experiment flags */
experiments: z.record(z.string(), z.boolean()).nullable(),
/** Custom node path override */
customNodePath: z.string().nullable(),
/** Map of provider ID -> whether configured (has API key). No secrets. */
providerSetupStatus: z.record(z.string(), z.boolean()),
});
// -- App metadata --
const DebugAppInfoSchema = z.object({
id: z.number(), id: z.number(),
title: z.string(), name: z.string(),
messages: z.array( /** Relative app path (not full filesystem path) */
z.object({ path: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
// Integration identifiers (non-secret)
githubOrg: z.string().nullable(),
githubRepo: z.string().nullable(),
githubBranch: z.string().nullable(),
supabaseProjectId: z.string().nullable(),
supabaseOrganizationSlug: z.string().nullable(),
neonProjectId: z.string().nullable(),
vercelProjectId: z.string().nullable(),
vercelProjectName: z.string().nullable(),
vercelDeploymentUrl: z.string().nullable(),
// Dev commands
installCommand: z.string().nullable(),
startCommand: z.string().nullable(),
// Chat context configuration
chatContext: z.any().nullable(),
// Theme
themeId: z.string().nullable(),
});
// -- Message with full debug detail --
const DebugMessageSchema = z.object({
id: z.number(), id: z.number(),
role: z.string(), role: z.enum(["user", "assistant"]),
/** Human-readable message text */
content: z.string(), content: z.string(),
approvalState: z.string().nullable().optional(), /** ISO 8601 timestamp */
createdAt: z.string(),
/**
* Full AI SDK structured message data (tool calls, image refs, multi-turn state).
* Base64 image data is stripped and replaced with:
* { type: "image", image: "[stripped]", mediaType: "...", _strippedByteLength: N }
*/
aiMessagesJson: z.any().nullable(),
/** Model name used to generate this response (assistant messages only) */
model: z.string().nullable(),
/** Total tokens used for this response (assistant messages only) */
totalTokens: z.number().nullable(),
/** Approval state: "approved" | "rejected" | null */
approvalState: z.enum(["approved", "rejected"]).nullable(),
/** Git commit hash of codebase when this message was created */
sourceCommitHash: z.string().nullable(),
/** Git commit hash of codebase after changes from this message were applied */
commitHash: z.string().nullable(),
/** Pro request UUID for billing/tracking */
requestId: z.string().nullable(),
/** Whether this message used the free agent mode quota */
usingFreeAgentModeQuota: z.boolean().nullable(),
});
// -- Chat with messages --
const DebugChatSchema = z.object({
id: z.number(),
appId: z.number(),
title: z.string().nullable(),
/** Git commit hash at start of this chat */
initialCommitHash: z.string().nullable(),
/** ISO 8601 timestamp */
createdAt: z.string(),
messages: z.array(DebugMessageSchema),
});
// -- Provider / model configuration (no secrets) --
const DebugProvidersSchema = z.object({
/** Custom provider definitions from language_model_providers table */
customProviders: z.array(
z.object({
id: z.string(),
name: z.string(),
hasApiBaseUrl: z.boolean(),
envVarName: z.string().nullable(),
}), }),
), ),
/** Custom model definitions from language_models table */
customModels: z.array(
z.object({
id: z.number(),
displayName: z.string(),
apiName: z.string(),
builtinProviderId: z.string().nullable(),
customProviderId: z.string().nullable(),
maxOutputTokens: z.number().nullable(),
contextWindow: z.number().nullable(),
}), }),
),
});
// -- MCP server configuration (no env/header secrets) --
const DebugMcpServerSchema = z.object({
id: z.number(),
name: z.string(),
transport: z.string(),
command: z.string().nullable(),
args: z.array(z.string()).nullable(),
url: z.string().nullable(),
enabled: z.boolean(),
// NOTE: envJson and headersJson are intentionally EXCLUDED (may contain secrets)
});
// -- Top-level bundle --
/**
* Complete session debug bundle for upload.
*
* Contains all non-sensitive data needed to debug a chat session:
* system info, user settings, app config, full chat messages with
* AI SDK JSON, provider/model setup, MCP servers, codebase snapshot,
* and application logs.
*
* Sensitive data (API keys, OAuth tokens, MCP env vars) is stripped.
* Base64 image data in AI SDK messages is replaced with placeholders.
*/
export const SessionDebugBundleSchema = z.object({
/** Schema version number. Bump on breaking changes. */
schemaVersion: z.number(),
/** ISO 8601 timestamp of when this bundle was exported */
exportedAt: z.string(),
/** Runtime environment info */
system: DebugSystemInfoSchema,
/** Non-sensitive user settings snapshot */
settings: DebugSettingsSchema,
/** App configuration and integration metadata */
app: DebugAppInfoSchema,
/** Chat with full message history including AI SDK JSON */
chat: DebugChatSchema,
/** Custom provider and model definitions (no secrets) */
providers: DebugProvidersSchema,
/** MCP server configurations (no env/header secrets) */
mcpServers: z.array(DebugMcpServerSchema),
/** Formatted codebase snapshot */
codebase: z.string(), codebase: z.string(),
/** Application logs (last 1000 lines) */
logs: z.string(),
}); });
export type ChatLogsData = z.infer<typeof ChatLogsDataSchema>; export type SessionDebugBundle = z.infer<typeof SessionDebugBundleSchema>;
// ============================================================================= // =============================================================================
// Deep Link Schemas // Deep Link Schemas
...@@ -133,11 +335,11 @@ export const miscContracts = { ...@@ -133,11 +335,11 @@ export const miscContracts = {
output: z.void(), output: z.void(),
}), }),
// Chat logs // Session debug bundle
getChatLogs: defineContract({ getSessionDebugBundle: defineContract({
channel: "get-chat-logs", channel: "get-session-debug-bundle",
input: z.number(), // chatId input: z.number(), // chatId
output: ChatLogsDataSchema, output: SessionDebugBundleSchema,
}), }),
// Console logs // Console logs
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论