mcp-tenant-lib 0.1.0 → 0.1.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/dist/tenant.d.ts CHANGED
@@ -36,6 +36,26 @@ export declare class Tenant<TSchema, TValues> {
36
36
  schema: TSchema;
37
37
  store: Store<TValues>;
38
38
  submitBus: EventEmitter;
39
+ /**
40
+ * Whether some caller currently has a `submitBus.once('submit', ...)`
41
+ * listener registered — i.e. wait_for_submit / define_form({wait:true})
42
+ * is actively blocked on this tenant right now. Tracked via submitBus's
43
+ * own newListener/removeListener events (see constructor) rather than a
44
+ * manual flag, so it can never drift from the real listener count.
45
+ * Broadcast to the browser as a 'waiting' WS message so the UI can show
46
+ * whether anyone is listening, without implying Submit is meaningless
47
+ * when nobody currently is (see `submitted`).
48
+ */
49
+ waiting: boolean;
50
+ /**
51
+ * Whether the user has clicked Submit at least once since the form was
52
+ * last (re)defined. Set unconditionally on the 'submit' WS message,
53
+ * independent of `waiting` — the user can fill in and submit a form
54
+ * with no agent currently waiting (e.g. it was defined with wait:false,
55
+ * or the agent's turn ended), and a later wait_for_submit call or
56
+ * list_fields read should be able to see that they're done.
57
+ */
58
+ submitted: boolean;
39
59
  wsClients: Set<WebSocket>;
40
60
  connections: Map<string, TenantConnection>;
41
61
  lastActivityAt: number;
package/dist/tenant.js CHANGED
@@ -39,6 +39,26 @@ export class Tenant {
39
39
  schema;
40
40
  store;
41
41
  submitBus;
42
+ /**
43
+ * Whether some caller currently has a `submitBus.once('submit', ...)`
44
+ * listener registered — i.e. wait_for_submit / define_form({wait:true})
45
+ * is actively blocked on this tenant right now. Tracked via submitBus's
46
+ * own newListener/removeListener events (see constructor) rather than a
47
+ * manual flag, so it can never drift from the real listener count.
48
+ * Broadcast to the browser as a 'waiting' WS message so the UI can show
49
+ * whether anyone is listening, without implying Submit is meaningless
50
+ * when nobody currently is (see `submitted`).
51
+ */
52
+ waiting = false;
53
+ /**
54
+ * Whether the user has clicked Submit at least once since the form was
55
+ * last (re)defined. Set unconditionally on the 'submit' WS message,
56
+ * independent of `waiting` — the user can fill in and submit a form
57
+ * with no agent currently waiting (e.g. it was defined with wait:false,
58
+ * or the agent's turn ended), and a later wait_for_submit call or
59
+ * list_fields read should be able to see that they're done.
60
+ */
61
+ submitted = false;
42
62
  wsClients;
43
63
  connections = new Map();
44
64
  #legacyManifest;
@@ -85,6 +105,16 @@ export class Tenant {
85
105
  this.store = new Store(initialValues);
86
106
  this.submitBus = new EventEmitter();
87
107
  this.submitBus.setMaxListeners(0);
108
+ this.submitBus.on('newListener', (event) => {
109
+ if (event !== 'submit')
110
+ return;
111
+ queueMicrotask(() => this.#syncWaiting());
112
+ });
113
+ this.submitBus.on('removeListener', (event) => {
114
+ if (event !== 'submit')
115
+ return;
116
+ this.#syncWaiting();
117
+ });
88
118
  this.wsClients = new Set();
89
119
  this.lastActivityAt = Date.now();
90
120
  this.store.onChange((field, value) => this.broadcastUpdate(field, value));
@@ -252,11 +282,12 @@ export class Tenant {
252
282
  this.store.dispose();
253
283
  this.schema = schema;
254
284
  this.store = new Store(values);
285
+ this.submitted = false;
255
286
  this.store.onChange((field, value) => this.broadcastUpdate(field, value));
256
287
  this.broadcastReinit();
257
288
  }
258
289
  broadcastReinit() {
259
- const payload = JSON.stringify({ type: 'reinit', schema: this.schema, state: this.store.snapshot() });
290
+ const payload = JSON.stringify({ type: 'reinit', schema: this.schema, state: this.store.snapshot(), waiting: this.waiting, submitted: this.submitted });
260
291
  for (const client of this.wsClients) {
261
292
  if (client.readyState === client.OPEN)
262
293
  client.send(payload);
@@ -269,6 +300,17 @@ export class Tenant {
269
300
  client.send(payload);
270
301
  }
271
302
  }
303
+ #syncWaiting() {
304
+ const nowWaiting = this.submitBus.listenerCount('submit') > 0;
305
+ if (nowWaiting === this.waiting)
306
+ return;
307
+ this.waiting = nowWaiting;
308
+ const payload = JSON.stringify({ type: 'waiting', waiting: this.waiting });
309
+ for (const client of this.wsClients) {
310
+ if (client.readyState === client.OPEN)
311
+ client.send(payload);
312
+ }
313
+ }
272
314
  dispose() {
273
315
  this.submitBus.emit('submit', { __interrupted: true, __disposed: true, ...this.store.snapshot() });
274
316
  this.store.dispose();
package/dist/types.d.ts CHANGED
@@ -7,10 +7,15 @@ export type ServerMessage<TSchema = unknown, TValues = unknown> = {
7
7
  type: 'init' | 'reinit';
8
8
  schema: TSchema;
9
9
  state: TValues;
10
+ waiting: boolean;
11
+ submitted: boolean;
10
12
  } | {
11
13
  type: 'update';
12
14
  field: string;
13
15
  value: unknown;
16
+ } | {
17
+ type: 'waiting';
18
+ waiting: boolean;
14
19
  } | CallMessage;
15
20
  export interface SetMessage {
16
21
  type: 'set';
package/dist/ws.js CHANGED
@@ -51,7 +51,7 @@ export function attachWebSocketServer(httpServer, port, initialSchema, initialVa
51
51
  if (state)
52
52
  state.isAlive = true;
53
53
  });
54
- ws.send(JSON.stringify({ type: 'init', schema: t.schema, state: t.store.snapshot() }));
54
+ ws.send(JSON.stringify({ type: 'init', schema: t.schema, state: t.store.snapshot(), waiting: t.waiting, submitted: t.submitted }));
55
55
  ws.on('message', (raw) => {
56
56
  t.touch();
57
57
  let msg;
@@ -65,6 +65,7 @@ export function attachWebSocketServer(httpServer, port, initialSchema, initialVa
65
65
  t.store.set(msg.field, msg.value);
66
66
  }
67
67
  if (msg.type === 'submit') {
68
+ t.submitted = true;
68
69
  t.submitBus.emit('submit', { __interrupted: false, ...t.store.snapshot() });
69
70
  }
70
71
  if (msg.type === 'interrupt') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-tenant-lib",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Generic tenant/session bookkeeping + MCP wiring, reusable across projects.",
6
6
  "repository": {