slotlock 0.7.0 → 0.9.0

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +281 -0
  2. package/agent-server.d.ts +25 -5
  3. package/agent-server.js +152 -7
  4. package/agent-store-backend.d.ts +6 -1
  5. package/agent-store-backend.js +861 -55
  6. package/application-api.d.ts +19 -1
  7. package/application-api.js +578 -21
  8. package/approvals.d.ts +2 -0
  9. package/approvals.js +12 -5
  10. package/assistant.d.ts +64 -9
  11. package/assistant.js +450 -82
  12. package/availability-store.d.ts +193 -0
  13. package/availability-store.js +558 -0
  14. package/booking-rules.d.ts +92 -5
  15. package/booking-rules.js +243 -15
  16. package/dashboard-assistant-ui.d.ts +21 -0
  17. package/dashboard-assistant-ui.js +539 -0
  18. package/dashboard-markdown.d.ts +2 -0
  19. package/dashboard-markdown.js +157 -0
  20. package/dashboard-settings-ui.d.ts +1 -0
  21. package/dashboard-settings-ui.js +78 -0
  22. package/dashboard.d.ts +84 -11
  23. package/dashboard.js +1654 -384
  24. package/ddl.d.ts +5 -1
  25. package/ddl.js +760 -9
  26. package/event-feed.d.ts +1 -1
  27. package/event-feed.js +18 -0
  28. package/holiday-import.d.ts +38 -0
  29. package/holiday-import.js +57 -0
  30. package/holidays.d.ts +38 -0
  31. package/holidays.js +150 -0
  32. package/hours.d.ts +125 -0
  33. package/hours.js +224 -0
  34. package/index.d.ts +14 -3
  35. package/index.js +10 -2
  36. package/interop.d.ts +25 -7
  37. package/interop.js +145 -17
  38. package/lifecycle.d.ts +31 -0
  39. package/lifecycle.js +115 -0
  40. package/maintenance.d.ts +19 -0
  41. package/maintenance.js +55 -1
  42. package/metrics.js +3 -0
  43. package/openapi.json +16237 -7795
  44. package/package.json +1 -1
  45. package/ranges.d.ts +28 -0
  46. package/ranges.js +135 -0
  47. package/resource-search.d.ts +19 -0
  48. package/resource-search.js +153 -0
  49. package/rest-api.js +7 -0
  50. package/rules.d.ts +2 -0
  51. package/rules.js +2 -0
  52. package/self-host.d.ts +1 -1
  53. package/self-host.js +221 -21
  54. package/slotlock.manifest.json +1 -1
  55. package/store.d.ts +367 -24
  56. package/store.js +1567 -97
  57. package/types.d.ts +198 -0
package/approvals.d.ts CHANGED
@@ -23,6 +23,8 @@ export interface PutApprovalPolicyParams {
23
23
  outcome: SlotlockApprovalPolicy['outcome'];
24
24
  approvalTtlMinutes?: number;
25
25
  leadMinutes?: number;
26
+ /** It also holds the tenant's own bookings through the application API (default false). */
27
+ includeApplication?: boolean;
26
28
  }
27
29
  /** Create or replace a policy by its reference. Throws `invalid_approval_policy`; at most 50. */
28
30
  export declare function putApprovalPolicy(sql: StoreSql, params: PutApprovalPolicyParams): Promise<SlotlockApprovalPolicy | null>;
package/approvals.js CHANGED
@@ -18,7 +18,7 @@ export class SlotlockApprovalPolicyError extends Error {
18
18
  super(`Slotlock approval policy: ${message}`);
19
19
  }
20
20
  }
21
- const POLICY_COLUMNS = 'ref, name, applies_to, conditions, outcome, approval_ttl_minutes, lead_minutes, created_at, updated_at';
21
+ const POLICY_COLUMNS = 'ref, name, applies_to, conditions, outcome, approval_ttl_minutes, lead_minutes, include_application, created_at, updated_at';
22
22
  function toPolicy(row) {
23
23
  const applies = row.applies_to;
24
24
  const conditions = row.conditions;
@@ -39,6 +39,7 @@ function toPolicy(row) {
39
39
  outcome: row.outcome,
40
40
  approvalTtlMinutes: row.approval_ttl_minutes,
41
41
  leadMinutes: row.lead_minutes,
42
+ includeApplication: row.include_application === true,
42
43
  createdAt: row.created_at,
43
44
  updatedAt: row.updated_at,
44
45
  };
@@ -99,24 +100,30 @@ function storedPolicy(params) {
99
100
  ...(given.outsideHours !== undefined ? { outside_hours: given.outsideHours } : {}),
100
101
  ...(given.kinds !== undefined ? { kinds: [...new Set(given.kinds)].sort() } : {}),
101
102
  };
102
- return { name, appliesTo, conditions, ttl, lead };
103
+ if (params.includeApplication !== undefined && typeof params.includeApplication !== 'boolean') {
104
+ throw new SlotlockApprovalPolicyError('include_application must be true or false');
105
+ }
106
+ return { name, appliesTo, conditions, ttl, lead, includeApplication: params.includeApplication === true };
103
107
  }
104
108
  /** Create or replace a policy by its reference. Throws `invalid_approval_policy`; at most 50. */
105
109
  export async function putApprovalPolicy(sql, params) {
106
110
  const stored = storedPolicy(params);
107
111
  const [row] = await sql `
108
112
  INSERT INTO slotlock.approval_policies (
109
- tenant_ref, ref, name, applies_to, conditions, outcome, approval_ttl_minutes, lead_minutes
113
+ tenant_ref, ref, name, applies_to, conditions, outcome, approval_ttl_minutes, lead_minutes,
114
+ include_application
110
115
  )
111
116
  SELECT ${params.tenantRef}, ${params.ref}, ${stored.name}, ${sql.json(stored.appliesTo)},
112
- ${sql.json(stored.conditions)}, ${params.outcome}, ${stored.ttl}, ${stored.lead}
117
+ ${sql.json(stored.conditions)}, ${params.outcome}, ${stored.ttl}, ${stored.lead},
118
+ ${stored.includeApplication}
113
119
  WHERE EXISTS (SELECT 1 FROM slotlock.approval_policies WHERE tenant_ref = ${params.tenantRef} AND ref = ${params.ref})
114
120
  OR (SELECT count(*) FROM slotlock.approval_policies WHERE tenant_ref = ${params.tenantRef})
115
121
  < ${SLOTLOCK_MAX_APPROVAL_POLICIES}
116
122
  ON CONFLICT (tenant_ref, ref) DO UPDATE
117
123
  SET name = EXCLUDED.name, applies_to = EXCLUDED.applies_to, conditions = EXCLUDED.conditions,
118
124
  outcome = EXCLUDED.outcome, approval_ttl_minutes = EXCLUDED.approval_ttl_minutes,
119
- lead_minutes = EXCLUDED.lead_minutes, updated_at = statement_timestamp()
125
+ lead_minutes = EXCLUDED.lead_minutes, include_application = EXCLUDED.include_application,
126
+ updated_at = statement_timestamp()
120
127
  RETURNING ${sql.unsafe(POLICY_COLUMNS)}`;
121
128
  return row ? toPolicy(row) : null;
122
129
  }
package/assistant.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type SlotlockAgentOperation } from './agent-server.js';
2
- import type { SlotlockAssistantConversationRecord, SlotlockAssistantSettingsRecord, SlotlockStore } from './store.js';
2
+ import type { SlotlockAssistantChatRecord, SlotlockAssistantChatSummary, SlotlockAssistantSettingsRecord, SlotlockStore } from './store.js';
3
3
  export type SlotlockAssistantProvider = 'anthropic' | 'openai';
4
4
  /** What the assistant does with each agent operation: run it, propose it, or never offer it. */
5
5
  export type SlotlockAssistantToolUse = 'read' | 'propose' | 'excluded';
@@ -95,18 +95,27 @@ export interface SlotlockAssistantStore {
95
95
  * still opens, and is opened that way each time.
96
96
  */
97
97
  resealKey?(tenantRef: string, from: string, to: string): Promise<boolean>;
98
- getConversation(tenantRef: string, memberRef: string): Promise<SlotlockAssistantConversationRecord | null>;
98
+ /** A member's conversations, the most recent first: what the Chats list shows. */
99
+ listChats(tenantRef: string, memberRef: string, limit?: number): Promise<SlotlockAssistantChatSummary[]>;
100
+ /** One of a member's conversations, or null when there is none by that id. */
101
+ getChat(tenantRef: string, memberRef: string, chatId: string): Promise<SlotlockAssistantChatRecord | null>;
99
102
  /**
100
- * Replace a member's messages; keep the stored actions `keep` names, as they stand, and add the
101
- * `added` ones not stored already (store.ts putAssistantConversation).
103
+ * Create one of a member's conversations (titled `title`), or replace its messages; keep the
104
+ * stored actions `keep` names, as they stand, and add the `added` ones not stored already
105
+ * (store.ts putAssistantChat). Kept until deleted, or for `ttlMs` when given. With `create`
106
+ * false, only one still there is changed (false when it was deleted meanwhile).
102
107
  */
103
- putConversation(tenantRef: string, memberRef: string, conversation: {
108
+ putChat(tenantRef: string, memberRef: string, chatId: string, chat: {
109
+ title: string;
104
110
  messages: unknown[];
105
111
  added: Record<string, Record<string, unknown>>;
106
112
  keep: readonly string[];
107
- }, ttlMs: number): Promise<void>;
108
- settleAction(tenantRef: string, memberRef: string, actionId: string, from: string, patch: Record<string, unknown>): Promise<Record<string, unknown> | null>;
109
- deleteConversation(tenantRef: string, memberRef: string): Promise<boolean>;
113
+ create?: boolean;
114
+ }, ttlMs: number | null): Promise<boolean>;
115
+ /** Give a conversation a new title (1-120 characters). False when there is none. */
116
+ renameChat(tenantRef: string, memberRef: string, chatId: string, title: string): Promise<boolean>;
117
+ settleAction(tenantRef: string, memberRef: string, chatId: string, actionId: string, from: string, patch: Record<string, unknown>): Promise<Record<string, unknown> | null>;
118
+ deleteChat(tenantRef: string, memberRef: string, chatId: string): Promise<boolean>;
110
119
  }
111
120
  /** The assistant's records in the store, each in a transaction under the tenant's row-level security. */
112
121
  export declare function createSlotlockAssistantStore(store: SlotlockStore): SlotlockAssistantStore;
@@ -191,6 +200,11 @@ interface ModelRequest {
191
200
  force?: string;
192
201
  fetch: typeof fetch;
193
202
  signal: AbortSignal;
203
+ /**
204
+ * Stream the answer: called with each piece of its text as it arrives, in order; the pieces join
205
+ * to the answer's text. Without it the call asks for one JSON answer.
206
+ */
207
+ onText?: (delta: string) => void;
194
208
  }
195
209
  /** One model call. Throws SlotlockAssistantProviderError, whose message never holds the key. */
196
210
  export declare function callSlotlockAssistantModel(request: ModelRequest): Promise<SlotlockAssistantModelTurn>;
@@ -257,7 +271,30 @@ export interface SlotlockAssistantTurnOptions {
257
271
  ok: false;
258
272
  code: string;
259
273
  }>;
274
+ /** Stream the turn: each model call's text as it arrives, and each tool call before and after it runs. */
275
+ onEvent?: (event: SlotlockAssistantTurnEvent) => void;
276
+ /** The member's Stop: the turn ends at once, keeping what it has, and says it stopped. */
277
+ signal?: AbortSignal;
260
278
  }
279
+ /**
280
+ * What a streamed turn tells as it goes, in order: text as the model writes it; then each tool call
281
+ * (`read` runs, `write` is proposed, `unknown` is refused) before it runs, and its result after.
282
+ */
283
+ export type SlotlockAssistantTurnEvent = {
284
+ type: 'text';
285
+ delta: string;
286
+ } | {
287
+ type: 'call';
288
+ id: string;
289
+ name: string;
290
+ use: 'read' | 'write' | 'unknown';
291
+ } | {
292
+ type: 'result';
293
+ id: string;
294
+ name: string;
295
+ ok: boolean;
296
+ actionId?: string;
297
+ };
261
298
  export interface SlotlockAssistantTurnResult {
262
299
  /** The entries this turn added. */
263
300
  added: SlotlockAssistantEntry[];
@@ -267,7 +304,8 @@ export interface SlotlockAssistantTurnResult {
267
304
  /**
268
305
  * One member message: call the model, run what it reads, keep what it would write, and call it
269
306
  * again with the results, up to `maxSteps` calls. Only the offered tools run, whatever the model
270
- * names; everything else is answered `tool_not_available`.
307
+ * names; everything else is answered `tool_not_available`. With `onEvent` each model call streams;
308
+ * once `signal` aborts, no model call or tool goes on, and every call kept has its result.
271
309
  */
272
310
  export declare function runSlotlockAssistantTurn(options: SlotlockAssistantTurnOptions): Promise<SlotlockAssistantTurnResult>;
273
311
  /**
@@ -284,11 +322,28 @@ export declare function pruneSlotlockAssistantActions(entries: readonly Slotlock
284
322
  export declare function pendingSlotlockAssistantActions(actions: Readonly<Record<string, Record<string, unknown>>>): number;
285
323
  /** Read stored entries back, dropping anything malformed. */
286
324
  export declare function readSlotlockAssistantEntries(value: unknown): SlotlockAssistantEntry[];
325
+ /** A resource as the assistant's system prompt lists it: its id, and what people call it. */
326
+ export interface SlotlockAssistantResourceEntry {
327
+ id: string;
328
+ name?: string;
329
+ ref?: string;
330
+ kind?: string;
331
+ location?: string;
332
+ timezone: string;
333
+ }
334
+ /** How many of the workspace's resources the system prompt lists; the rest are found by search. */
335
+ export declare const SLOTLOCK_ASSISTANT_DIRECTORY_SIZE = 50;
287
336
  /** The system prompt: who the model works for, and that what it reads is data, not instructions. */
288
337
  export declare function slotlockAssistantSystemPrompt(args: {
289
338
  brand: string;
290
339
  workspace: string;
291
340
  login: string;
292
341
  now: Date;
342
+ /**
343
+ * The workspace's resources agents may see, so a resource the member names is matched to its id
344
+ * without a lookup; `resourceCount` is how many there are when that is more than are listed.
345
+ */
346
+ resources?: readonly SlotlockAssistantResourceEntry[];
347
+ resourceCount?: number;
293
348
  }): string;
294
349
  export {};