Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

17 KiB
Raw Permalink Blame History

@tutti-os/workspace-file-manager contract

Status: agreed package boundary (direction). Open questions are listed under Open questions when any remain.

This document is the authoritative ownership contract for packages/workspace/file-manager. Prefer it over chat history when planning or reviewing changes. The package README.md remains a short usage overview and must stay consistent with this contract.

Sibling preview ownership lives in @tutti-os/workspace-file-preview CONTRACT.md. When preview vs open/activation ownership is in doubt, that document wins for preview concerns; this document wins for file-manager session and host concerns.

Purpose

This package owns a host-neutral workspace file manager surface:

  1. Session state and interaction orchestration for one workspace-scoped file-manager instance (navigation, selection, locations, search projection, mutation busy/dialogs).
  2. The WorkspaceFileManagerHost contract that hosts implement for listing, mutation, activation, open-with, reveal, and preview bytes.
  3. File-manager-specific activation-target shaping, view-model derivation, and capability projection from optional host methods.
  4. Optional React UI that binds to the shared session and delegates context-menu actions to the host through resolveContextMenu.

It does not own:

  • Daemon / VM / preload transport, absolute host paths, or product globals
  • Import / export / upload / download pipelines, transfer-center tasks, or progress UI
  • Share / exposure / product collaboration flows
  • Full-content editors, dirty/save, or workbench framing outside this surface
  • A second preview kind taxonomy (that belongs to @tutti-os/workspace-file-preview)

File manager vs preview vs host product

Concern Owner
Browse / navigate / select / search projection / locations This package (+ host listing/search)
Side-pane preview loading + built-in preview shells @tutti-os/workspace-file-preview
File-manager preview projection into session previewState This package
Open / reveal / open-with / browser / system activation This package orchestrates; host executes
Import / export / upload / download / drag-to-import UX Host (toolbar slot / menus / overlays)
Multi-step upload UX, transfer progress, gitignore pickers Host
Share / link / exposure / room collaboration Host
Context-menu action lists for blank / directory / file targets Host via resolveContextMenu
Logical workspace paths in shared state This package
Absolute / VM / OS paths and path projection Host adapter
User-visible copy overrides and product chrome Host

Decided route: this package is a reusable frontend workspace-domain surface, not only a transport-agnostic data kernel. Shared session orchestration and React-facing interaction state may live here when they are part of the reusable file-manager experience across hosts.

Ownership layers

1. Paths and identity

Shared state uses logical workspace paths (for example /workspace/src/index.ts). The package does not depend on tuttid, TSH, TACH, VM roots, or host absolute paths.

Workspace identity (decided): session input and host methods use workspaceID. Hosts that natively key workspaces differently (for example TSH roomId) map at the adapter edge. Do not rename the public field for a single host.

Entry shape (decided for now): keep the current WorkspaceFileEntry fields (kind, name, path, hasChildren, sizes, timestamps). Do not add MIME / MIME-source fields to the shared entry contract for classification or preview routing; preview classification stays on the preview package’s extension-based model. Hosts may keep richer metadata locally without pushing it into this package.

2. Host contract and capabilities

Hosts integrate primarily by implementing WorkspaceFileManagerHost and calling createWorkspaceFileManagerService().createSession(...).

Host shape (decided): keep one flat WorkspaceFileManagerHost interface. Do not split into TSH-style listing/mutation/share ports inside this package unless a later contract revision explicitly does so. Optional methods remain optional; the package derives WorkspaceFileManagerCapabilities from method presence (for example search → canSearch, moveEntry → canMove).

Minimum host obligation (decided):

  • Required: listDirectory
  • Everything else is optional and gated by capabilities / UI flags

Practical capability notes (not additional required methods):

  • Side-pane preview bytes need readPreviewFile
  • Double-click / Open need activateFile and/or the relevant openFile* / revealEntry methods the host wants to expose
  • Search UI needs search
  • Recent location content needs listRecentEntries when the host supplies a recent location

Import, export, upload, and download stay outside this package. Hosts that need those flows implement them in host adapters and expose them through host context-menu DI rather than package-owned import/export host methods.

Hosts must not reimplement a second file-manager session stack for the same product job when they already consume this package; extend this contract or keep truly product-specific flows outside the shared session.

3. Session orchestration

WorkspaceFileManagerSession owns, for one workspace-scoped instance:

  • Directory listing and navigation stacks
  • Selection, context menu anchor state, create/delete/rename dialogs
  • Location selection (selectedLocationId) and host-supplied locationSections
  • Search query/results projection when canSearch
  • Search request lifecycle: starting a replacement search, clearing search state, or disposing the session aborts the active request. The session passes that lifecycle through AbortSignal on search; recent-location filtering passes the same signal to listRecentEntries.
  • Mutation busy state and shared error projection helpers
  • Entry-name validation for create and rename operations before host mutation; one name segment is limited to 255 UTF-8 bytes
  • Preview projection into previewState via the preview package controller
  • Activation orchestration that calls host activateFile / open / reveal APIs
  • Persisted navigation/location snapshot via WorkspaceFileManagerPersistedState

Locations (decided): the package owns the location model (directory / external / recent and location sections). Hosts inject locationSections (and optional defaultLocationId). A host may supply a minimal single-root section set; Tutti-shaped project/home/recent sections are host policy, not a required package shape.

External locations (decided): browsing content for external locations is host-owned UI injected through React props such as renderExternalLocationContent. This package does not depend on @tutti-os/workspace-file-reference for that content. Reference pickers may reuse this package’s entry types, icons, and arrange helpers without inverting ownership.

4. Preview integration

Preview classification and loading lifecycle are owned by @tutti-os/workspace-file-preview. This package:

  • Shapes file-manager activation targets for host open policy
  • Wires host.readPreviewFile into the shared preview controller
  • Projects controller state into session previewState and optional UI

Open / reveal / open-with / browser activation remain file-manager + host concerns, consistent with the preview contract. Open-with advisories such as isWorkspaceFileBrowserOpenable and shouldFilterVideoPlayersForOpenWith live in this package’s /services export, not in preview.

5. Context menu DI

Context menu contract (decided): the package owns only the menu anchor and interaction shell (contextMenu, currentDirectoryPath, busy/loading flags). Menu items are host-owned.

When the user opens a blank-area, directory, or file context menu, the React surface calls the required resolveContextMenu(request) prop. The request includes:

  • target: "blank", "directory", or "file" (via resolveWorkspaceFileManagerContextMenuTarget)
  • Current directory path, selected location, search/external/recent flags, and busy state

The host returns the menu item tree (WorkspaceFileManagerContextMenuItem[]). Import, export, upload, create, delete, open-with, and other product actions belong in that host resolver (or in host wrappers outside the shared menu), not in package-owned menu visibility flags or package host import/export methods.

6. Activation

When the user opens a file, the session orchestrates activation and calls host hooks. Typical Tutti policy (host-owned, not package law): try in-app preview presentation, then fall back to system open / unsupported dialogs.

activateFile results may be handled, fallback, or unsupported, with optional host-provided fallback actions. The package owns dialog/shell presentation of those results; the host owns what the actions do.

Default opener hints may be supplied through session input resolveFileDefaultOpener (appBrowser / defaultBrowser / fileViewer / system) without moving transport into the package.

7. Persistence

Persisted state (decided): the package owns WorkspaceFileManagerPersistedState and its schemaVersion. Current fields:

  • currentDirectoryPath
  • navigationBackStack / navigationForwardStack
  • selectedLocationId
  • schemaVersion

Hosts that historically persisted other fields (selected file focus tokens, cached listings, product-specific focus request IDs) must map at the adapter or workbench edge. Do not widen the shared schema for one host’s focus protocol without a contract revision.

Device-local UI chrome widths (locations sidebar / details pane) may persist in the optional React surface; that is presentation chrome, not workbench node external state.

8. Optional React UI and /services

Public entrypoints:

  • @tutti-os/workspace-file-manager — UI + common types/helpers
  • @tutti-os/workspace-file-manager/services — service/session/host contracts without requiring the React tree
  • @tutti-os/workspace-file-manager/i18n — default resources / namespace helpers

Consumption modes (decided):

  • /services alone is a valid integration (host brings its own UI)
  • Consuming the package React UI implies accepting its peer dependencies (including valtio) and session-store binding

UI injection points that remain host-owned presentation hooks include, among others: resolveContextMenu, renderExternalLocationContent, renderToolbarTrailingActions, resolveEntryIconUrl, showPreviewPanel, showLocationSidebar, showInternalOpenWithActions, open-with icon overrides, and analytics callbacks. These do not become a general product-action plugin system in this phase.

Toolbar trailing actions (decided): hosts may inject a narrow React node cluster through renderToolbarTrailingActions. The package places it in the toolbar trailing cluster after Refresh and before Search, and passes the current directory path plus busy/loading/mutating flags. Product primary affordances such as upload belong here (or in host chrome outside the shared surface); multi-step transfer UX stays host-owned.

Published UI assets (decided): folder/archive fallback glyphs ship as inlined data: URLs in the published dist (tsup loader: { ".png": "dataurl" }). Do not rely on new URL(..., import.meta.url) against loose files next to dist — Electron hosts with strict img-src (no file:) will show broken images. Hosts that alias this package to source for local development still get Vite-rewritten asset URLs; published consumers must keep working without that alias.

9. i18n

The package owns default file-manager copy under its i18n namespace. Hosts merge workspaceFileManagerI18nResources into an app-level runtime and scope with createWorkspaceFileManagerI18nRuntime(...). Product-specific wording overrides belong in the host runtime, not as forks of package message files.

resolveRevealInFolderLabel(copy, platform) remains available for host menu builders that need platform-specific reveal labels.

Host responsibilities

Hosts own:

  • Transport: daemon/VM/local reads and writes, uploads/downloads, auth
  • Mapping host identity (roomId, and similar) onto workspaceID
  • Mapping absolute/VM paths onto logical workspace paths (and the reverse at the OS edge)
  • Implementing required listDirectory plus whichever optional Host methods they want capabilities for
  • Forwarding the session-provided search AbortSignal through the host transport so superseded work can stop promptly; hosts must still tolerate a request completing after cancellation
  • Import / export / upload / download pipelines, including any transfer-center integration
  • Context-menu action trees through resolveContextMenu
  • Share / exposure / collaboration product flows
  • In-app preview canvas / workbench presentation policy beyond the shared side-pane projection
  • Product chrome outside this surface (toolbars, dock, room shell)
  • App-level i18n overrides and host-only analytics

Non-goals

  • Embedding tuttid, TSH desktopd, preload bridges, or product globals
  • Owning share-link / exposure / agent-session binding UI or state
  • Owning multi-phase transfer-center protocols or progress UI
  • Owning package-level import/export host methods or drag-to-import overlays
  • Becoming a general document editor platform
  • Requiring every host to implement Tutti’s full capability set
  • Inventing a parallel preview taxonomy or open-with advisory stack in hosts that already consume the workspace file packages
  • Promising a general host menu/action contribution API for arbitrary product commands in this phase (hosts use resolveContextMenu, renderToolbarTrailingActions, or wrappers outside the shared surface)

Relationship to other workspace packages

Package Relationship
@tutti-os/workspace-file-preview Classification + preview loading; consumed by this package
@tutti-os/workspace-file-reference May reuse types/icons/UI helpers; external location body is host-injected
Host adapters (Tutti desktop, TSH, …) Implement WorkspaceFileManagerHost and product-only flows

Keep as-is

  • Flat WorkspaceFileManagerHost + capabilities-from-optional-methods
  • Logical paths and workspaceID naming
  • Session-owned shared interaction state with optional React UI
  • Preview package as the only preview taxonomy owner
  • Open-with advisories on /services
  • Location model with host-supplied sections
  • Persisted navigation/location schema versioning in this package
  • Host-owned context menus through resolveContextMenu

Current shipped shape vs this contract

The Tutti desktop adapter and package implementation are aligned with the sections above:

  • Desktop feature code implements WorkspaceFileManagerHost and keeps tuttid / preload / analytics / reference external panes outside the package
  • Tutti desktop does not ship file-manager import/export/upload at all (no package host methods and no desktop resolveContextMenu actions for them). Other hosts that need those product flows must own them entirely outside this package
  • Desktop wires resolveContextMenu for blank / directory / file targets (open / open-with / create / rename / copy / reveal / delete)
  • Side-pane preview uses @tutti-os/workspace-file-preview; activation policy for canvas vs system open stays in the desktop adapter
  • Other hosts (for example TSH) should adapt onto this contract rather than growing a second shared file-manager session for the same job

Open questions

None right now. New questions should be added here when they appear, then moved into the main sections once decided.

Change policy

  • Contract changes that alter host obligations, session ownership, persistence schema meaning, activation/preview boundaries, or context-menu DI expectations require updating this file in the same change.
  • Prefer extending optional host methods or UI injection props over inventing parallel file-manager stacks in hosts.
  • When an open question is decided, move the decision into the main sections and remove it from Open questions.