Fix task export button: resolve the SDK session folder reliably (#12772)

* Restore task export to markdown and show download button in all builds

* Render untyped tool outputs and object tool inputs as JSON in task export

* Open the task's SDK session folder from the export button and show it in all builds

* Keep the task header session-folder button dev-only
This commit is contained in:
Saoud Rizwan
2026-07-30 17:34:29 -07:00
committed by GitHub
parent 4ec2b68c7c
commit 09aec528d0
4 changed files with 33 additions and 15 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ service TaskService {
rpc newTask(NewTaskRequest) returns (String);
// Shows a task with the specified ID
rpc showTaskWithId(StringRequest) returns (TaskResponse);
// Exports a task with the given ID to markdown
// Opens the on-disk session directory (messages json, logs) for the task with the given ID
rpc exportTaskWithId(StringRequest) returns (Empty);
// Toggles the favorite status of a task
rpc toggleTaskFavorite(TaskFavoriteRequest) returns (Empty);
+2 -9
View File
@@ -1799,16 +1799,9 @@ export class Controller {
}
async exportTaskWithId(id: string): Promise<void> {
const historyItem = (await this.taskHistory.listHistory({ hydrate: false })).find((item) => item.sessionId === id)
if (!historyItem) {
throw new Error(`Task not found in history: ${id}`)
}
const taskDirPath = historyItem.messagesPath
? path.dirname(historyItem.messagesPath)
: this.taskHistory.getLegacyTaskDirPath(id)
const taskDirPath = await this.taskHistory.getTaskDirPath(id)
if (!taskDirPath) {
throw new Error(`Task history item has no artifact path: ${id}`)
throw new Error(`Task not found in history: ${id}`)
}
await fs.access(taskDirPath)
+29 -5
View File
@@ -1,7 +1,9 @@
import { existsSync } from "node:fs"
import path from "node:path"
import type { ClineCoreListHistoryOptions, SessionHistoryRecord } from "@cline/core"
import type { Message as SdkMessage } from "@cline/llms"
import { formatDisplayUserInput, parseUserInputMode } from "@cline/shared"
import { resolveSessionDataDir } from "@cline/shared/storage"
import type { ClineMessage } from "@shared/ExtensionMessage"
import type { HistoryItem } from "@shared/HistoryItem"
import getFolderSize from "get-folder-size"
@@ -473,6 +475,33 @@ export class SdkTaskHistory {
return clineMessages
}
/**
* Absolute path of the directory holding the task's on-disk artifacts: the SDK
* session folder (manifest json + messages json) for SDK tasks, or the legacy
* tasks/<id> folder for pre-SDK tasks. Undefined when the task is unknown.
*/
async getTaskDirPath(taskId: string): Promise<string | undefined> {
const sdkRecord = await this.getSdkRecord(taskId)
if (sdkRecord) {
const messagesPath = typeof sdkRecord.messagesPath === "string" ? sdkRecord.messagesPath.trim() : ""
if (messagesPath) {
return path.dirname(messagesPath)
}
// Older records may lack messagesPath; fall back to the canonical
// session directory when it exists on disk.
const sessionDir = path.join(resolveSessionDataDir(), taskId)
if (existsSync(sessionDir)) {
return sessionDir
}
}
return this.getLegacyTaskDirPath(taskId)
}
getLegacyTaskDirPath(taskId: string): string | undefined {
const legacyTask = this.findLegacyTask(taskId)
return legacyTask ? taskDirPath(taskId, legacyTask.dataDir) : undefined
}
/**
* The persisted session status ("completed" | "cancelled" | "failed" | ...).
* Persisted messages cannot distinguish a completed conversation from one
@@ -513,11 +542,6 @@ export class SdkTaskHistory {
return appendLegacyResumeWarning(fallbackMessages as { role: string; content: unknown }[])
}
getLegacyTaskDirPath(taskId: string): string | undefined {
const legacyTask = this.findLegacyTask(taskId)
return legacyTask ? taskDirPath(taskId, legacyTask.dataDir) : undefined
}
private async updateSession(sessionId: string, item: HistoryItem): Promise<void> {
const { metadata: writtenMetadata, updated } = await this.withHistoryHost(async (host) => {
const existing = await host.get(sessionId)
+1
View File
@@ -1,3 +1,4 @@
declare module "@cline/shared/storage" {
export function resolveGlobalSettingsPath(): string
export function resolveSessionDataDir(): string
}