@vincemakes/kiso-server 0.53.0 → 0.54.1
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/http.d.ts +1 -0
- package/dist/http.js +5 -1
- package/dist/service.d.ts +10 -1
- package/dist/service.js +43 -11
- package/package.json +5 -5
package/dist/http.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { type ProjectionOptions } from "./wire.js";
|
|
|
18
18
|
* POST {prefix}/:id/run 202 { runId } — or the run's own stream with ?stream=1
|
|
19
19
|
* POST {prefix}/:id/resume 202 { runId } — or the stream with ?stream=1
|
|
20
20
|
* POST {prefix}/:id/steer 202 { runId } — 409 idle | closed (ADR-0057)
|
|
21
|
+
* POST {prefix}/:id/retract 200 { runId, count } — the person's steers not admitted yet, taken back (ADR-0057)
|
|
21
22
|
* POST {prefix}/:id/abort 200 idle | stopped — 409 parked (who is waited for)
|
|
22
23
|
* POST {prefix}/:id/approve 200 { needsResume }
|
|
23
24
|
* POST {prefix}/:id/uncertain 200 { remaining }
|
package/dist/http.js
CHANGED
|
@@ -6,7 +6,7 @@ import { toWireEvent } from "./wire.js";
|
|
|
6
6
|
* store never did — a nanoid can start with `_` or `-` (about 1 in 32 do)
|
|
7
7
|
* and every such session answered 404. */
|
|
8
8
|
const SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
9
|
-
const ACTIONS = new Set(["", "state", "events", "replay", "run", "resume", "steer", "abort", "approve", "uncertain"]);
|
|
9
|
+
const ACTIONS = new Set(["", "state", "events", "replay", "run", "resume", "steer", "retract", "abort", "approve", "uncertain"]);
|
|
10
10
|
export function createHttpHandler(service, options) {
|
|
11
11
|
const prefix = (options.prefix ?? "/v1/sessions").replace(/\/+$/, "");
|
|
12
12
|
const keepaliveMs = options.keepaliveMs ?? 15_000;
|
|
@@ -238,6 +238,10 @@ export function createHttpHandler(service, options) {
|
|
|
238
238
|
json(res, 202, service.steer(sessionId, input));
|
|
239
239
|
return true;
|
|
240
240
|
}
|
|
241
|
+
if (action === "retract") {
|
|
242
|
+
json(res, 200, service.retract(sessionId));
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
241
245
|
if (action === "abort") {
|
|
242
246
|
const outcome = await service.abort(sessionId, body["force"] === true ? { force: true } : {});
|
|
243
247
|
const reply = outcome.kind === "stopped" ? { kind: "stopped", runId: outcome.runId } : outcome;
|
package/dist/service.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentBlock, Event, MessageSource } from "@vincemakes/kiso-core";
|
|
1
|
+
import type { ContentBlock, Event, MessageSource, UserInputVia } from "@vincemakes/kiso-core";
|
|
2
2
|
import type { Agent, ApprovalRequest, Session, SessionStore } from "@vincemakes/kiso-runtime";
|
|
3
3
|
import { type Listener } from "./tail.js";
|
|
4
4
|
/**
|
|
@@ -81,6 +81,8 @@ export interface RunHandle {
|
|
|
81
81
|
}
|
|
82
82
|
export interface RunOptions {
|
|
83
83
|
readonly source?: MessageSource;
|
|
84
|
+
/** ADR-0058 §8: the receipt a task wake carries (the host's onWake hands it over), recorded on the run's user_input. */
|
|
85
|
+
readonly via?: UserInputVia;
|
|
84
86
|
/** The log holds an open run from a previous process: resume it to its
|
|
85
87
|
* terminal first, then start this turn. Without this the call refuses
|
|
86
88
|
* with `OpenRunError` — the service never resumes silently. */
|
|
@@ -127,6 +129,13 @@ export declare class SessionService {
|
|
|
127
129
|
steer(sessionId: string, input: string | readonly ContentBlock[]): {
|
|
128
130
|
readonly runId: string;
|
|
129
131
|
};
|
|
132
|
+
/** ADR-0057 — take back the person's input the live run has not admitted yet (a task notice stays). The host
|
|
133
|
+
* steers again what it still wants, in order: a page cancelling one waiting line takes them all back and
|
|
134
|
+
* re-steers the rest. Nothing to take back when no run is live. */
|
|
135
|
+
retract(sessionId: string): {
|
|
136
|
+
readonly runId: string | null;
|
|
137
|
+
readonly count: number;
|
|
138
|
+
};
|
|
130
139
|
/** ADR-0057 — what the run that ended at `terminalSeq` accepted and never
|
|
131
140
|
* admitted; null when nothing, or when that is not the latest terminal. */
|
|
132
141
|
unadmittedAt(sessionId: string, terminalSeq: number): {
|
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
|
-
|
|
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 {
|
|
@@ -113,6 +128,15 @@ export class SessionService {
|
|
|
113
128
|
live.steer(input);
|
|
114
129
|
return { runId: live.runId };
|
|
115
130
|
}
|
|
131
|
+
/** ADR-0057 — take back the person's input the live run has not admitted yet (a task notice stays). The host
|
|
132
|
+
* steers again what it still wants, in order: a page cancelling one waiting line takes them all back and
|
|
133
|
+
* re-steers the rest. Nothing to take back when no run is live. */
|
|
134
|
+
retract(sessionId) {
|
|
135
|
+
const live = this.#held.get(sessionId)?.live ?? null;
|
|
136
|
+
if (live === null)
|
|
137
|
+
return { runId: null, count: 0 };
|
|
138
|
+
return { runId: live.runId, count: live.retract().length };
|
|
139
|
+
}
|
|
116
140
|
/** ADR-0057 — what the run that ended at `terminalSeq` accepted and never
|
|
117
141
|
* admitted; null when nothing, or when that is not the latest terminal. */
|
|
118
142
|
unadmittedAt(sessionId, terminalSeq) {
|
|
@@ -136,7 +160,7 @@ export class SessionService {
|
|
|
136
160
|
throw new OpenRunError(sessionId, open);
|
|
137
161
|
await this.#pump(held, held.session.resume()).done;
|
|
138
162
|
}
|
|
139
|
-
const run = held.session.run(input, options.source !== undefined ? { source: options.source } : {});
|
|
163
|
+
const run = held.session.run(input, { ...(options.source !== undefined ? { source: options.source } : {}), ...(options.via !== undefined ? { via: options.via } : {}) });
|
|
140
164
|
return this.#pump(held, run);
|
|
141
165
|
}
|
|
142
166
|
/** Drive the open run (one a previous process left, or one parked at an
|
|
@@ -182,6 +206,10 @@ export class SessionService {
|
|
|
182
206
|
const held = await this.#hold(sessionId);
|
|
183
207
|
const wasLive = held.live !== null;
|
|
184
208
|
await held.session.approve(decisionId, allow, reason);
|
|
209
|
+
// a live run appends the verdict itself and `#pump` fans it out once it
|
|
210
|
+
// is on disk; only a verdict appended with no run live is published here
|
|
211
|
+
if (held.live === null)
|
|
212
|
+
this.#publish(held);
|
|
185
213
|
return { needsResume: !wasLive };
|
|
186
214
|
}
|
|
187
215
|
async pendingApprovals(sessionId) {
|
|
@@ -196,6 +224,8 @@ export class SessionService {
|
|
|
196
224
|
async resolveUncertain(sessionId, executionId, resolution) {
|
|
197
225
|
const held = await this.#hold(sessionId);
|
|
198
226
|
await held.session.resolveUncertain(executionId, resolution);
|
|
227
|
+
if (held.live === null)
|
|
228
|
+
this.#publish(held);
|
|
199
229
|
return { remaining: held.session.uncertainExecutions().length };
|
|
200
230
|
}
|
|
201
231
|
// ---- observation ----------------------------------------------------------
|
|
@@ -212,7 +242,9 @@ export class SessionService {
|
|
|
212
242
|
events(sessionId, after = -1) {
|
|
213
243
|
const held = this.#held.get(sessionId);
|
|
214
244
|
const events = held !== undefined ? held.session.log.all : this.#store.load(sessionId).map((r) => r.event);
|
|
215
|
-
|
|
245
|
+
// the log's seq is its index (EventLog refuses a discontinuity): no scan, and with no `after` no copy either — a
|
|
246
|
+
// host that read every event's frames through a filtered copy spent seconds per replay on a long Work. Read-only.
|
|
247
|
+
return (after < 0 ? events : events.slice(after + 1));
|
|
216
248
|
}
|
|
217
249
|
isRunning(sessionId) {
|
|
218
250
|
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.
|
|
3
|
+
"version": "0.54.1",
|
|
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.
|
|
29
|
-
"@vincemakes/kiso-protocol": "0.
|
|
30
|
-
"@vincemakes/kiso-runtime": "0.
|
|
28
|
+
"@vincemakes/kiso-core": "0.54.1",
|
|
29
|
+
"@vincemakes/kiso-protocol": "0.54.1",
|
|
30
|
+
"@vincemakes/kiso-runtime": "0.54.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@vincemakes/kiso-evals": "0.
|
|
33
|
+
"@vincemakes/kiso-evals": "0.54.1",
|
|
34
34
|
"@types/node": "^26.1.2",
|
|
35
35
|
"typescript": "^5.7.2",
|
|
36
36
|
"vitest": "^3.0.0"
|