@ouispec/agent-realtime 0.2.2 → 0.7.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/README.md +18 -0
- package/dist/approvals/routes.d.ts +4 -0
- package/dist/approvals/routes.d.ts.map +1 -1
- package/dist/approvals/routes.js +32 -0
- package/dist/approvals/routes.js.map +1 -1
- package/dist/approvals/store.d.ts +25 -2
- package/dist/approvals/store.d.ts.map +1 -1
- package/dist/approvals/store.js +119 -12
- package/dist/approvals/store.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +7 -0
- package/dist/server.js.map +1 -1
- package/dist/turns/client-event.d.ts +5 -0
- package/dist/turns/client-event.d.ts.map +1 -0
- package/dist/turns/client-event.js +47 -0
- package/dist/turns/client-event.js.map +1 -0
- package/dist/turns/routes.d.ts +33 -0
- package/dist/turns/routes.d.ts.map +1 -0
- package/dist/turns/routes.js +91 -0
- package/dist/turns/routes.js.map +1 -0
- package/dist/turns/stops.d.ts +41 -0
- package/dist/turns/stops.d.ts.map +1 -0
- package/dist/turns/stops.js +55 -0
- package/dist/turns/stops.js.map +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -63,6 +63,10 @@ All of them require the `x-api-key` header.
|
|
|
63
63
|
| `POST /internal/approvals/:id/decide` `{ userId, decision, channel }` | A conversation channel's decision (`voice`, `phone`, `sms`, `chat`), made by the engine for the session's user. A `ui` decision is refused here: in a UI only the user's click counts. |
|
|
64
64
|
| `POST /internal/approvals/redeem` `{ token, userId, conversationId }` | Atomic single use. `200 { call }` returns exactly the stored call. `403` for a token this environment did not sign, another user's or conversation's, or one not for the stored call; `410` for an expired or already used one. |
|
|
65
65
|
| `GET /internal/approvals/:id?userId=` | Where an approval of this user's stands: `pending`, `approved` or `declined` (remembered for 30 minutes). |
|
|
66
|
+
| `POST /internal/approvals/:id/settle` `{ userId, conversationId, confirm? }` | Whether an approval a conversation still shows as waiting expired with nobody deciding it. Always `200 { approvalId, outcome, expiresAt? }`: `claimed` (it expired without having run, and this caller holds the claim to store that, for two minutes; `decided: "approved"` when the user had approved it and it was never used), `already` (another turn holds the claim, or it is stored), `open` (pending, or approved and not yet used), `unknown` (declined, used, another user's or conversation's, or not remembered). With `confirm: true`, the caller has stored it and the claim becomes permanent: `confirmed`. |
|
|
67
|
+
| `POST /internal/approvals/:id/settle` `{ userId, conversationId, expire }` | Expires an approval now instead of at its time limit, saying why: `expire` is `superseded` (the user sent a new message while its card waited) or `stopped` (the turn that asked was stopped). `200 { approvalId, outcome }`: `withdrawn` (it was pending, or approved and not yet used, and is now gone: a decision is refused and its token answers `used`), `settled` (it had already ended), `unknown`. A later settle then answers `claimed` with `withdrawn: "superseded" \| "stopped"`. |
|
|
68
|
+
| `POST /internal/turns/:turnId/stop` `{ userId, reason }` | Asks that a turn stop, for its user: `reason` is `superseded` (a newer message runs instead) or `user_stop`. Your API calls it after checking the turn is the user's. `200 { ok, stop: "requested" \| "already", record }`; the first request stands. |
|
|
69
|
+
| `GET /internal/turns/:turnId/stop?userId=&waitMs=` | The stop asked for on a turn by its user, which the turn's worker holds open for as long as the turn runs. `200` carries `{ turnId, by, reason, at }`; `204` means none yet. A wait lasts at most 25 s. The record is kept 30 minutes. |
|
|
66
70
|
| `GET /internal/events/settlements/:kind/:id?waitMs=` | How a job of a declared kind ended: `{ kind, role, event, id, payload }`, with `role` `completion` or `failure`. `204` means not yet; `400` an undeclared kind; `404` a server without `events`. A wait lasts at most 25 s. |
|
|
67
71
|
| `GET /health` | Returns `200` with connection and pending-wait counts. No key needed. |
|
|
68
72
|
|
|
@@ -72,10 +76,24 @@ All of them require the `x-api-key` header.
|
|
|
72
76
|
2. The user clicks the approval card. Their socket sends `approval:decide { approvalId, decision }`, a declared client event. The store checks that the socket's verified user is the approval's own. An approval's token is answered only to that socket, in the ack: `{ ok: true, decision: 'approve', token, argsHash, expiresAt }`.
|
|
73
77
|
3. The next turn carries the token outside the message text. The worker redeems it, and the store returns the stored call exactly once.
|
|
74
78
|
|
|
79
|
+
An approval nobody decides sends nothing when it expires, so a later turn settles it. The store remembers, for 30 days, that an approval was asked for and is still undecided; a decline or a redemption removes that memory in the same step. Once the approval's own keys have lapsed in Redis, `POST /internal/approvals/:id/settle` answers that it expired undecided, and gives the claim to store that to one caller at a time. Expiry is Redis's clock: neither this server's nor the caller's is consulted. Approvals stored before this memory existed are answered `unknown`.
|
|
80
|
+
|
|
81
|
+
An approval can also be withdrawn before its time is up (`settle` with `expire`): when the user sends a new message while its card waits, or the turn that asked for it is stopped. That is one script with the decision and the redemption, so exactly one of them happens. From then it is an expiry like any other, settled by a later turn, which is told why: it was withdrawn, not left to time out. One the user had approved keeps that they did.
|
|
82
|
+
|
|
75
83
|
A declined approval is deleted and remembered for the next turn. Every issue, decision and redemption is logged with the approval id, user, tool, effect, channel and args hash. The arguments are logged only when their declaration marks them not sensitive, and a token never is.
|
|
76
84
|
|
|
77
85
|
The token's claims are `aid sub cid tool ah eff ch iat exp jti`. `ah` is `argsHash(args)` from `@ouispec/agent-core`: SHA-256 over the RFC 8785 canonical JSON. The package ships `approval-vectors.json`, which every other implementation must match.
|
|
78
86
|
|
|
87
|
+
## Stopping a turn
|
|
88
|
+
|
|
89
|
+
A person can stop a running turn, and a newer message supersedes one. The worker has no socket, so the server keeps the request and the worker asks for it:
|
|
90
|
+
|
|
91
|
+
1. The tab sends `agent:turn_stop { turnId, room }` on the user's socket, where `room` is the turn's room. The server takes it only from a socket that is in the room it names, and only when that room is one your policy guards with a token.
|
|
92
|
+
2. The stop is kept under the socket's verified user, and that is what makes it the owner's. A worker's wait is answered only with the stop of the user it asks for, which is its turn's own. The server keeps no record of who owns a turn and does not know which room is which turn's, so the room check does not prove the turn is the user's: a socket in any token-guarded room can write a stop for any turn id. It writes it under its own user, where no other user's worker reads it, and it cannot block the owner's.
|
|
93
|
+
3. Your API asks the same way with `POST /internal/turns/:turnId/stop`, after its own check, when a new message arrives for a chat with a turn running.
|
|
94
|
+
|
|
95
|
+
The worker stores what the turn had produced and then emits `agent:turn_complete` with `stopReason`: `user_stop` or `superseded`.
|
|
96
|
+
|
|
79
97
|
## Example
|
|
80
98
|
|
|
81
99
|
```ts
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* POST /internal/approvals/:id/decide a conversation channel's decision (engine)
|
|
9
9
|
* POST /internal/approvals/redeem atomic single use (worker, engine)
|
|
10
10
|
* GET /internal/approvals/:id?userId= where an approval stands (worker, for the turn after a decision)
|
|
11
|
+
* POST /internal/approvals/:id/settle whether it expired undecided, claimed for one turn to store; with
|
|
12
|
+
* `confirm`, that it has been stored (worker, at the start of a later turn);
|
|
13
|
+
* with `expire`, expire it now, saying why (host, when a new message
|
|
14
|
+
* arrives; worker, when the turn that asked is stopped: ADR-0252 §2.6)
|
|
11
15
|
*/
|
|
12
16
|
import { Router } from 'express';
|
|
13
17
|
import type { ApprovalStore } from './store.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/approvals/routes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/approvals/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,MAAM,EAA+B,MAAM,SAAS,CAAC;AAU9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,aAAa,CAAC;IACzB,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC;CAC3D;AAkBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAqG9D"}
|
package/dist/approvals/routes.js
CHANGED
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
* POST /internal/approvals/:id/decide a conversation channel's decision (engine)
|
|
9
9
|
* POST /internal/approvals/redeem atomic single use (worker, engine)
|
|
10
10
|
* GET /internal/approvals/:id?userId= where an approval stands (worker, for the turn after a decision)
|
|
11
|
+
* POST /internal/approvals/:id/settle whether it expired undecided, claimed for one turn to store; with
|
|
12
|
+
* `confirm`, that it has been stored (worker, at the start of a later turn);
|
|
13
|
+
* with `expire`, expire it now, saying why (host, when a new message
|
|
14
|
+
* arrives; worker, when the turn that asked is stopped: ADR-0252 §2.6)
|
|
11
15
|
*/
|
|
12
16
|
import { Router } from 'express';
|
|
17
|
+
import { APPROVAL_WITHDRAW_REASONS, } from '@ouispec/agent-core';
|
|
13
18
|
import { internalDecideSchema, pendingApprovalSchema, redeemSchema } from './schemas.js';
|
|
14
19
|
const DECIDE_STATUS = {
|
|
15
20
|
unknown: 404,
|
|
@@ -78,6 +83,33 @@ export function approvalRouter(deps) {
|
|
|
78
83
|
}
|
|
79
84
|
res.json(result);
|
|
80
85
|
});
|
|
86
|
+
router.post('/internal/approvals/:id/settle', async (req, res) => {
|
|
87
|
+
if (!deps.isInternalKey(req.headers['x-api-key'])) {
|
|
88
|
+
res.status(401).json({ error: 'Invalid or missing API key' });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const body = (req.body ?? {});
|
|
92
|
+
if (typeof body.userId !== 'string' || !body.userId || typeof body.conversationId !== 'string' || !body.conversationId) {
|
|
93
|
+
res.status(400).json({ error: 'userId and conversationId are required' });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const approvalId = req.params.id;
|
|
97
|
+
const owner = { userId: body.userId, conversationId: body.conversationId };
|
|
98
|
+
// Always 200: `unknown` is an answer, and a worker must tell it from a server that has no such route (404).
|
|
99
|
+
if (body.confirm === true) {
|
|
100
|
+
res.json({ approvalId, outcome: (await deps.approvals.confirmExpirySettled(approvalId, owner)) ? 'confirmed' : 'unknown' });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (body.expire !== undefined) {
|
|
104
|
+
if (!APPROVAL_WITHDRAW_REASONS.includes(body.expire)) {
|
|
105
|
+
res.status(400).json({ error: `expire must be one of ${APPROVAL_WITHDRAW_REASONS.join(', ')}` });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
res.json(await deps.approvals.withdraw(approvalId, owner, body.expire));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
res.json(await deps.approvals.settleExpired(approvalId, owner));
|
|
112
|
+
});
|
|
81
113
|
router.get('/internal/approvals/:id', async (req, res) => {
|
|
82
114
|
if (!deps.isInternalKey(req.headers['x-api-key'])) {
|
|
83
115
|
res.status(401).json({ error: 'Invalid or missing API key' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/approvals/routes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/approvals/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ApprovalChannel, type ApprovalDecideResult, type ApprovalDecision, type ApprovalRedeemResult, type ApprovalRefusal, type ApprovalStatus, type PendingApprovalInput } from '@ouispec/agent-core';
|
|
1
|
+
import { type ApprovalChannel, type ApprovalDecideResult, type ApprovalDecision, type ApprovalOwner, type ApprovalRedeemResult, type ApprovalRefusal, type ApprovalSettlement, type ApprovalStatus, type ApprovalWithdrawal, type ApprovalWithdrawReason, type PendingApprovalInput } from '@ouispec/agent-core';
|
|
2
2
|
import type { RealtimeLogger } from '../logger.js';
|
|
3
3
|
import type { ApprovalTokenSigner } from './token.js';
|
|
4
4
|
/** The Redis command the store uses. ioredis satisfies it. */
|
|
@@ -26,8 +26,31 @@ export interface ApprovalStore {
|
|
|
26
26
|
}): Promise<ApprovalRedeemResult>;
|
|
27
27
|
/** Where an approval of this user's stands, or null when it is not theirs or not known. */
|
|
28
28
|
status(approvalId: string, userId: string): Promise<ApprovalStatus | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Settle an approval a conversation still shows as waiting: whether it
|
|
31
|
+
* expired undecided, and whether this caller holds the claim to store that.
|
|
32
|
+
* One script, so of two turns asking together exactly one is `claimed`.
|
|
33
|
+
* Expiry is the approval's keys lapsing in Redis: this server's clock is
|
|
34
|
+
* not consulted, and nor is the caller's.
|
|
35
|
+
*/
|
|
36
|
+
settleExpired(approvalId: string, owner: ApprovalOwner): Promise<ApprovalSettlement>;
|
|
37
|
+
/** The claimed expiry is stored: the claim no longer lapses. False when the store does not know the approval as this owner's. */
|
|
38
|
+
confirmExpirySettled(approvalId: string, owner: ApprovalOwner): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Expire an approval now instead of at its time limit (ADR-0252 §2.6): the
|
|
41
|
+
* person sent a new message while its card waited, or the turn that asked
|
|
42
|
+
* was stopped. One script, with decide and redeem, so it cannot interleave
|
|
43
|
+
* with either: a pending approval, or an approved one not yet used, is gone
|
|
44
|
+
* from that moment, its memory kept with why. A later turn then settles it
|
|
45
|
+
* as any expiry (`settleExpired`), and is told it was withdrawn.
|
|
46
|
+
*/
|
|
47
|
+
withdraw(approvalId: string, owner: ApprovalOwner, reason: ApprovalWithdrawReason): Promise<ApprovalWithdrawal>;
|
|
29
48
|
}
|
|
30
49
|
/** How long a decline is remembered for the turn that follows it. */
|
|
31
50
|
export declare const DECLINED_TTL_SEC: number;
|
|
32
|
-
export
|
|
51
|
+
export interface ApprovalStoreOptions {
|
|
52
|
+
/** How long a turn's claim on an expiry lasts unconfirmed. Default `EXPIRY_CLAIM_LEASE_MS`; shorter in tests. */
|
|
53
|
+
expiryClaimLeaseMs?: number;
|
|
54
|
+
}
|
|
55
|
+
export declare function createApprovalStore(redis: ApprovalRedis, signer: ApprovalTokenSigner, logger: RealtimeLogger, options?: ApprovalStoreOptions): ApprovalStore;
|
|
33
56
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/approvals/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/approvals/store.ts"],"names":[],"mappings":"AA6BA,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EAEpB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAE3B,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1F;AAED,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,eAAe,CAAC,CAAC;IACtI,sFAAsF;IACtF,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,eAAe,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACxI,8GAA8G;IAC9G,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACzG,2FAA2F;IAC3F,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC3E;;;;;;OAMG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrF,iIAAiI;IACjI,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjF;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACjH;AAED,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,QAAU,CAAC;AA2IxC,MAAM,WAAW,oBAAoB;IACnC,iHAAiH;IACjH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,oBAAyB,GACjC,aAAa,CA8Of"}
|
package/dist/approvals/store.js
CHANGED
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
* conversationId, tool, argsHash, expiresAt, and
|
|
9
9
|
* once approved the token's jti and the channel
|
|
10
10
|
* approval:declined:{id} → what the next turn tells the model, 30 min
|
|
11
|
+
* approval:{id}:seen → that it was asked for and has not run or been
|
|
12
|
+
* declined (userId, conversationId, expiresAt,
|
|
13
|
+
* and `decided: approved` once the user has
|
|
14
|
+
* approved it): written with the approval,
|
|
15
|
+
* rewritten in the script that approves it,
|
|
16
|
+
* removed in the script that declines or redeems
|
|
17
|
+
* it, and kept 30 days past its expiry. Present
|
|
18
|
+
* with the approval gone, it expired without
|
|
19
|
+
* having run: undecided, or approved and unused.
|
|
20
|
+
* approval:{id}:settled → a turn's claim to store that expiry: a two
|
|
21
|
+
* minute lease, permanent once confirmed
|
|
11
22
|
*
|
|
12
23
|
* Each decision and each redemption is one Lua script, so two instances can
|
|
13
24
|
* never both approve one call, or both redeem one token. The stored call is
|
|
@@ -16,22 +27,30 @@
|
|
|
16
27
|
* exactly the one the user approved.
|
|
17
28
|
*/
|
|
18
29
|
import crypto from 'node:crypto';
|
|
19
|
-
import { argsHash as hashOf, MAX_APPROVAL_TTL_MS, } from '@ouispec/agent-core';
|
|
30
|
+
import { APPROVAL_WITHDRAW_REASONS, argsHash as hashOf, EXPIRED_APPROVAL_MEMORY_MS, EXPIRY_CLAIM_LEASE_MS, MAX_APPROVAL_TTL_MS, } from '@ouispec/agent-core';
|
|
20
31
|
/** How long a decline is remembered for the turn that follows it. */
|
|
21
32
|
export const DECLINED_TTL_SEC = 30 * 60;
|
|
22
33
|
const recordKey = (id) => `approval:${id}`;
|
|
23
34
|
const stateKey = (id) => `approval:${id}:state`;
|
|
24
35
|
const declinedKey = (id) => `approval:declined:${id}`;
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
const seenKey = (id) => `approval:${id}:seen`;
|
|
37
|
+
const settledKey = (id) => `approval:${id}:settled`;
|
|
38
|
+
const withdrawnKey = (id) => `approval:${id}:withdrawn`;
|
|
39
|
+
// KEYS: record, state, declined, seen, settled, withdrawn. ARGV: record JSON, ttl ms, userId, conversationId, tool,
|
|
40
|
+
// argsHash, expiresAt, seen JSON, seen ttl ms.
|
|
41
|
+
// A new approval supersedes an old decline under the same id, an old expiry's settlement, and an old withdrawal.
|
|
27
42
|
const CREATE = `
|
|
28
43
|
if redis.call('EXISTS', KEYS[1]) == 1 or redis.call('EXISTS', KEYS[2]) == 1 then return 'exists' end
|
|
29
|
-
redis.call('DEL', KEYS[3])
|
|
44
|
+
redis.call('DEL', KEYS[3], KEYS[5], KEYS[6])
|
|
45
|
+
redis.call('SET', KEYS[4], ARGV[8], 'PX', ARGV[9])
|
|
30
46
|
redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])
|
|
31
47
|
redis.call('HSET', KEYS[2], 'status', 'pending', 'userId', ARGV[3], 'conversationId', ARGV[4], 'tool', ARGV[5], 'argsHash', ARGV[6], 'expiresAt', ARGV[7])
|
|
32
48
|
redis.call('PEXPIRE', KEYS[2], ARGV[2])
|
|
33
49
|
return 'ok'`;
|
|
34
|
-
// KEYS: state, record, declined. ARGV: userId, decision, now ms, jti, channel, declined ttl s, declined JSON
|
|
50
|
+
// KEYS: state, record, declined, seen. ARGV: userId, decision, now ms, jti, channel, declined ttl s, declined JSON,
|
|
51
|
+
// seen JSON once approved.
|
|
52
|
+
// A decline decides it: it is no longer something that can expire. An approval is recorded in the
|
|
53
|
+
// memory (same TTL), so if the approved call is never run its expiry is not told as "undecided".
|
|
35
54
|
const DECIDE = `
|
|
36
55
|
if redis.call('EXISTS', KEYS[1]) == 0 then return {'unknown'} end
|
|
37
56
|
local s = redis.call('HMGET', KEYS[1], 'status', 'userId', 'expiresAt')
|
|
@@ -41,12 +60,14 @@ if tonumber(ARGV[3]) >= tonumber(s[3]) then return {'expired'} end
|
|
|
41
60
|
local record = redis.call('GET', KEYS[2])
|
|
42
61
|
if ARGV[2] == 'approve' then
|
|
43
62
|
redis.call('HSET', KEYS[1], 'status', 'approved', 'jti', ARGV[4], 'channel', ARGV[5])
|
|
63
|
+
if redis.call('EXISTS', KEYS[4]) == 1 then redis.call('SET', KEYS[4], ARGV[8], 'KEEPTTL') end
|
|
44
64
|
return {'approved', record}
|
|
45
65
|
end
|
|
46
|
-
redis.call('DEL', KEYS[1], KEYS[2])
|
|
66
|
+
redis.call('DEL', KEYS[1], KEYS[2], KEYS[4])
|
|
47
67
|
redis.call('SET', KEYS[3], ARGV[7], 'EX', ARGV[6])
|
|
48
68
|
return {'declined', record}`;
|
|
49
|
-
// KEYS: state, record. ARGV: jti, argsHash, tool, userId, conversationId.
|
|
69
|
+
// KEYS: state, record, seen. ARGV: jti, argsHash, tool, userId, conversationId.
|
|
70
|
+
// A redemption uses it: it did not expire undecided.
|
|
50
71
|
const REDEEM = `
|
|
51
72
|
if redis.call('EXISTS', KEYS[1]) == 0 then return {'used'} end
|
|
52
73
|
local s = redis.call('HMGET', KEYS[1], 'status', 'jti', 'argsHash', 'tool', 'userId', 'conversationId', 'channel')
|
|
@@ -54,8 +75,58 @@ if s[1] ~= 'approved' or s[2] ~= ARGV[1] or s[3] ~= ARGV[2] or s[4] ~= ARGV[3] o
|
|
|
54
75
|
return {'mismatch'}
|
|
55
76
|
end
|
|
56
77
|
local record = redis.call('GET', KEYS[2])
|
|
57
|
-
redis.call('DEL', KEYS[1], KEYS[2])
|
|
78
|
+
redis.call('DEL', KEYS[1], KEYS[2], KEYS[3])
|
|
58
79
|
return {'ok', record, s[7]}`;
|
|
80
|
+
// KEYS: state, record, seen, withdrawn. ARGV: userId, conversationId, reason.
|
|
81
|
+
// Expire-now. A pending approval, or an approved one not yet redeemed, loses its state and record,
|
|
82
|
+
// which is exactly what its time running out does: a decision is then refused, a token redeemed
|
|
83
|
+
// after it answers 'used', and SETTLE finds "state gone, seen present". The memory (seen) is left
|
|
84
|
+
// as it is, so one the person had approved still says 'approved'; why it was withdrawn is kept
|
|
85
|
+
// beside it for as long as the memory lasts.
|
|
86
|
+
const WITHDRAW = `
|
|
87
|
+
if redis.call('EXISTS', KEYS[1]) == 0 then
|
|
88
|
+
local seen = redis.call('GET', KEYS[3])
|
|
89
|
+
if not seen then return 'unknown' end
|
|
90
|
+
local m = cjson.decode(seen)
|
|
91
|
+
if m['userId'] ~= ARGV[1] or m['conversationId'] ~= ARGV[2] then return 'unknown' end
|
|
92
|
+
return 'settled'
|
|
93
|
+
end
|
|
94
|
+
local o = redis.call('HMGET', KEYS[1], 'userId', 'conversationId')
|
|
95
|
+
if o[1] ~= ARGV[1] or o[2] ~= ARGV[2] then return 'unknown' end
|
|
96
|
+
redis.call('DEL', KEYS[1], KEYS[2])
|
|
97
|
+
local left = redis.call('PTTL', KEYS[3])
|
|
98
|
+
if left > 0 then redis.call('SET', KEYS[4], ARGV[3], 'PX', left) end
|
|
99
|
+
return 'withdrawn'`;
|
|
100
|
+
// KEYS: state, seen, settled, withdrawn. ARGV: userId, conversationId, lease ms.
|
|
101
|
+
// The approval's own keys lapse in Redis at its expiry, so "state gone, seen present" is this
|
|
102
|
+
// server's Redis saying it expired undecided. The claim is one SET NX: exactly one caller gets it,
|
|
103
|
+
// and it lapses unless confirmed. An approval that was withdrawn says why.
|
|
104
|
+
const SETTLE = `
|
|
105
|
+
if redis.call('EXISTS', KEYS[1]) == 1 then
|
|
106
|
+
local o = redis.call('HMGET', KEYS[1], 'userId', 'conversationId')
|
|
107
|
+
if o[1] ~= ARGV[1] or o[2] ~= ARGV[2] then return {'unknown'} end
|
|
108
|
+
return {'open'}
|
|
109
|
+
end
|
|
110
|
+
local seen = redis.call('GET', KEYS[2])
|
|
111
|
+
if not seen then return {'unknown'} end
|
|
112
|
+
local m = cjson.decode(seen)
|
|
113
|
+
if m['userId'] ~= ARGV[1] or m['conversationId'] ~= ARGV[2] then return {'unknown'} end
|
|
114
|
+
local withdrawn = redis.call('GET', KEYS[4]) or ''
|
|
115
|
+
if redis.call('SET', KEYS[3], 'lease', 'NX', 'PX', ARGV[3]) then return {'claimed', seen, withdrawn} end
|
|
116
|
+
return {'already', seen, withdrawn}`;
|
|
117
|
+
// KEYS: state, seen, settled. ARGV: userId, conversationId, memory ms.
|
|
118
|
+
// The expiry is stored in the conversation: the claim stays for as long as the memory of the approval does.
|
|
119
|
+
// A confirmation with no live claim (the lease lapsed while the host was storing) is still kept, and said.
|
|
120
|
+
const CONFIRM = `
|
|
121
|
+
if redis.call('EXISTS', KEYS[1]) == 1 then return 'unknown' end
|
|
122
|
+
local seen = redis.call('GET', KEYS[2])
|
|
123
|
+
if not seen then return 'unknown' end
|
|
124
|
+
local m = cjson.decode(seen)
|
|
125
|
+
if m['userId'] ~= ARGV[1] or m['conversationId'] ~= ARGV[2] then return 'unknown' end
|
|
126
|
+
local claimed = redis.call('EXISTS', KEYS[3])
|
|
127
|
+
redis.call('SET', KEYS[3], 'confirmed', 'PX', ARGV[3])
|
|
128
|
+
if claimed == 1 then return 'confirmed' end
|
|
129
|
+
return 'unclaimed'`;
|
|
59
130
|
// KEYS: state, record, declined. ARGV: userId. A live approval first, then a remembered decline.
|
|
60
131
|
const STATUS = `
|
|
61
132
|
if redis.call('EXISTS', KEYS[1]) == 1 then
|
|
@@ -75,7 +146,8 @@ const REFUSALS = {
|
|
|
75
146
|
used: 'this approval has already been used',
|
|
76
147
|
mismatch: 'this token is not for the stored call',
|
|
77
148
|
};
|
|
78
|
-
export function createApprovalStore(redis, signer, logger) {
|
|
149
|
+
export function createApprovalStore(redis, signer, logger, options = {}) {
|
|
150
|
+
const expiryClaimLeaseMs = options.expiryClaimLeaseMs ?? EXPIRY_CLAIM_LEASE_MS;
|
|
79
151
|
/** An approval as this user may see it. */
|
|
80
152
|
async function read(approvalId, userId) {
|
|
81
153
|
const [status, raw] = ((await redis.eval(STATUS, 3, stateKey(approvalId), recordKey(approvalId), declinedKey(approvalId), userId)) ??
|
|
@@ -117,7 +189,8 @@ export function createApprovalStore(redis, signer, logger) {
|
|
|
117
189
|
}
|
|
118
190
|
if (computed !== input.argsHash)
|
|
119
191
|
return refusal('invalid', 'argsHash does not match the arguments');
|
|
120
|
-
const
|
|
192
|
+
const seen = { userId: input.userId, conversationId: input.conversationId, expiresAt: input.expiresAt };
|
|
193
|
+
const result = await redis.eval(CREATE, 6, recordKey(input.approvalId), stateKey(input.approvalId), declinedKey(input.approvalId), seenKey(input.approvalId), settledKey(input.approvalId), withdrawnKey(input.approvalId), JSON.stringify(input), input.expiresAt - now, input.userId, input.conversationId, input.tool, input.argsHash, input.expiresAt, JSON.stringify(seen), input.expiresAt - now + EXPIRED_APPROVAL_MEMORY_MS);
|
|
121
194
|
if (result !== 'ok')
|
|
122
195
|
return refusal('decided', `an approval ${input.approvalId} already exists`);
|
|
123
196
|
logger.info(audit(input, { expiresAt: input.expiresAt }), 'Approval stored');
|
|
@@ -133,7 +206,11 @@ export function createApprovalStore(redis, signer, logger) {
|
|
|
133
206
|
tool: seen.kind === 'open' ? seen.pending.tool : '',
|
|
134
207
|
title: seen.kind === 'open' ? seen.pending.preview.title : '',
|
|
135
208
|
};
|
|
136
|
-
const [outcome, raw] = (await redis.eval(DECIDE,
|
|
209
|
+
const [outcome, raw] = (await redis.eval(DECIDE, 4, stateKey(approvalId), recordKey(approvalId), declinedKey(approvalId), seenKey(approvalId), userId, decision, Date.now(), jti, channel, DECLINED_TTL_SEC, JSON.stringify(declined),
|
|
210
|
+
// The memory of the approval once it is approved: written by the script, never re-encoded in Lua.
|
|
211
|
+
seen.kind === 'open'
|
|
212
|
+
? JSON.stringify({ userId: seen.pending.userId, conversationId: seen.pending.conversationId, expiresAt: seen.pending.expiresAt, decided: 'approved' })
|
|
213
|
+
: ''));
|
|
137
214
|
if (outcome !== 'approved' && outcome !== 'declined') {
|
|
138
215
|
logger.warn({ approvalId, userId, decision, channel, reason: outcome }, 'Approval decision refused');
|
|
139
216
|
return refusal(outcome, REFUSALS[outcome] ?? outcome);
|
|
@@ -170,7 +247,7 @@ export function createApprovalStore(redis, signer, logger) {
|
|
|
170
247
|
logger.warn({ ...context, tokenUser: claims.sub, reason: 'forbidden' }, 'Approval redemption refused');
|
|
171
248
|
return refusal('forbidden', "this approval is another user's or another conversation's");
|
|
172
249
|
}
|
|
173
|
-
const [outcome, raw, channel] = (await redis.eval(REDEEM,
|
|
250
|
+
const [outcome, raw, channel] = (await redis.eval(REDEEM, 3, stateKey(claims.aid), recordKey(claims.aid), seenKey(claims.aid), claims.jti, claims.ah, claims.tool, claims.sub, claims.cid));
|
|
174
251
|
if (outcome !== 'ok') {
|
|
175
252
|
logger.warn({ ...context, reason: outcome }, 'Approval redemption refused');
|
|
176
253
|
return refusal(outcome, REFUSALS[outcome] ?? outcome);
|
|
@@ -201,6 +278,36 @@ export function createApprovalStore(redis, signer, logger) {
|
|
|
201
278
|
return { approvalId, status: seen.status, tool: seen.pending.tool, title: seen.pending.preview.title };
|
|
202
279
|
return null;
|
|
203
280
|
},
|
|
281
|
+
async settleExpired(approvalId, { userId, conversationId }) {
|
|
282
|
+
const [outcome, raw, why] = ((await redis.eval(SETTLE, 4, stateKey(approvalId), seenKey(approvalId), settledKey(approvalId), withdrawnKey(approvalId), userId, conversationId, expiryClaimLeaseMs)) ?? []);
|
|
283
|
+
if (outcome === 'open')
|
|
284
|
+
return { approvalId, outcome };
|
|
285
|
+
if ((outcome === 'claimed' || outcome === 'already') && raw) {
|
|
286
|
+
const { expiresAt, decided } = JSON.parse(raw);
|
|
287
|
+
const withdrawn = APPROVAL_WITHDRAW_REASONS.includes(why ?? '') ? why : null;
|
|
288
|
+
if (outcome === 'claimed') {
|
|
289
|
+
logger.info({ approvalId, userId, conversationId, expiresAt, decided: decided ?? null, withdrawn }, 'Approval expired without running: claimed for a turn to store');
|
|
290
|
+
}
|
|
291
|
+
return { approvalId, outcome, expiresAt, ...(decided === 'approved' ? { decided } : {}), ...(withdrawn ? { withdrawn } : {}) };
|
|
292
|
+
}
|
|
293
|
+
return { approvalId, outcome: 'unknown' };
|
|
294
|
+
},
|
|
295
|
+
async withdraw(approvalId, { userId, conversationId }, reason) {
|
|
296
|
+
const outcome = (await redis.eval(WITHDRAW, 4, stateKey(approvalId), recordKey(approvalId), seenKey(approvalId), withdrawnKey(approvalId), userId, conversationId, reason));
|
|
297
|
+
if (outcome === 'withdrawn')
|
|
298
|
+
logger.info({ approvalId, userId, conversationId, reason }, 'Approval withdrawn before it was used');
|
|
299
|
+
return { approvalId, outcome: outcome === 'withdrawn' || outcome === 'settled' ? outcome : 'unknown' };
|
|
300
|
+
},
|
|
301
|
+
async confirmExpirySettled(approvalId, { userId, conversationId }) {
|
|
302
|
+
const outcome = await redis.eval(CONFIRM, 3, stateKey(approvalId), seenKey(approvalId), settledKey(approvalId), userId, conversationId, EXPIRED_APPROVAL_MEMORY_MS);
|
|
303
|
+
if (outcome === 'confirmed')
|
|
304
|
+
logger.info({ approvalId, userId, conversationId }, 'Approval expiry stored in the conversation');
|
|
305
|
+
if (outcome === 'unclaimed') {
|
|
306
|
+
// The turn's claim had lapsed before it confirmed: another turn may have claimed and stored the expiry too.
|
|
307
|
+
logger.warn({ approvalId, userId, conversationId }, 'Approval expiry confirmed with no live claim: the lease had lapsed, so it may be stored twice');
|
|
308
|
+
}
|
|
309
|
+
return outcome === 'confirmed' || outcome === 'unclaimed';
|
|
310
|
+
},
|
|
204
311
|
};
|
|
205
312
|
}
|
|
206
313
|
//# sourceMappingURL=store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/approvals/store.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/approvals/store.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ export type { OUIResultStore } from './oui/results.js';
|
|
|
24
24
|
export type { ResultsRedis, ResultsSubscriber } from './redis-waits.js';
|
|
25
25
|
export { createSettlementStore, SETTLEMENT_TTL_SEC } from './events/settlements.js';
|
|
26
26
|
export type { SettlementStore } from './events/settlements.js';
|
|
27
|
+
export { createTurnStopStore, TURN_STOP_TTL_SEC } from './turns/stops.js';
|
|
28
|
+
export type { TurnStopStore } from './turns/stops.js';
|
|
29
|
+
export { createTurnStopEvent } from './turns/client-event.js';
|
|
30
|
+
export { turnStopsRouter, MAX_STOP_WAIT_MS } from './turns/routes.js';
|
|
27
31
|
export { MAX_RESULT_WAIT_MS } from './http/routes.js';
|
|
28
32
|
export { createConsoleLogger } from './logger.js';
|
|
29
33
|
export type { RealtimeLogger } from './logger.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,WAAW,EACX,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACnF,YAAY,EACV,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACzF,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC9F,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACpF,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,WAAW,EACX,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACnF,YAAY,EACV,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACzF,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC9F,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACpF,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,9 @@ export { createRoomTokenSigner, DEFAULT_ROOM_TOKEN_TTL_MS } from './rooms/room-t
|
|
|
17
17
|
export { joinRefusal } from './rooms/join.js';
|
|
18
18
|
export { createOUIResultStore, RESULT_TTL_SEC, FINAL_RESULT_TTL_SEC } from './oui/results.js';
|
|
19
19
|
export { createSettlementStore, SETTLEMENT_TTL_SEC } from './events/settlements.js';
|
|
20
|
+
export { createTurnStopStore, TURN_STOP_TTL_SEC } from './turns/stops.js';
|
|
21
|
+
export { createTurnStopEvent } from './turns/client-event.js';
|
|
22
|
+
export { turnStopsRouter, MAX_STOP_WAIT_MS } from './turns/routes.js';
|
|
20
23
|
export { MAX_RESULT_WAIT_MS } from './http/routes.js';
|
|
21
24
|
export { createConsoleLogger } from './logger.js';
|
|
22
25
|
export { createApprovalStore, DECLINED_TTL_SEC } from './approvals/store.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAaH,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAQnF,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAEzF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG9F,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7E,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAaH,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAQnF,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAEzF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG9F,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAEpF,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7E,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { RealtimeServerConfig } from './types.js';
|
|
|
13
13
|
import { type RoomTokenSigner } from './rooms/room-token.js';
|
|
14
14
|
import { type OUIResultStore } from './oui/results.js';
|
|
15
15
|
import { type ApprovalStore } from './approvals/store.js';
|
|
16
|
+
import { type TurnStopStore } from './turns/stops.js';
|
|
16
17
|
import { type SettlementStore } from './events/settlements.js';
|
|
17
18
|
export interface RealtimeServerInstance {
|
|
18
19
|
io: Server;
|
|
@@ -27,6 +28,8 @@ export interface RealtimeServerInstance {
|
|
|
27
28
|
approvals: ApprovalStore;
|
|
28
29
|
/** Job settlements, when the server has event declarations. */
|
|
29
30
|
settlements: SettlementStore | null;
|
|
31
|
+
/** Stop requests for turns (ADR-0252). */
|
|
32
|
+
stops: TurnStopStore;
|
|
30
33
|
close(): Promise<void>;
|
|
31
34
|
}
|
|
32
35
|
export declare function createRealtimeServer(config: RealtimeServerConfig): Promise<RealtimeServerInstance>;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAK7B,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAK7B,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAM3E,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAWtF,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC;IACxB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,UAAU,EAAE,eAAe,CAAC;IAC5B,kCAAkC;IAClC,OAAO,EAAE,cAAc,CAAC;IACxB,qCAAqC;IACrC,SAAS,EAAE,aAAa,CAAC;IACzB,+DAA+D;IAC/D,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,0CAA0C;IAC1C,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAqBD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAsIxG"}
|
package/dist/server.js
CHANGED
|
@@ -22,6 +22,9 @@ import { createRoomTokenSigner } from './rooms/room-token.js';
|
|
|
22
22
|
import { createOUIResultStore } from './oui/results.js';
|
|
23
23
|
import { createApprovalTokenSigner } from './approvals/token.js';
|
|
24
24
|
import { createApprovalStore } from './approvals/store.js';
|
|
25
|
+
import { createTurnStopStore } from './turns/stops.js';
|
|
26
|
+
import { createTurnStopEvent } from './turns/client-event.js';
|
|
27
|
+
import { turnStopsRouter } from './turns/routes.js';
|
|
25
28
|
import { createApprovalDecideEvent } from './approvals/client-event.js';
|
|
26
29
|
import { approvalRouter } from './approvals/routes.js';
|
|
27
30
|
import { createDeclaredEvents } from './events/declared.js';
|
|
@@ -70,6 +73,7 @@ export async function createRealtimeServer(config) {
|
|
|
70
73
|
}
|
|
71
74
|
const results = createOUIResultStore(pub, resultsSub, logger);
|
|
72
75
|
const approvals = createApprovalStore(pub, approvalTokens, logger);
|
|
76
|
+
const stops = createTurnStopStore(pub, resultsSub, logger);
|
|
73
77
|
const settlements = declared ? createSettlementStore(pub, resultsSub, logger) : null;
|
|
74
78
|
const clientEvents = [
|
|
75
79
|
...createBuiltinClientEvents({
|
|
@@ -80,6 +84,7 @@ export async function createRealtimeServer(config) {
|
|
|
80
84
|
results: () => results,
|
|
81
85
|
}),
|
|
82
86
|
createApprovalDecideEvent(() => approvals),
|
|
87
|
+
createTurnStopEvent(config.roomPolicy, () => stops),
|
|
83
88
|
...(config.clientEvents ?? []),
|
|
84
89
|
];
|
|
85
90
|
assertUniqueClientEvents(clientEvents);
|
|
@@ -110,6 +115,7 @@ export async function createRealtimeServer(config) {
|
|
|
110
115
|
app.use(ouiResultsRouter(deps));
|
|
111
116
|
app.use(approvalRouter({ approvals, isInternalKey: deps.isInternalKey }));
|
|
112
117
|
app.use(settlementsRouter(deps));
|
|
118
|
+
app.use(turnStopsRouter({ stops: () => stops, isInternalKey: deps.isInternalKey, logger }));
|
|
113
119
|
// Authentication, once per socket, through the product's verifier.
|
|
114
120
|
io.use(async (socket, next) => {
|
|
115
121
|
const token = socket.handshake.auth?.token ||
|
|
@@ -159,6 +165,7 @@ export async function createRealtimeServer(config) {
|
|
|
159
165
|
results,
|
|
160
166
|
approvals,
|
|
161
167
|
settlements,
|
|
168
|
+
stops,
|
|
162
169
|
async close() {
|
|
163
170
|
await new Promise((resolve) => io.close(() => resolve()));
|
|
164
171
|
await Promise.all(redisClients.map((c) => c.quit().catch(() => c.disconnect())));
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAuB,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAA4B,MAAM,0BAA0B,CAAC;AACpH,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAwB,MAAM,uBAAuB,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAuB,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAsB,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAwB,MAAM,yBAAyB,CAAC;AACtF,OAAO,EACL,sBAAsB,EACtB,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GAElB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ClientEvent } from '../client-events/chain.js';
|
|
2
|
+
import type { RoomPolicy } from '../types.js';
|
|
3
|
+
import type { TurnStopStore } from './stops.js';
|
|
4
|
+
export declare function createTurnStopEvent(roomPolicy: RoomPolicy, stops: () => TurnStopStore): ClientEvent;
|
|
5
|
+
//# sourceMappingURL=client-event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-event.d.ts","sourceRoot":"","sources":["../../src/turns/client-event.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAOhD,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,aAAa,GAAG,WAAW,CAqBnG"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agent:turn_stop` (ADR-0252 §2.1): the person's Stop, on their own
|
|
3
|
+
* authenticated socket.
|
|
4
|
+
*
|
|
5
|
+
* What makes a stop the owner's is the key it is kept under, not the room.
|
|
6
|
+
* Who is asking is the socket's verified identity, never one the payload
|
|
7
|
+
* names; the stop is kept under that user; and a turn's worker reads only the
|
|
8
|
+
* stop of its turn's own user. So a stop from anyone else is never heard, and
|
|
9
|
+
* cannot stand in the way of the owner's.
|
|
10
|
+
*
|
|
11
|
+
* The room check is narrower than it may look. The server does not know which
|
|
12
|
+
* room belongs to which turn (each host names its own), so it cannot check
|
|
13
|
+
* that `room` is this turn's. It checks only that the socket is in the room it
|
|
14
|
+
* names and that the room is one that needs a token. A socket in any
|
|
15
|
+
* token-guarded room can therefore write a stop for any turn id, but only
|
|
16
|
+
* under its own user, where no other user's worker will read it. The check
|
|
17
|
+
* keeps the event to sockets that hold a room token at all, and nothing more.
|
|
18
|
+
*/
|
|
19
|
+
import Joi from 'joi';
|
|
20
|
+
import { TURN_STOP_EVENT } from '@ouispec/agent-core';
|
|
21
|
+
import { defineClientEvent } from '../client-events/chain.js';
|
|
22
|
+
const turnStopSchema = Joi.object({
|
|
23
|
+
turnId: Joi.string().min(1).max(128).required(),
|
|
24
|
+
room: Joi.string().min(1).max(200).required(),
|
|
25
|
+
});
|
|
26
|
+
export function createTurnStopEvent(roomPolicy, stops) {
|
|
27
|
+
return defineClientEvent({
|
|
28
|
+
name: TURN_STOP_EVENT,
|
|
29
|
+
schema: turnStopSchema,
|
|
30
|
+
maxBytes: 1024,
|
|
31
|
+
// A person presses Stop; a burst is a script.
|
|
32
|
+
rate: { perSecond: 2, burst: 5 },
|
|
33
|
+
authorize: ({ socket }, payload) => roomPolicy.isValidRoom(payload.room) && roomPolicy.requiresToken(payload.room) && socket.rooms.has(payload.room)
|
|
34
|
+
? { ok: true }
|
|
35
|
+
: { ok: false, reason: `not in the turn's room` },
|
|
36
|
+
refusal: (reason) => ({
|
|
37
|
+
ok: false,
|
|
38
|
+
reason: reason === `not in the turn's room` ? 'not_in_turn_room' : 'invalid',
|
|
39
|
+
}),
|
|
40
|
+
handle: async ({ user }, payload, ack) => {
|
|
41
|
+
const { first } = await stops().request(payload.turnId, user.userId, 'user_stop');
|
|
42
|
+
const result = { ok: true, stop: first ? 'requested' : 'already' };
|
|
43
|
+
ack?.(result);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=client-event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-event.js","sourceRoot":"","sources":["../../src/turns/client-event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,eAAe,EAA6C,MAAM,qBAAqB,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAoB,MAAM,2BAA2B,CAAC;AAIhF,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC;IAChC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC/C,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;CAC9C,CAAC,CAAC;AAEH,MAAM,UAAU,mBAAmB,CAAC,UAAsB,EAAE,KAA0B;IACpF,OAAO,iBAAiB,CAAkB;QACxC,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,cAAc;QACtB,QAAQ,EAAE,IAAI;QACd,8CAA8C;QAC9C,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;QAChC,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,CACjC,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;YAC9G,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE;YACd,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE;QACrD,OAAO,EAAE,CAAC,MAAM,EAAkB,EAAE,CAAC,CAAC;YACpC,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,MAAM,KAAK,wBAAwB,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS;SAC7E,CAAC;QACF,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;YACvC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YAClF,MAAM,MAAM,GAAmB,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;YACnF,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;QAChB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker's and the host's side of a turn stop (ADR-0252 §2.1). Both need
|
|
3
|
+
* the internal API key.
|
|
4
|
+
*
|
|
5
|
+
* POST /internal/turns/:turnId/stop { userId, reason }
|
|
6
|
+
* The host asks, for its user, that a turn stop: a newer message supersedes
|
|
7
|
+
* it, a chat is being deleted, or the tab could not reach its socket. The
|
|
8
|
+
* host has checked that the turn is `userId`'s.
|
|
9
|
+
* 200 — { ok: true, stop: 'requested' | 'already', record }
|
|
10
|
+
* 400 — userId missing, or reason not one of the stop reasons
|
|
11
|
+
*
|
|
12
|
+
* GET /internal/turns/:turnId/stop?userId=&waitMs=
|
|
13
|
+
* The stop asked for on a turn by its user, waiting up to `waitMs` (capped
|
|
14
|
+
* at 25 s) for one. A worker holds this open for the life of its turn.
|
|
15
|
+
* 200 — the TurnStopRecord
|
|
16
|
+
* 204 — no stop yet; ask again
|
|
17
|
+
* 400 — userId missing or waitMs invalid
|
|
18
|
+
*
|
|
19
|
+
* Both: 401 without the key, 503 while the store is not connected.
|
|
20
|
+
*/
|
|
21
|
+
import { Router } from 'express';
|
|
22
|
+
import type { RealtimeLogger } from '../logger.js';
|
|
23
|
+
import type { TurnStopStore } from './stops.js';
|
|
24
|
+
/** The longest one request waits for a stop. The worker asks again. */
|
|
25
|
+
export declare const MAX_STOP_WAIT_MS = 25000;
|
|
26
|
+
export interface TurnStopRouteDeps {
|
|
27
|
+
/** Null until Redis is connected. */
|
|
28
|
+
stops: () => TurnStopStore | null;
|
|
29
|
+
isInternalKey: (presented: string | undefined) => boolean;
|
|
30
|
+
logger: RealtimeLogger;
|
|
31
|
+
}
|
|
32
|
+
export declare function turnStopsRouter(deps: TurnStopRouteDeps): Router;
|
|
33
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/turns/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,MAAM,EAA+B,MAAM,SAAS,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,qCAAqC;IACrC,KAAK,EAAE,MAAM,aAAa,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC;IAC1D,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAgE/D"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker's and the host's side of a turn stop (ADR-0252 §2.1). Both need
|
|
3
|
+
* the internal API key.
|
|
4
|
+
*
|
|
5
|
+
* POST /internal/turns/:turnId/stop { userId, reason }
|
|
6
|
+
* The host asks, for its user, that a turn stop: a newer message supersedes
|
|
7
|
+
* it, a chat is being deleted, or the tab could not reach its socket. The
|
|
8
|
+
* host has checked that the turn is `userId`'s.
|
|
9
|
+
* 200 — { ok: true, stop: 'requested' | 'already', record }
|
|
10
|
+
* 400 — userId missing, or reason not one of the stop reasons
|
|
11
|
+
*
|
|
12
|
+
* GET /internal/turns/:turnId/stop?userId=&waitMs=
|
|
13
|
+
* The stop asked for on a turn by its user, waiting up to `waitMs` (capped
|
|
14
|
+
* at 25 s) for one. A worker holds this open for the life of its turn.
|
|
15
|
+
* 200 — the TurnStopRecord
|
|
16
|
+
* 204 — no stop yet; ask again
|
|
17
|
+
* 400 — userId missing or waitMs invalid
|
|
18
|
+
*
|
|
19
|
+
* Both: 401 without the key, 503 while the store is not connected.
|
|
20
|
+
*/
|
|
21
|
+
import { Router } from 'express';
|
|
22
|
+
import { isTurnStopReason } from '@ouispec/agent-core';
|
|
23
|
+
/** The longest one request waits for a stop. The worker asks again. */
|
|
24
|
+
export const MAX_STOP_WAIT_MS = 25_000;
|
|
25
|
+
export function turnStopsRouter(deps) {
|
|
26
|
+
const router = Router();
|
|
27
|
+
/** The store, or null after answering why the request cannot be served. */
|
|
28
|
+
function storeFor(req, res) {
|
|
29
|
+
if (!deps.isInternalKey(req.headers['x-api-key'])) {
|
|
30
|
+
res.status(401).json({ error: 'Invalid or missing API key' });
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const store = deps.stops();
|
|
34
|
+
if (!store)
|
|
35
|
+
res.status(503).json({ error: 'Turn stop store not connected' });
|
|
36
|
+
return store;
|
|
37
|
+
}
|
|
38
|
+
router.post('/internal/turns/:turnId/stop', async (req, res) => {
|
|
39
|
+
const store = storeFor(req, res);
|
|
40
|
+
if (!store)
|
|
41
|
+
return;
|
|
42
|
+
const turnId = req.params.turnId;
|
|
43
|
+
const { userId, reason } = (req.body ?? {});
|
|
44
|
+
if (typeof userId !== 'string' || !userId) {
|
|
45
|
+
res.status(400).json({ error: 'userId is required' });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!isTurnStopReason(reason)) {
|
|
49
|
+
res.status(400).json({ error: 'reason must be user_stop or superseded' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const { record, first } = await store.request(turnId, userId, reason);
|
|
54
|
+
res.json({ ok: true, stop: first ? 'requested' : 'already', record });
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
deps.logger.error({ err, turnId, userId }, 'Turn stop request failed');
|
|
58
|
+
res.status(500).json({ error: 'Turn stop request failed' });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
router.get('/internal/turns/:turnId/stop', async (req, res) => {
|
|
62
|
+
const store = storeFor(req, res);
|
|
63
|
+
if (!store)
|
|
64
|
+
return;
|
|
65
|
+
const turnId = req.params.turnId;
|
|
66
|
+
const userId = typeof req.query.userId === 'string' ? req.query.userId : '';
|
|
67
|
+
const waitMs = req.query.waitMs === undefined ? 0 : Number(req.query.waitMs);
|
|
68
|
+
if (!userId) {
|
|
69
|
+
res.status(400).json({ error: 'userId is required' });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!Number.isFinite(waitMs) || waitMs < 0) {
|
|
73
|
+
res.status(400).json({ error: 'waitMs must be a non-negative number' });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const record = await store.await(turnId, userId, Math.min(waitMs, MAX_STOP_WAIT_MS));
|
|
78
|
+
if (!record) {
|
|
79
|
+
res.status(204).end();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
res.json(record);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
deps.logger.error({ err, turnId, userId }, 'Turn stop lookup failed');
|
|
86
|
+
res.status(500).json({ error: 'Turn stop lookup failed' });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return router;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/turns/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stop requests for agent turns, held for the worker that runs the turn
|
|
3
|
+
* (ADR-0252 §2.1).
|
|
4
|
+
*
|
|
5
|
+
* The worker has no socket. A stop is written here, by the user's own socket
|
|
6
|
+
* or by the host's API, and the worker asks for it for as long as its turn
|
|
7
|
+
* runs. The request may arrive on one instance and the worker's wait be
|
|
8
|
+
* served by another, so both meet in Redis:
|
|
9
|
+
*
|
|
10
|
+
* turn:stop:{turnId}:{userId} → JSON TurnStopRecord, TTL 30 min, the first request stands
|
|
11
|
+
* channel turn:stops → `{turnId}:{userId}`, published when a stop is stored
|
|
12
|
+
*
|
|
13
|
+
* A stop is kept under the user who asked, and a wait is answered only with
|
|
14
|
+
* the stop of the user it asks for. The server keeps no record of who owns a
|
|
15
|
+
* turn, so this is what makes a stop the owner's: the worker asks for its
|
|
16
|
+
* turn's own user, and nobody else's request is ever read. One user's stop for
|
|
17
|
+
* another user's turn is a record nobody waits on.
|
|
18
|
+
*
|
|
19
|
+
* The record outlives any turn: a worker that starts late, or whose wait
|
|
20
|
+
* failed and was retried, still hears it.
|
|
21
|
+
*/
|
|
22
|
+
import { type TurnStopReason, type TurnStopRecord } from '@ouispec/agent-core';
|
|
23
|
+
import type { RealtimeLogger } from '../logger.js';
|
|
24
|
+
import { type ResultsRedis, type ResultsSubscriber } from '../redis-waits.js';
|
|
25
|
+
export interface TurnStopStore {
|
|
26
|
+
/**
|
|
27
|
+
* Record that `userId` asks for `turnId` to stop. The first request stands:
|
|
28
|
+
* a later one changes nothing and gets the first back, with `first: false`.
|
|
29
|
+
*/
|
|
30
|
+
request(turnId: string, userId: string, reason: TurnStopReason): Promise<{
|
|
31
|
+
record: TurnStopRecord;
|
|
32
|
+
first: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
/** The stop `userId` asked for on `turnId`, waiting up to `waitMs` for one. */
|
|
35
|
+
await(turnId: string, userId: string, waitMs: number): Promise<TurnStopRecord | null>;
|
|
36
|
+
/** Waits in progress on this instance. */
|
|
37
|
+
pendingCount(): number;
|
|
38
|
+
}
|
|
39
|
+
export declare const TURN_STOP_TTL_SEC: number;
|
|
40
|
+
export declare function createTurnStopStore(redis: ResultsRedis, subscriber: ResultsSubscriber, logger: RealtimeLogger): TurnStopStore;
|
|
41
|
+
//# sourceMappingURL=stops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stops.d.ts","sourceRoot":"","sources":["../../src/turns/stops.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAA2B,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACxG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAiB,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE7F,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,cAAc,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrH,+EAA+E;IAC/E,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACtF,0CAA0C;IAC1C,YAAY,IAAI,MAAM,CAAC;CACxB;AAGD,eAAO,MAAM,iBAAiB,QAA4C,CAAC;AAK3E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,cAAc,GAAG,aAAa,CA+B7H"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stop requests for agent turns, held for the worker that runs the turn
|
|
3
|
+
* (ADR-0252 §2.1).
|
|
4
|
+
*
|
|
5
|
+
* The worker has no socket. A stop is written here, by the user's own socket
|
|
6
|
+
* or by the host's API, and the worker asks for it for as long as its turn
|
|
7
|
+
* runs. The request may arrive on one instance and the worker's wait be
|
|
8
|
+
* served by another, so both meet in Redis:
|
|
9
|
+
*
|
|
10
|
+
* turn:stop:{turnId}:{userId} → JSON TurnStopRecord, TTL 30 min, the first request stands
|
|
11
|
+
* channel turn:stops → `{turnId}:{userId}`, published when a stop is stored
|
|
12
|
+
*
|
|
13
|
+
* A stop is kept under the user who asked, and a wait is answered only with
|
|
14
|
+
* the stop of the user it asks for. The server keeps no record of who owns a
|
|
15
|
+
* turn, so this is what makes a stop the owner's: the worker asks for its
|
|
16
|
+
* turn's own user, and nobody else's request is ever read. One user's stop for
|
|
17
|
+
* another user's turn is a record nobody waits on.
|
|
18
|
+
*
|
|
19
|
+
* The record outlives any turn: a worker that starts late, or whose wait
|
|
20
|
+
* failed and was retried, still hears it.
|
|
21
|
+
*/
|
|
22
|
+
import { TURN_STOP_RECORD_TTL_MS } from '@ouispec/agent-core';
|
|
23
|
+
import { createWaitHub } from '../redis-waits.js';
|
|
24
|
+
const CHANNEL = 'turn:stops';
|
|
25
|
+
export const TURN_STOP_TTL_SEC = Math.ceil(TURN_STOP_RECORD_TTL_MS / 1000);
|
|
26
|
+
const waitId = (turnId, userId) => `${turnId}:${userId}`;
|
|
27
|
+
const stopKey = (turnId, userId) => `turn:stop:${waitId(turnId, userId)}`;
|
|
28
|
+
export function createTurnStopStore(redis, subscriber, logger) {
|
|
29
|
+
const hub = createWaitHub(subscriber, CHANNEL, logger, 'Turn stops');
|
|
30
|
+
async function read(turnId, userId) {
|
|
31
|
+
const raw = await redis.get(stopKey(turnId, userId));
|
|
32
|
+
return raw ? JSON.parse(raw) : null;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
async request(turnId, userId, reason) {
|
|
36
|
+
const record = { turnId, by: userId, reason, at: Date.now() };
|
|
37
|
+
const written = await redis.set(stopKey(turnId, userId), JSON.stringify(record), 'EX', TURN_STOP_TTL_SEC, 'NX');
|
|
38
|
+
if (written !== 'OK') {
|
|
39
|
+
const held = await read(turnId, userId);
|
|
40
|
+
// Expired between the two commands: nothing stands, so this one does on a second try.
|
|
41
|
+
if (!held)
|
|
42
|
+
return this.request(turnId, userId, reason);
|
|
43
|
+
return { record: held, first: false };
|
|
44
|
+
}
|
|
45
|
+
await redis.publish(CHANNEL, waitId(turnId, userId));
|
|
46
|
+
logger.info({ turnId, userId, reason }, 'Turn stop requested');
|
|
47
|
+
return { record, first: true };
|
|
48
|
+
},
|
|
49
|
+
async await(turnId, userId, waitMs) {
|
|
50
|
+
return hub.wait(waitId(turnId, userId), waitMs, () => read(turnId, userId), (err) => logger.error({ err, turnId }, 'Turn stop read failed'));
|
|
51
|
+
},
|
|
52
|
+
pendingCount: () => hub.pendingCount(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=stops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stops.js","sourceRoot":"","sources":["../../src/turns/stops.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,uBAAuB,EAA4C,MAAM,qBAAqB,CAAC;AAExG,OAAO,EAAE,aAAa,EAA6C,MAAM,mBAAmB,CAAC;AAc7F,MAAM,OAAO,GAAG,YAAY,CAAC;AAC7B,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAC;AAE3E,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,MAAc,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AACzE,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,MAAc,EAAE,EAAE,CAAC,aAAa,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AAE1F,MAAM,UAAU,mBAAmB,CAAC,KAAmB,EAAE,UAA6B,EAAE,MAAsB;IAC5G,MAAM,GAAG,GAAG,aAAa,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IAErE,KAAK,UAAU,IAAI,CAAC,MAAc,EAAE,MAAc;QAChD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACrD,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,CAAC;IAED,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM;YAClC,MAAM,MAAM,GAAmB,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC9E,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,CAAC,CAAC;YAChH,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBACxC,sFAAsF;gBACtF,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;gBACvD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YACxC,CAAC;YACD,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,qBAAqB,CAAC,CAAC;YAC/D,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACjC,CAAC;QAED,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM;YAChC,OAAO,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAClF,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,uBAAuB,CAAC,CACvD,CAAC;QACJ,CAAC;QAED,YAAY,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,YAAY,EAAE;KACvC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouispec/agent-realtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Realtime server for agent turns and UI actions — declared client events, room tokens, the UI action result store, the approval store, declared product events and their job settlements, Redis fan-out; the product supplies auth, room policy and keys",
|
|
5
5
|
"homepage": "https://github.com/wesreid/oui/tree/main/packages/agent-realtime#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"ioredis": "^5.6.0",
|
|
38
38
|
"joi": "^17.13.3",
|
|
39
39
|
"socket.io": "^4.8.0",
|
|
40
|
-
"@ouispec/agent-core": "^0.
|
|
41
|
-
"@ouispec/agent-events": "^0.
|
|
42
|
-
"oui-spec": "^0.
|
|
40
|
+
"@ouispec/agent-core": "^0.7.0",
|
|
41
|
+
"@ouispec/agent-events": "^0.7.0",
|
|
42
|
+
"oui-spec": "^0.10.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/cors": "^2.8.0",
|