slotlock 0.5.0 → 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 +37 -0
- package/README.md +55 -1
- package/agent-server.d.ts +14 -0
- package/agent-server.js +15 -0
- package/assistant.d.ts +263 -0
- package/assistant.js +771 -0
- package/config.d.ts +10 -0
- package/config.js +11 -0
- package/dashboard.d.ts +61 -0
- package/dashboard.js +589 -11
- package/ddl.d.ts +5 -5
- package/ddl.js +202 -4
- package/event-feed.js +20 -8
- package/index.d.ts +4 -2
- package/index.js +2 -1
- package/maintenance.d.ts +2 -1
- package/maintenance.js +22 -0
- package/package.json +1 -1
- package/rate-limit.d.ts +8 -0
- package/rate-limit.js +20 -0
- package/self-host.d.ts +2 -2
- package/self-host.js +34 -3
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +101 -1
- package/store.js +158 -0
- package/webhooks.d.ts +24 -0
- package/webhooks.js +96 -19
package/config.d.ts
CHANGED
|
@@ -36,6 +36,16 @@ export interface SlotlockServeDashboardConfig {
|
|
|
36
36
|
organizations?: string[];
|
|
37
37
|
/** What the deployment is called in the dashboard (SLOTLOCK_DASHBOARD_NAME). */
|
|
38
38
|
name?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Seals each tenant's model API key for the dashboard assistant (SLOTLOCK_ASSISTANT_SECRET).
|
|
41
|
+
* Without it the assistant is off.
|
|
42
|
+
*/
|
|
43
|
+
assistantSecret?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Let the assistant reach a model at a local or private network address
|
|
46
|
+
* (SLOTLOCK_ASSISTANT_PRIVATE_URLS=on): a self-hosted model on the same machine or network.
|
|
47
|
+
*/
|
|
48
|
+
assistantPrivateUrls?: boolean;
|
|
39
49
|
}
|
|
40
50
|
/**
|
|
41
51
|
* The dashboard's settings, or undefined when none is set. Setting any of them means serving the
|
package/config.js
CHANGED
|
@@ -134,6 +134,15 @@ export function readDashboardConfig(env) {
|
|
|
134
134
|
if (users === undefined || (users !== '*' && !/^[1-9]\d{0,19}(,[1-9]\d{0,19})*$/.test(users))) {
|
|
135
135
|
throw new SlotlockConfigError('SLOTLOCK_DASHBOARD_USERS is required to serve the dashboard: * for every GitHub account, or a comma-separated list of GitHub user ids (gh api users/<login> --jq .id)');
|
|
136
136
|
}
|
|
137
|
+
const assistantSecret = readSecret(env, 'SLOTLOCK_ASSISTANT_SECRET');
|
|
138
|
+
if (assistantSecret !== undefined &&
|
|
139
|
+
[sessionSecret, ...others].includes(assistantSecret)) {
|
|
140
|
+
throw new SlotlockConfigError('SLOTLOCK_ASSISTANT_SECRET must differ from every other secret');
|
|
141
|
+
}
|
|
142
|
+
const privateUrls = optional(env, 'SLOTLOCK_ASSISTANT_PRIVATE_URLS') ?? 'off';
|
|
143
|
+
if (privateUrls !== 'on' && privateUrls !== 'off') {
|
|
144
|
+
throw new SlotlockConfigError('SLOTLOCK_ASSISTANT_PRIVATE_URLS must be on or off');
|
|
145
|
+
}
|
|
137
146
|
const orgs = readDashboardOrganizations(env);
|
|
138
147
|
const name = optional(env, 'SLOTLOCK_DASHBOARD_NAME')?.trim();
|
|
139
148
|
if (name !== undefined && name !== '' && !SLOTLOCK_DASHBOARD_NAME_PATTERN.test(name)) {
|
|
@@ -146,6 +155,8 @@ export function readDashboardConfig(env) {
|
|
|
146
155
|
allowedUsers: users === '*' ? '*' : [...new Set(users.split(','))],
|
|
147
156
|
...(orgs !== undefined ? { organizations: orgs } : {}),
|
|
148
157
|
...(name !== undefined && name !== '' ? { name } : {}),
|
|
158
|
+
...(assistantSecret !== undefined ? { assistantSecret } : {}),
|
|
159
|
+
...(assistantSecret !== undefined && privateUrls === 'on' ? { assistantPrivateUrls: true } : {}),
|
|
149
160
|
};
|
|
150
161
|
}
|
|
151
162
|
/**
|
package/dashboard.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { SlotlockApiKeyStore } from './api-keys.js';
|
|
2
|
+
import { type SlotlockAgentCalendarBackend, type SlotlockAgentOperation, type SlotlockAgentOperationDispatchOptions, type SlotlockAgentPrincipal } from './agent-server.js';
|
|
3
|
+
import { type SlotlockAssistantStore } from './assistant.js';
|
|
4
|
+
import { type SlotlockRate } from './config.js';
|
|
2
5
|
import { type SlotlockBookingRules } from './booking-rules.js';
|
|
3
6
|
import { type CreateWebhookEndpointResult, type SlotlockChange, type SlotlockWebhookEndpoint } from './event-feed.js';
|
|
4
7
|
import type { SlotlockSql, SlotlockStore, SlotlockWaitlistEntry } from './store.js';
|
|
@@ -41,6 +44,8 @@ export interface SlotlockDashboardResources {
|
|
|
41
44
|
list(tenantRef: string, limit: number): Promise<SlotlockWaitlistEntry[]>;
|
|
42
45
|
leave(tenantRef: string, id: string, actor?: string): Promise<boolean>;
|
|
43
46
|
};
|
|
47
|
+
/** The tenant's assistant settings and its members' conversations (assistant.ts). */
|
|
48
|
+
assistant?: SlotlockAssistantStore;
|
|
44
49
|
/** The tenant's webhook endpoints, as the event feed keeps them. */
|
|
45
50
|
webhooks?: {
|
|
46
51
|
list(tenantRef: string): Promise<SlotlockWebhookEndpoint[]>;
|
|
@@ -74,6 +79,60 @@ export interface SlotlockDashboardState {
|
|
|
74
79
|
*/
|
|
75
80
|
sessionEnded(sid: string, expiresAt: number): Promise<boolean>;
|
|
76
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* The chat assistant (assistant.ts): each tenant's admins connect a model of their own in Settings,
|
|
84
|
+
* and every member may chat with it. Its reads and approved writes run through the agent operation
|
|
85
|
+
* boundary with `backend`, as the principal `dashboard-assistant:<GitHub login>` of the member's
|
|
86
|
+
* tenant, with `authorize` and `consumeRateLimit` as an agent's are. Needs the resources adapter's
|
|
87
|
+
* `assistant` too.
|
|
88
|
+
*/
|
|
89
|
+
export interface SlotlockDashboardAssistantOptions {
|
|
90
|
+
/** Seals each tenant's API key (SLOTLOCK_ASSISTANT_SECRET): 32+ random characters. */
|
|
91
|
+
secret: string;
|
|
92
|
+
backend: SlotlockAgentCalendarBackend;
|
|
93
|
+
/**
|
|
94
|
+
* As the agent server's: false refuses an operation. Every offered operation by default. It also
|
|
95
|
+
* checks a write the model proposes, so it must hold nothing: limits that hold a slot go in
|
|
96
|
+
* `admit`.
|
|
97
|
+
*/
|
|
98
|
+
authorize?: SlotlockAgentOperationDispatchOptions['authorize'];
|
|
99
|
+
/**
|
|
100
|
+
* Taken when a read runs and when an approved write runs, never for a proposal: the agent
|
|
101
|
+
* request limits' `admit` (serve's series-write slots), which may hold a slot until the request
|
|
102
|
+
* is answered, or throw a SlotlockAgentOperationError to refuse.
|
|
103
|
+
*/
|
|
104
|
+
admit?: (args: {
|
|
105
|
+
principal: SlotlockAgentPrincipal;
|
|
106
|
+
operation: SlotlockAgentOperation;
|
|
107
|
+
input: Record<string, unknown>;
|
|
108
|
+
}) => Promise<void>;
|
|
109
|
+
/** As the agent server's; charged for each read and each approved write, never for a proposal. */
|
|
110
|
+
consumeRateLimit?: SlotlockAgentOperationDispatchOptions['consumeRateLimit'];
|
|
111
|
+
/**
|
|
112
|
+
* Allow a model at a local or private network address (SLOTLOCK_ASSISTANT_PRIVATE_URLS): a
|
|
113
|
+
* base URL whose host is, or resolves to, a loopback, private, link-local, shared, unique-local
|
|
114
|
+
* or metadata address. Off by default: such URLs are refused when saved and at each call.
|
|
115
|
+
*/
|
|
116
|
+
privateUrls?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Reaches the model providers. By default Node.js's http client, which checks every address a
|
|
119
|
+
* host resolves to as the connection is made (createSlotlockGuardedFetch), so a name rebound to
|
|
120
|
+
* a private address is refused too. A replacement must make that check itself, or reach no
|
|
121
|
+
* private network (a Cloudflare Worker's fetch).
|
|
122
|
+
*/
|
|
123
|
+
fetch?: typeof fetch;
|
|
124
|
+
/** The addresses a host resolves to, to refuse a private one when saved: dns.lookup by default. */
|
|
125
|
+
resolve?: (hostname: string) => Promise<string[]>;
|
|
126
|
+
/** Messages and connection tests one member may send: 10 a minute, 10 at once, by default. */
|
|
127
|
+
rate?: SlotlockRate;
|
|
128
|
+
/** One model call's limit (30 s by default) and one message's (90 s). */
|
|
129
|
+
timeoutMs?: number;
|
|
130
|
+
turnTimeoutMs?: number;
|
|
131
|
+
/** Model calls one message may make (8 by default, at most 20). */
|
|
132
|
+
maxSteps?: number;
|
|
133
|
+
/** How long a conversation is kept after its last message: 2 hours by default. */
|
|
134
|
+
conversationTtlMs?: number;
|
|
135
|
+
}
|
|
77
136
|
export interface SlotlockDashboardOptions {
|
|
78
137
|
/** Where people reach the server; the dashboard lives at `<publicUrl>/dashboard`. */
|
|
79
138
|
publicUrl: string;
|
|
@@ -118,6 +177,8 @@ export interface SlotlockDashboardOptions {
|
|
|
118
177
|
state: SlotlockDashboardState;
|
|
119
178
|
/** The server's bookable hours: shown, not edited, and used by resources without their own. */
|
|
120
179
|
availability?: readonly WeeklyAvailabilityRule[];
|
|
180
|
+
/** The chat assistant; off unless set. */
|
|
181
|
+
assistant?: SlotlockDashboardAssistantOptions;
|
|
121
182
|
fetch?: typeof fetch;
|
|
122
183
|
now?: () => number;
|
|
123
184
|
/**
|