/** * Per-conversation workspaces. `/cd` points one conversation at a directory, * and the conversation's session id is derived from BOTH facts, so every * (conversation × directory) pair owns a durable session of its own. That turns * a host constraint into the feature: a session's cwd is fixed at creation, so * "switching" is really reaching a different session — and coming back to a * directory resumes the context that was built there instead of erasing it. * * The mapping persists through the host settings service, in the same section * that already holds onboarded credentials, so a restarted process routes every * conversation to the session it served before. Entries never need deletion — * the persistence layer deep-merges patches — so "back to the default" is an * explicit marker value rather than an absent key. * @module dsh-lark-channel/workspace */ /** Switch or show this conversation's workspace. Channel-owned: it needs no agent. */ export declare const CD_COMMAND = "cd"; /** List the workspaces this channel knows. Channel-owned: it needs no agent. */ export declare const WS_COMMAND = "ws"; /** * Verdict on one directory: its canonical path, or why it cannot be a workspace. * Injectable so command tests need no real filesystem. */ export type WorkspaceProbe = (path: string) => { readonly canonical: string; } | { readonly error: string; }; /** The real-filesystem probe: the directory must exist, and spellings collapse to one. */ export declare const probeDirectory: WorkspaceProbe; /** * Expand a leading `~` against the operating-system home, the one path shorthand * a phone keyboard makes worth supporting. * @param input - the operator's path input. * @param home - substitutable home directory. * @returns the expanded path, or the input untouched. */ export declare function expandHome(input: string, home?: string): string; /** * Why a directory can never be a workspace, however permissive the roots are. * These are the directories a `/cd` typo or a lazy shortcut lands on — and an * agent whose sandbox writes "the workspace" must not have that be the * filesystem root or someone's entire home. * @param canonical - the canonicalized candidate. * @param home - the home directory, canonicalized by the caller's probe. * @returns the refusal, or undefined when the directory is specific enough. */ export declare function forbiddenReason(canonical: string, home?: string): string | undefined; /** * Whether a path falls under one of the configured roots. An empty list allows * anywhere: the platform already decides who can reach the bot, and this knob * only narrows what those people may point it at. * @param path - canonical candidate directory. * @param roots - allowed directory prefixes. * @returns true when allowed. */ export declare function withinRoots(path: string, roots: readonly string[]): boolean; /** * The session id one conversation-and-workspace pair owns. The default * workspace keeps the historical plain id, so existing conversations keep their * sessions across this feature's arrival; an override appends a digest of the * canonical directory, so two spellings of one directory reach one session and * two directories never share. * @param key - conversation key. * @param overridePath - canonical override directory, absent for the default. * @returns the branded session id. */ export declare function workspaceSessionId(key: string, overridePath?: string, prefix?: string): string; /** What one `/cd` attempt concluded. */ export type SwitchResult = { readonly ok: true; /** The conversation's workspace after the switch. */ readonly path: string; /** False when the conversation was already there. */ readonly changed: boolean; /** Whether the target is the deployment default. */ readonly toDefault: boolean; /** Whether the mapping survives a restart. */ readonly durable: boolean; } | { readonly ok: false; readonly reason: string; }; /** Construction options for {@link ChatWorkspaces}. */ export interface ChatWorkspacesOptions { /** The deployment default directory (resolved, not necessarily canonical). */ readonly defaultPath: string; /** Persisted conversation-key → directory entries; {@link DEFAULT_MARKER} means default. */ readonly entries?: Record | undefined; /** Directory prefixes `/cd` may enter; empty allows anywhere. */ readonly roots?: readonly string[] | undefined; /** Deep-merge one patch into the plugin's settings section; false = not composed. */ readonly persist?: ((patch: { chatWorkspaces: Record; }) => Promise) | undefined; /** Operator console line. */ readonly report?: ((line: string) => void) | undefined; /** Directory verdicts; tests substitute one. */ readonly probe?: WorkspaceProbe | undefined; /** Home for `~` expansion and the forbidden-directory rules; tests substitute one. */ readonly home?: string | undefined; /** Prefix this row's session ids carry; absent keeps the original one. */ readonly sessionPrefix?: string | undefined; /** * How many times a conversation has started over, by the id it derives at * epoch zero. Absent keeps every conversation on its first. */ readonly epochOf?: ((baseId: string) => number) | undefined; /** * Directories known outside this channel — the host workspace registry's * listing, when the deployment composes one. What `/ws` shows and what a * bare-name `/cd` can reach, so a chat can discover every project its human * already uses with the host instead of memorizing paths. */ readonly known?: (() => readonly string[]) | undefined; } /** * The per-conversation workspace state: which directory each conversation is * pointed at, the session id that pair owns, and the `/cd` transition between * them. Pure state plus injected effects, so tests drive it without a * filesystem or a settings service. */ export declare class ChatWorkspaces { private readonly entries; private readonly defaultPath; /** The default's canonical form, for deciding that a `/cd` target IS the default. */ private readonly defaultCanonical; private readonly roots; private readonly persist; private readonly report; private readonly probe; private readonly home; private readonly known; private readonly sessionPrefix; private readonly epochOf; /** The non-durable warning is orientation; once is enough. */ private warnedNotDurable; constructor(options: ChatWorkspacesOptions); /** The directory one conversation's next session runs in. */ pathFor(key: string): string; /** * The id this conversation derives before it ever started over. The epoch * map is keyed by it, so a `/new` in one directory leaves the thread in * another untouched. * @param key - conversation key. * @returns the session id at epoch zero. */ baseSessionIdFor(key: string): string; /** The session id one conversation currently resolves to. */ sessionIdFor(key: string): string; /** * Every directory this channel can name: the default first, then what this * channel switched to, then every workspace the host registry lists — the * projects its human already uses with the host, which is what makes `/ws` * a discovery surface rather than a diary. */ knownPaths(): string[]; /** Whether one conversation currently runs in the deployment default. */ isDefault(key: string): boolean; /** * Point one conversation at a directory. Accepts an absolute path, a `~` * path, or the unique basename of a known workspace — the shorthand `/ws` * advertises, because a full path is miserable to type on a phone. * @param key - conversation key. * @param input - the operator's target exactly as typed. * @returns what happened, for the chat reply. */ switch(key: string, input: string): Promise; } /** * Run one workspace command line and produce the chat reply. * @param name - the parsed command name, {@link CD_COMMAND} or {@link WS_COMMAND}. * @param line - the complete line, slash included. * @param key - the conversation the command is about. * @param store - the workspace state. * @param onSwitched - awaited after a change of directory, before the reply; * the bridge releases the conversation's current agent here so the next message * walks the ladder under the new id. * @returns markdown for the chat. */ export declare function runWorkspaceCommand(name: string, line: string, key: string, store: ChatWorkspaces, onSwitched: () => Promise): Promise; //# sourceMappingURL=workspace.d.ts.map