mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
feat(web-shell): manage sessions from the sidebar (archive, unarchive, delete) (#6293)
Add an Archive quick action and a "..." overflow menu (Rename / Archive / Delete) to each session row in the web-shell sidebar, plus a collapsible "Archived" section that lazily lists archived sessions with Restore / Delete. Thread the daemon's existing archiveState filter and archive/unarchive endpoints through the webui workspace facade and the useDaemonSessions hook; rename stays limited to the current live session.
This commit is contained in:
parent
2a6a9514e3
commit
c37cb23ccc
7 changed files with 863 additions and 48 deletions
|
|
@ -58,6 +58,30 @@ export function createDaemonWorkspaceActions({
|
|||
);
|
||||
},
|
||||
|
||||
async archiveSession(sessionId: string) {
|
||||
const client = requireClient(getClient, 'Archive session failed');
|
||||
const result = await withActionTimeout(
|
||||
client.archiveSessionsData([sessionId]),
|
||||
'Archive session timed out',
|
||||
);
|
||||
if (result.errors.length > 0) {
|
||||
throw new Error(result.errors[0].error);
|
||||
}
|
||||
return result.archived.length > 0 || result.alreadyArchived.length > 0;
|
||||
},
|
||||
|
||||
async unarchiveSession(sessionId: string) {
|
||||
const client = requireClient(getClient, 'Unarchive session failed');
|
||||
const result = await withActionTimeout(
|
||||
client.unarchiveSessionsData([sessionId]),
|
||||
'Unarchive session timed out',
|
||||
);
|
||||
if (result.errors.length > 0) {
|
||||
throw new Error(result.errors[0].error);
|
||||
}
|
||||
return result.unarchived.length > 0 || result.alreadyActive.length > 0;
|
||||
},
|
||||
|
||||
async loadMcpStatus() {
|
||||
const client = requireClient(getClient, 'Load MCP status failed');
|
||||
return withActionTimeout(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import type { DaemonSessionArchiveState } from '@qwen-code/sdk/daemon';
|
||||
import { useOptionalDaemonActions } from '../../session/DaemonSessionProvider.js';
|
||||
import { useDaemonWorkspace } from '../DaemonWorkspaceProvider.js';
|
||||
import type { DaemonResourceOptions } from '../types.js';
|
||||
|
|
@ -12,15 +13,17 @@ import { useDaemonResource } from './useDaemonResource.js';
|
|||
|
||||
export interface DaemonSessionsOptions extends DaemonResourceOptions {
|
||||
pageSize?: number;
|
||||
/** Which session directory to list. Defaults to the daemon's `active`. */
|
||||
archiveState?: DaemonSessionArchiveState;
|
||||
}
|
||||
|
||||
export function useDaemonSessions(options: DaemonSessionsOptions = {}) {
|
||||
const { pageSize, ...resourceOptions } = options;
|
||||
const { pageSize, archiveState, ...resourceOptions } = options;
|
||||
const workspace = useDaemonWorkspace();
|
||||
const sessionActions = useOptionalDaemonActions();
|
||||
const load = useCallback(
|
||||
() => workspace.actions.listSessions({ pageSize }),
|
||||
[pageSize, workspace.actions],
|
||||
() => workspace.actions.listSessions({ pageSize, archiveState }),
|
||||
[archiveState, pageSize, workspace.actions],
|
||||
);
|
||||
const workspaceReady = !!workspace.workspaceCwd;
|
||||
const result = useDaemonResource(load, {
|
||||
|
|
@ -44,6 +47,22 @@ export function useDaemonSessions(options: DaemonSessionsOptions = {}) {
|
|||
},
|
||||
[workspace.actions, reload],
|
||||
);
|
||||
const archiveSession = useCallback(
|
||||
async (sessionId: string) => {
|
||||
const archived = await workspace.actions.archiveSession(sessionId);
|
||||
if (archived) reload();
|
||||
return archived;
|
||||
},
|
||||
[workspace.actions, reload],
|
||||
);
|
||||
const unarchiveSession = useCallback(
|
||||
async (sessionId: string) => {
|
||||
const unarchived = await workspace.actions.unarchiveSession(sessionId);
|
||||
if (unarchived) reload();
|
||||
return unarchived;
|
||||
},
|
||||
[workspace.actions, reload],
|
||||
);
|
||||
return {
|
||||
...result,
|
||||
sessions: result.data ?? [],
|
||||
|
|
@ -53,5 +72,7 @@ export function useDaemonSessions(options: DaemonSessionsOptions = {}) {
|
|||
releaseSession: sessionActions?.releaseSession,
|
||||
deleteSession,
|
||||
deleteSessions,
|
||||
archiveSession,
|
||||
unarchiveSession,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import type {
|
|||
DaemonMcpRestartResult,
|
||||
DaemonMcpManageAction,
|
||||
DaemonMcpManageResult,
|
||||
DaemonSessionArchiveState,
|
||||
DaemonUpdateAgentRequest,
|
||||
DaemonWorkspaceAgentDetail,
|
||||
DaemonWorkspaceAgentsStatus,
|
||||
|
|
@ -146,6 +147,7 @@ export interface DaemonWorkspaceActions {
|
|||
// Sessions
|
||||
listSessions(options?: {
|
||||
pageSize?: number;
|
||||
archiveState?: DaemonSessionArchiveState;
|
||||
}): Promise<DaemonSessionSummary[]>;
|
||||
deleteSession(sessionId: string): Promise<boolean>;
|
||||
deleteSessions(sessionIds: string[]): Promise<{
|
||||
|
|
@ -153,6 +155,14 @@ export interface DaemonWorkspaceActions {
|
|||
notFound: string[];
|
||||
errors: Array<{ sessionId: string; error: string }>;
|
||||
}>;
|
||||
/**
|
||||
* Move a session to the archived directory. Idempotent: an
|
||||
* already-archived session resolves `true`. Rejects if the daemon
|
||||
* reports a per-session error (e.g. an archive/unarchive conflict).
|
||||
*/
|
||||
archiveSession(sessionId: string): Promise<boolean>;
|
||||
/** Restore an archived session to the active directory. Idempotent. */
|
||||
unarchiveSession(sessionId: string): Promise<boolean>;
|
||||
|
||||
// MCP
|
||||
loadMcpStatus(): Promise<DaemonWorkspaceMcpStatus>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue