slotlock 0.8.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.
- package/CHANGELOG.md +237 -0
- package/agent-server.d.ts +25 -5
- package/agent-server.js +152 -7
- package/agent-store-backend.d.ts +6 -1
- package/agent-store-backend.js +861 -55
- package/application-api.d.ts +19 -1
- package/application-api.js +578 -21
- package/approvals.d.ts +2 -0
- package/approvals.js +12 -5
- package/assistant.d.ts +34 -8
- package/assistant.js +81 -26
- package/availability-store.d.ts +193 -0
- package/availability-store.js +558 -0
- package/booking-rules.d.ts +92 -5
- package/booking-rules.js +243 -15
- package/dashboard-assistant-ui.d.ts +1 -1
- package/dashboard-assistant-ui.js +109 -8
- package/dashboard.d.ts +55 -6
- package/dashboard.js +790 -105
- package/ddl.d.ts +5 -1
- package/ddl.js +760 -9
- package/event-feed.d.ts +1 -1
- package/event-feed.js +18 -0
- package/holiday-import.d.ts +38 -0
- package/holiday-import.js +57 -0
- package/holidays.d.ts +38 -0
- package/holidays.js +150 -0
- package/hours.d.ts +125 -0
- package/hours.js +224 -0
- package/index.d.ts +14 -3
- package/index.js +10 -2
- package/interop.d.ts +25 -7
- package/interop.js +145 -17
- package/lifecycle.d.ts +31 -0
- package/lifecycle.js +115 -0
- package/maintenance.d.ts +19 -0
- package/maintenance.js +55 -1
- package/metrics.js +3 -0
- package/openapi.json +16237 -7795
- package/package.json +1 -1
- package/ranges.d.ts +28 -0
- package/ranges.js +135 -0
- package/resource-search.d.ts +19 -0
- package/resource-search.js +153 -0
- package/rest-api.js +7 -0
- package/rules.d.ts +2 -0
- package/rules.js +2 -0
- package/self-host.d.ts +1 -1
- package/self-host.js +221 -21
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +367 -24
- package/store.js +1567 -97
- package/types.d.ts +198 -0
|
@@ -6,6 +6,7 @@ const STEP_WORDS = Object.freeze({
|
|
|
6
6
|
slotlock_get_free_busy: ['Checking free and busy time', 'Checked free and busy time', 'check free and busy time'],
|
|
7
7
|
slotlock_find_next_available: ['Finding the next free time', 'Found the next free time', 'find the next free time'],
|
|
8
8
|
slotlock_find_available_slots: ['Checking availability', 'Checked availability', 'check availability'],
|
|
9
|
+
slotlock_find_available_ranges: ['Finding free ranges', 'Found free ranges', 'find free ranges'],
|
|
9
10
|
slotlock_get_pool_availability: ['Checking the pool', 'Checked the pool', 'check the pool'],
|
|
10
11
|
slotlock_get_event: ['Reading a booking', 'Read a booking', 'read the booking'],
|
|
11
12
|
slotlock_list_events: ['Reading bookings', 'Read bookings', 'read bookings'],
|
|
@@ -43,6 +44,7 @@ export function slotlockAssistantActionVerb(operation) {
|
|
|
43
44
|
case 'slotlock_delete_event':
|
|
44
45
|
return { verb: 'Delete booking', busy: 'Deleting…', done: 'Deleted', danger: true };
|
|
45
46
|
case 'slotlock_hold_slots':
|
|
47
|
+
case 'slotlock_hold_together':
|
|
46
48
|
return { verb: 'Hold', busy: 'Holding…', done: 'Held', danger: false };
|
|
47
49
|
case 'slotlock_release_slots':
|
|
48
50
|
return { verb: 'Release', busy: 'Releasing…', done: 'Released', danger: true };
|
|
@@ -159,8 +161,59 @@ button.ghost:hover { background: var(--hover); color: var(--ink); }
|
|
|
159
161
|
.assistant.streaming .send .stop { display: block; }
|
|
160
162
|
.jump { position: absolute; left: 50%; transform: translate(-50%, 0); top: -8px; width: 32px; height: 32px; padding: 0; border-radius: 50%; border: 1px solid var(--line); background: var(--surface); color: var(--ink-2); display: grid; place-items: center; cursor: pointer; box-shadow: var(--pop-shadow); }
|
|
161
163
|
.jump[hidden] { display: none; }
|
|
162
|
-
.new-chat svg { width: 14px; height: 14px; }
|
|
164
|
+
.new-chat svg, .chats-link svg { width: 14px; height: 14px; }
|
|
163
165
|
.assistant-page[data-empty] .new-chat { display: none; }
|
|
166
|
+
.assistant-actions { display: flex; gap: var(--space-2); }
|
|
167
|
+
.chats-link { display: none; }
|
|
168
|
+
/* Chats: the member's conversations beside the one shown, most recent first, grouped by day. */
|
|
169
|
+
.assistant-shell { display: grid; grid-template-columns: 252px minmax(0, 1fr); gap: var(--space-6); align-items: start; }
|
|
170
|
+
/* It scrolls with the page: a column that scrolled on its own would clip a row's menu. */
|
|
171
|
+
.chats { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
|
|
172
|
+
.chats-head { display: flex; align-items: center; justify-content: space-between; min-height: 36px; }
|
|
173
|
+
.chats-head h2 { margin: 0; font-size: var(--text-md); font-weight: 600; color: var(--ink-2); }
|
|
174
|
+
.icon-button { display: grid; place-items: center; width: 30px; height: 30px; border-radius: var(--radius-sm); color: var(--ink-2); transition: background var(--fast) var(--ease), color var(--fast) var(--ease); }
|
|
175
|
+
.icon-button:hover { background: var(--hover); color: var(--ink); text-decoration: none; }
|
|
176
|
+
.icon-button svg { width: 15px; height: 15px; }
|
|
177
|
+
.chats-filter { height: 32px; font-size: var(--text-md); }
|
|
178
|
+
.chats-filter[hidden] { display: none; }
|
|
179
|
+
/* Sentence case, not the dashboard's uppercase h3. */
|
|
180
|
+
.chat-group { margin: var(--space-3) 0 2px; padding: 0 var(--space-2); font-family: inherit; font-size: var(--text-xs); font-weight: 500; line-height: 1.5; text-transform: none; letter-spacing: 0; color: var(--ink-3); }
|
|
181
|
+
.chat-group[hidden] { display: none; }
|
|
182
|
+
.chat-list { min-width: 0; }
|
|
183
|
+
/* minmax(0, 1fr): a long title is cut with an ellipsis, never widening the column. */
|
|
184
|
+
.chat-rows { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 1px; }
|
|
185
|
+
.chat-row { position: relative; display: flex; align-items: center; border-radius: var(--radius); transition: background var(--fast) var(--ease); }
|
|
186
|
+
.chat-row[hidden] { display: none; }
|
|
187
|
+
.chat-row:hover, .chat-row:focus-within, .chat-row.current { background: var(--hover); }
|
|
188
|
+
.chat-link { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: var(--space-2); padding: 7px 4px 7px var(--space-2); color: var(--ink-2); font-size: var(--text-md); }
|
|
189
|
+
.chat-link:hover { color: var(--ink); text-decoration: none; }
|
|
190
|
+
.chat-row.current .chat-link { color: var(--ink); font-weight: 500; }
|
|
191
|
+
.chat-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
192
|
+
.chat-when { flex: none; font-size: var(--text-xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }
|
|
193
|
+
.chat-menu { position: relative; flex: none; }
|
|
194
|
+
.chat-menu summary { list-style: none; display: grid; place-items: center; width: 28px; height: 28px; margin-right: 2px; border-radius: var(--radius-sm); color: var(--ink-3); cursor: pointer; opacity: 0; transition: opacity var(--fast) var(--ease), background var(--fast) var(--ease); }
|
|
195
|
+
.chat-menu summary::-webkit-details-marker { display: none; }
|
|
196
|
+
.chat-row:hover .chat-menu summary, .chat-row:focus-within .chat-menu summary, .chat-row.current .chat-menu summary, .chat-menu[open] summary { opacity: 1; }
|
|
197
|
+
.chat-menu summary:hover, .chat-menu[open] summary { background: var(--line-soft); color: var(--ink); }
|
|
198
|
+
.chat-menu summary svg { width: 14px; height: 14px; }
|
|
199
|
+
.chat-menu .pop { z-index: 20; width: 260px; display: grid; gap: var(--space-2); }
|
|
200
|
+
.chat-menu .pop form { display: grid; gap: var(--space-2); margin: 0; }
|
|
201
|
+
.chat-menu .pop form + form { padding-top: var(--space-2); border-top: 1px solid var(--line-soft); }
|
|
202
|
+
.chat-menu .pop button.danger { background: none; border-color: transparent; box-shadow: none; color: var(--bad); }
|
|
203
|
+
.chat-menu .pop button.danger:hover { background: var(--bad-wash); }
|
|
204
|
+
.chat-rename input { width: 100%; min-width: 0; height: 34px; }
|
|
205
|
+
.chats-empty { padding: var(--space-2); font-size: var(--text-md); }
|
|
206
|
+
.chats-page .chats { max-width: 560px; }
|
|
207
|
+
.chats-page .chats-head { display: none; }
|
|
208
|
+
.chats-page .chat-menu summary { opacity: 1; }
|
|
209
|
+
@media (max-width: 1100px) {
|
|
210
|
+
.assistant-shell { grid-template-columns: minmax(0, 1fr); }
|
|
211
|
+
.assistant-shell > .chats { display: none; }
|
|
212
|
+
.chats-link { display: inline-flex; }
|
|
213
|
+
}
|
|
214
|
+
@media (hover: none) {
|
|
215
|
+
.chat-menu summary { opacity: 1; }
|
|
216
|
+
}
|
|
164
217
|
@media (prefers-reduced-motion: reduce) {
|
|
165
218
|
.live-steps .step.run, .thinking { animation: none; color: var(--ink-3); background: none; }
|
|
166
219
|
.prose.streaming > :last-child::after { animation: none; }
|
|
@@ -198,6 +251,29 @@ button.ghost:hover { background: var(--hover); color: var(--ink); }
|
|
|
198
251
|
*/
|
|
199
252
|
export function slotlockAssistantScript(markdownSource) {
|
|
200
253
|
return `
|
|
254
|
+
// Search chats: rows whose title holds what is typed (ignoring case and accents) stay shown.
|
|
255
|
+
function filterChats() {
|
|
256
|
+
var filter = document.getElementById('chats-filter');
|
|
257
|
+
if (!filter) return;
|
|
258
|
+
filter.hidden = false;
|
|
259
|
+
var fold = function (text) { return text.normalize('NFKD').replace(/\\p{M}/gu, '').toLowerCase(); };
|
|
260
|
+
var apply = function () {
|
|
261
|
+
var wanted = fold(filter.value.trim());
|
|
262
|
+
for (var group of document.querySelectorAll('.chat-rows')) {
|
|
263
|
+
var shown = 0;
|
|
264
|
+
for (var row of group.children) {
|
|
265
|
+
var title = row.querySelector('.chat-title');
|
|
266
|
+
row.hidden = wanted !== '' && fold(title ? title.textContent : '').indexOf(wanted) === -1;
|
|
267
|
+
if (!row.hidden) shown += 1;
|
|
268
|
+
}
|
|
269
|
+
var label = group.previousElementSibling;
|
|
270
|
+
if (label && label.classList.contains('chat-group')) label.hidden = shown === 0;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
filter.addEventListener('input', apply);
|
|
274
|
+
apply();
|
|
275
|
+
}
|
|
276
|
+
filterChats();
|
|
201
277
|
(function () {
|
|
202
278
|
var page = document.querySelector('[data-assistant]');
|
|
203
279
|
if (!page || typeof fetch !== 'function' || typeof TextDecoder !== 'function') return;
|
|
@@ -267,14 +343,20 @@ export function slotlockAssistantScript(markdownSource) {
|
|
|
267
343
|
event.preventDefault();
|
|
268
344
|
if (!busy && box.value.trim() !== '') form.requestSubmit();
|
|
269
345
|
});
|
|
270
|
-
|
|
346
|
+
// A chip that asks sends through the box, so its reply streams; one that writes only fills it.
|
|
347
|
+
for (var chip of page.querySelectorAll('.chips button.chip')) {
|
|
271
348
|
chip.addEventListener('click', function (event) {
|
|
272
349
|
event.preventDefault();
|
|
273
|
-
|
|
350
|
+
var fill = this.getAttribute('data-fill');
|
|
351
|
+
box.value = fill !== null ? fill : (this.value || '');
|
|
274
352
|
grow();
|
|
275
353
|
update();
|
|
276
|
-
|
|
277
|
-
|
|
354
|
+
if (fill !== null) {
|
|
355
|
+
box.focus();
|
|
356
|
+
box.setSelectionRange(box.value.length, box.value.length);
|
|
357
|
+
} else if (!busy && box.value.trim() !== '') {
|
|
358
|
+
form.requestSubmit();
|
|
359
|
+
}
|
|
278
360
|
});
|
|
279
361
|
}
|
|
280
362
|
for (var decide of document.querySelectorAll('.change-buttons form')) {
|
|
@@ -323,7 +405,8 @@ export function slotlockAssistantScript(markdownSource) {
|
|
|
323
405
|
var prose = null;
|
|
324
406
|
var proseText = '';
|
|
325
407
|
var steps = null;
|
|
326
|
-
|
|
408
|
+
// Keyed by the provider's call id: no id, __proto__ included, may name an inherited property.
|
|
409
|
+
var stepNodes = Object.create(null);
|
|
327
410
|
var frame = 0;
|
|
328
411
|
follow(was);
|
|
329
412
|
function draw() {
|
|
@@ -374,6 +457,18 @@ export function slotlockAssistantScript(markdownSource) {
|
|
|
374
457
|
added = [];
|
|
375
458
|
thread.insertAdjacentHTML('beforeend', e.html || '');
|
|
376
459
|
setOnce(e.once);
|
|
460
|
+
// The conversation it went to: a new one has its id now, its own address, and a place in Chats.
|
|
461
|
+
if (e.chat) {
|
|
462
|
+
for (var field of page.querySelectorAll('input[name="chat"]')) field.value = e.chat;
|
|
463
|
+
if (e.url && window.history && history.replaceState) history.replaceState(null, '', e.url);
|
|
464
|
+
var heading = document.querySelector('.assistant-page .page-head h1');
|
|
465
|
+
if (heading && e.title) heading.textContent = e.title;
|
|
466
|
+
var panel = document.querySelector('aside.chats');
|
|
467
|
+
if (panel && e.chats) {
|
|
468
|
+
panel.outerHTML = e.chats;
|
|
469
|
+
filterChats();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
377
472
|
}
|
|
378
473
|
follow(was);
|
|
379
474
|
}
|
|
@@ -386,7 +481,8 @@ export function slotlockAssistantScript(markdownSource) {
|
|
|
386
481
|
}).then(function (response) {
|
|
387
482
|
var type = response.headers.get('content-type') || '';
|
|
388
483
|
if (!response.body || type.indexOf('application/x-ndjson') !== 0) {
|
|
389
|
-
|
|
484
|
+
var current = form.querySelector('input[name="chat"]');
|
|
485
|
+
location.assign(page.getAttribute('data-page') + (current && current.value ? '?c=' + current.value : '') + '#latest');
|
|
390
486
|
location.reload();
|
|
391
487
|
return;
|
|
392
488
|
}
|
|
@@ -432,7 +528,12 @@ export function slotlockAssistantScript(markdownSource) {
|
|
|
432
528
|
});
|
|
433
529
|
grow();
|
|
434
530
|
update();
|
|
435
|
-
|
|
531
|
+
// To the latest message only when it would be behind the box: a short conversation keeps its
|
|
532
|
+
// heading (and Chats) in view.
|
|
533
|
+
var dockAtLoad = page.querySelector('.dock');
|
|
534
|
+
var lastAtLoad = thread.lastElementChild;
|
|
535
|
+
var behind = !!lastAtLoad && lastAtLoad.getBoundingClientRect().bottom > window.innerHeight - (dockAtLoad ? dockAtLoad.offsetHeight : 0);
|
|
536
|
+
if (!page.hasAttribute('data-empty') && behind) window.scrollTo(0, document.documentElement.scrollHeight);
|
|
436
537
|
})();
|
|
437
538
|
`;
|
|
438
539
|
}
|
package/dashboard.d.ts
CHANGED
|
@@ -9,8 +9,11 @@ import { type SlotlockLocation, type SlotlockResourceGroup, type SlotlockResourc
|
|
|
9
9
|
import { type SlotlockUsageRow } from './usage.js';
|
|
10
10
|
import { type CreateWebhookEndpointResult, type SlotlockChange, type SlotlockWebhookEndpoint } from './event-feed.js';
|
|
11
11
|
import type { SlotlockSql, SlotlockStore, SlotlockWaitlistEntry } from './store.js';
|
|
12
|
+
import type { SlotlockAvailabilityDefaults, SlotlockAvailabilityExceptionInput, SlotlockHolidayCalendar, SlotlockOverrideResources } from './availability-store.js';
|
|
13
|
+
import { type SlotlockAvailabilityException } from './hours.js';
|
|
14
|
+
import { type SlotlockHolidaySource } from './holiday-import.js';
|
|
12
15
|
import { type SlotlockSandboxScenario } from './sandbox.js';
|
|
13
|
-
import type { Interval, SlotlockAgendaEntry, SlotlockApproval, SlotlockResource, SlotlockBookingRuleDefaults, WeeklyAvailabilityRule } from './types.js';
|
|
16
|
+
import type { Interval, SlotlockAgendaEntry, SlotlockApproval, SlotlockContact, SlotlockResource, SlotlockBookingRuleDefaults, WeeklyAvailabilityRule } from './types.js';
|
|
14
17
|
/** Resources one dashboard tenant may hold. */
|
|
15
18
|
export declare const SLOTLOCK_DASHBOARD_MAX_RESOURCES = 500;
|
|
16
19
|
/**
|
|
@@ -30,13 +33,51 @@ export interface SlotlockDashboardResources {
|
|
|
30
33
|
*/
|
|
31
34
|
add(tenantRef: string, externalRef: string, timezone: string, actor?: string): Promise<SlotlockResource>;
|
|
32
35
|
/**
|
|
33
|
-
* Set the bookable hours of one of the tenant's resources (`{ id }`)
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
+
* Set the bookable hours of one of the tenant's resources (`{ id }`), one of its locations
|
|
37
|
+
* (`{ location }`), or the tenant's own (`'all'`), which every resource without its own inherits
|
|
38
|
+
* (RFC 0004 B2); `null` hands them to what is above and `[]` closes. For the tenant's, `override`
|
|
39
|
+
* says what becomes of resources' own hours (SlotlockOverrideResources: `none` keeps them).
|
|
40
|
+
* Returns 0 for an id or location that is not the tenant's, else at least 1 (for the tenant's, how
|
|
41
|
+
* many resources they now apply to).
|
|
36
42
|
*/
|
|
37
43
|
setHours(tenantRef: string, target: {
|
|
38
44
|
id: string;
|
|
39
|
-
} |
|
|
45
|
+
} | {
|
|
46
|
+
location: string;
|
|
47
|
+
} | 'all', rules: WeeklyAvailabilityRule[] | null, actor?: string, override?: SlotlockOverrideResources): Promise<number>;
|
|
48
|
+
/** The people the tenant keeps to tell about bookings (RFC 0004 P3), and forgetting one. */
|
|
49
|
+
contacts?: {
|
|
50
|
+
list(tenantRef: string, limit: number, after?: string): Promise<SlotlockContact[]>;
|
|
51
|
+
forget(tenantRef: string, ref: string, actor?: string): Promise<{
|
|
52
|
+
deleted: boolean;
|
|
53
|
+
bookings: number;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
/** The tenant's stored hours and its closures and special hours (RFC 0004 B1, B2). */
|
|
57
|
+
hours?: {
|
|
58
|
+
defaults(tenantRef: string): Promise<SlotlockAvailabilityDefaults>;
|
|
59
|
+
exceptions(tenantRef: string, limit: number): Promise<SlotlockAvailabilityException[]>;
|
|
60
|
+
addException(tenantRef: string, exception: Omit<SlotlockAvailabilityExceptionInput, 'tenantRef'>, actor?: string): Promise<SlotlockAvailabilityException>;
|
|
61
|
+
deleteException(tenantRef: string, id: string, actor?: string): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Import a holiday calendar (its text, or a URL with `byUrl`): each all-day event closes its
|
|
64
|
+
* days for the next two years. Kept by name at its scope, so importing it again updates it and
|
|
65
|
+
* removes the holidays it dropped; `subscribe` imports a URL again every 30 days.
|
|
66
|
+
*/
|
|
67
|
+
importHolidays(tenantRef: string, source: SlotlockHolidaySource, scope: {
|
|
68
|
+
location?: string;
|
|
69
|
+
resourceId?: string;
|
|
70
|
+
}, actor?: string): Promise<{
|
|
71
|
+
imported: number;
|
|
72
|
+
skipped: number;
|
|
73
|
+
removed: number;
|
|
74
|
+
}>;
|
|
75
|
+
/** Whether importHolidays reads calendars by URL. */
|
|
76
|
+
byUrl?: boolean;
|
|
77
|
+
/** The tenant's holiday calendars, and deleting one with its closures (false: not the tenant's). */
|
|
78
|
+
calendars?(tenantRef: string): Promise<SlotlockHolidayCalendar[]>;
|
|
79
|
+
deleteCalendar?(tenantRef: string, id: string, actor?: string): Promise<boolean>;
|
|
80
|
+
};
|
|
40
81
|
/** What occupies the tenant's resources in `window`, by start: the overview's agenda. */
|
|
41
82
|
agenda?(tenantRef: string, window: Interval, limit: number): Promise<SlotlockAgendaEntry[]>;
|
|
42
83
|
/** The tenant's latest changes, newest first: the overview's activity. */
|
|
@@ -229,7 +270,10 @@ export interface SlotlockDashboardAssistantOptions {
|
|
|
229
270
|
turnTimeoutMs?: number;
|
|
230
271
|
/** Model calls one message may make (8 by default, at most 20). */
|
|
231
272
|
maxSteps?: number;
|
|
232
|
-
/**
|
|
273
|
+
/**
|
|
274
|
+
* How long a conversation is kept after its last message (1 second to 3,650 days). Without it, a
|
|
275
|
+
* member keeps each conversation until they delete it, up to SLOTLOCK_ASSISTANT_MAX_CHATS.
|
|
276
|
+
*/
|
|
233
277
|
conversationTtlMs?: number;
|
|
234
278
|
}
|
|
235
279
|
/**
|
|
@@ -424,6 +468,11 @@ export declare function createSlotlockDashboardResources(store: SlotlockStore, o
|
|
|
424
468
|
* through: with it, the dashboard lists approvals and decides them.
|
|
425
469
|
*/
|
|
426
470
|
agentBackend?: SlotlockAgentCalendarBackend;
|
|
471
|
+
/**
|
|
472
|
+
* Fetches holiday calendars by URL (the guarded fetch under `serve`): with it, Closures imports
|
|
473
|
+
* and subscribes to a calendar's address.
|
|
474
|
+
*/
|
|
475
|
+
holidayFetch?: typeof fetch;
|
|
427
476
|
}): SlotlockDashboardResources;
|
|
428
477
|
/**
|
|
429
478
|
* Records accepted write confirmations (`SlotlockAgentConfirmationOptions.consumeAcceptance`) in
|