/** * v0.8 §3.3 — Per-user yaml stored at * `//.solosquad/users/.yaml`. Each user has exactly one * yaml per org. The yaml is the durable mapping between a messenger handle and * a bot's gateway identity (`bot_user_id`). * * Spec: docs/plan/v0.8-multiuser-messenger.md §3.3. */ export interface UserYaml { schema_version: number; handle: string; display_name?: string; messenger: "discord" | "slack"; /** v0.8 — sender id used by §4.5 owner-only gate (v1.2). Discord: message.author.id. Slack: event.user. */ messenger_user_id?: string; bot_application_id?: string; bot_user_id: string; joined_at: string; workspace_path?: string; session_id?: string; /** * v1.3.3 §B (cron 개인화) — IANA timezone override for this user's personal * crons (e.g. "America/Los_Angeles"). Falls back to the workspace timezone. */ timezone?: string; /** * v1.3.3 §B — opt-in per-user cron settings. When present, the user receives * personalized briefs in their own `works-` channel at their own * timezone, *in addition to* the org-level #workflow briefs (disable the * org-level one via workspace.yaml.briefings.*.enabled:false if you only want * personal briefs). Keyed by built-in cron id ("morning-brief"/"evening-brief"). */ crons?: Record; channels: { command: string; works: string; /** * DEPRECATED (v1.2.10) — formerly the `git-` VCS event feed * channel (v1.2.9 Part B). SoloSquad no longer creates or notifies a git * channel; push approval is handled by the dev-confirm gate (v1.3.0) and * push *notifications* are left to the user's own GitHub→messenger webhook. * The field is retained (read-only, inert) so pre-v1.2.10 yamls that were * migrated to schema_version 2 still load, and the shipped 1.2.8→1.2.9 * migration keeps compiling. Nothing reads or writes it anymore. */ git?: string; }; } /** Folder that holds all `.yaml` files for one org. */ export declare function getUsersDir(orgSlug: string, workspace?: string): string; export declare function userYamlPath(orgSlug: string, handle: string, workspace?: string): string; /** * v0.8 §3.1 — Normalize a messenger handle into the channel-naming charset * (lowercase a-z, 0-9, underscore). Other characters become `_`. The init * flow shows the normalized form to the user for confirmation before saving. */ export declare function normalizeHandle(raw: string): string; export declare function isValidHandle(handle: string): boolean; /** * v0.8 §3.5 — Handle collision policy is *explicit refusal* (박제). The caller * should branch on `exists()` before writing a fresh yaml. */ export declare function userYamlExists(orgSlug: string, handle: string, workspace?: string): boolean; export declare function loadUserYaml(file: string): UserYaml | null; /** Save a user yaml. Refuses to clobber when `allowOverwrite` is false. */ export declare function saveUserYaml(orgSlug: string, doc: UserYaml, workspace?: string, allowOverwrite?: boolean): void; /** Enumerate every user yaml registered for an org. */ export declare function listUserYamls(orgSlug: string, workspace?: string): UserYaml[]; /** * v0.8 §3.2 — Find the yaml whose `bot_user_id` matches the live gateway ID. * Used at bot startup to decide which channel pair to listen on. Returns * `null` when no yaml matches — caller should log + guide the user to * `solosquad init` or the 0.7→0.8 migration. */ export declare function findUserByBotId(orgSlug: string, botUserId: string, workspace?: string): UserYaml | null; /** * Scan every org under the workspace and return all users with their org * slug. Used by adapters that need to know "is *any* user in this workspace * tied to this bot?". */ export declare function listAllUsers(workspace: string): Array<{ orgSlug: string; user: UserYaml; }>; /** Derive the expected channel names from a handle. */ export declare function deriveChannelNames(handle: string): { command: string; works: string; }; /** * v0.8 §3.5 — Channel name parser used by author-guard and routing. * Returns null for unrelated channels (broadcast, system, etc.). */ export declare function parseChannelName(channelName: string): { kind: "command" | "works"; handle: string; } | null; export type IncomingRoute = { kind: "command"; } | { kind: "works-thread"; }; /** * v1.4.1 — decide whether an incoming Discord message should be handled by this * bot, and as what surface: * - a `command-` channel → "command" (the v0.8 path). * - a thread whose PARENT channel is `works-` → "works-thread" * (Chief reads/replies inside task threads — PRD v1.4.1). * Anything else — or a handle that isn't THIS bot's — returns null (ignored, so * other users' / other bots' channels and threads keep the v0.8 §3.5 isolation). */ export declare function classifyIncoming(opts: { channelName: string; isThread: boolean; parentChannelName: string | null; ownHandle: string | null; }): IncomingRoute | null;