@vincemakes/kiso-server 0.52.0 → 0.54.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 (2) hide show
  1. package/dist/service.js +33 -10
  2. package/package.json +5 -5
package/dist/service.js CHANGED
@@ -24,7 +24,7 @@ export class SessionService {
24
24
  const opening = (async () => {
25
25
  const agent = await this.#open(sessionId);
26
26
  const session = await agent.session({ id: sessionId });
27
- const held = { id: sessionId, agent, session, listeners: new Set(), live: null, pumping: null, executing: 0, highWater: -1, mismatch: null, unadmitted: null };
27
+ const held = { id: sessionId, agent, session, listeners: new Set(), live: null, pumping: null, executing: 0, highWater: -1, published: session.log.lastSeq, mismatch: null, unadmitted: null };
28
28
  this.#held.set(sessionId, held);
29
29
  return held;
30
30
  })();
@@ -48,6 +48,28 @@ export class SessionService {
48
48
  held.agent = agent;
49
49
  held.session = await agent.session({ id: sessionId });
50
50
  }
51
+ /** Fan out every event on the open log after `published`, in seq order.
52
+ * A run's events arrive through `#pump`, but a verdict given while no
53
+ * run is live (`resolveUncertain`, an offline `approve`) is appended
54
+ * straight to the log; reading the log here — not the one event in
55
+ * hand — is what keeps an event appended outside a run from being
56
+ * skipped when a run's next event reaches a subscriber first. */
57
+ #publish(held) {
58
+ const log = held.session.log.all;
59
+ // the log's seq is its index (EventLog refuses a discontinuity)
60
+ for (let i = held.published + 1; i < log.length; i++) {
61
+ const event = log[i];
62
+ held.published = event.seq;
63
+ for (const listener of [...held.listeners]) {
64
+ try {
65
+ listener(event);
66
+ }
67
+ catch {
68
+ held.listeners.delete(listener); // a throwing observer leaves; the run does not
69
+ }
70
+ }
71
+ }
72
+ }
51
73
  // ---- runs -----------------------------------------------------------------
52
74
  #pump(held, run) {
53
75
  held.live = run;
@@ -60,14 +82,7 @@ export class SessionService {
60
82
  held.unadmitted = { runId: run.runId, terminalSeq: event.seq, items: run.unadmitted() };
61
83
  held.executing = Math.max(0, held.executing + executionDelta(event));
62
84
  held.highWater = Math.max(held.highWater, event.seq);
63
- for (const listener of [...held.listeners]) {
64
- try {
65
- listener(event);
66
- }
67
- catch {
68
- held.listeners.delete(listener); // a throwing observer leaves; the run does not
69
- }
70
- }
85
+ this.#publish(held);
71
86
  }
72
87
  }
73
88
  finally {
@@ -182,6 +197,10 @@ export class SessionService {
182
197
  const held = await this.#hold(sessionId);
183
198
  const wasLive = held.live !== null;
184
199
  await held.session.approve(decisionId, allow, reason);
200
+ // a live run appends the verdict itself and `#pump` fans it out once it
201
+ // is on disk; only a verdict appended with no run live is published here
202
+ if (held.live === null)
203
+ this.#publish(held);
185
204
  return { needsResume: !wasLive };
186
205
  }
187
206
  async pendingApprovals(sessionId) {
@@ -196,6 +215,8 @@ export class SessionService {
196
215
  async resolveUncertain(sessionId, executionId, resolution) {
197
216
  const held = await this.#hold(sessionId);
198
217
  await held.session.resolveUncertain(executionId, resolution);
218
+ if (held.live === null)
219
+ this.#publish(held);
199
220
  return { remaining: held.session.uncertainExecutions().length };
200
221
  }
201
222
  // ---- observation ----------------------------------------------------------
@@ -212,7 +233,9 @@ export class SessionService {
212
233
  events(sessionId, after = -1) {
213
234
  const held = this.#held.get(sessionId);
214
235
  const events = held !== undefined ? held.session.log.all : this.#store.load(sessionId).map((r) => r.event);
215
- return events.filter((e) => e.seq > after);
236
+ // the log's seq is its index (EventLog refuses a discontinuity): no scan, and with no `after` no copy either — a
237
+ // host that read every event's frames through a filtered copy spent seconds per replay on a long Work. Read-only.
238
+ return (after < 0 ? events : events.slice(after + 1));
216
239
  }
217
240
  isRunning(sessionId) {
218
241
  return this.#held.get(sessionId)?.live !== null && this.#held.get(sessionId)?.live !== undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-server",
3
- "version": "0.52.0",
3
+ "version": "0.54.0",
4
4
  "description": "kiso server — the hosted-session service: many durable sessions in one process, observers with exact replay from a sequence number, approve / abort / resume routed to the right run, drain and close. No transport; a product supplies the agent factory.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,12 +25,12 @@
25
25
  "test": "vitest run"
26
26
  },
27
27
  "dependencies": {
28
- "@vincemakes/kiso-core": "0.52.0",
29
- "@vincemakes/kiso-protocol": "0.52.0",
30
- "@vincemakes/kiso-runtime": "0.52.0"
28
+ "@vincemakes/kiso-core": "0.54.0",
29
+ "@vincemakes/kiso-protocol": "0.54.0",
30
+ "@vincemakes/kiso-runtime": "0.54.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@vincemakes/kiso-evals": "0.52.0",
33
+ "@vincemakes/kiso-evals": "0.54.0",
34
34
  "@types/node": "^26.1.2",
35
35
  "typescript": "^5.7.2",
36
36
  "vitest": "^3.0.0"