slotlock 0.5.1 → 0.5.2

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,30 @@
3
3
  All notable changes follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and semantic
4
4
  versioning.
5
5
 
6
+ ## 0.5.2 - 2026-10-28
7
+
8
+ ### Added
9
+
10
+ - The dashboard assistant: with `SLOTLOCK_ASSISTANT_SECRET`, an organization's owners connect a
11
+ model of their own in Settings (the Anthropic Messages API, or OpenAI-compatible chat
12
+ completions: OpenAI, OpenRouter, vLLM, Ollama, LM Studio), with a connection test, and every
13
+ member can operate Slotlock by chat on a new Assistant page. The server runs the tool loop (up to
14
+ 8 model calls a message) with a fixed list of agent tools: reads run through the agent operation
15
+ boundary, and every write is a proposed change the member approves on a form before it runs once,
16
+ recorded as `dashboard-assistant:github:<user id>`, whose card shows the change in full under the
17
+ confirmation question's sentence. A proposal takes no series-write slot or rate token until it
18
+ runs, a message's reads share a budget, maintenance deletes every tenant's expired conversations
19
+ (`slotlock.sweep_assistant_conversations`, over the `assistant_tenants` list), and a new provider
20
+ or base URL takes the key again. The API key is sealed with AES-256-GCM, bound to the
21
+ tenant, and only its last four characters are shown. Two tenant tables under forced row-level
22
+ security, `assistant_settings` and `assistant_conversations`, which erasure deletes.
23
+ `createSlotlockRateLimiter` is the per-key token bucket it limits members' messages with, and
24
+ organization sessions now remember each organization's role (owners are admins). As webhooks
25
+ do, it refuses a model URL whose host is, or resolves to, a loopback, private, link-local, CGNAT,
26
+ unique-local or metadata address, when saved and at each connection (the resolved address is
27
+ checked as the socket connects, against DNS rebinding); `SLOTLOCK_ASSISTANT_PRIVATE_URLS=on`
28
+ allows them for a self-hosted model. `createSlotlockGuardedFetch` is that `fetch`.
29
+
6
30
  ## 0.5.1 - 2026-10-28
7
31
 
8
32
  ### Fixed
package/README.md CHANGED
@@ -304,6 +304,59 @@ resources, so you can give each person API keys without running `slotlock key cr
304
304
  own on the hours page: two windows a day, overnight or all day, or closed. One form sets them
305
305
  for every resource at once.
306
306
 
307
+ #### Assistant: operate Slotlock by chat
308
+
309
+ With `SLOTLOCK_ASSISTANT_SECRET` set (32+ random characters, `openssl rand -hex 32`), the dashboard
310
+ gains an Assistant page where every member chats with a model their organization connects, instead
311
+ of clicking. Each organization brings its own model:
312
+
313
+ - **Connect it** in Settings, Assistant (owners only; with personal tenants, each person): the
314
+ provider (`Anthropic Messages API`, or `OpenAI-compatible`, which covers OpenAI, OpenRouter,
315
+ vLLM, Ollama and LM Studio), the base URL (`https://api.anthropic.com`, or the one ending in
316
+ `/v1`: `https://api.openai.com/v1`, `http://localhost:11434/v1`; https, or http only on
317
+ `localhost` or `127.0.0.1`), the model name (`your-model`) and the API key, which a local model
318
+ may not need. As with webhooks, a URL whose host is, or resolves to, a loopback, private,
319
+ link-local, shared (CGNAT), unique-local or metadata address (`169.254.169.254`) is refused, when
320
+ it is saved and at each connection (the address the socket connects to is checked, so a name
321
+ rebound to one is refused too), and the page says why. For a self-hosted model on the server's
322
+ own machine or network (Ollama, vLLM), the operator sets `SLOTLOCK_ASSISTANT_PRIVATE_URLS=on`.
323
+ On Cloudflare Workers the literal check applies and names are not resolved: a Worker's fetch
324
+ reaches no private network. Changing the provider or the base URL takes the key again (decided in the
325
+ database write, so racing saves cannot get past it), so the stored key is only ever sent where it
326
+ was entered for, even on an origin that serves several accounts. **Test connection** makes one real request
327
+ and checks the model answers with one correct tool call. The organization's messages, and the bookings and resources the assistant
328
+ reads, are sent to the provider it configures.
329
+ - **The key** is stored sealed with AES-256-GCM under a key derived from `SLOTLOCK_ASSISTANT_SECRET`,
330
+ bound to the organization, in `slotlock.assistant_settings` (forced row-level security, erased
331
+ with the tenant). The dashboard shows only that it is set and its last four characters. Changing
332
+ the secret makes stored keys unreadable: owners enter them again.
333
+ - **Reads run, writes wait.** The server runs the tool loop, up to 8 model calls a message, with a
334
+ fixed list of the agent tools (the reads: resources, free-busy, next free time, slots, pool
335
+ availability, events; the writes: holds, bookings, pool and bundle bookings, changes and
336
+ cancellations). A read runs at once through the same operation boundary as an agent's. A write
337
+ never runs when the model asks: it appears as a proposed change the member approves (or
338
+ dismisses) with a form, and only then runs, once (its idempotency key is the dashboard's), with
339
+ the same booking rules, tenant limits and rate limits as an agent, recorded as
340
+ `dashboard-assistant:github:<user id>` (shown as "Assistant for @login"). The card leads with
341
+ the sentence a confirmation would ask (the resource by name, the times in its zone, and for a
342
+ change or cancellation the event's title, resource and time as it stands) and then shows every
343
+ argument in full; a change too large to show (16 KB of arguments) is not proposed, and a
344
+ conversation has at most 4 waiting. A proposal is only checked: the series-write slot and the
345
+ write's rate token are taken when the approved change runs. A message's reads share a budget of
346
+ 16 calls and 400 resource-days. API keys, webhooks, booking rules, pools, erasure and
347
+ coverage are never offered; a tool the model names outside the list is refused.
348
+ - Tool results and booking data are data to the model, never instructions, and the system prompt
349
+ says so; the tool list does not change with what the model says. A member may send 10 messages
350
+ a minute; each model call has 30 seconds and a message 90. Conversations are kept per member in
351
+ `slotlock.assistant_conversations` for 2 hours after the last message, then deleted: by
352
+ `serve`'s maintenance, which sweeps every tenant's expired conversations every quarter hour (a
353
+ tenant only the dashboard serves included), by `slotlock maintain`, and whenever the tenant's
354
+ conversations are read (so on Workers too). They are bounded in UTF-8 bytes; **New conversation** forgets one. On Workers, the
355
+ assistant's agent calls are capped at 24 a request and by `tenant_read` and `tenant_write` of
356
+ `SLOTLOCK_LIMITS` in each isolate. A provider's failure is shown as a plain message, never with
357
+ the key or the provider's answer.
358
+ - It works as plain forms: no script is needed.
359
+
307
360
  ### Dashboard on Cloudflare Workers
308
361
 
309
362
  The same dashboard can run as a Cloudflare Worker at an origin of its own (`workers/dashboard`),
@@ -337,7 +390,8 @@ this one and read its session secret, which signs in as any tenant.
337
390
  3. Create the GitHub OAuth app as above, with the callback URL
338
391
  `<SLOTLOCK_PUBLIC_URL>/dashboard/callback` on the Worker's origin.
339
392
  4. Store the secrets on the Worker: `npx wrangler secret put` for `SLOTLOCK_GITHUB_CLIENT_ID`,
340
- `SLOTLOCK_GITHUB_CLIENT_SECRET` and `SLOTLOCK_SESSION_SECRET`.
393
+ `SLOTLOCK_GITHUB_CLIENT_SECRET` and `SLOTLOCK_SESSION_SECRET`, and `SLOTLOCK_ASSISTANT_SECRET`
394
+ for the assistant.
341
395
  5. Deploy: `npm ci --omit=dev` at the repository root (the Worker bundles Slotlock's sources), then
342
396
  `npx wrangler deploy --config <your configuration>` in `workers/dashboard`.
343
397
 
package/agent-server.d.ts CHANGED
@@ -464,6 +464,20 @@ export declare function parseSlotlockA2ATimestamp(value: unknown): Date | null;
464
464
  export declare const SLOTLOCK_MCP_RESOURCE_URI_TEMPLATE = "slotlock://resources/{resource_id}";
465
465
  /** The MCP resource URI of one calendar resource: `SLOTLOCK_MCP_RESOURCE_URI_TEMPLATE`, expanded. */
466
466
  export declare function slotlockCalendarResourceUri(resourceId: string): string;
467
+ /**
468
+ * The sentence a person is asked to confirm for a write, as the confirmation flow words it: the
469
+ * resource by name and the times in its own zone, and for a change or deletion the event as it
470
+ * stands (its title, resource and time), each read as `principal` only where its scopes and
471
+ * `authorize` allow that read. The dashboard assistant shows it on a proposed change.
472
+ */
473
+ export declare function describeSlotlockAgentWrite(args: {
474
+ backend: SlotlockAgentCalendarBackend;
475
+ authorize: SlotlockAgentServerOptions['authorize'];
476
+ principal: SlotlockAgentPrincipal;
477
+ operation: SlotlockAgentWriteOperation;
478
+ input: Record<string, unknown>;
479
+ signal: AbortSignal;
480
+ }): Promise<string>;
467
481
  export declare function createSlotlockAgentServer(options: SlotlockAgentServerOptions): {
468
482
  fetch(request: Request): Promise<Response>;
469
483
  manifest: Readonly<Record<string, unknown>>;
package/agent-server.js CHANGED
@@ -1912,6 +1912,21 @@ function confirmationLookup(options, context) {
1912
1912
  },
1913
1913
  };
1914
1914
  }
1915
+ /**
1916
+ * The sentence a person is asked to confirm for a write, as the confirmation flow words it: the
1917
+ * resource by name and the times in its own zone, and for a change or deletion the event as it
1918
+ * stands (its title, resource and time), each read as `principal` only where its scopes and
1919
+ * `authorize` allow that read. The dashboard assistant shows it on a proposed change.
1920
+ */
1921
+ export async function describeSlotlockAgentWrite(args) {
1922
+ const lookup = confirmationLookup({ backend: args.backend, authorize: args.authorize }, { principal: args.principal, signal: args.signal });
1923
+ try {
1924
+ return await confirmationMessage(args.operation, args.input, lookup);
1925
+ }
1926
+ finally {
1927
+ lookup.close();
1928
+ }
1929
+ }
1915
1930
  /**
1916
1931
  * The sentence a person confirms: what changes, where and when. The facts the server establishes
1917
1932
  * (the resource, the times in its own zone, every date a repeat books, the event as it stands) come
package/assistant.d.ts ADDED
@@ -0,0 +1,263 @@
1
+ import { type SlotlockAgentOperation } from './agent-server.js';
2
+ import type { SlotlockAssistantConversationRecord, SlotlockAssistantSettingsRecord, SlotlockStore } from './store.js';
3
+ export type SlotlockAssistantProvider = 'anthropic' | 'openai';
4
+ /** What the assistant does with each agent operation: run it, propose it, or never offer it. */
5
+ export type SlotlockAssistantToolUse = 'read' | 'propose' | 'excluded';
6
+ /**
7
+ * The tools the model is offered, by agent operation. A record over every operation, so one a later
8
+ * release adds does not compile until it is placed here. Reads run when the model calls them;
9
+ * every write is only proposed, and runs when the member approves it. API keys, webhooks, booking
10
+ * rules, pools, erasure and coverage are application operations, never agent ones, so no entry can
11
+ * offer them.
12
+ */
13
+ export declare const SLOTLOCK_ASSISTANT_TOOL_USE: Readonly<Record<SlotlockAgentOperation, SlotlockAssistantToolUse>>;
14
+ /** Model calls one message may make before the assistant stops and says so. */
15
+ export declare const SLOTLOCK_ASSISTANT_MAX_STEPS = 8;
16
+ /** Characters of one message from a member. */
17
+ export declare const SLOTLOCK_ASSISTANT_MAX_MESSAGE = 2000;
18
+ /**
19
+ * UTF-8 bytes of the stored conversation as JSON, past which the oldest exchanges go first.
20
+ * PostgreSQL's jsonb text adds at most one space per `:` or `,`, so at most half again: under the
21
+ * table's 262,144-byte CHECK on `messages::text` whatever the text.
22
+ */
23
+ export declare const SLOTLOCK_ASSISTANT_MAX_STORED_BYTES = 160000;
24
+ /** UTF-8 bytes of one proposed change's arguments as JSON; a larger one is not proposed. */
25
+ export declare const SLOTLOCK_ASSISTANT_MAX_ACTION_BYTES = 16000;
26
+ /** Proposed changes one conversation may have waiting at once (4 of the largest fit the bound). */
27
+ export declare const SLOTLOCK_ASSISTANT_MAX_PENDING = 4;
28
+ /**
29
+ * The Messages API version this sends: the one @anthropic-ai/sdk 1.3.0 (2026-10-23) sends, read
30
+ * from its source on 2026-10-28. Top-level `system`, `tools[].input_schema`, `tool_use` and
31
+ * `tool_result` blocks are as before in it.
32
+ */
33
+ export declare const SLOTLOCK_ANTHROPIC_VERSION = "2026-10-21";
34
+ /** A model as the assistant calls it, with the key opened. */
35
+ export interface SlotlockAssistantModel {
36
+ provider: SlotlockAssistantProvider;
37
+ baseUrl: string;
38
+ model: string;
39
+ apiKey: string | null;
40
+ }
41
+ export interface SlotlockAssistantCall {
42
+ id: string;
43
+ name: string;
44
+ input: unknown;
45
+ }
46
+ /** One entry of a stored conversation. `note`s are shown to the member and never sent to the model. */
47
+ export type SlotlockAssistantEntry = {
48
+ role: 'user';
49
+ text: string;
50
+ } | {
51
+ role: 'assistant';
52
+ text: string;
53
+ calls: SlotlockAssistantCall[];
54
+ } | {
55
+ role: 'tool';
56
+ results: {
57
+ id: string;
58
+ name: string;
59
+ content: string;
60
+ error: boolean;
61
+ action?: string;
62
+ }[];
63
+ } | {
64
+ role: 'event';
65
+ text: string;
66
+ } | {
67
+ role: 'note';
68
+ text: string;
69
+ };
70
+ /** A proposed write, as the conversation keeps it. */
71
+ export interface SlotlockAssistantAction {
72
+ id: string;
73
+ operation: SlotlockAgentOperation;
74
+ input: Record<string, unknown>;
75
+ status: 'pending' | 'approved' | 'dismissed' | 'done' | 'failed';
76
+ /** What it did (`done`), or the code it failed with (`failed`). */
77
+ outcome?: string;
78
+ /** The change in a sentence, with its resource and event resolved (the confirmation question). */
79
+ summary?: string;
80
+ }
81
+ /** Where the dashboard keeps assistant settings and conversations: the store, under each tenant's RLS. */
82
+ export interface SlotlockAssistantStore {
83
+ getSettings(tenantRef: string): Promise<SlotlockAssistantSettingsRecord | null>;
84
+ putSettings(tenantRef: string, settings: {
85
+ provider: SlotlockAssistantProvider;
86
+ baseUrl: string;
87
+ model: string;
88
+ key: {
89
+ sealed: string;
90
+ hint: string;
91
+ } | null | 'keep';
92
+ }, actor?: string): Promise<{
93
+ saved: boolean;
94
+ }>;
95
+ deleteSettings(tenantRef: string, actor?: string): Promise<boolean>;
96
+ getConversation(tenantRef: string, memberRef: string): Promise<SlotlockAssistantConversationRecord | null>;
97
+ /**
98
+ * Replace a member's messages; keep the stored actions `keep` names, as they stand, and add the
99
+ * `added` ones not stored already (store.ts putAssistantConversation).
100
+ */
101
+ putConversation(tenantRef: string, memberRef: string, conversation: {
102
+ messages: unknown[];
103
+ added: Record<string, Record<string, unknown>>;
104
+ keep: readonly string[];
105
+ }, ttlMs: number): Promise<void>;
106
+ settleAction(tenantRef: string, memberRef: string, actionId: string, from: string, patch: Record<string, unknown>): Promise<Record<string, unknown> | null>;
107
+ deleteConversation(tenantRef: string, memberRef: string): Promise<boolean>;
108
+ }
109
+ /** The assistant's records in the store, each in a transaction under the tenant's row-level security. */
110
+ export declare function createSlotlockAssistantStore(store: SlotlockStore): SlotlockAssistantStore;
111
+ /** The assistant's records in this process: for tests, or a server that only ever runs as one. */
112
+ export declare function createSlotlockMemoryAssistantStore(now?: () => number): SlotlockAssistantStore;
113
+ /** The key sealed for one tenant: `v1.<iv>.<ciphertext and tag>`, base64url. */
114
+ export declare function sealSlotlockAssistantKey(secret: string, tenantRef: string, apiKey: string): Promise<string>;
115
+ /** The key a seal holds, or null when it is not one this secret sealed for this tenant. */
116
+ export declare function openSlotlockAssistantKey(secret: string, tenantRef: string, sealed: string): Promise<string | null>;
117
+ /**
118
+ * A provider base URL as stored: https, or http on localhost or 127.0.0.1 (a model on the server's
119
+ * own machine); no credentials, query or fragment; no trailing slash. Unless `privateUrls`, a URL
120
+ * whose host is `localhost` or a loopback, private, link-local, shared (CGNAT), unique-local or
121
+ * metadata address (the webhooks' isPrivateAddress) is refused as `private`; a name that resolves
122
+ * to one is refused when it is resolved (checkSlotlockAssistantHost, and at each connection).
123
+ */
124
+ export declare function readSlotlockAssistantBaseUrl(value: string, options?: {
125
+ privateUrls?: boolean;
126
+ }): {
127
+ ok: true;
128
+ url: string;
129
+ } | {
130
+ ok: false;
131
+ reason: 'invalid' | 'private';
132
+ };
133
+ /**
134
+ * Whether a base URL's host resolves only to public addresses: `resolve` lists them (dns.lookup's,
135
+ * on Node.js). True when `privateUrls`, or when the host is an IP literal readSlotlockAssistantBaseUrl
136
+ * already checked; false when any address is private. A name that does not resolve is not refused
137
+ * here: the connection fails later, plainly.
138
+ */
139
+ export declare function checkSlotlockAssistantHost(baseUrl: string, options: {
140
+ privateUrls?: boolean;
141
+ resolve?: (hostname: string) => Promise<string[]>;
142
+ }): Promise<boolean>;
143
+ /** A model name: 1-200 printable characters, no spaces. */
144
+ export declare const SLOTLOCK_ASSISTANT_MODEL_NAME: RegExp;
145
+ /** An API key as typed: 1-4,096 printable characters, no spaces. */
146
+ export declare const SLOTLOCK_ASSISTANT_API_KEY: RegExp;
147
+ export interface SlotlockAssistantTool {
148
+ name: SlotlockAgentOperation;
149
+ title: string;
150
+ description: string;
151
+ inputSchema: Record<string, unknown>;
152
+ use: 'read' | 'propose';
153
+ }
154
+ /**
155
+ * The tools the model is offered: every agent tool the assistant reads or proposes, with its input
156
+ * schema less `idempotency_key`, which the dashboard sets itself so an approval runs once.
157
+ */
158
+ export declare function slotlockAssistantTools(): SlotlockAssistantTool[];
159
+ /** Why a provider call failed, in words safe to show (never the key, never the provider's body). */
160
+ export declare class SlotlockAssistantProviderError extends Error {
161
+ readonly reason: 'timeout' | 'refused_key' | 'not_found' | 'rate_limited' | 'unavailable' | 'bad_answer' | 'private_address';
162
+ readonly status?: number | undefined;
163
+ constructor(reason: 'timeout' | 'refused_key' | 'not_found' | 'rate_limited' | 'unavailable' | 'bad_answer' | 'private_address', status?: number | undefined);
164
+ }
165
+ /** What to tell the member a provider failure was. */
166
+ export declare function slotlockAssistantProviderMessage(error: unknown): string;
167
+ /** One answer of the model: its text and the tools it called. */
168
+ export interface SlotlockAssistantModelTurn {
169
+ text: string;
170
+ calls: SlotlockAssistantCall[];
171
+ }
172
+ interface ModelRequest {
173
+ model: SlotlockAssistantModel;
174
+ system: string;
175
+ entries: readonly SlotlockAssistantEntry[];
176
+ tools: readonly {
177
+ name: string;
178
+ description: string;
179
+ inputSchema: Record<string, unknown>;
180
+ }[];
181
+ /** Make the model call this tool (the connection test). */
182
+ force?: string;
183
+ fetch: typeof fetch;
184
+ signal: AbortSignal;
185
+ }
186
+ /** One model call. Throws SlotlockAssistantProviderError, whose message never holds the key. */
187
+ export declare function callSlotlockAssistantModel(request: ModelRequest): Promise<SlotlockAssistantModelTurn>;
188
+ /**
189
+ * The connection test: one request offering one read tool the model must call. True when it
190
+ * answers with exactly one call of that tool with object arguments.
191
+ */
192
+ export declare function testSlotlockAssistantModel(args: {
193
+ model: SlotlockAssistantModel;
194
+ fetch: typeof fetch;
195
+ timeoutMs: number;
196
+ }): Promise<boolean>;
197
+ /** How a tool call came out, as the loop tells the model. */
198
+ export type SlotlockAssistantToolOutcome = {
199
+ ok: true;
200
+ data: Record<string, unknown>;
201
+ } | {
202
+ ok: false;
203
+ code: string;
204
+ details?: Readonly<Record<string, string>>;
205
+ };
206
+ /** A URL checked again before each call: one stored under looser rules is refused, not reached. */
207
+ export declare function slotlockAssistantUrlAllowed(baseUrl: string, privateUrls: boolean): boolean;
208
+ export interface SlotlockAssistantTurnOptions {
209
+ model: SlotlockAssistantModel;
210
+ system: string;
211
+ /** The conversation so far, ending with the member's new message. */
212
+ entries: readonly SlotlockAssistantEntry[];
213
+ fetch: typeof fetch;
214
+ /** Each model call's limit, and the whole turn's. */
215
+ timeoutMs: number;
216
+ turnTimeoutMs: number;
217
+ maxSteps?: number;
218
+ /** Run a read the model asked for. */
219
+ read(operation: SlotlockAgentOperation, input: unknown): Promise<SlotlockAssistantToolOutcome>;
220
+ /** Check a write the model asked for and keep it for the member to approve: its action id. */
221
+ propose(operation: SlotlockAgentOperation, input: unknown): Promise<{
222
+ ok: true;
223
+ actionId: string;
224
+ } | {
225
+ ok: false;
226
+ code: string;
227
+ }>;
228
+ }
229
+ export interface SlotlockAssistantTurnResult {
230
+ /** The entries this turn added. */
231
+ added: SlotlockAssistantEntry[];
232
+ /** Set when the provider failed; `added` then ends with a note saying so. */
233
+ failure?: unknown;
234
+ }
235
+ /**
236
+ * One member message: call the model, run what it reads, keep what it would write, and call it
237
+ * again with the results, up to `maxSteps` calls. Only the offered tools run, whatever the model
238
+ * names; everything else is answered `tool_not_available`.
239
+ */
240
+ export declare function runSlotlockAssistantTurn(options: SlotlockAssistantTurnOptions): Promise<SlotlockAssistantTurnResult>;
241
+ /**
242
+ * The conversation within its bounds: at most MAX_ENTRIES entries and MAX_STORED_CHARACTERS, the
243
+ * oldest exchanges dropped whole, so it always starts at a member's message (a tool result is never
244
+ * kept without the call it answers).
245
+ */
246
+ export declare function boundSlotlockAssistantEntries(entries: readonly SlotlockAssistantEntry[]): SlotlockAssistantEntry[];
247
+ /**
248
+ * The actions a conversation keeps: those its kept entries show, the oldest settled ones dropped
249
+ * first past MAX_ACTIONS_BYTES (a waiting one is kept: SLOTLOCK_ASSISTANT_MAX_PENDING bounds them).
250
+ */
251
+ export declare function pruneSlotlockAssistantActions(entries: readonly SlotlockAssistantEntry[], actions: Readonly<Record<string, Record<string, unknown>>>): Record<string, Record<string, unknown>>;
252
+ /** How many of a conversation's actions wait for the member. */
253
+ export declare function pendingSlotlockAssistantActions(actions: Readonly<Record<string, Record<string, unknown>>>): number;
254
+ /** Read stored entries back, dropping anything malformed. */
255
+ export declare function readSlotlockAssistantEntries(value: unknown): SlotlockAssistantEntry[];
256
+ /** The system prompt: who the model works for, and that what it reads is data, not instructions. */
257
+ export declare function slotlockAssistantSystemPrompt(args: {
258
+ brand: string;
259
+ workspace: string;
260
+ login: string;
261
+ now: Date;
262
+ }): string;
263
+ export {};