slotlock 0.1.0 → 0.2.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/SPEC.md CHANGED
@@ -129,19 +129,29 @@ event, an exhausted quota) is a result, not a protocol error:
129
129
  - A2A returns the agent's reply message with one `application/json` data part holding the result or
130
130
  `{"error":{"code":"…"}}`.
131
131
 
132
- Protocol errors stay in the protocol. MCP: an unknown tool is JSON-RPC `-32602`, a server fault
132
+ A body must be `application/json`, compared without regard to case (RFC 9110 §8.3.1), or the request
133
+ is HTTP 415; one longer than the server's limit is HTTP 413, refused as soon as it passes the limit,
134
+ whether or not it declares a `Content-Length`.
135
+
136
+ Protocol errors stay in the protocol. MCP: an unknown tool, or `arguments` that are not an object
137
+ (left out or null is none), is JSON-RPC `-32602`, a server fault
133
138
  (including backend output that violates the declared schema) is `-32603` with HTTP 500, and a
134
139
  missing or unsupported `MCP-Protocol-Version` header is HTTP 400. A 2026-07-28 request is checked in
135
140
  that revision's order, each failure HTTP 400: a missing or malformed `_meta` field is `-32602`, an
136
141
  `MCP-Protocol-Version`, `Mcp-Method` or (Base64-sentinel-decoded) `Mcp-Name` that disagrees with the
137
142
  body is `-32020`, and an unimplemented revision is `-32022` naming the supported ones. An
138
143
  unimplemented method is HTTP 404 `-32601`; a rate-limited request is HTTP 429 `-31029`, outside the
139
- `-32020`..`-32099` range that revision reserves. A2A: every JSON-RPC error travels
140
- in an HTTP 200 envelope with a `google.rpc.ErrorInfo` detail; an `A2A-Version` other than `1.0`,
141
- including none (read as 0.3), is `VersionNotSupportedError` (`-32009`); push-notification methods are
144
+ `-32020`..`-32099` range that revision reserves. A2A: every JSON-RPC error travels in an HTTP 200
145
+ envelope (a body over the size limit is HTTP 413); A2A's own errors, below, carry a
146
+ `google.rpc.ErrorInfo` detail and JSON-RPC's (`-32700`, `-32600`, `-32601`, `-32603`) none. An
147
+ `A2A-Version` other than `1.0`, including none (read as 0.3), is `VersionNotSupportedError`
148
+ (`-32009`); push-notification methods are
142
149
  `PushNotificationNotSupportedError` (`-32003`); streaming, task subscription and the extended card
143
150
  are `UnsupportedOperationError` (`-32004`); a message that is not exactly one data part
144
- `{"skill": "<id>", "arguments": {…}}` is `-32602`. The server replies with messages and keeps no
151
+ `{"skill": "<id>", "arguments": {…}}` (`arguments` left out or null is none; any other value that is
152
+ not an object is not arguments) is `-32602`, as is a `configuration` or `metadata` that is set but
153
+ is not an object; a rate-limited request is `-32029` with the reason `RATE_LIMITED` and a null id,
154
+ answered before its body is read. The server replies with messages and keeps no
145
155
  tasks, so the core task operations answer as for an unknown task: `GetTask` and `CancelTask` are
146
156
  `TaskNotFoundError` (`-32001`), `ListTasks` returns an empty page (`pageSize` 1-100, default 50).
147
157
  Their params are checked first, as `SendMessage`'s are: a field the method does not define, a
@@ -237,4 +247,7 @@ rejects absolute-form request targets, applies finite header/body/handler and co
237
247
  propagates disconnects through `AbortSignal`, and force-closes only after a bounded graceful-shutdown
238
248
  window. When it closes it first asks the target to end long-lived responses (the agent server's
239
249
  `shutdown()`), and closes keep-alive connections whose response finished during shutdown. It exposes
240
- sanitized transport failures and leaves public TLS termination to the deployment.
250
+ sanitized transport failures and leaves public TLS termination to the deployment. A body the target
251
+ stops reading (one past its size limit) does not abort the request: once the response is written,
252
+ the rest is discarded as it arrives, within `requestTimeout`. Closing the adapter while it is still
253
+ starting rejects the pending `listen()` with `listen_aborted`.
package/agent-server.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type SlotlockTraceContext } from './mcp-modern.js';
2
+ import { type SlotlockApplicationBackend, type SlotlockApplicationOperation } from './application-api.js';
2
3
  export type { SlotlockTraceContext } from './mcp-modern.js';
3
4
  export { clientSupportsSlotlockMcpApp, SLOTLOCK_MCP_APP_HTML, SLOTLOCK_MCP_APP_MIME_TYPE, SLOTLOCK_MCP_APP_RESOURCE, SLOTLOCK_MCP_APP_RESOURCE_URI, SLOTLOCK_MCP_APPS_EXTENSION, } from './mcp-app.js';
4
5
  /**
@@ -163,10 +164,31 @@ export type SlotlockAgentServerEvent = {
163
164
  operation: SlotlockAgentOperation;
164
165
  outcome: 'requested' | 'accepted' | 'declined' | 'cancelled' | 'expired' | 'refused' | 'unavailable';
165
166
  } | {
166
- /** A REST call that reached an operation, and the HTTP status it answered. */
167
+ /**
168
+ * A REST call that named an operation, and the HTTP status it answered: run, or refused before
169
+ * it ran (a body that is not JSON, too large or not an object).
170
+ */
167
171
  type: 'rest';
168
- operation: SlotlockAgentOperation;
172
+ operation: SlotlockAgentOperation | SlotlockApplicationOperation;
169
173
  status: number;
174
+ /** How long the call took from naming its operation, in milliseconds to a tenth. */
175
+ durationMs: number;
176
+ /** The W3C trace id of the call's valid `traceparent`, when it carried one. */
177
+ traceId?: string;
178
+ } | {
179
+ /** An MCP `tools/call` or A2A `SendMessage` that named an operation, and how it ended. */
180
+ type: 'call';
181
+ protocol: 'mcp' | 'a2a';
182
+ operation: SlotlockAgentOperation;
183
+ /**
184
+ * `ok`, or the refusal's code (`overlap`, `forbidden`, `rate_limited`, …), or why the request
185
+ * was refused before the operation ran: `invalid_arguments` (MCP arguments that are not an
186
+ * object), `missing_capability` (a guarded write from a 2026-07-28 client without form
187
+ * elicitation), `invalid_message` or `invalid_message_parameters` (A2A).
188
+ */
189
+ outcome: string;
190
+ durationMs: number;
191
+ traceId?: string;
170
192
  } | {
171
193
  type: 'subscription';
172
194
  outcome: 'opened' | 'closed';
@@ -204,6 +226,21 @@ export interface SlotlockAgentServerOptions {
204
226
  * to ask a person. Off unless set.
205
227
  */
206
228
  rest?: boolean;
229
+ /**
230
+ * Also serve the application API under `/v1` (application-api.ts): resources, bookable hours,
231
+ * holds, reservations, calendar coverage and maintenance, for the application around the agents,
232
+ * in any language. Needs `rest`. A principal whose `scopes` are set needs `admin`
233
+ * (SLOTLOCK_APPLICATION_SCOPE); then `authorize` decides. They are never MCP tools or A2A skills,
234
+ * and never wait for a confirmation. Off unless set.
235
+ */
236
+ application?: {
237
+ backend: SlotlockApplicationBackend;
238
+ authorize(args: {
239
+ principal: SlotlockAgentPrincipal;
240
+ operation: SlotlockApplicationOperation;
241
+ input: Record<string, unknown>;
242
+ }): Promise<boolean>;
243
+ };
207
244
  /** Require a person's confirmation before the listed writes run. Off unless set. */
208
245
  confirmation?: SlotlockAgentConfirmationOptions;
209
246
  /**
@@ -218,23 +255,24 @@ export interface SlotlockAgentServerOptions {
218
255
  */
219
256
  subscriptions?: SlotlockAgentSubscriptionOptions | false;
220
257
  /**
221
- * Observe confirmations and subscriptions (counts and outcomes, no arguments or identities) for
222
- * metrics or audit. It runs synchronously and whatever it throws is ignored.
258
+ * Observe calls, confirmations and subscriptions (counts and outcomes, no arguments or
259
+ * identities) for metrics or audit. It is not awaited, and whatever it throws, or its promise
260
+ * rejects with, is ignored.
223
261
  */
224
262
  onEvent?: (event: SlotlockAgentServerEvent) => void;
225
263
  /**
226
264
  * Called with a failure no client is told about: an operation's backend threw something other
227
265
  * than a `SlotlockAgentOperationError`, or returned a result outside the operation's schema. The
228
266
  * client gets `internal_error` and nothing more, so log it here to find out why. It receives the
229
- * error and the operation, never the arguments or the request; it runs synchronously and whatever
230
- * it throws is ignored.
267
+ * error and the operation, never the arguments or the request. It is not awaited, and whatever it
268
+ * throws, or its promise rejects with, is ignored.
231
269
  */
232
270
  onError?: (error: unknown, context: SlotlockAgentErrorContext) => void;
233
271
  }
234
272
  /** What `onError` is told about an unexpected failure besides the error itself. */
235
273
  export interface SlotlockAgentErrorContext {
236
274
  /** The operation that failed; `null` when the failure came before one was resolved. */
237
- operation: SlotlockAgentOperation | null;
275
+ operation: SlotlockAgentOperation | SlotlockApplicationOperation | null;
238
276
  }
239
277
  export interface SlotlockAgentOperationDispatchOptions {
240
278
  backend: SlotlockAgentCalendarBackend;
@@ -309,6 +347,25 @@ export declare function slotlockMcpToolResult(outcome: {
309
347
  code: string;
310
348
  details?: Readonly<Record<string, string | number | boolean | null>>;
311
349
  }): SlotlockMcpToolResult;
350
+ /** The application operations with their input and output as JSON Schema, as REST serves them. */
351
+ export declare function slotlockApplicationOperations(): {
352
+ name: SlotlockApplicationOperation;
353
+ title: string;
354
+ description: string;
355
+ method: "DELETE" | "GET" | "POST" | "PUT";
356
+ path: string;
357
+ inputSchema: Readonly<Record<string, unknown>>;
358
+ outputSchema: Readonly<Record<string, unknown>>;
359
+ }[];
360
+ /**
361
+ * The OpenAPI 3.1 document of the REST API: what a server with `rest` (and `application`) serves at
362
+ * /openapi.json. Without `publicBaseUrl` the server URL is a variable, `{baseUrl}`, as in the
363
+ * repository's openapi.json, which client generators read.
364
+ */
365
+ export declare function slotlockOpenApi(options?: {
366
+ publicBaseUrl?: string;
367
+ application?: boolean;
368
+ }): Record<string, unknown>;
312
369
  export declare function slotlockAgentTools(): {
313
370
  name: "slotlock_create_event" | "slotlock_delete_event" | "slotlock_find_next_available" | "slotlock_get_event" | "slotlock_get_free_busy" | "slotlock_list_events" | "slotlock_list_resources" | "slotlock_update_event";
314
371
  title: "Create resource event" | "Delete resource event" | "Find next available interval" | "Get free-busy" | "Get resource event" | "List calendar resources" | "List resource events" | "Update resource event";