@adelie/server 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -265,6 +265,14 @@ interface UploadLimits {
265
265
  * Trace, where its size is paid again on every history page and every resume.
266
266
  */
267
267
  imageMaxMb: number;
268
+ /**
269
+ * Per-video cap for videos that ride the conversation inline, on a model that takes video
270
+ * input. Fixed server-side and larger than the image cap (a video is megabytes, and the
271
+ * provider takes a video under 50MB inside a 64MB body — this is that ceiling with base64's
272
+ * 4/3 accounted for). The composer's pre-flight check reads it from here so the number a user
273
+ * is refused by is the number the server enforces, not a second copy that can drift.
274
+ */
275
+ videoMaxMb: number;
268
276
  /**
269
277
  * The range the two admin-settable limits may be set to. Carried here so the admin form states
270
278
  * the real bounds without compiling its own copy of them — the server is the only place that
@@ -796,6 +804,15 @@ interface ModelInfo {
796
804
  * unset (= treated as supported).
797
805
  */
798
806
  vision?: boolean;
807
+ /**
808
+ * Whether video input is supported: the TOML `video` annotation (preset-written) or the
809
+ * built-in catalog's own annotation. Unlike `vision` the default is **unsupported** — a
810
+ * video is megabytes of base64, and a Session whose model does not take one is better served
811
+ * by the clip arriving as an ordinary file attachment than by a request the provider
812
+ * refuses. What the composer reads to decide between the two, and what the server's own
813
+ * routes decide it with (core's `catalogSupportsVideo`).
814
+ */
815
+ video?: boolean;
799
816
  /**
800
817
  * Per-model max output tokens (TOML `max_tokens` annotation; user-only, never preset by the
801
818
  * built-in catalog): when set it wins over the Agent's `system_config.model.max_tokens`;
@@ -2089,6 +2106,37 @@ interface ImportedThemesResponse {
2089
2106
  errors: string[];
2090
2107
  }>;
2091
2108
  }
2109
+ /** What an import (upload or download) wrote: the file it landed in, and the theme it holds. */
2110
+ interface ThemeImportResponse {
2111
+ /** The file under the themes directory — the theme's own id plus `.json`. */
2112
+ file: string;
2113
+ theme: ExternalTheme;
2114
+ }
2115
+ /**
2116
+ * POST /api/themes/upload: install a theme from a file the operator picked. The file is the theme
2117
+ * JSON itself, base64 in the body (the same shape the plugin, skill and hook imports take, so the
2118
+ * request stays JSON and needs no multipart handling). Admin-only.
2119
+ *
2120
+ * Errors, shared with the download route below: 403 `admin_required`, 413 `theme_too_large` (past
2121
+ * the format's own cap of 256KB, checked on the decoded bytes), 400 `theme_invalid` (not UTF-8,
2122
+ * not JSON, or the reader's reasons), 409 `theme_exists` (another file already provides this
2123
+ * theme's id — `overwrite` replaces that file) and 400 `bad_request` for a malformed request.
2124
+ */
2125
+ interface ThemeUploadRequest {
2126
+ dataBase64: string;
2127
+ overwrite?: boolean;
2128
+ }
2129
+ /**
2130
+ * POST /api/themes/download: the same import, fetched server-side from an http(s) address — the
2131
+ * server is what can reach it. Answers the same errors as the upload route above, plus 400
2132
+ * `unsupported_url` (not an absolute http(s) URL), 400 `blocked_url` (a loopback, private or
2133
+ * link-local host), 400 `download_failed` (the request or its status). Admin-only.
2134
+ */
2135
+ interface ThemeDownloadRequest {
2136
+ /** The address of a theme JSON file. */
2137
+ url: string;
2138
+ overwrite?: boolean;
2139
+ }
2092
2140
  interface SessionCreateRequest {
2093
2141
  /** Upstream id of the session's model; always sent together with provider. Omit both for the Project's default Model. */
2094
2142
  modelId?: string;
@@ -2261,8 +2309,11 @@ interface MessagesResponse {
2261
2309
  * `tracePosition`, an inline PNG / JPEG / GIF / WebP `data:` URL — a user `image_url`, or
2262
2310
  * an entry of a tool output's `images` — is replaced by
2263
2311
  * `/api/sessions/<sessionId>/trace-image?file=<fileIndex>&ordinal=<ordinal>[&i=<k>]`
2264
- * (`i` = the index into `images`), an immutable, access-checked image response. Expanded
2265
- * subagent messages, held inputs not yet in the Trace, other image types and the
2312
+ * (`i` = the index into `images`), an immutable, access-checked image response. A user
2313
+ * `video_url` is referenced the same way, through
2314
+ * `/api/sessions/<sessionId>/trace-video?file=<fileIndex>&ordinal=<ordinal>` — a video is
2315
+ * tens of megabytes of base64 and a page that carried one inline would never load.
2316
+ * Expanded subagent messages, held inputs not yet in the Trace, other image types and the
2266
2317
  * parameterless full read keep their data URLs.
2267
2318
  */
2268
2319
  messages: HistoryMessage[];
@@ -2299,6 +2350,28 @@ type TaskInputPart = {
2299
2350
  type: "image_url";
2300
2351
  imageUrl: string;
2301
2352
  }
2353
+ /**
2354
+ * A video that rides the conversation inline on a Session whose model takes video input
2355
+ * (the Volcengine Ark Seed family — see ModelCatalogEntry.supportsVideo): a base64
2356
+ * `data:video/…` URL or an http(s) URL, capped at 32MB (413 `video_too_large`) for the same
2357
+ * reason an image is capped, one order of magnitude up: it enters the conversation and is
2358
+ * written into the Trace, which is read back whole on every history page and every resume.
2359
+ * The provider's own ceiling is what the number is derived from (Ark: video under 50MB,
2360
+ * request body under 64MB). One request carries at most one video — the video budget in
2361
+ * core's llm/generative-model.ts drops the older ones with a note — because a video is
2362
+ * megabytes of base64 and the body is the limit.
2363
+ *
2364
+ * On a model that takes no video, the part is NOT refused: the clip is written into the
2365
+ * Session scratchpad and handed over as an `[attached file: <path>]` line exactly like a
2366
+ * `file` part, so one composer draft goes to either kind of model. `fileName` names it on
2367
+ * disk (default derived from the data URL's type); an http(s) URL cannot be written to disk
2368
+ * and is a 400 in that case.
2369
+ */
2370
+ | {
2371
+ type: "video_url";
2372
+ videoUrl: string;
2373
+ fileName?: string;
2374
+ }
2302
2375
  /**
2303
2376
  * File attachment (the composer's "+" menu): `dataUrl` is a base64 `data:` URL of the
2304
2377
  * file's bytes, capped per file and per message by the admin-settable upload limits
@@ -2393,14 +2466,23 @@ interface SteerRequest {
2393
2466
  * `TaskInputPart.image_url`): delivered as user image messages right behind the
2394
2467
  * `[user_steering]` text. A model without vision receives them as scratchpad path lines
2395
2468
  * instead, exactly as it would a Prompt's images. At least one of `text` / `images` /
2396
- * `files` must be non-empty.
2469
+ * `files` / `videos` must be non-empty.
2397
2470
  */
2398
2471
  images?: string[];
2472
+ /**
2473
+ * Videos sent with the steering message (`data:video/…` or http(s) URLs, same rule and cap
2474
+ * as a task input's `video_url` part): delivered as user video messages right behind the
2475
+ * images. Only for a Session whose model takes video input — on any other model a video
2476
+ * belongs in `files`, where it reaches the model as an `[attached file: <path>]` line (the
2477
+ * server refuses an inline one rather than sending a request the model would refuse).
2478
+ */
2479
+ videos?: string[];
2399
2480
  /**
2400
2481
  * File attachments riding the steering message — the same shape, caps and handling as a
2401
2482
  * task input's `{type:"file"}` parts: written into the Session scratchpad and delivered
2402
2483
  * as `[attached file: <path>]` lines on the `[user_steering]` text, so a file-only draft
2403
2484
  * steers exactly like an image-only one instead of falling back to the follow-up queue.
2485
+ * A video on a model without video input is sent this way.
2404
2486
  */
2405
2487
  files?: {
2406
2488
  fileName: string;
@@ -2421,6 +2503,8 @@ interface PendingSteeringInfo {
2421
2503
  text: string;
2422
2504
  /** Number of images that rode along. */
2423
2505
  images: number;
2506
+ /** Number of inline videos that rode along (a model without video input gets them as files instead). */
2507
+ videos: number;
2424
2508
  /** Number of file attachments that rode along. */
2425
2509
  files: number;
2426
2510
  }
@@ -2437,6 +2521,8 @@ interface PendingFollowUpInfo {
2437
2521
  text: string;
2438
2522
  /** Number of images in the queued input. */
2439
2523
  images: number;
2524
+ /** Number of inline videos in the queued input (a model without video input gets them as files instead). */
2525
+ videos: number;
2440
2526
  /** Number of file attachments in the queued input. */
2441
2527
  files: number;
2442
2528
  }
@@ -2478,6 +2564,8 @@ interface RecalledMessageResponse {
2478
2564
  text: string;
2479
2565
  /** The images as submitted (`data:` / http(s) URLs). */
2480
2566
  images: string[];
2567
+ /** The inline videos as submitted (`data:video/…` / http(s) URLs); a model without video input got them as files instead. */
2568
+ videos: string[];
2481
2569
  /** The file attachments, re-encoded as base64 data URLs (the shape the composer submits them in). */
2482
2570
  files: {
2483
2571
  fileName: string;
@@ -6244,4 +6332,4 @@ interface FeedbackResponse {
6244
6332
  id?: string;
6245
6333
  }
6246
6334
 
6247
- export { type AdminPasswordResetRequest, type AdminUserCreateRequest, type AdminUserCreateResponse, type AdminUsersResponse, type AgentApiKeyCreateRequest, type AgentApiKeyCreateResponse, type AgentApiKeyInfo, type AgentApiResponse, type AgentApiSettings, type AgentApiUpdateRequest, type AgentCompactionConfigDto, type AgentConfigDto, type AgentConfigResponse, type AgentConfigUpdateRequest, type AgentCreateRequest, type AgentCreateResponse, type AgentHooksConfigDto, type AgentHooksResponse, type AgentImportRequest, type AgentImportResponse, type AgentKernelUpdateResponse, type AgentMemoryConfigDto, type AgentModelConfigDto, type AgentPluginsInstallResponse, type AgentSchedulesConfigDto, type AgentSkillsConfigDto, type AgentSkillsResponse, type AgentSummary, type AgentTraceDateGroup, type AgentTraceFileRef, type AgentTraceSessionEntry, type AgentTraceSessionFile, type AgentTraceSessionGroup, type AgentTracesResponse, type AgentVaultConfigDto, type AgentsResponse, type AllProjectSchedulesResponse, type ApprovalDecisionRequest, type ApprovalMode, type AuthLoginRequest, type AuthResponse, type BenchmarkCaseScore, type BenchmarkCaseSummary, type BenchmarkCasesResponse, type BenchmarkCreateCase, type BenchmarkCreateRequest, type BenchmarkCreateResponse, type BenchmarkEvaluation, type BenchmarkRunScore, type BenchmarkStatus, type BenchmarkSummary, type BenchmarksResponse, type BuiltinBrowserAction, type BuiltinBrowserDialog, type BuiltinBrowserExecResult, type BuiltinBrowserHistoryEntry, type BuiltinBrowserHistoryResponse, type BuiltinBrowserImportBrowser, type BuiltinBrowserImportRequest, type BuiltinBrowserImportResult, type BuiltinBrowserImportSource, type BuiltinBrowserImportSourcesResponse, type BuiltinBrowserLoadWarning, type BuiltinBrowserMetrics, type BuiltinBrowserScanResult, type BuiltinBrowserScreenshot, type BuiltinBrowserServerEvent, type BuiltinBrowserSettings, type BuiltinBrowserStatus, type BuiltinBrowserTab, type BuiltinBrowserTabMetrics, type BuiltinBrowserTabsResponse, type BuiltinBrowserUnavailableReason, type CaseMaterial, type ChatDefaultsDto, type CommandPolicyDto, type CommandPolicyRuleDto, type CompanyServerEvent, type ContextFileShare, type ContextToolShare, type ContributionsResponse, type CredentialInfo, type DefaultModelResponse, type DefaultModelUpdateRequest, type DesktopBrowserCommand, type DesktopBrowserCommandMessage, type DesktopBrowserCookie, type DesktopBrowserEvent, type DesktopBrowserEventMessage, type DesktopBrowserReplyMessage, type DesktopFolderAccessMessage, type DesktopFolderAccessResult, type DesktopFolderAccessResultMessage, type DesktopOpenPrivacySettingsMessage, type DesktopPetCommandMessage, type DesktopPetPatch, type DesktopPetStatus, type DesktopPetStatusMessage, type DesktopPetStatusResponse, type DesktopPrivacyPane, type DesktopTrayCommandMessage, type DesktopTrayLocale, type DesktopTrayPatch, type DesktopTrayStatus, type DesktopTrayStatusMessage, type DesktopTrayStatusResponse, type DesktopUpdateStatus, type DesktopUpdateStatusResponse, type DesktopUpdaterCommandMessage, type DesktopUpdaterStatusMessage, type DirAccessResponse, type DirCreateResponse, type DirDeleteResponse, type DirEntryInfo, type DirListResponse, type DirLocation, type DirStandardFolders, type DirectorySkillItem, type DirectorySkillsResponse, type DraftShortcut, type EndpointModelListRequest, type EndpointModelListResponse, type ErrorBody, type FeedbackConfigResponse, type FeedbackRequest, type FeedbackResponse, type FeishuBindingInfo, type FeishuBindingPutRequest, type FeishuBindingResponse, type FeishuTestRequest, type FeishuTestResponse, type FilesCreateRequest, type FilesMoveRequest, type FilesStatRequest, type FilesStatResponse, type FilesWriteRequest, type GoalResponse, type GoalServerEvent, type GoalStateView, type HistoryMessage, type HookArchiveInstallRequest, type HookItem, type ImportedTheme, type ImportedThemesResponse, type InstallResponse, type InstalledPlugin, type InstalledPluginsResponse, MACHINE_PHASES, type MachineInfo, type MachineJob, type MachinePhase, type MachineServerStatus, type MachineUseRefusal, type MachinesResponse, type MachinesStopUsingRequest, type MachinesUseRequest, type MachinesUseResponse, type McpServerTestResponse, type MeResponse, type MemberAddRequest, type MemberAddResponse, type MemberInfo, type MembersResponse, type MemoryFileInfo, type MemoryFileResponse, type MemoryFilesResponse, type MemoryImportMode, type MemoryImportRequest, type MemoryImportResponse, type MemoryOverviewResponse, type MemoryScopeExport, type MemoryScopeInfo, type MemoryTransferFile, type MessagesLiveTail, type MessagesPageInfo, type MessagesResponse, type MessagingBindingCommon, type MessagingBindingInfo, type MessagingBindingStateRequest, type MessagingBindingsResponse, type MessagingChannel, type MessagingChannelState, type MessagingDeliveryError, type MessagingDeliveryPatch, type MessagingRuntimeState, type MessagingRuntimeStatus, type MessagingTestMessageResponse, type ModelBalanceAmount, type ModelBalanceErrorCode, type ModelBalanceFailure, type ModelBalanceReading, type ModelBalanceResponse, type ModelInfo, type ModelOAuthCodeRequest, type ModelOAuthCodeResponse, type ModelOAuthErrorCode, type ModelOAuthMode, type ModelOAuthStartRequest, type ModelOAuthStartResponse, type ModelOAuthStatusResponse, type ModelPricingDto, type ModelProtocolDetectRequest, type ModelProtocolDetectResponse, type ModelProtocolProbeDto, type ModelRefDto, type ModelScopeAuthFlowErrorCode, type ModelScopeAuthFlowStatusResponse, type ModelScopeAuthStartResponse, type ModelTestRequest, type ModelTestResponse, type ModelUpdateEntry, type ModelVisionDetectRequest, type ModelVisionDetectResponse, type ModelVisionOutcome, type ModelsResponse, type ModelsUpdateRequest, type OrgApprovalMode, type OrgBudgetAlert, type OrgCalendarItem, type OrgCalendarOutcome, type OrgCalendarResponse, type OrgCalendarUpsertRequest, type OrgCalendarWriteResponse, type OrgChannelCreateRequest, type OrgChannelDetail, type OrgChannelItem, type OrgChannelMember, type OrgChannelMemberRequest, type OrgChannelMessage, type OrgChannelMessageSendRequest, type OrgChannelMessagesResponse, type OrgChannelNotice, type OrgChannelNoticeKind, type OrgChannelPatchRequest, type OrgChannelReadRequest, type OrgChannelsResponse, type OrgChartResponse, type OrgDeskItem, type OrgDeskResponse, type OrgEmployeeItem, type OrgEmployeePatchRequest, type OrgEmployeeState, type OrgFinanceEmployee, type OrgFinanceResponse, type OrgFinanceTicket, type OrgHandbookFile, type OrgHandbookFileResponse, type OrgHandbookFileWriteRequest, type OrgHandbookFilesResponse, type OrgHandbookResponse, type OrgHireRequest, type OrgInbox, type OrgLanguage, type OrgSessionsResponse, type OrgSpendSummary, type OrgStatus, type OrgTicketAttachRequest, type OrgTicketBlockRequest, type OrgTicketChange, type OrgTicketCreateRequest, type OrgTicketDetail, type OrgTicketHistoryAction, type OrgTicketHistoryEntry, type OrgTicketItem, type OrgTicketMoveRequest, type OrgTicketPriority, type OrgTicketProgressRequest, type OrgTicketSessionItem, type OrgTicketStartRequest, type OrgTicketStartResponse, type OrgTicketStatus, type OrgTicketUpdateRequest, type OrgTicketsResponse, type OrgTriggerKind, type OrganizationCreateRequest, type OrganizationDetail, type OrganizationPatchRequest, type OrganizationSettings, type OrganizationSummary, type OrganizationsResponse, type PasswordChangeRequest, type PendingFollowUpInfo, type PendingSteeringInfo, type PinnedBalance, type PlatformAuthFlowErrorCode, type PlatformAuthFlowStatusResponse, type PlatformAuthStartResponse, type PlatformModelSyncResponse, type PluginConfigActionDecl, type PluginConfigActionResponse, type PluginConfigBackend, type PluginConfigColumnGroup, type PluginConfigEntry, type PluginConfigField, type PluginConfigNewRow, type PluginConfigNotice, type PluginConfigOption, type PluginConfigPinColumn, type PluginConfigResponse, type PluginConfigRowChoice, type PluginConfigTableColumn, type PluginConfigTableRow, type PluginConfigUnavailableDecl, type PluginConfigUpdateRequest, type PluginConfiguration, type PluginDirectoryResponse, type PluginDownloadRequest, type PluginFilesResponse, type PluginGroupItem, type PluginImportResponse, type PluginIndexEntry, type PluginIndexResponse, type PluginInstallRequest, type PluginItem, type PluginLibraryResponse, type PluginReadmeResponse, type PluginSource, type PluginUpdateRef, type PluginUploadRequest, type PrefsResponse, type ProjectCreateRequest, type ProjectCreateResponse, type ProjectRole, type ProjectScheduleItem, type ProjectSchedulesResponse, type ProjectSummary, type ProjectUpdateRequest, type ProjectUpdateResponse, type ProjectsResponse, type ProtocolProbeOutcome, type ProxyProbeDto, type ProxyProbeOutcome, type ProxyProbeProvider, type ProxyProbeResponse, type ProxyProbeTargetDto, type ProxyProbeTargetsResponse, type QQBindingInfo, type QQBindingPutRequest, type QQBindingResponse, type QQScanPollResponse, type QQScanStartResponse, type QQTestRequest, type QQTestResponse, type RecalledMessageResponse, type RendererRef, type RequestSpan, type RestartResponse, type RetryNowResponse, type ScheduleItem, type ScheduleServerEvent, type ScheduleStatus, type ScheduleUpsertRequest, type SchedulesResponse, type SemanticIdKind, type SemanticIdSuggestReason, type SemanticIdSuggestRequest, type SemanticIdSuggestResponse, type ServerEvent, type ServerSettings, type ServerSettingsResponse, type ServerSettingsUpdateRequest, type SessionBackgroundTasks, type SessionBatchPageRequest, type SessionBatchResult, type SessionCategory, type SessionCategoryCounts, type SessionContextParts, type SessionContextResponse, type SessionCreateRequest, type SessionCreateResponse, type SessionForkRequest, type SessionForkResponse, type SessionInfo, type SessionPatchRequest, type SessionProcessInfo, type SessionProcessesResponse, type SessionResponse, type SessionSandbox, type SessionSandboxMode, type SessionSandboxNetwork, type SessionSandboxPreset, type SessionStatus, type SessionSwitchModelRequest, type SessionTracesResponse, type SessionsBatchRequest, type SessionsBatchResponse, type SessionsResponse, type SkillArchiveInstallRequest, type SkillMetadataItem, type SshHostRequest, type SshHostResponse, type SteerRequest, type StorageApplyRequest, type StorageApplyResponse, type StorageCandidate, type StorageCandidateRule, type StorageClass, type StorageClassSummary, type StorageEnvGroup, type StoragePinRequest, type StoragePlanEntryView, type StoragePlanResponse, type StoragePlanView, type StoragePlansResponse, type StoragePurgeResponse, type StorageReport, type StorageReportResponse, type StorageRestoreResponse, type StorageSettings, type StorageSettingsResponse, type StorageSettingsUpdateRequest, type StorageTrashEntry, type StorageTrashItem, type StorageTrashResponse, type StoredKeybindings, type SubagentMessageResponse, type SubagentRuntimeInfo, TICKET_SLUG_PATTERN, type TaskCreateRequest, type TaskCreateResponse, type TaskInputPart, type TelegramBindingInfo, type TelegramBindingPutRequest, type TelegramBindingResponse, type TelegramTestRequest, type TelegramTestResponse, type ToolCallSpan, type TraceAnalysisResponse, type TraceBundle, type TraceBundleShard, type TraceEventsResponse, type TraceFileInfo, type TraceImportRequest, type TraceImportResponse, type TraceModelSegment, type TraceOtherSpan, type TracePosition, type TraceTaskStats, type TraceToolSpan, type UiPrefs, type UnavailableSandboxBackend, type UpdateCheckResponse, type UpdateJobPhase, type UpdateJobStatus, type UpdateProfileRequest, type UpdateProfileResponse, type UpdateRunResponse, type UploadLimits, type UsageAgentSeries, type UsageBucket, type UsageEntitySeriesCounts, type UsageErrorCount, type UsageErrorItem, type UsageErrorKind, type UsageErrors, type UsageErrorsClearResponse, type UsageErrorsPage, type UsageGranularity, type UsageGroupBy, type UsageGroupRow, type UsageModelSeries, type UsageModelTotal, type UsageModelTotals, type UsageResponse, type UsageSeriesPoint, type UsageTrendPointInTrace, type UserInfo, type UserVaultAssignRequest, type UserVaultImportRequest, type VaultEntryInfo, type VaultEntryUpdate, type VaultResponse, type VaultUpdateRequest, type VersionHistoryDiffResponse, type VersionHistoryResponse, type VersionResponse, type VersionRollbackResponse, type WeChatBindingInfo, type WeChatBindingPutRequest, type WeChatBindingResponse, type WeChatScanPollResponse, type WeChatScanStartResponse, type WeChatScanStatus, type WeChatScanVerifyRequest, type WeChatTestResponse, type WebContribution, type WorkflowInfo, type WorkflowRequest, type WorkflowResponse, type WorkflowVersion, type WorkspaceFileEntry, type WorkspaceFilesResponse, type WorkspaceSearchHit, type WorkspaceSearchResponse };
6335
+ export { type AdminPasswordResetRequest, type AdminUserCreateRequest, type AdminUserCreateResponse, type AdminUsersResponse, type AgentApiKeyCreateRequest, type AgentApiKeyCreateResponse, type AgentApiKeyInfo, type AgentApiResponse, type AgentApiSettings, type AgentApiUpdateRequest, type AgentCompactionConfigDto, type AgentConfigDto, type AgentConfigResponse, type AgentConfigUpdateRequest, type AgentCreateRequest, type AgentCreateResponse, type AgentHooksConfigDto, type AgentHooksResponse, type AgentImportRequest, type AgentImportResponse, type AgentKernelUpdateResponse, type AgentMemoryConfigDto, type AgentModelConfigDto, type AgentPluginsInstallResponse, type AgentSchedulesConfigDto, type AgentSkillsConfigDto, type AgentSkillsResponse, type AgentSummary, type AgentTraceDateGroup, type AgentTraceFileRef, type AgentTraceSessionEntry, type AgentTraceSessionFile, type AgentTraceSessionGroup, type AgentTracesResponse, type AgentVaultConfigDto, type AgentsResponse, type AllProjectSchedulesResponse, type ApprovalDecisionRequest, type ApprovalMode, type AuthLoginRequest, type AuthResponse, type BenchmarkCaseScore, type BenchmarkCaseSummary, type BenchmarkCasesResponse, type BenchmarkCreateCase, type BenchmarkCreateRequest, type BenchmarkCreateResponse, type BenchmarkEvaluation, type BenchmarkRunScore, type BenchmarkStatus, type BenchmarkSummary, type BenchmarksResponse, type BuiltinBrowserAction, type BuiltinBrowserDialog, type BuiltinBrowserExecResult, type BuiltinBrowserHistoryEntry, type BuiltinBrowserHistoryResponse, type BuiltinBrowserImportBrowser, type BuiltinBrowserImportRequest, type BuiltinBrowserImportResult, type BuiltinBrowserImportSource, type BuiltinBrowserImportSourcesResponse, type BuiltinBrowserLoadWarning, type BuiltinBrowserMetrics, type BuiltinBrowserScanResult, type BuiltinBrowserScreenshot, type BuiltinBrowserServerEvent, type BuiltinBrowserSettings, type BuiltinBrowserStatus, type BuiltinBrowserTab, type BuiltinBrowserTabMetrics, type BuiltinBrowserTabsResponse, type BuiltinBrowserUnavailableReason, type CaseMaterial, type ChatDefaultsDto, type CommandPolicyDto, type CommandPolicyRuleDto, type CompanyServerEvent, type ContextFileShare, type ContextToolShare, type ContributionsResponse, type CredentialInfo, type DefaultModelResponse, type DefaultModelUpdateRequest, type DesktopBrowserCommand, type DesktopBrowserCommandMessage, type DesktopBrowserCookie, type DesktopBrowserEvent, type DesktopBrowserEventMessage, type DesktopBrowserReplyMessage, type DesktopFolderAccessMessage, type DesktopFolderAccessResult, type DesktopFolderAccessResultMessage, type DesktopOpenPrivacySettingsMessage, type DesktopPetCommandMessage, type DesktopPetPatch, type DesktopPetStatus, type DesktopPetStatusMessage, type DesktopPetStatusResponse, type DesktopPrivacyPane, type DesktopTrayCommandMessage, type DesktopTrayLocale, type DesktopTrayPatch, type DesktopTrayStatus, type DesktopTrayStatusMessage, type DesktopTrayStatusResponse, type DesktopUpdateStatus, type DesktopUpdateStatusResponse, type DesktopUpdaterCommandMessage, type DesktopUpdaterStatusMessage, type DirAccessResponse, type DirCreateResponse, type DirDeleteResponse, type DirEntryInfo, type DirListResponse, type DirLocation, type DirStandardFolders, type DirectorySkillItem, type DirectorySkillsResponse, type DraftShortcut, type EndpointModelListRequest, type EndpointModelListResponse, type ErrorBody, type FeedbackConfigResponse, type FeedbackRequest, type FeedbackResponse, type FeishuBindingInfo, type FeishuBindingPutRequest, type FeishuBindingResponse, type FeishuTestRequest, type FeishuTestResponse, type FilesCreateRequest, type FilesMoveRequest, type FilesStatRequest, type FilesStatResponse, type FilesWriteRequest, type GoalResponse, type GoalServerEvent, type GoalStateView, type HistoryMessage, type HookArchiveInstallRequest, type HookItem, type ImportedTheme, type ImportedThemesResponse, type InstallResponse, type InstalledPlugin, type InstalledPluginsResponse, MACHINE_PHASES, type MachineInfo, type MachineJob, type MachinePhase, type MachineServerStatus, type MachineUseRefusal, type MachinesResponse, type MachinesStopUsingRequest, type MachinesUseRequest, type MachinesUseResponse, type McpServerTestResponse, type MeResponse, type MemberAddRequest, type MemberAddResponse, type MemberInfo, type MembersResponse, type MemoryFileInfo, type MemoryFileResponse, type MemoryFilesResponse, type MemoryImportMode, type MemoryImportRequest, type MemoryImportResponse, type MemoryOverviewResponse, type MemoryScopeExport, type MemoryScopeInfo, type MemoryTransferFile, type MessagesLiveTail, type MessagesPageInfo, type MessagesResponse, type MessagingBindingCommon, type MessagingBindingInfo, type MessagingBindingStateRequest, type MessagingBindingsResponse, type MessagingChannel, type MessagingChannelState, type MessagingDeliveryError, type MessagingDeliveryPatch, type MessagingRuntimeState, type MessagingRuntimeStatus, type MessagingTestMessageResponse, type ModelBalanceAmount, type ModelBalanceErrorCode, type ModelBalanceFailure, type ModelBalanceReading, type ModelBalanceResponse, type ModelInfo, type ModelOAuthCodeRequest, type ModelOAuthCodeResponse, type ModelOAuthErrorCode, type ModelOAuthMode, type ModelOAuthStartRequest, type ModelOAuthStartResponse, type ModelOAuthStatusResponse, type ModelPricingDto, type ModelProtocolDetectRequest, type ModelProtocolDetectResponse, type ModelProtocolProbeDto, type ModelRefDto, type ModelScopeAuthFlowErrorCode, type ModelScopeAuthFlowStatusResponse, type ModelScopeAuthStartResponse, type ModelTestRequest, type ModelTestResponse, type ModelUpdateEntry, type ModelVisionDetectRequest, type ModelVisionDetectResponse, type ModelVisionOutcome, type ModelsResponse, type ModelsUpdateRequest, type OrgApprovalMode, type OrgBudgetAlert, type OrgCalendarItem, type OrgCalendarOutcome, type OrgCalendarResponse, type OrgCalendarUpsertRequest, type OrgCalendarWriteResponse, type OrgChannelCreateRequest, type OrgChannelDetail, type OrgChannelItem, type OrgChannelMember, type OrgChannelMemberRequest, type OrgChannelMessage, type OrgChannelMessageSendRequest, type OrgChannelMessagesResponse, type OrgChannelNotice, type OrgChannelNoticeKind, type OrgChannelPatchRequest, type OrgChannelReadRequest, type OrgChannelsResponse, type OrgChartResponse, type OrgDeskItem, type OrgDeskResponse, type OrgEmployeeItem, type OrgEmployeePatchRequest, type OrgEmployeeState, type OrgFinanceEmployee, type OrgFinanceResponse, type OrgFinanceTicket, type OrgHandbookFile, type OrgHandbookFileResponse, type OrgHandbookFileWriteRequest, type OrgHandbookFilesResponse, type OrgHandbookResponse, type OrgHireRequest, type OrgInbox, type OrgLanguage, type OrgSessionsResponse, type OrgSpendSummary, type OrgStatus, type OrgTicketAttachRequest, type OrgTicketBlockRequest, type OrgTicketChange, type OrgTicketCreateRequest, type OrgTicketDetail, type OrgTicketHistoryAction, type OrgTicketHistoryEntry, type OrgTicketItem, type OrgTicketMoveRequest, type OrgTicketPriority, type OrgTicketProgressRequest, type OrgTicketSessionItem, type OrgTicketStartRequest, type OrgTicketStartResponse, type OrgTicketStatus, type OrgTicketUpdateRequest, type OrgTicketsResponse, type OrgTriggerKind, type OrganizationCreateRequest, type OrganizationDetail, type OrganizationPatchRequest, type OrganizationSettings, type OrganizationSummary, type OrganizationsResponse, type PasswordChangeRequest, type PendingFollowUpInfo, type PendingSteeringInfo, type PinnedBalance, type PlatformAuthFlowErrorCode, type PlatformAuthFlowStatusResponse, type PlatformAuthStartResponse, type PlatformModelSyncResponse, type PluginConfigActionDecl, type PluginConfigActionResponse, type PluginConfigBackend, type PluginConfigColumnGroup, type PluginConfigEntry, type PluginConfigField, type PluginConfigNewRow, type PluginConfigNotice, type PluginConfigOption, type PluginConfigPinColumn, type PluginConfigResponse, type PluginConfigRowChoice, type PluginConfigTableColumn, type PluginConfigTableRow, type PluginConfigUnavailableDecl, type PluginConfigUpdateRequest, type PluginConfiguration, type PluginDirectoryResponse, type PluginDownloadRequest, type PluginFilesResponse, type PluginGroupItem, type PluginImportResponse, type PluginIndexEntry, type PluginIndexResponse, type PluginInstallRequest, type PluginItem, type PluginLibraryResponse, type PluginReadmeResponse, type PluginSource, type PluginUpdateRef, type PluginUploadRequest, type PrefsResponse, type ProjectCreateRequest, type ProjectCreateResponse, type ProjectRole, type ProjectScheduleItem, type ProjectSchedulesResponse, type ProjectSummary, type ProjectUpdateRequest, type ProjectUpdateResponse, type ProjectsResponse, type ProtocolProbeOutcome, type ProxyProbeDto, type ProxyProbeOutcome, type ProxyProbeProvider, type ProxyProbeResponse, type ProxyProbeTargetDto, type ProxyProbeTargetsResponse, type QQBindingInfo, type QQBindingPutRequest, type QQBindingResponse, type QQScanPollResponse, type QQScanStartResponse, type QQTestRequest, type QQTestResponse, type RecalledMessageResponse, type RendererRef, type RequestSpan, type RestartResponse, type RetryNowResponse, type ScheduleItem, type ScheduleServerEvent, type ScheduleStatus, type ScheduleUpsertRequest, type SchedulesResponse, type SemanticIdKind, type SemanticIdSuggestReason, type SemanticIdSuggestRequest, type SemanticIdSuggestResponse, type ServerEvent, type ServerSettings, type ServerSettingsResponse, type ServerSettingsUpdateRequest, type SessionBackgroundTasks, type SessionBatchPageRequest, type SessionBatchResult, type SessionCategory, type SessionCategoryCounts, type SessionContextParts, type SessionContextResponse, type SessionCreateRequest, type SessionCreateResponse, type SessionForkRequest, type SessionForkResponse, type SessionInfo, type SessionPatchRequest, type SessionProcessInfo, type SessionProcessesResponse, type SessionResponse, type SessionSandbox, type SessionSandboxMode, type SessionSandboxNetwork, type SessionSandboxPreset, type SessionStatus, type SessionSwitchModelRequest, type SessionTracesResponse, type SessionsBatchRequest, type SessionsBatchResponse, type SessionsResponse, type SkillArchiveInstallRequest, type SkillMetadataItem, type SshHostRequest, type SshHostResponse, type SteerRequest, type StorageApplyRequest, type StorageApplyResponse, type StorageCandidate, type StorageCandidateRule, type StorageClass, type StorageClassSummary, type StorageEnvGroup, type StoragePinRequest, type StoragePlanEntryView, type StoragePlanResponse, type StoragePlanView, type StoragePlansResponse, type StoragePurgeResponse, type StorageReport, type StorageReportResponse, type StorageRestoreResponse, type StorageSettings, type StorageSettingsResponse, type StorageSettingsUpdateRequest, type StorageTrashEntry, type StorageTrashItem, type StorageTrashResponse, type StoredKeybindings, type SubagentMessageResponse, type SubagentRuntimeInfo, TICKET_SLUG_PATTERN, type TaskCreateRequest, type TaskCreateResponse, type TaskInputPart, type TelegramBindingInfo, type TelegramBindingPutRequest, type TelegramBindingResponse, type TelegramTestRequest, type TelegramTestResponse, type ThemeDownloadRequest, type ThemeImportResponse, type ThemeUploadRequest, type ToolCallSpan, type TraceAnalysisResponse, type TraceBundle, type TraceBundleShard, type TraceEventsResponse, type TraceFileInfo, type TraceImportRequest, type TraceImportResponse, type TraceModelSegment, type TraceOtherSpan, type TracePosition, type TraceTaskStats, type TraceToolSpan, type UiPrefs, type UnavailableSandboxBackend, type UpdateCheckResponse, type UpdateJobPhase, type UpdateJobStatus, type UpdateProfileRequest, type UpdateProfileResponse, type UpdateRunResponse, type UploadLimits, type UsageAgentSeries, type UsageBucket, type UsageEntitySeriesCounts, type UsageErrorCount, type UsageErrorItem, type UsageErrorKind, type UsageErrors, type UsageErrorsClearResponse, type UsageErrorsPage, type UsageGranularity, type UsageGroupBy, type UsageGroupRow, type UsageModelSeries, type UsageModelTotal, type UsageModelTotals, type UsageResponse, type UsageSeriesPoint, type UsageTrendPointInTrace, type UserInfo, type UserVaultAssignRequest, type UserVaultImportRequest, type VaultEntryInfo, type VaultEntryUpdate, type VaultResponse, type VaultUpdateRequest, type VersionHistoryDiffResponse, type VersionHistoryResponse, type VersionResponse, type VersionRollbackResponse, type WeChatBindingInfo, type WeChatBindingPutRequest, type WeChatBindingResponse, type WeChatScanPollResponse, type WeChatScanStartResponse, type WeChatScanStatus, type WeChatScanVerifyRequest, type WeChatTestResponse, type WebContribution, type WorkflowInfo, type WorkflowRequest, type WorkflowResponse, type WorkflowVersion, type WorkspaceFileEntry, type WorkspaceFilesResponse, type WorkspaceSearchHit, type WorkspaceSearchResponse };
package/dist/api/types.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MACHINE_PHASES,
3
3
  TICKET_SLUG_PATTERN
4
- } from "../chunk-FC7VCPLY.js";
4
+ } from "../chunk-6IXN3YH4.js";
5
5
  import "../chunk-B5IP37ZQ.js";
6
6
  export {
7
7
  MACHINE_PHASES,
@@ -111,6 +111,7 @@ function tokensEqual(a, b) {
111
111
  }
112
112
 
113
113
  // src/http/errors.ts
114
+ import { DirectoryBusyError } from "@adelie/core";
114
115
  var HttpError = class extends Error {
115
116
  constructor(status, code, message, retryAfterSeconds) {
116
117
  super(message);
@@ -137,6 +138,16 @@ function modelCredentialMissing(modelId) {
137
138
  `Model ${modelId} has no API key yet. Configure it on the Models page first.`
138
139
  );
139
140
  }
141
+ function normalizeError(err) {
142
+ if (err instanceof DirectoryBusyError) {
143
+ return new HttpError(
144
+ 409,
145
+ "skill_dir_busy",
146
+ `Another program is holding this directory open: ${err.directory}. Close it (an editor, a scanner, a process started in that directory) and try again.`
147
+ );
148
+ }
149
+ return err;
150
+ }
140
151
  function handleError(err, c) {
141
152
  if (err instanceof HttpError) {
142
153
  if (err.retryAfterSeconds !== void 0) {
@@ -469,6 +480,7 @@ export {
469
480
  errorBody,
470
481
  isMissingCredential,
471
482
  modelCredentialMissing,
483
+ normalizeError,
472
484
  handleError,
473
485
  mintApiToken,
474
486
  storeApiToken,
@@ -482,4 +494,4 @@ export {
482
494
  AuthService,
483
495
  InitialProjectProvisioner
484
496
  };
485
- //# sourceMappingURL=chunk-CXGWSWJA.js.map
497
+ //# sourceMappingURL=chunk-3Y7I7J3W.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth/password.ts","../src/auth/service.ts","../src/auth/api-token.ts","../src/http/errors.ts"],"sourcesContent":["import { Component, Interface } from \"@adelie/core/kernel\";\n/**\n * Password hashing (slow-hash storage).\n *\n * Uses node:crypto's scrypt (built-in, no extra dependency, meets the same\n * slow-hash requirement as bcrypt/argon2). Storage format:\n * `scrypt$N$r$p$<salt b64>$<hash b64>` — parameters are stored alongside the hash,\n * so old hashes remain verifiable after future parameter tuning; comparison uses\n * timingSafeEqual to guard against timing side-channels, and a sign-in for an account\n * that does not exist costs the same derivation as a wrong password\n * (verifyAccountPassword).\n */\nimport { randomBytes, scrypt, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Work factor for new hashes. `hashPassword` takes it as a defaulted argument so the server's\n * own test suite can hash at a token cost: almost every case there seeds an admin, provisions\n * users and logs them in, and one derivation at this strength costs on the order of 100ms.\n * Nothing on the production path passes the argument, and no configuration reaches it —\n * `buildAppDeps`, which carries the test override, is not part of this package's exports.\n */\nexport const SCRYPT_COST = 16384;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\nconst SALT_BYTES = 16;\nconst KEY_BYTES = 64;\n\nfunction scryptAsync(\n password: string,\n salt: Buffer,\n keyLen: number,\n n: number,\n r: number,\n p: number,\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keyLen, { N: n, r, p, maxmem: 128 * 1024 * 1024 }, (err, key) => {\n if (err) reject(err);\n else resolve(key);\n });\n });\n}\n\n/**\n * Generates a password hash in `scrypt$N$r$p$salt$hash` format. `cost` is scrypt's N and\n * is recorded in the hash, so `verifyPassword` re-derives at whatever cost produced the\n * stored string.\n */\nexport async function hashPassword(password: string, cost: number = SCRYPT_COST): Promise<string> {\n const salt = randomBytes(SALT_BYTES);\n const key = await scryptAsync(password, salt, KEY_BYTES, cost, SCRYPT_R, SCRYPT_P);\n return [\n \"scrypt\",\n String(cost),\n String(SCRYPT_R),\n String(SCRYPT_P),\n salt.toString(\"base64\"),\n key.toString(\"base64\"),\n ].join(\"$\");\n}\n\n/**\n * What checking a password against a stored string found. `match` and `mismatch` each cost one\n * scrypt derivation; `unverifiable` — not the `scrypt$N$r$p$salt$hash` format, or parameters\n * scrypt refuses — cost none.\n */\nexport type PasswordCheck = \"match\" | \"mismatch\" | \"unverifiable\";\n\n/** Checks a password against a stored hash. Never throws. */\nexport async function checkPassword(password: string, stored: string): Promise<PasswordCheck> {\n const parts = stored.split(\"$\");\n if (parts.length !== 6 || parts[0] !== \"scrypt\") return \"unverifiable\";\n const n = Number(parts[1]);\n const r = Number(parts[2]);\n const p = Number(parts[3]);\n if (!Number.isInteger(n) || !Number.isInteger(r) || !Number.isInteger(p)) return \"unverifiable\";\n const salt = Buffer.from(parts[4]!, \"base64\");\n const expected = Buffer.from(parts[5]!, \"base64\");\n if (salt.length === 0 || expected.length === 0) return \"unverifiable\";\n let actual: Buffer;\n try {\n actual = await scryptAsync(password, salt, expected.length, n, r, p);\n } catch {\n return \"unverifiable\";\n }\n return timingSafeEqual(actual, expected) ? \"match\" : \"mismatch\";\n}\n\n/** Verifies a password; returns false if the stored string has an invalid format (never throws, so the login path can uniformly treat it as a credential error). */\nexport async function verifyPassword(password: string, stored: string): Promise<boolean> {\n return (await checkPassword(password, stored)) === \"match\";\n}\n\n/**\n * Verifies a sign-in password for an account that may not exist, at the cost a real account's\n * check has. `stored` is the account's hash, or null when there is no such account. When there is\n * nothing to derive against — no account, an empty hash, or one `checkPassword` finds\n * unverifiable — the password is checked against `dummyHash` instead and the result is false, so\n * every call runs exactly one scrypt derivation and how long a failure takes does not tell a\n * missing username from a wrong password. `check` is a parameter so a test can count the\n * derivations.\n */\nexport async function verifyAccountPassword(\n password: string,\n stored: string | null,\n dummyHash: () => Promise<string>,\n check: (password: string, stored: string) => Promise<PasswordCheck> = checkPassword,\n): Promise<boolean> {\n const outcome = stored === null ? \"unverifiable\" : await check(password, stored);\n if (outcome !== \"unverifiable\") return outcome === \"match\";\n await check(password, await dummyHash());\n return false;\n}\n\n/** Hashes the passwords this server writes; a test stands in a cheap one. */\n@Interface()\nexport abstract class PasswordHasher {\n abstract hash(password: string): Promise<string>;\n}\n\n/** scrypt at full strength. */\n@Component()\nexport class ScryptHasher implements PasswordHasher {\n hash(password: string): Promise<string> {\n return hashPassword(password, SCRYPT_COST);\n }\n}\n","/**\n * Auth service: admin seeding / login / logout / password change / session validation.\n *\n * No open registration: an empty users table seeds `admin` with a random password hashed and\n * discarded unseen, claimed through the first-login link; every other user is created by an\n * admin. Sessions are rows in auth_sessions (cookie holds the token, the row its sha256), so\n * they outlive a restart and renew in place.\n */\nimport { randomBytes } from \"node:crypto\";\nimport { tokensEqual } from \"./api-token.js\";\nimport type { UserInfo } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { UserRow } from \"../db/repos/users.js\";\nimport { sessionTokenHash } from \"../db/repos/auth-sessions.js\";\nimport type { SessionViaValue, AuthSessionsRepo } from \"../db/repos/auth-sessions.js\";\nimport type { AuthRuntimeState } from \"./runtime-state.js\";\nimport { verifyAccountPassword, verifyPassword } from \"./password.js\";\nimport type { PasswordHasher } from \"./password.js\";\nimport { Component, Use, Interface } from \"@adelie/core/kernel\";\nimport type { AuthState, Clock, Config } from \"../hmr/capabilities.js\";\nimport type { Auth, AuthSessions, Users } from \"../mechanisms/identity.js\";\n\nexport const MIN_PASSWORD_LENGTH = 8;\n\n/** Built-in admin user_id. */\nexport const ADMIN_USER_ID = \"admin\";\n\n/**\n * Login throttling, per userId. After LOGIN_FREE_ATTEMPTS failures each attempt waits an\n * exponentially growing delay (1s doubling to a 60s cap), settling at a guess per minute.\n * Kept for nonexistent userIds too, so it is not an account-existence oracle — and neither is\n * the time a failure takes: a nonexistent userId is checked against a dummy hash (loginDummyHash).\n * Deliberately NOT covered: a concurrent burst before the first failure lands, and guesses\n * spread across many accounts.\n */\nconst LOGIN_FREE_ATTEMPTS = 5;\nconst LOGIN_BACKOFF_START_MS = 1000;\nconst LOGIN_BACKOFF_CAP_MS = 60_000;\n/** Failure entries idle longer than this are swept (bounds the map; far above the cap). */\nconst LOGIN_FAILURE_IDLE_MS = 15 * 60_000;\n\n/** Characters of base64url in a seeded password — 18 random bytes, 144 bits. */\nconst SEED_PASSWORD_CHARS = 24;\n\n/**\n * Never read or typed — hashed at once and discarded — which is what lets it be long enough\n * that the login endpoint, reachable the moment the account exists, has no search space.\n */\nexport function generateInitialAdminPassword(): string {\n return randomBytes(SEED_PASSWORD_CHARS).toString(\"base64url\").slice(0, SEED_PASSWORD_CHARS);\n}\n\n/**\n * \"desktop\" and \"setup\" may set a password without the old one (theirs is random and was never\n * shown); desktop-only routes open to \"desktop\" alone. \"token\" is the local API token's Bearer\n * header — per request, never a stored row. Anything else reads as \"password\".\n */\nexport type SessionVia = \"password\" | \"desktop\" | \"setup\" | \"token\";\n\n/** Row -> DTO. The three optional profile/sign-in columns are omitted rather than sent as null when unset. */\nexport function toUserInfo(row: UserRow): UserInfo {\n return {\n userId: row.userId,\n isAdmin: row.isAdmin,\n passwordIsInitial: row.passwordIsInitial,\n ...(row.displayName !== null ? { displayName: row.displayName } : {}),\n ...(row.avatar !== null ? { avatar: row.avatar } : {}),\n ...(row.lastLoginAt !== null ? { lastLoginAt: row.lastLoginAt } : {}),\n createdAt: row.createdAt,\n };\n}\n\n@Component()\nexport class AuthService implements Auth {\n @Use() private readonly users!: Users;\n @Use() private readonly authSessions!: AuthSessions;\n /**\n * Process-scoped auth values (runtime-state.ts). Held by the RUNTIME so the link a boot\n * printed survives a platform push; everything else about auth is rebuilt with the App.\n */\n @Use() private readonly state!: AuthState;\n /** Provisions the initial Project at signup — declared at the consumer, below. */\n @Use() private readonly provisioner!: InitialProjectProvisioner;\n @Use() private readonly config!: Config;\n @Use() private readonly clock!: Clock;\n @Use() private readonly hasher!: PasswordHasher;\n /** Session lifetime, for the cookie that must expire WITH the session, not before it. */\n get sessionTtlMs(): number {\n return this.config.authSessionTtlMs;\n }\n private get sessionRenewMs(): number {\n return this.config.authSessionRenewMs;\n }\n /** Fixed initial password for the seeded admin; null generates a random one at seed time. */\n private get seedAdminPassword(): string | null {\n return this.config.seedAdminPassword;\n }\n\n setup(): void {\n this.authSessions.deleteExpired(this.clock.now().toISOString());\n }\n\n /**\n * Null once the server is claimed, so the only setup session that exists is a printed one.\n * On demand rather than in the constructor: seedAdmin() runs after it, so \"claimed?\" has no\n * answer there. A cached link whose row is gone is re-minted, not handed out dead.\n */\n mintFirstLogin(): string | null {\n if (!this.adminPasswordIsInitial()) return null;\n if (\n this.state.firstLoginToken !== null &&\n this.authenticateWithMeta(this.state.firstLoginToken) === null\n ) {\n this.state.firstLoginToken = null;\n }\n this.state.firstLoginToken ??= this.issueSession(ADMIN_USER_ID, \"setup\");\n return this.state.firstLoginToken;\n }\n\n /**\n * For the startup notice's pinned-seed gate (index.ts): a pin usually means the operator\n * knows the password, but an offline reset makes it stale, and the gate must see through\n * that or the rescue link stays suppressed.\n */\n async adminPasswordIs(password: string): Promise<boolean> {\n const row = this.users.findById(ADMIN_USER_ID);\n return row !== null && (await verifyPassword(password, row.passwordHash));\n }\n\n /**\n * Startup seeding (idempotent): creates the built-in admin and adopts default_project when\n * the users table is empty; if the initial Project fails, the user row is rolled back and\n * the server retries on next startup.\n */\n async seedAdmin(): Promise<void> {\n if (this.users.count() > 0) return;\n const password = this.seedAdminPassword ?? generateInitialAdminPassword();\n // A pinned override must meet the same policy as every other password — rejected before\n // any insert, so a configuration typo cannot create a trivially weak privileged account.\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new Error(\n `ADELIE_SEED_ADMIN_PASSWORD must be at least ${MIN_PASSWORD_LENGTH} characters.`,\n );\n }\n const user: UserRow = {\n userId: ADMIN_USER_ID,\n passwordHash: await this.hasher.hash(password),\n isAdmin: true,\n passwordIsInitial: true,\n displayName: null,\n avatar: null,\n lastLoginAt: null,\n createdAt: this.clock.now().toISOString(),\n };\n this.users.insert(user);\n try {\n await this.provisioner.provisionInitialProject(user, true);\n } catch (err) {\n this.users.delete(user.userId);\n throw err;\n }\n }\n\n /**\n * Compared against the printed value, not merely verified: a cookie made out of ANY valid\n * token would let one person hand another a link into the sender's account. Not single-use,\n * because a prefetching browser would burn it before its reader clicked.\n */\n redeemFirstLogin(given: string): string | null {\n const expected = this.state.firstLoginToken;\n if (expected === null || given === \"\" || !tokensEqual(given, expected)) return null;\n return this.authenticateWithMeta(expected) === null ? null : expected;\n }\n\n /** Whether the built-in admin still runs on its initial password (gates the first-login link). */\n adminPasswordIsInitial(): boolean {\n return this.users.findById(ADMIN_USER_ID)?.passwordIsInitial === true;\n }\n\n /** Consecutive login failures per userId (see the throttling comment on the constants). */\n private readonly loginFailures = new Map<string, { failures: number; lastFailureAt: number }>();\n\n /** Cache for loginDummyHash. */\n private dummyHash: string | null = null;\n\n /**\n * The hash a sign-in with no account to check is verified against (verifyAccountPassword), so\n * it costs what a wrong password costs. Made by this server's own hasher, hence at the cost its\n * real hashes carry, from a random password nobody holds; computed on the first such sign-in\n * and kept.\n */\n private async loginDummyHash(): Promise<string> {\n this.dummyHash ??= await this.hasher.hash(randomBytes(18).toString(\"base64url\"));\n return this.dummyHash;\n }\n\n /** The wait imposed after `failures` consecutive failures (0 while within the free attempts). */\n private loginDelayMs(failures: number): number {\n const excess = failures - LOGIN_FREE_ATTEMPTS;\n if (excess <= 0) return 0;\n return Math.min(LOGIN_BACKOFF_START_MS * 2 ** (excess - 1), LOGIN_BACKOFF_CAP_MS);\n }\n\n async login(userId: string, password: string): Promise<{ user: UserInfo; token: string }> {\n const nowMs = this.clock.now().getTime();\n for (const [key, entry] of this.loginFailures) {\n if (nowMs - entry.lastFailureAt > LOGIN_FAILURE_IDLE_MS) this.loginFailures.delete(key);\n }\n const failed = this.loginFailures.get(userId);\n if (failed) {\n const readyAt = failed.lastFailureAt + this.loginDelayMs(failed.failures);\n if (nowMs < readyAt) {\n throw new HttpError(\n 429,\n \"too_many_attempts\",\n `Too many failed sign-in attempts. Try again in ${Math.ceil((readyAt - nowMs) / 1000)}s.`,\n );\n }\n }\n const row = this.users.findById(userId);\n // A missing account, or one whose hash cannot be checked, still costs one scrypt\n // derivation, and fails with the same 401 as a wrong password.\n const ok = await verifyAccountPassword(password, row?.passwordHash ?? null, () =>\n this.loginDummyHash(),\n );\n if (!row || !ok) {\n this.loginFailures.set(userId, {\n failures: (failed?.failures ?? 0) + 1,\n lastFailureAt: this.clock.now().getTime(),\n });\n throw new HttpError(401, \"invalid_credentials\", \"Incorrect username or password.\");\n }\n this.loginFailures.delete(userId);\n this.authSessions.deleteExpired(this.clock.now().toISOString());\n // The account's last sign-in, stamped on the one path that verified a password. Written\n // before the DTO is built, so the response's own user carries the value this call set.\n const at = this.clock.now().toISOString();\n this.users.touchLastLogin(row.userId, at);\n return {\n user: toUserInfo({ ...row, lastLoginAt: at }),\n token: this.issueSession(row.userId, \"password\"),\n };\n }\n\n /**\n * Whether a user is an admin, by id — for a caller that has a user's NAME rather than an\n * authenticated request: the terminal relay reads the owner out of a pty reference the\n * runtime has already matched against the signed-in user (machines/terminal-relay.ts), and\n * what is left to decide is whether that user may reach machines at all. Unknown users are\n * not admins.\n */\n isAdmin(userId: string): boolean {\n return this.users.findById(userId)?.isAdmin === true;\n }\n\n /**\n * Desktop-mode sign-in: an admin session with no password check — the claim route already\n * redeemed the shell's one-shot token, which is the credential here. Throws only on a\n * broken deployment (seeding runs before the route exists).\n */\n loginDesktop(): { user: UserInfo; token: string } {\n const row = this.users.findById(ADMIN_USER_ID);\n if (!row) {\n throw new HttpError(500, \"internal\", \"Built-in admin has not been seeded.\");\n }\n return { user: toUserInfo(row), token: this.issueSession(row.userId, \"desktop\") };\n }\n\n /** Self password change (user settings): validates the old password first; the current session stays valid. */\n async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void> {\n const row = this.users.findById(userId);\n if (!row || !(await verifyPassword(oldPassword, row.passwordHash))) {\n throw new HttpError(400, \"password_mismatch\", \"Current password is incorrect.\");\n }\n await this.setPassword(userId, newPassword);\n }\n\n /** No old-password check; the me route gates which sessions may call this. */\n async setInitialPassword(userId: string, newPassword: string): Promise<void> {\n await this.setPassword(userId, newPassword);\n }\n\n /**\n * Validation comes FIRST, so a rejected password leaves the first-login link alive: burning\n * it on a typo would strand the claimer until a restart.\n */\n private async setPassword(userId: string, newPassword: string): Promise<void> {\n if (newPassword.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"Password must be at least 8 characters.\");\n }\n this.users.updatePassword(userId, await this.hasher.hash(newPassword), false);\n // A successful set proves at least what the successful login that resets the counter\n // proves (the old password, or a desktop/setup session), so it resets it too. Without\n // this, anyone spamming the login endpoint holds the backoff window open, and the sign-in\n // that follows a first-login claim (routes/me.ts) would 429 AFTER the password committed —\n // reporting failure for a change that took, with the claimer's setup session already gone.\n this.loginFailures.delete(userId);\n // EVERY first-login session for this account, not just the link this process printed: an\n // earlier boot's link can still be live in a terminal scrollback, and a `setup` session is\n // allowed to change the password without knowing the old one — so one left behind is an\n // account takeover waiting to happen.\n if (userId === ADMIN_USER_ID) {\n this.authSessions.deleteByUserAndVia(userId, \"setup\");\n this.state.firstLoginToken = null;\n }\n }\n\n /** Ends a session: the row is the session, so logout deletes it. Unknown token is a no-op. */\n logout(token: string): void {\n this.authSessions.delete(sessionTokenHash(token));\n }\n\n /**\n * The current boot's local API token (what control-env injection hands to tool\n * subprocesses); null when none was minted. It lives on the runtime state, not on this\n * service: the token was written to `<root>/api-token` by the process, so it must\n * outlive the App a push replaces (auth/runtime-state.ts).\n */\n localApiToken(): string | null {\n return this.state.apiToken;\n }\n\n /**\n * Validates a Bearer token against the boot's local API token (constant-time compare):\n * a match authenticates as the built-in admin — holding the token proves filesystem\n * access to the data root, which is admin authority (see auth/api-token.ts). Returns\n * null on mismatch, when no token was minted, or when the admin row is missing.\n */\n authenticateApiToken(token: string): { user: UserRow; via: SessionVia } | null {\n const apiToken = this.state.apiToken;\n if (apiToken === null || token.length === 0) return null;\n if (!tokensEqual(token, apiToken)) return null;\n const user = this.users.findById(ADMIN_USER_ID);\n return user === null ? null : { user, via: \"token\" };\n }\n\n /**\n * Validates the cookie token. Sliding renewal tops the expiry up IN PLACE, so the cookie\n * value never changes and there is no second identity to revoke. Only a session whose own\n * span reaches the renewal window slides — a one-hour minted token must expire at its hour,\n * not stretch by being used.\n */\n authenticateWithMeta(token: string): { user: UserRow; via: SessionVia; renewed: boolean } | null {\n const tokenHash = sessionTokenHash(token);\n const session = this.authSessions.findByTokenHash(tokenHash);\n if (!session) return null;\n const now = this.clock.now();\n const expiresAt = Date.parse(session.expiresAt);\n if (expiresAt <= now.getTime()) {\n this.authSessions.delete(tokenHash);\n return null;\n }\n const user = this.users.findById(session.userId);\n if (!user) return null;\n const renewable = expiresAt - Date.parse(session.createdAt) >= this.sessionRenewMs;\n let renewed = false;\n if (renewable && expiresAt - now.getTime() < this.sessionRenewMs) {\n this.authSessions.touch(tokenHash, new Date(now.getTime() + this.sessionTtlMs).toISOString());\n renewed = true;\n }\n const via: SessionVia =\n session.via === \"desktop\" ? \"desktop\" : session.via === \"setup\" ? \"setup\" : \"password\";\n return { user, via, renewed };\n }\n\n /**\n * Whether the session behind a cookie token still exists and has not expired — the same\n * two facts {@link authenticateWithMeta} establishes, without its sliding renewal or its\n * user lookup. A connection that outlives the request that opened it has to re-ask\n * periodically (the SSE heartbeat does, once per beat), and asking through the renewing\n * path would top the expiry up every time: a window nobody has touched since last week\n * would stay signed in purely because it is still connected.\n */\n sessionIsLive(token: string): boolean {\n const session = this.authSessions.findByTokenHash(sessionTokenHash(token));\n if (!session) return false;\n return Date.parse(session.expiresAt) > this.clock.now().getTime();\n }\n\n private issueSession(userId: string, via: SessionViaValue): string {\n return this.authSessions.issue({\n userId,\n via,\n now: this.clock.now(),\n ttlMs: this.sessionTtlMs,\n }).token;\n }\n}\n\n/** What signing a new user in needs of project administration — declared at the consumer. */\n@Interface()\nexport abstract class InitialProjectProvisioner {\n abstract provisionInitialProject(user: UserRow, isAdmin: boolean): Promise<void>;\n}\n","/**\n * Local API token (`<root>/api-token`): the machine-local credential behind\n * `Authorization: Bearer`.\n *\n * Minted fresh at every server boot (same recipe as auth-session tokens) and written to\n * the data root with owner-only permissions, so anything that can read the file can call\n * the API as the admin. That equivalence is the authorization model, not an accident:\n * local filesystem access to the data root already IS admin authority — the same rule\n * `penguin server reset-admin-password` stands on (whoever can run it owns web.db\n * anyway) — and agents driving their own harness through the CLI is the product feature\n * this token exists for (server-driven Sessions hand it to tool subprocesses as\n * ADELIE_API_TOKEN). Rotation per boot bounds the life of any leaked copy to the\n * process that minted it.\n */\nimport { createHash, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport function apiTokenPath(root: string): string {\n return path.join(root, \"api-token\");\n}\n\n/** Mints a boot-scoped local API token (the auth-session token recipe). */\nexport function mintApiToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/**\n * Persists the boot token (owner-only file, tmp + rename so a concurrent reader never\n * sees a partial write). Best-effort like the initial-password file: an exotic read-only\n * root must not stop the server — local CLI callers then fall back to\n * ADELIE_API_TOKEN.\n */\nexport function storeApiToken(root: string, token: string): void {\n try {\n fs.mkdirSync(root, { recursive: true });\n const target = apiTokenPath(root);\n const tmp = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(tmp, `${token}\\n`, { mode: 0o600 });\n fs.renameSync(tmp, target);\n } catch {\n // Best-effort: Bearer auth still works for callers holding the token some other way.\n }\n}\n\n/** The stored token, or null when absent/unreadable/empty (shared with the CLI's file fallback). */\nexport function readApiToken(root: string): string | null {\n try {\n const value = fs.readFileSync(apiTokenPath(root), \"utf8\").trim();\n return value === \"\" ? null : value;\n } catch {\n return null;\n }\n}\n\n/**\n * Constant-time equality for token checks: both sides are reduced to fixed-size sha256\n * digests first, so the comparison's timing depends on neither the content nor the\n * length of an attacker-supplied value.\n */\nexport function tokensEqual(a: string, b: string): boolean {\n const digest = (v: string): Buffer => createHash(\"sha256\").update(v).digest();\n return timingSafeEqual(digest(a), digest(b));\n}\n","/**\n * Unified HTTP error: `{error: {code, message}}` response body,\n * with message in English.\n *\n * Routes and services express business errors via throw HttpError; app-level onError\n * uniformly converges these into a JSON response, with unknown errors converged to 500\n * (never leaking internal details to the client).\n */\nimport type { Context } from \"hono\";\nimport type { ErrorBody } from \"../api/types.js\";\n\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n readonly retryAfterSeconds?: number,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport function errorBody(code: string, message: string): ErrorBody {\n return { error: { code, message } };\n}\n\n/**\n * Model missing a credential: the provider SDK throws this at **client-construction\n * time** (hit by both creating a Session and resuming a Session); the original message\n * is full of environment variable names, meaningless to a user — uniformly replaced\n * with a single actionable sentence. The frontend produces localized text by code\n * (message is only a fallback); see web's lib/api-error.ts.\n */\nexport function isMissingCredential(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /missing credentials|api[_ ]?key/i.test(message);\n}\n\nexport function modelCredentialMissing(modelId: string): HttpError {\n return new HttpError(\n 400,\n \"model_credential_missing\",\n `Model ${modelId} has no API key yet. Configure it on the Models page first.`,\n );\n}\n\n/** app.onError handler: maps HttpError through as-is; everything else is logged and converged to 500. */\nexport function handleError(err: Error, c: Context): Response {\n if (err instanceof HttpError) {\n if (err.retryAfterSeconds !== undefined) {\n c.header(\"Retry-After\", String(err.retryAfterSeconds));\n }\n return c.json(errorBody(err.code, err.message), err.status as 400);\n }\n // Unknown error: print the stack for diagnosis, but never expose details externally.\n console.error(`[server] Unhandled exception: ${err.stack ?? err.message}`);\n return c.json(errorBody(\"internal\", \"Internal server error.\"), 500);\n}\n"],"mappings":"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","names":["_init","randomBytes","randomBytes","timingSafeEqual","timingSafeEqual","Component","Interface","randomBytes","_init","Component","Interface"]}
1
+ {"version":3,"sources":["../src/auth/password.ts","../src/auth/service.ts","../src/auth/api-token.ts","../src/http/errors.ts"],"sourcesContent":["import { Component, Interface } from \"@adelie/core/kernel\";\n/**\n * Password hashing (slow-hash storage).\n *\n * Uses node:crypto's scrypt (built-in, no extra dependency, meets the same\n * slow-hash requirement as bcrypt/argon2). Storage format:\n * `scrypt$N$r$p$<salt b64>$<hash b64>` — parameters are stored alongside the hash,\n * so old hashes remain verifiable after future parameter tuning; comparison uses\n * timingSafeEqual to guard against timing side-channels, and a sign-in for an account\n * that does not exist costs the same derivation as a wrong password\n * (verifyAccountPassword).\n */\nimport { randomBytes, scrypt, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Work factor for new hashes. `hashPassword` takes it as a defaulted argument so the server's\n * own test suite can hash at a token cost: almost every case there seeds an admin, provisions\n * users and logs them in, and one derivation at this strength costs on the order of 100ms.\n * Nothing on the production path passes the argument, and no configuration reaches it —\n * `buildAppDeps`, which carries the test override, is not part of this package's exports.\n */\nexport const SCRYPT_COST = 16384;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\nconst SALT_BYTES = 16;\nconst KEY_BYTES = 64;\n\nfunction scryptAsync(\n password: string,\n salt: Buffer,\n keyLen: number,\n n: number,\n r: number,\n p: number,\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keyLen, { N: n, r, p, maxmem: 128 * 1024 * 1024 }, (err, key) => {\n if (err) reject(err);\n else resolve(key);\n });\n });\n}\n\n/**\n * Generates a password hash in `scrypt$N$r$p$salt$hash` format. `cost` is scrypt's N and\n * is recorded in the hash, so `verifyPassword` re-derives at whatever cost produced the\n * stored string.\n */\nexport async function hashPassword(password: string, cost: number = SCRYPT_COST): Promise<string> {\n const salt = randomBytes(SALT_BYTES);\n const key = await scryptAsync(password, salt, KEY_BYTES, cost, SCRYPT_R, SCRYPT_P);\n return [\n \"scrypt\",\n String(cost),\n String(SCRYPT_R),\n String(SCRYPT_P),\n salt.toString(\"base64\"),\n key.toString(\"base64\"),\n ].join(\"$\");\n}\n\n/**\n * What checking a password against a stored string found. `match` and `mismatch` each cost one\n * scrypt derivation; `unverifiable` — not the `scrypt$N$r$p$salt$hash` format, or parameters\n * scrypt refuses — cost none.\n */\nexport type PasswordCheck = \"match\" | \"mismatch\" | \"unverifiable\";\n\n/** Checks a password against a stored hash. Never throws. */\nexport async function checkPassword(password: string, stored: string): Promise<PasswordCheck> {\n const parts = stored.split(\"$\");\n if (parts.length !== 6 || parts[0] !== \"scrypt\") return \"unverifiable\";\n const n = Number(parts[1]);\n const r = Number(parts[2]);\n const p = Number(parts[3]);\n if (!Number.isInteger(n) || !Number.isInteger(r) || !Number.isInteger(p)) return \"unverifiable\";\n const salt = Buffer.from(parts[4]!, \"base64\");\n const expected = Buffer.from(parts[5]!, \"base64\");\n if (salt.length === 0 || expected.length === 0) return \"unverifiable\";\n let actual: Buffer;\n try {\n actual = await scryptAsync(password, salt, expected.length, n, r, p);\n } catch {\n return \"unverifiable\";\n }\n return timingSafeEqual(actual, expected) ? \"match\" : \"mismatch\";\n}\n\n/** Verifies a password; returns false if the stored string has an invalid format (never throws, so the login path can uniformly treat it as a credential error). */\nexport async function verifyPassword(password: string, stored: string): Promise<boolean> {\n return (await checkPassword(password, stored)) === \"match\";\n}\n\n/**\n * Verifies a sign-in password for an account that may not exist, at the cost a real account's\n * check has. `stored` is the account's hash, or null when there is no such account. When there is\n * nothing to derive against — no account, an empty hash, or one `checkPassword` finds\n * unverifiable — the password is checked against `dummyHash` instead and the result is false, so\n * every call runs exactly one scrypt derivation and how long a failure takes does not tell a\n * missing username from a wrong password. `check` is a parameter so a test can count the\n * derivations.\n */\nexport async function verifyAccountPassword(\n password: string,\n stored: string | null,\n dummyHash: () => Promise<string>,\n check: (password: string, stored: string) => Promise<PasswordCheck> = checkPassword,\n): Promise<boolean> {\n const outcome = stored === null ? \"unverifiable\" : await check(password, stored);\n if (outcome !== \"unverifiable\") return outcome === \"match\";\n await check(password, await dummyHash());\n return false;\n}\n\n/** Hashes the passwords this server writes; a test stands in a cheap one. */\n@Interface()\nexport abstract class PasswordHasher {\n abstract hash(password: string): Promise<string>;\n}\n\n/** scrypt at full strength. */\n@Component()\nexport class ScryptHasher implements PasswordHasher {\n hash(password: string): Promise<string> {\n return hashPassword(password, SCRYPT_COST);\n }\n}\n","/**\n * Auth service: admin seeding / login / logout / password change / session validation.\n *\n * No open registration: an empty users table seeds `admin` with a random password hashed and\n * discarded unseen, claimed through the first-login link; every other user is created by an\n * admin. Sessions are rows in auth_sessions (cookie holds the token, the row its sha256), so\n * they outlive a restart and renew in place.\n */\nimport { randomBytes } from \"node:crypto\";\nimport { tokensEqual } from \"./api-token.js\";\nimport type { UserInfo } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { UserRow } from \"../db/repos/users.js\";\nimport { sessionTokenHash } from \"../db/repos/auth-sessions.js\";\nimport type { SessionViaValue, AuthSessionsRepo } from \"../db/repos/auth-sessions.js\";\nimport type { AuthRuntimeState } from \"./runtime-state.js\";\nimport { verifyAccountPassword, verifyPassword } from \"./password.js\";\nimport type { PasswordHasher } from \"./password.js\";\nimport { Component, Use, Interface } from \"@adelie/core/kernel\";\nimport type { AuthState, Clock, Config } from \"../hmr/capabilities.js\";\nimport type { Auth, AuthSessions, Users } from \"../mechanisms/identity.js\";\n\nexport const MIN_PASSWORD_LENGTH = 8;\n\n/** Built-in admin user_id. */\nexport const ADMIN_USER_ID = \"admin\";\n\n/**\n * Login throttling, per userId. After LOGIN_FREE_ATTEMPTS failures each attempt waits an\n * exponentially growing delay (1s doubling to a 60s cap), settling at a guess per minute.\n * Kept for nonexistent userIds too, so it is not an account-existence oracle — and neither is\n * the time a failure takes: a nonexistent userId is checked against a dummy hash (loginDummyHash).\n * Deliberately NOT covered: a concurrent burst before the first failure lands, and guesses\n * spread across many accounts.\n */\nconst LOGIN_FREE_ATTEMPTS = 5;\nconst LOGIN_BACKOFF_START_MS = 1000;\nconst LOGIN_BACKOFF_CAP_MS = 60_000;\n/** Failure entries idle longer than this are swept (bounds the map; far above the cap). */\nconst LOGIN_FAILURE_IDLE_MS = 15 * 60_000;\n\n/** Characters of base64url in a seeded password — 18 random bytes, 144 bits. */\nconst SEED_PASSWORD_CHARS = 24;\n\n/**\n * Never read or typed — hashed at once and discarded — which is what lets it be long enough\n * that the login endpoint, reachable the moment the account exists, has no search space.\n */\nexport function generateInitialAdminPassword(): string {\n return randomBytes(SEED_PASSWORD_CHARS).toString(\"base64url\").slice(0, SEED_PASSWORD_CHARS);\n}\n\n/**\n * \"desktop\" and \"setup\" may set a password without the old one (theirs is random and was never\n * shown); desktop-only routes open to \"desktop\" alone. \"token\" is the local API token's Bearer\n * header — per request, never a stored row. Anything else reads as \"password\".\n */\nexport type SessionVia = \"password\" | \"desktop\" | \"setup\" | \"token\";\n\n/** Row -> DTO. The three optional profile/sign-in columns are omitted rather than sent as null when unset. */\nexport function toUserInfo(row: UserRow): UserInfo {\n return {\n userId: row.userId,\n isAdmin: row.isAdmin,\n passwordIsInitial: row.passwordIsInitial,\n ...(row.displayName !== null ? { displayName: row.displayName } : {}),\n ...(row.avatar !== null ? { avatar: row.avatar } : {}),\n ...(row.lastLoginAt !== null ? { lastLoginAt: row.lastLoginAt } : {}),\n createdAt: row.createdAt,\n };\n}\n\n@Component()\nexport class AuthService implements Auth {\n @Use() private readonly users!: Users;\n @Use() private readonly authSessions!: AuthSessions;\n /**\n * Process-scoped auth values (runtime-state.ts). Held by the RUNTIME so the link a boot\n * printed survives a platform push; everything else about auth is rebuilt with the App.\n */\n @Use() private readonly state!: AuthState;\n /** Provisions the initial Project at signup — declared at the consumer, below. */\n @Use() private readonly provisioner!: InitialProjectProvisioner;\n @Use() private readonly config!: Config;\n @Use() private readonly clock!: Clock;\n @Use() private readonly hasher!: PasswordHasher;\n /** Session lifetime, for the cookie that must expire WITH the session, not before it. */\n get sessionTtlMs(): number {\n return this.config.authSessionTtlMs;\n }\n private get sessionRenewMs(): number {\n return this.config.authSessionRenewMs;\n }\n /** Fixed initial password for the seeded admin; null generates a random one at seed time. */\n private get seedAdminPassword(): string | null {\n return this.config.seedAdminPassword;\n }\n\n setup(): void {\n this.authSessions.deleteExpired(this.clock.now().toISOString());\n }\n\n /**\n * Null once the server is claimed, so the only setup session that exists is a printed one.\n * On demand rather than in the constructor: seedAdmin() runs after it, so \"claimed?\" has no\n * answer there. A cached link whose row is gone is re-minted, not handed out dead.\n */\n mintFirstLogin(): string | null {\n if (!this.adminPasswordIsInitial()) return null;\n if (\n this.state.firstLoginToken !== null &&\n this.authenticateWithMeta(this.state.firstLoginToken) === null\n ) {\n this.state.firstLoginToken = null;\n }\n this.state.firstLoginToken ??= this.issueSession(ADMIN_USER_ID, \"setup\");\n return this.state.firstLoginToken;\n }\n\n /**\n * For the startup notice's pinned-seed gate (index.ts): a pin usually means the operator\n * knows the password, but an offline reset makes it stale, and the gate must see through\n * that or the rescue link stays suppressed.\n */\n async adminPasswordIs(password: string): Promise<boolean> {\n const row = this.users.findById(ADMIN_USER_ID);\n return row !== null && (await verifyPassword(password, row.passwordHash));\n }\n\n /**\n * Startup seeding (idempotent): creates the built-in admin and adopts default_project when\n * the users table is empty; if the initial Project fails, the user row is rolled back and\n * the server retries on next startup.\n */\n async seedAdmin(): Promise<void> {\n if (this.users.count() > 0) return;\n const password = this.seedAdminPassword ?? generateInitialAdminPassword();\n // A pinned override must meet the same policy as every other password — rejected before\n // any insert, so a configuration typo cannot create a trivially weak privileged account.\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new Error(\n `ADELIE_SEED_ADMIN_PASSWORD must be at least ${MIN_PASSWORD_LENGTH} characters.`,\n );\n }\n const user: UserRow = {\n userId: ADMIN_USER_ID,\n passwordHash: await this.hasher.hash(password),\n isAdmin: true,\n passwordIsInitial: true,\n displayName: null,\n avatar: null,\n lastLoginAt: null,\n createdAt: this.clock.now().toISOString(),\n };\n this.users.insert(user);\n try {\n await this.provisioner.provisionInitialProject(user, true);\n } catch (err) {\n this.users.delete(user.userId);\n throw err;\n }\n }\n\n /**\n * Compared against the printed value, not merely verified: a cookie made out of ANY valid\n * token would let one person hand another a link into the sender's account. Not single-use,\n * because a prefetching browser would burn it before its reader clicked.\n */\n redeemFirstLogin(given: string): string | null {\n const expected = this.state.firstLoginToken;\n if (expected === null || given === \"\" || !tokensEqual(given, expected)) return null;\n return this.authenticateWithMeta(expected) === null ? null : expected;\n }\n\n /** Whether the built-in admin still runs on its initial password (gates the first-login link). */\n adminPasswordIsInitial(): boolean {\n return this.users.findById(ADMIN_USER_ID)?.passwordIsInitial === true;\n }\n\n /** Consecutive login failures per userId (see the throttling comment on the constants). */\n private readonly loginFailures = new Map<string, { failures: number; lastFailureAt: number }>();\n\n /** Cache for loginDummyHash. */\n private dummyHash: string | null = null;\n\n /**\n * The hash a sign-in with no account to check is verified against (verifyAccountPassword), so\n * it costs what a wrong password costs. Made by this server's own hasher, hence at the cost its\n * real hashes carry, from a random password nobody holds; computed on the first such sign-in\n * and kept.\n */\n private async loginDummyHash(): Promise<string> {\n this.dummyHash ??= await this.hasher.hash(randomBytes(18).toString(\"base64url\"));\n return this.dummyHash;\n }\n\n /** The wait imposed after `failures` consecutive failures (0 while within the free attempts). */\n private loginDelayMs(failures: number): number {\n const excess = failures - LOGIN_FREE_ATTEMPTS;\n if (excess <= 0) return 0;\n return Math.min(LOGIN_BACKOFF_START_MS * 2 ** (excess - 1), LOGIN_BACKOFF_CAP_MS);\n }\n\n async login(userId: string, password: string): Promise<{ user: UserInfo; token: string }> {\n const nowMs = this.clock.now().getTime();\n for (const [key, entry] of this.loginFailures) {\n if (nowMs - entry.lastFailureAt > LOGIN_FAILURE_IDLE_MS) this.loginFailures.delete(key);\n }\n const failed = this.loginFailures.get(userId);\n if (failed) {\n const readyAt = failed.lastFailureAt + this.loginDelayMs(failed.failures);\n if (nowMs < readyAt) {\n throw new HttpError(\n 429,\n \"too_many_attempts\",\n `Too many failed sign-in attempts. Try again in ${Math.ceil((readyAt - nowMs) / 1000)}s.`,\n );\n }\n }\n const row = this.users.findById(userId);\n // A missing account, or one whose hash cannot be checked, still costs one scrypt\n // derivation, and fails with the same 401 as a wrong password.\n const ok = await verifyAccountPassword(password, row?.passwordHash ?? null, () =>\n this.loginDummyHash(),\n );\n if (!row || !ok) {\n this.loginFailures.set(userId, {\n failures: (failed?.failures ?? 0) + 1,\n lastFailureAt: this.clock.now().getTime(),\n });\n throw new HttpError(401, \"invalid_credentials\", \"Incorrect username or password.\");\n }\n this.loginFailures.delete(userId);\n this.authSessions.deleteExpired(this.clock.now().toISOString());\n // The account's last sign-in, stamped on the one path that verified a password. Written\n // before the DTO is built, so the response's own user carries the value this call set.\n const at = this.clock.now().toISOString();\n this.users.touchLastLogin(row.userId, at);\n return {\n user: toUserInfo({ ...row, lastLoginAt: at }),\n token: this.issueSession(row.userId, \"password\"),\n };\n }\n\n /**\n * Whether a user is an admin, by id — for a caller that has a user's NAME rather than an\n * authenticated request: the terminal relay reads the owner out of a pty reference the\n * runtime has already matched against the signed-in user (machines/terminal-relay.ts), and\n * what is left to decide is whether that user may reach machines at all. Unknown users are\n * not admins.\n */\n isAdmin(userId: string): boolean {\n return this.users.findById(userId)?.isAdmin === true;\n }\n\n /**\n * Desktop-mode sign-in: an admin session with no password check — the claim route already\n * redeemed the shell's one-shot token, which is the credential here. Throws only on a\n * broken deployment (seeding runs before the route exists).\n */\n loginDesktop(): { user: UserInfo; token: string } {\n const row = this.users.findById(ADMIN_USER_ID);\n if (!row) {\n throw new HttpError(500, \"internal\", \"Built-in admin has not been seeded.\");\n }\n return { user: toUserInfo(row), token: this.issueSession(row.userId, \"desktop\") };\n }\n\n /** Self password change (user settings): validates the old password first; the current session stays valid. */\n async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void> {\n const row = this.users.findById(userId);\n if (!row || !(await verifyPassword(oldPassword, row.passwordHash))) {\n throw new HttpError(400, \"password_mismatch\", \"Current password is incorrect.\");\n }\n await this.setPassword(userId, newPassword);\n }\n\n /** No old-password check; the me route gates which sessions may call this. */\n async setInitialPassword(userId: string, newPassword: string): Promise<void> {\n await this.setPassword(userId, newPassword);\n }\n\n /**\n * Validation comes FIRST, so a rejected password leaves the first-login link alive: burning\n * it on a typo would strand the claimer until a restart.\n */\n private async setPassword(userId: string, newPassword: string): Promise<void> {\n if (newPassword.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"Password must be at least 8 characters.\");\n }\n this.users.updatePassword(userId, await this.hasher.hash(newPassword), false);\n // A successful set proves at least what the successful login that resets the counter\n // proves (the old password, or a desktop/setup session), so it resets it too. Without\n // this, anyone spamming the login endpoint holds the backoff window open, and the sign-in\n // that follows a first-login claim (routes/me.ts) would 429 AFTER the password committed —\n // reporting failure for a change that took, with the claimer's setup session already gone.\n this.loginFailures.delete(userId);\n // EVERY first-login session for this account, not just the link this process printed: an\n // earlier boot's link can still be live in a terminal scrollback, and a `setup` session is\n // allowed to change the password without knowing the old one — so one left behind is an\n // account takeover waiting to happen.\n if (userId === ADMIN_USER_ID) {\n this.authSessions.deleteByUserAndVia(userId, \"setup\");\n this.state.firstLoginToken = null;\n }\n }\n\n /** Ends a session: the row is the session, so logout deletes it. Unknown token is a no-op. */\n logout(token: string): void {\n this.authSessions.delete(sessionTokenHash(token));\n }\n\n /**\n * The current boot's local API token (what control-env injection hands to tool\n * subprocesses); null when none was minted. It lives on the runtime state, not on this\n * service: the token was written to `<root>/api-token` by the process, so it must\n * outlive the App a push replaces (auth/runtime-state.ts).\n */\n localApiToken(): string | null {\n return this.state.apiToken;\n }\n\n /**\n * Validates a Bearer token against the boot's local API token (constant-time compare):\n * a match authenticates as the built-in admin — holding the token proves filesystem\n * access to the data root, which is admin authority (see auth/api-token.ts). Returns\n * null on mismatch, when no token was minted, or when the admin row is missing.\n */\n authenticateApiToken(token: string): { user: UserRow; via: SessionVia } | null {\n const apiToken = this.state.apiToken;\n if (apiToken === null || token.length === 0) return null;\n if (!tokensEqual(token, apiToken)) return null;\n const user = this.users.findById(ADMIN_USER_ID);\n return user === null ? null : { user, via: \"token\" };\n }\n\n /**\n * Validates the cookie token. Sliding renewal tops the expiry up IN PLACE, so the cookie\n * value never changes and there is no second identity to revoke. Only a session whose own\n * span reaches the renewal window slides — a one-hour minted token must expire at its hour,\n * not stretch by being used.\n */\n authenticateWithMeta(token: string): { user: UserRow; via: SessionVia; renewed: boolean } | null {\n const tokenHash = sessionTokenHash(token);\n const session = this.authSessions.findByTokenHash(tokenHash);\n if (!session) return null;\n const now = this.clock.now();\n const expiresAt = Date.parse(session.expiresAt);\n if (expiresAt <= now.getTime()) {\n this.authSessions.delete(tokenHash);\n return null;\n }\n const user = this.users.findById(session.userId);\n if (!user) return null;\n const renewable = expiresAt - Date.parse(session.createdAt) >= this.sessionRenewMs;\n let renewed = false;\n if (renewable && expiresAt - now.getTime() < this.sessionRenewMs) {\n this.authSessions.touch(tokenHash, new Date(now.getTime() + this.sessionTtlMs).toISOString());\n renewed = true;\n }\n const via: SessionVia =\n session.via === \"desktop\" ? \"desktop\" : session.via === \"setup\" ? \"setup\" : \"password\";\n return { user, via, renewed };\n }\n\n /**\n * Whether the session behind a cookie token still exists and has not expired — the same\n * two facts {@link authenticateWithMeta} establishes, without its sliding renewal or its\n * user lookup. A connection that outlives the request that opened it has to re-ask\n * periodically (the SSE heartbeat does, once per beat), and asking through the renewing\n * path would top the expiry up every time: a window nobody has touched since last week\n * would stay signed in purely because it is still connected.\n */\n sessionIsLive(token: string): boolean {\n const session = this.authSessions.findByTokenHash(sessionTokenHash(token));\n if (!session) return false;\n return Date.parse(session.expiresAt) > this.clock.now().getTime();\n }\n\n private issueSession(userId: string, via: SessionViaValue): string {\n return this.authSessions.issue({\n userId,\n via,\n now: this.clock.now(),\n ttlMs: this.sessionTtlMs,\n }).token;\n }\n}\n\n/** What signing a new user in needs of project administration — declared at the consumer. */\n@Interface()\nexport abstract class InitialProjectProvisioner {\n abstract provisionInitialProject(user: UserRow, isAdmin: boolean): Promise<void>;\n}\n","/**\n * Local API token (`<root>/api-token`): the machine-local credential behind\n * `Authorization: Bearer`.\n *\n * Minted fresh at every server boot (same recipe as auth-session tokens) and written to\n * the data root with owner-only permissions, so anything that can read the file can call\n * the API as the admin. That equivalence is the authorization model, not an accident:\n * local filesystem access to the data root already IS admin authority — the same rule\n * `penguin server reset-admin-password` stands on (whoever can run it owns web.db\n * anyway) — and agents driving their own harness through the CLI is the product feature\n * this token exists for (server-driven Sessions hand it to tool subprocesses as\n * ADELIE_API_TOKEN). Rotation per boot bounds the life of any leaked copy to the\n * process that minted it.\n */\nimport { createHash, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport function apiTokenPath(root: string): string {\n return path.join(root, \"api-token\");\n}\n\n/** Mints a boot-scoped local API token (the auth-session token recipe). */\nexport function mintApiToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/**\n * Persists the boot token (owner-only file, tmp + rename so a concurrent reader never\n * sees a partial write). Best-effort like the initial-password file: an exotic read-only\n * root must not stop the server — local CLI callers then fall back to\n * ADELIE_API_TOKEN.\n */\nexport function storeApiToken(root: string, token: string): void {\n try {\n fs.mkdirSync(root, { recursive: true });\n const target = apiTokenPath(root);\n const tmp = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(tmp, `${token}\\n`, { mode: 0o600 });\n fs.renameSync(tmp, target);\n } catch {\n // Best-effort: Bearer auth still works for callers holding the token some other way.\n }\n}\n\n/** The stored token, or null when absent/unreadable/empty (shared with the CLI's file fallback). */\nexport function readApiToken(root: string): string | null {\n try {\n const value = fs.readFileSync(apiTokenPath(root), \"utf8\").trim();\n return value === \"\" ? null : value;\n } catch {\n return null;\n }\n}\n\n/**\n * Constant-time equality for token checks: both sides are reduced to fixed-size sha256\n * digests first, so the comparison's timing depends on neither the content nor the\n * length of an attacker-supplied value.\n */\nexport function tokensEqual(a: string, b: string): boolean {\n const digest = (v: string): Buffer => createHash(\"sha256\").update(v).digest();\n return timingSafeEqual(digest(a), digest(b));\n}\n","/**\n * Unified HTTP error: `{error: {code, message}}` response body,\n * with message in English.\n *\n * Routes and services express business errors via throw HttpError; app-level onError\n * uniformly converges these into a JSON response, with unknown errors converged to 500\n * (never leaking internal details to the client).\n */\nimport type { Context } from \"hono\";\nimport { DirectoryBusyError } from \"@adelie/core\";\nimport type { ErrorBody } from \"../api/types.js\";\n\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n readonly retryAfterSeconds?: number,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport function errorBody(code: string, message: string): ErrorBody {\n return { error: { code, message } };\n}\n\n/**\n * Model missing a credential: the provider SDK throws this at **client-construction\n * time** (hit by both creating a Session and resuming a Session); the original message\n * is full of environment variable names, meaningless to a user — uniformly replaced\n * with a single actionable sentence. The frontend produces localized text by code\n * (message is only a fallback); see web's lib/api-error.ts.\n */\nexport function isMissingCredential(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /missing credentials|api[_ ]?key/i.test(message);\n}\n\nexport function modelCredentialMissing(modelId: string): HttpError {\n return new HttpError(\n 400,\n \"model_credential_missing\",\n `Model ${modelId} has no API key yet. Configure it on the Models page first.`,\n );\n}\n\n/**\n * Converges an error caught by onError into the one `handleError` and the error recorder\n * understand. Almost everything passes through untouched; a `DirectoryBusyError` — where Core\n * already exhausted its removal retries because another program (an editor, a virus scanner, a\n * process that was started inside that directory) is holding a Skill or hook directory open —\n * becomes the 409 refusal below.\n *\n * Both are needed because the raw error is handed to two consumers: the response (otherwise a\n * bare 500) and the error recorder, which classifies by `instanceof HttpError` alone — with the\n * original error the error center would show \"internal / unexpected\", i.e. a bug for the user to\n * report, when the fix is on their machine. Normalizing once, ahead of both, keeps the two in\n * step. Both onError funnels call this (src/app.ts and src/http/app.ts).\n */\nexport function normalizeError(err: Error): Error {\n if (err instanceof DirectoryBusyError) {\n return new HttpError(\n 409,\n \"skill_dir_busy\",\n `Another program is holding this directory open: ${err.directory}. ` +\n `Close it (an editor, a scanner, a process started in that directory) and try again.`,\n );\n }\n return err;\n}\n\n/** app.onError handler: maps HttpError through as-is; everything else is logged and converged to 500. */\nexport function handleError(err: Error, c: Context): Response {\n if (err instanceof HttpError) {\n if (err.retryAfterSeconds !== undefined) {\n c.header(\"Retry-After\", String(err.retryAfterSeconds));\n }\n return c.json(errorBody(err.code, err.message), err.status as 400);\n }\n // Unknown error: print the stack for diagnosis, but never expose details externally.\n console.error(`[server] Unhandled exception: ${err.stack ?? err.message}`);\n return c.json(errorBody(\"internal\", \"Internal server error.\"), 500);\n}\n"],"mappings":"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","names":["_init","randomBytes","randomBytes","timingSafeEqual","timingSafeEqual","Component","Interface","randomBytes","_init","Component","Interface"]}
@@ -13,4 +13,4 @@ export {
13
13
  MACHINE_PHASES,
14
14
  TICKET_SLUG_PATTERN
15
15
  };
16
- //# sourceMappingURL=chunk-FC7VCPLY.js.map
16
+ //# sourceMappingURL=chunk-6IXN3YH4.js.map