feat(web): add native file actions to artifact cards

This commit is contained in:
yudshj
2026-09-09 15:26:28 +08:00
committed by imccyu
parent da042b78ff
commit 4158a96e14
44 changed files with 702 additions and 94 deletions
+17 -2
View File
@@ -1,10 +1,11 @@
/** Session Remote owner: cold reads, explicit Agent commands, and live control state. */
import { hostname } from 'node:os'
import { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { errorChain } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/dsh-client-file-upload'
import { canOpenNativePath, openNativePath } from '@deepseek-ai/dsh-native-command'
import { canOpenNativePath, nativeFileManager, openNativePath, revealNativePath } from '@deepseek-ai/dsh-native-command'
import type { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionInspection } from '@deepseek-ai/dsh-session-persistence'
import type { SessionObservation } from '@deepseek-ai/dsh-session-query'
@@ -76,6 +77,8 @@ export interface Config {
export interface SessionControllerInternals {
/** Native default-application handoff. */
readonly openPath?: (path: string, signal: AbortSignal) => Promise<void>
/** Native file-manager handoff. */
readonly revealPath?: (path: string, signal: AbortSignal) => Promise<void>
/** Native handoff availability probe. */
readonly canOpenPath?: () => boolean
}
@@ -105,6 +108,7 @@ export class SessionController extends TypertRemoteService {
private readonly history: SessionHistoryController
private readonly listState: ApiSessionList
private readonly openPath: (path: string, signal: AbortSignal) => Promise<void>
private readonly revealPath: (path: string, signal: AbortSignal) => Promise<void>
private readonly canOpenPath: () => boolean
private readonly promotions = new Set<Promise<void>>()
@@ -132,6 +136,7 @@ export class SessionController extends TypertRemoteService {
this.history = new SessionHistoryController(ctx, (observation) => { this.promote(observation) })
this.listState = new ApiSessionList(ctx)
this.openPath = internals.openPath ?? openNativePath
this.revealPath = internals.revealPath ?? revealNativePath
this.canOpenPath = internals.canOpenPath
?? (() => config.nativeOpen ?? (internals.openPath !== undefined || canOpenNativePath()))
ctx.plugin(SessionFileReferences)
@@ -268,6 +273,15 @@ export class SessionController extends TypertRemoteService {
return this.canOpenPath()
}
/**
* Describe the serving desktop for authenticated file-action routes.
* @returns Host name, configured availability, and platform-specific file-manager behavior.
*/
workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' | 'explorer' | 'directory' | null } {
const fileManager = nativeFileManager()
return { name: hostname(), available: fileManager !== null && this.canOpenPath(), fileManager }
}
/**
* Open one path prepared by a Session-aware caller on the Host desktop.
* @param request - path after best-effort Session workspace resolution.
@@ -289,7 +303,8 @@ export class SessionController extends TypertRemoteService {
}
signal.throwIfAborted()
try {
await this.openPath(request.path, signal)
if (request.action === 'reveal') await this.revealPath(request.path, signal)
else await this.openPath(request.path, signal)
return { opened: true }
} catch (error: unknown) {
if (signal.aborted) throw new RemoteError('gateway/cancelled', 'path open was aborted', {})
@@ -358,6 +358,8 @@ export interface SessionCancelValue {
/** Request to open one path prepared by a Session-aware caller on the Host desktop. */
export interface SessionOpenWorkspacePathRequest {
/** File-manager navigation when requested; omission uses the default application. */
readonly action?: 'reveal'
/** Path after best-effort Session workspace resolution, in Host filesystem syntax. */
readonly path: string
}