@adonis-agora/agent 0.76.3 → 0.77.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/CHANGELOG.md +16 -0
- package/dist/providers/agent_provider.d.ts.map +1 -1
- package/dist/providers/agent_provider.js +9 -0
- package/dist/providers/agent_provider.js.map +1 -1
- package/dist/providers/mcp_provider.d.ts.map +1 -1
- package/dist/providers/mcp_provider.js +46 -10
- package/dist/providers/mcp_provider.js.map +1 -1
- package/dist/providers/poppy_connections_provider.d.ts +17 -0
- package/dist/providers/poppy_connections_provider.d.ts.map +1 -0
- package/dist/providers/poppy_connections_provider.js +186 -0
- package/dist/providers/poppy_connections_provider.js.map +1 -0
- package/dist/providers/poppy_provider.d.ts +12 -0
- package/dist/providers/poppy_provider.d.ts.map +1 -0
- package/dist/providers/poppy_provider.js +159 -0
- package/dist/providers/poppy_provider.js.map +1 -0
- package/dist/src/a2a/handler.d.ts.map +1 -1
- package/dist/src/a2a/handler.js +4 -2
- package/dist/src/a2a/handler.js.map +1 -1
- package/dist/src/a2a/turn.d.ts +27 -4
- package/dist/src/a2a/turn.d.ts.map +1 -1
- package/dist/src/a2a/turn.js +66 -12
- package/dist/src/a2a/turn.js.map +1 -1
- package/dist/src/action-proposal-approval.d.ts.map +1 -1
- package/dist/src/action-proposal-approval.js +3 -3
- package/dist/src/action-proposal-approval.js.map +1 -1
- package/dist/src/action-proposal-listeners.d.ts +12 -0
- package/dist/src/action-proposal-listeners.d.ts.map +1 -0
- package/dist/src/action-proposal-listeners.js +27 -0
- package/dist/src/action-proposal-listeners.js.map +1 -0
- package/dist/src/action-proposal-receipt.d.ts +4 -0
- package/dist/src/action-proposal-receipt.d.ts.map +1 -1
- package/dist/src/action-proposal-receipt.js +3 -1
- package/dist/src/action-proposal-receipt.js.map +1 -1
- package/dist/src/action-proposal-worker.d.ts +6 -1
- package/dist/src/action-proposal-worker.d.ts.map +1 -1
- package/dist/src/action-proposal-worker.js +10 -1
- package/dist/src/action-proposal-worker.js.map +1 -1
- package/dist/src/agent-loop.d.ts.map +1 -1
- package/dist/src/agent-loop.js +8 -5
- package/dist/src/agent-loop.js.map +1 -1
- package/dist/src/channels/handler.d.ts +10 -0
- package/dist/src/channels/handler.d.ts.map +1 -1
- package/dist/src/channels/handler.js +44 -1
- package/dist/src/channels/handler.js.map +1 -1
- package/dist/src/channels/texts.d.ts +9 -0
- package/dist/src/channels/texts.d.ts.map +1 -1
- package/dist/src/channels/texts.js +4 -0
- package/dist/src/channels/texts.js.map +1 -1
- package/dist/src/client/sse.d.ts.map +1 -1
- package/dist/src/client/sse.js +3 -0
- package/dist/src/client/sse.js.map +1 -1
- package/dist/src/define_config.d.ts +3 -0
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/auth.d.ts +17 -0
- package/dist/src/mcp/auth.d.ts.map +1 -1
- package/dist/src/mcp/auth.js +56 -1
- package/dist/src/mcp/auth.js.map +1 -1
- package/dist/src/mcp/discovery.d.ts +13 -2
- package/dist/src/mcp/discovery.d.ts.map +1 -1
- package/dist/src/mcp/discovery.js +17 -2
- package/dist/src/mcp/discovery.js.map +1 -1
- package/dist/src/mcp/index.d.ts +1 -1
- package/dist/src/mcp/index.d.ts.map +1 -1
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/mcp/scope.d.ts +13 -0
- package/dist/src/mcp/scope.d.ts.map +1 -0
- package/dist/src/mcp/scope.js +24 -0
- package/dist/src/mcp/scope.js.map +1 -0
- package/dist/src/mcp/server.d.ts +6 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/poppy/auth.d.ts +77 -0
- package/dist/src/poppy/auth.d.ts.map +1 -0
- package/dist/src/poppy/auth.js +16 -0
- package/dist/src/poppy/auth.js.map +1 -0
- package/dist/src/poppy/conversations.d.ts +249 -0
- package/dist/src/poppy/conversations.d.ts.map +1 -0
- package/dist/src/poppy/conversations.js +1017 -0
- package/dist/src/poppy/conversations.js.map +1 -0
- package/dist/src/poppy/define_config.d.ts +98 -0
- package/dist/src/poppy/define_config.d.ts.map +1 -0
- package/dist/src/poppy/define_config.js +11 -0
- package/dist/src/poppy/define_config.js.map +1 -0
- package/dist/src/poppy/endpoints.d.ts +25 -0
- package/dist/src/poppy/endpoints.d.ts.map +1 -0
- package/dist/src/poppy/endpoints.js +31 -0
- package/dist/src/poppy/endpoints.js.map +1 -0
- package/dist/src/poppy/handler.d.ts +80 -0
- package/dist/src/poppy/handler.d.ts.map +1 -0
- package/dist/src/poppy/handler.js +362 -0
- package/dist/src/poppy/handler.js.map +1 -0
- package/dist/src/poppy/index.d.ts +17 -0
- package/dist/src/poppy/index.d.ts.map +1 -0
- package/dist/src/poppy/index.js +9 -0
- package/dist/src/poppy/index.js.map +1 -0
- package/dist/src/poppy/protocol.d.ts +132 -0
- package/dist/src/poppy/protocol.d.ts.map +1 -0
- package/dist/src/poppy/protocol.js +178 -0
- package/dist/src/poppy/protocol.js.map +1 -0
- package/dist/src/poppy/redis-store.d.ts +27 -0
- package/dist/src/poppy/redis-store.d.ts.map +1 -0
- package/dist/src/poppy/redis-store.js +148 -0
- package/dist/src/poppy/redis-store.js.map +1 -0
- package/dist/src/poppy/runtime.d.ts +6 -0
- package/dist/src/poppy/runtime.d.ts.map +1 -0
- package/dist/src/poppy/runtime.js +12 -0
- package/dist/src/poppy/runtime.js.map +1 -0
- package/dist/src/poppy/server_middleware.d.ts +11 -0
- package/dist/src/poppy/server_middleware.d.ts.map +1 -0
- package/dist/src/poppy/server_middleware.js +16 -0
- package/dist/src/poppy/server_middleware.js.map +1 -0
- package/dist/src/poppy/store.d.ts +207 -0
- package/dist/src/poppy/store.d.ts.map +1 -0
- package/dist/src/poppy/store.js +444 -0
- package/dist/src/poppy/store.js.map +1 -0
- package/dist/src/poppy-client/api.d.ts +36 -0
- package/dist/src/poppy-client/api.d.ts.map +1 -0
- package/dist/src/poppy-client/api.js +77 -0
- package/dist/src/poppy-client/api.js.map +1 -0
- package/dist/src/poppy-client/bin/poppy-agent.d.ts +3 -0
- package/dist/src/poppy-client/bin/poppy-agent.d.ts.map +1 -0
- package/dist/src/poppy-client/bin/poppy-agent.js +9 -0
- package/dist/src/poppy-client/bin/poppy-agent.js.map +1 -0
- package/dist/src/poppy-client/browser.d.ts +24 -0
- package/dist/src/poppy-client/browser.d.ts.map +1 -0
- package/dist/src/poppy-client/browser.js +83 -0
- package/dist/src/poppy-client/browser.js.map +1 -0
- package/dist/src/poppy-client/cli/main.d.ts +2 -0
- package/dist/src/poppy-client/cli/main.d.ts.map +1 -0
- package/dist/src/poppy-client/cli/main.js +776 -0
- package/dist/src/poppy-client/cli/main.js.map +1 -0
- package/dist/src/poppy-client/cli/prompt.d.ts +13 -0
- package/dist/src/poppy-client/cli/prompt.d.ts.map +1 -0
- package/dist/src/poppy-client/cli/prompt.js +50 -0
- package/dist/src/poppy-client/cli/prompt.js.map +1 -0
- package/dist/src/poppy-client/company.d.ts +87 -0
- package/dist/src/poppy-client/company.d.ts.map +1 -0
- package/dist/src/poppy-client/company.js +336 -0
- package/dist/src/poppy-client/company.js.map +1 -0
- package/dist/src/poppy-client/conversations.d.ts +89 -0
- package/dist/src/poppy-client/conversations.d.ts.map +1 -0
- package/dist/src/poppy-client/conversations.js +332 -0
- package/dist/src/poppy-client/conversations.js.map +1 -0
- package/dist/src/poppy-client/discovery.d.ts +41 -0
- package/dist/src/poppy-client/discovery.d.ts.map +1 -0
- package/dist/src/poppy-client/discovery.js +217 -0
- package/dist/src/poppy-client/discovery.js.map +1 -0
- package/dist/src/poppy-client/dpop.d.ts +19 -0
- package/dist/src/poppy-client/dpop.d.ts.map +1 -0
- package/dist/src/poppy-client/dpop.js +43 -0
- package/dist/src/poppy-client/dpop.js.map +1 -0
- package/dist/src/poppy-client/errors.d.ts +50 -0
- package/dist/src/poppy-client/errors.d.ts.map +1 -0
- package/dist/src/poppy-client/errors.js +55 -0
- package/dist/src/poppy-client/errors.js.map +1 -0
- package/dist/src/poppy-client/http.d.ts +40 -0
- package/dist/src/poppy-client/http.d.ts.map +1 -0
- package/dist/src/poppy-client/http.js +95 -0
- package/dist/src/poppy-client/http.js.map +1 -0
- package/dist/src/poppy-client/identity.d.ts +61 -0
- package/dist/src/poppy-client/identity.d.ts.map +1 -0
- package/dist/src/poppy-client/identity.js +111 -0
- package/dist/src/poppy-client/identity.js.map +1 -0
- package/dist/src/poppy-client/identity_server.d.ts +29 -0
- package/dist/src/poppy-client/identity_server.d.ts.map +1 -0
- package/dist/src/poppy-client/identity_server.js +84 -0
- package/dist/src/poppy-client/identity_server.js.map +1 -0
- package/dist/src/poppy-client/index.d.ts +19 -0
- package/dist/src/poppy-client/index.d.ts.map +1 -0
- package/dist/src/poppy-client/index.js +18 -0
- package/dist/src/poppy-client/index.js.map +1 -0
- package/dist/src/poppy-client/keys.d.ts +13 -0
- package/dist/src/poppy-client/keys.d.ts.map +1 -0
- package/dist/src/poppy-client/keys.js +31 -0
- package/dist/src/poppy-client/keys.js.map +1 -0
- package/dist/src/poppy-client/llm.d.ts +31 -0
- package/dist/src/poppy-client/llm.d.ts.map +1 -0
- package/dist/src/poppy-client/llm.js +230 -0
- package/dist/src/poppy-client/llm.js.map +1 -0
- package/dist/src/poppy-client/mcp.d.ts +16 -0
- package/dist/src/poppy-client/mcp.d.ts.map +1 -0
- package/dist/src/poppy-client/mcp.js +57 -0
- package/dist/src/poppy-client/mcp.js.map +1 -0
- package/dist/src/poppy-client/open_url.d.ts +3 -0
- package/dist/src/poppy-client/open_url.d.ts.map +1 -0
- package/dist/src/poppy-client/open_url.js +13 -0
- package/dist/src/poppy-client/open_url.js.map +1 -0
- package/dist/src/poppy-client/signin.d.ts +59 -0
- package/dist/src/poppy-client/signin.d.ts.map +1 -0
- package/dist/src/poppy-client/signin.js +210 -0
- package/dist/src/poppy-client/signin.js.map +1 -0
- package/dist/src/poppy-client/sse.d.ts +15 -0
- package/dist/src/poppy-client/sse.d.ts.map +1 -0
- package/dist/src/poppy-client/sse.js +74 -0
- package/dist/src/poppy-client/sse.js.map +1 -0
- package/dist/src/poppy-client/store.d.ts +89 -0
- package/dist/src/poppy-client/store.d.ts.map +1 -0
- package/dist/src/poppy-client/store.js +79 -0
- package/dist/src/poppy-client/store.js.map +1 -0
- package/dist/src/poppy-client/token_client.d.ts +69 -0
- package/dist/src/poppy-client/token_client.d.ts.map +1 -0
- package/dist/src/poppy-client/token_client.js +154 -0
- package/dist/src/poppy-client/token_client.js.map +1 -0
- package/dist/src/poppy-client/types.d.ts +184 -0
- package/dist/src/poppy-client/types.d.ts.map +1 -0
- package/dist/src/poppy-client/types.js +8 -0
- package/dist/src/poppy-client/types.js.map +1 -0
- package/dist/src/poppy-client/util.d.ts +42 -0
- package/dist/src/poppy-client/util.d.ts.map +1 -0
- package/dist/src/poppy-client/util.js +122 -0
- package/dist/src/poppy-client/util.js.map +1 -0
- package/dist/src/poppy-connections/config.d.ts +134 -0
- package/dist/src/poppy-connections/config.d.ts.map +1 -0
- package/dist/src/poppy-connections/config.js +44 -0
- package/dist/src/poppy-connections/config.js.map +1 -0
- package/dist/src/poppy-connections/crypto.d.ts +12 -0
- package/dist/src/poppy-connections/crypto.d.ts.map +1 -0
- package/dist/src/poppy-connections/crypto.js +36 -0
- package/dist/src/poppy-connections/crypto.js.map +1 -0
- package/dist/src/poppy-connections/http.d.ts +63 -0
- package/dist/src/poppy-connections/http.d.ts.map +1 -0
- package/dist/src/poppy-connections/http.js +217 -0
- package/dist/src/poppy-connections/http.js.map +1 -0
- package/dist/src/poppy-connections/index.d.ts +14 -0
- package/dist/src/poppy-connections/index.d.ts.map +1 -0
- package/dist/src/poppy-connections/index.js +17 -0
- package/dist/src/poppy-connections/index.js.map +1 -0
- package/dist/src/poppy-connections/resume.d.ts +46 -0
- package/dist/src/poppy-connections/resume.d.ts.map +1 -0
- package/dist/src/poppy-connections/resume.js +109 -0
- package/dist/src/poppy-connections/resume.js.map +1 -0
- package/dist/src/poppy-connections/runtime.d.ts +4 -0
- package/dist/src/poppy-connections/runtime.d.ts.map +1 -0
- package/dist/src/poppy-connections/runtime.js +9 -0
- package/dist/src/poppy-connections/runtime.js.map +1 -0
- package/dist/src/poppy-connections/server_middleware.d.ts +10 -0
- package/dist/src/poppy-connections/server_middleware.d.ts.map +1 -0
- package/dist/src/poppy-connections/server_middleware.js +14 -0
- package/dist/src/poppy-connections/server_middleware.js.map +1 -0
- package/dist/src/poppy-connections/service.d.ts +179 -0
- package/dist/src/poppy-connections/service.d.ts.map +1 -0
- package/dist/src/poppy-connections/service.js +648 -0
- package/dist/src/poppy-connections/service.js.map +1 -0
- package/dist/src/poppy-connections/store.d.ts +139 -0
- package/dist/src/poppy-connections/store.d.ts.map +1 -0
- package/dist/src/poppy-connections/store.js +260 -0
- package/dist/src/poppy-connections/store.js.map +1 -0
- package/dist/src/poppy-connections/tools.d.ts +65 -0
- package/dist/src/poppy-connections/tools.d.ts.map +1 -0
- package/dist/src/poppy-connections/tools.js +574 -0
- package/dist/src/poppy-connections/tools.js.map +1 -0
- package/dist/src/react/core/approvals/use-action-proposals.d.ts +6 -1
- package/dist/src/react/core/approvals/use-action-proposals.d.ts.map +1 -1
- package/dist/src/react/core/approvals/use-action-proposals.js +18 -3
- package/dist/src/react/core/approvals/use-action-proposals.js.map +1 -1
- package/dist/src/react/core/use-agent-chat.d.ts +6 -0
- package/dist/src/react/core/use-agent-chat.d.ts.map +1 -1
- package/dist/src/react/core/use-agent-chat.js +47 -1
- package/dist/src/react/core/use-agent-chat.js.map +1 -1
- package/dist/src/tool-presentation.d.ts +6 -0
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +1 -0
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/types.d.ts +6 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +41 -2
- package/skills/agent-governance/SKILL.md +13 -2
- package/skills/agent-personas-agents/SKILL.md +1 -1
- package/skills/agent-tools/SKILL.md +20 -3
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { LucidClientLike, LucidRawRunner } from '../stores/lucid.js';
|
|
2
|
+
import type { Actor } from '../types.js';
|
|
3
|
+
import type { PoppyContext, PoppyEvent, PoppyEventBody, PoppyResponder, PoppyStatus } from './protocol.js';
|
|
4
|
+
/**
|
|
5
|
+
* A Poppy conversation as this surface keeps it. It belongs to a Personal Agent (`clientId`) and
|
|
6
|
+
* one of its Users (`userId`); once it used an account here, to that account too (`accountRef`,
|
|
7
|
+
* §7.2). The agent side of it is an agent thread (`threadId`) — a Direct Conversation shares its
|
|
8
|
+
* parent's thread, which is how the Company Agent knows what was said in either (§7.10).
|
|
9
|
+
*/
|
|
10
|
+
export interface PoppyConversationRecord {
|
|
11
|
+
id: string;
|
|
12
|
+
clientId: string;
|
|
13
|
+
userId: string;
|
|
14
|
+
accountRef: string | null;
|
|
15
|
+
agentName: string;
|
|
16
|
+
threadId: string | null;
|
|
17
|
+
/** Set on a Direct Conversation: the conversation it was opened from. */
|
|
18
|
+
parentId: string | null;
|
|
19
|
+
/** On a parent: its open Direct Conversation. */
|
|
20
|
+
openDirectId: string | null;
|
|
21
|
+
status: PoppyStatus;
|
|
22
|
+
responder: PoppyResponder;
|
|
23
|
+
/** The User's context as last shared — fields a message leaves out keep their value (§7.4). */
|
|
24
|
+
context: PoppyContext;
|
|
25
|
+
/** The run whose reply is being written, while there is one. */
|
|
26
|
+
activeRunId: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* The position of the last message event a turn has taken up. The event log IS the turn queue:
|
|
29
|
+
* user messages after it are what the next turn answers — on whichever replica picks it up.
|
|
30
|
+
*/
|
|
31
|
+
turnSeq: number;
|
|
32
|
+
/** Who the latest request came as — what the next turn runs as. */
|
|
33
|
+
grant: PoppyGrant | null;
|
|
34
|
+
/** Events at or below this sequence number were pruned: a cursor there is `cursor_expired`. */
|
|
35
|
+
prunedSeq: number;
|
|
36
|
+
createdAt: number;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
/** The identity a conversation's turns run as: that of its latest request (§7.11). */
|
|
40
|
+
export interface PoppyGrant {
|
|
41
|
+
actor: Actor;
|
|
42
|
+
/** The token's account scopes; empty when signed out. */
|
|
43
|
+
scopes: string[];
|
|
44
|
+
signedIn: boolean;
|
|
45
|
+
}
|
|
46
|
+
export type PoppyConversationPatch = Partial<Pick<PoppyConversationRecord, 'accountRef' | 'threadId' | 'openDirectId' | 'status' | 'responder' | 'context' | 'activeRunId' | 'turnSeq' | 'grant' | 'prunedSeq'>>;
|
|
47
|
+
/** An event with its position in the conversation. */
|
|
48
|
+
export interface StoredPoppyEvent {
|
|
49
|
+
seq: number;
|
|
50
|
+
event: PoppyEvent;
|
|
51
|
+
}
|
|
52
|
+
/** What an accepted message keeps for its retries (§7.3). */
|
|
53
|
+
export interface PoppyMessageReceipt {
|
|
54
|
+
fingerprint: string;
|
|
55
|
+
conversationId: string;
|
|
56
|
+
/** The HTTP status and body the first delivery got. */
|
|
57
|
+
response: {
|
|
58
|
+
status: number;
|
|
59
|
+
body: Record<string, unknown>;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export type PoppyMessageClaim = {
|
|
63
|
+
status: 'claimed';
|
|
64
|
+
} | ({
|
|
65
|
+
status: 'existing';
|
|
66
|
+
} & PoppyMessageReceipt);
|
|
67
|
+
/**
|
|
68
|
+
* Where conversations, their events and message receipts live. Events are append-only and read by
|
|
69
|
+
* position, so a cursor replays exactly what came after it, on any replica.
|
|
70
|
+
*
|
|
71
|
+
* @experimental Tracks Personal Agent Protocol Draft 0.1 (https://personalagentprotocol.org/docs/spec),
|
|
72
|
+
* a spec still in development: this API WILL change as the spec evolves — possibly in breaking
|
|
73
|
+
* ways, outside semver majors while it is a draft.
|
|
74
|
+
*/
|
|
75
|
+
export interface PoppyStore {
|
|
76
|
+
createConversation(record: PoppyConversationRecord): Promise<void>;
|
|
77
|
+
getConversation(id: string): Promise<PoppyConversationRecord | null>;
|
|
78
|
+
updateConversation(id: string, patch: PoppyConversationPatch): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Append an event, numbered after the conversation's last. With `key`, at most one event per key
|
|
81
|
+
* is ever appended: a second append answers the first event with `created: false` — what keeps a
|
|
82
|
+
* re-read turn from writing its reply twice.
|
|
83
|
+
*/
|
|
84
|
+
appendEvent(conversationId: string, body: PoppyEventBody, options?: {
|
|
85
|
+
key?: string;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
event: StoredPoppyEvent;
|
|
88
|
+
created: boolean;
|
|
89
|
+
}>;
|
|
90
|
+
/** Events after `afterSeq`, in order, at most `limit`. */
|
|
91
|
+
listEvents(conversationId: string, afterSeq: number, limit: number): Promise<StoredPoppyEvent[]>;
|
|
92
|
+
/** The position of an event, `null` when the conversation has no such event (any more). */
|
|
93
|
+
findEventSeq(conversationId: string, eventId: string): Promise<number | null>;
|
|
94
|
+
/** Drop the events created before `before` (epoch ms), keeping at least the last one. */
|
|
95
|
+
pruneEvents(conversationId: string, before: number): Promise<void>;
|
|
96
|
+
/** Claim `messageId` for `owner` — atomically, so two deliveries of one message are one. */
|
|
97
|
+
claimMessage(owner: string, messageId: string, receipt: PoppyMessageReceipt): Promise<PoppyMessageClaim>;
|
|
98
|
+
/** Drop a claim whose message could not be taken, so its retry is processed afresh. */
|
|
99
|
+
releaseMessage(owner: string, messageId: string): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Take (or renew) the right to write the reply of the conversation's active run for `ttlMs` —
|
|
102
|
+
* `false` while another holder's lease is live. What lets any replica pick up a reply whose
|
|
103
|
+
* writer died, without two writing it at once.
|
|
104
|
+
*/
|
|
105
|
+
leaseReader(conversationId: string, holder: string, ttlMs: number): Promise<boolean>;
|
|
106
|
+
releaseReader(conversationId: string, holder: string): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
/** `evt_<seq in base 36>_<random>`: unique, and its position survives the event being pruned. */
|
|
109
|
+
export declare function newEventId(seq: number): string;
|
|
110
|
+
/** The position an event id was minted at, or `null` for an id this surface never minted. */
|
|
111
|
+
export declare function eventIdSeq(id: string): number | null;
|
|
112
|
+
/** Process-local {@link PoppyStore} — tests and single-instance dev. */
|
|
113
|
+
export declare class InMemoryPoppyStore implements PoppyStore {
|
|
114
|
+
#private;
|
|
115
|
+
createConversation(record: PoppyConversationRecord): Promise<void>;
|
|
116
|
+
getConversation(id: string): Promise<PoppyConversationRecord | null>;
|
|
117
|
+
updateConversation(id: string, patch: PoppyConversationPatch): Promise<void>;
|
|
118
|
+
appendEvent(conversationId: string, body: PoppyEventBody, options?: {
|
|
119
|
+
key?: string;
|
|
120
|
+
}): Promise<{
|
|
121
|
+
event: {
|
|
122
|
+
seq: number;
|
|
123
|
+
event: PoppyEvent;
|
|
124
|
+
};
|
|
125
|
+
created: boolean;
|
|
126
|
+
}>;
|
|
127
|
+
listEvents(conversationId: string, afterSeq: number, limit: number): Promise<{
|
|
128
|
+
seq: number;
|
|
129
|
+
event: PoppyEvent;
|
|
130
|
+
}[]>;
|
|
131
|
+
findEventSeq(conversationId: string, eventId: string): Promise<number | null>;
|
|
132
|
+
pruneEvents(conversationId: string, before: number): Promise<void>;
|
|
133
|
+
claimMessage(owner: string, messageId: string, receipt: PoppyMessageReceipt): Promise<{
|
|
134
|
+
fingerprint: string;
|
|
135
|
+
conversationId: string;
|
|
136
|
+
/** The HTTP status and body the first delivery got. */
|
|
137
|
+
response: {
|
|
138
|
+
status: number;
|
|
139
|
+
body: Record<string, unknown>;
|
|
140
|
+
};
|
|
141
|
+
status: 'existing';
|
|
142
|
+
} | {
|
|
143
|
+
status: 'claimed';
|
|
144
|
+
}>;
|
|
145
|
+
releaseMessage(owner: string, messageId: string): Promise<void>;
|
|
146
|
+
leaseReader(conversationId: string, holder: string, ttlMs: number): Promise<boolean>;
|
|
147
|
+
releaseReader(conversationId: string, holder: string): Promise<void>;
|
|
148
|
+
}
|
|
149
|
+
export declare const POPPY_TABLES: {
|
|
150
|
+
readonly conversations: 'agent_poppy_conversation';
|
|
151
|
+
readonly events: 'agent_poppy_event';
|
|
152
|
+
readonly messages: 'agent_poppy_message';
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* `CREATE TABLE IF NOT EXISTS` DDL for the Poppy tables — the agent tables' portable dialect
|
|
156
|
+
* (quoted identifiers, epoch-ms `BIGINT`, `TEXT` JSON; `forDialect` adapts it to MySQL). An event's
|
|
157
|
+
* primary key is its position, so two appends racing for one position cannot both win; a message's
|
|
158
|
+
* is its (owner, id), the idempotency lock of §7.3.
|
|
159
|
+
*/
|
|
160
|
+
export declare function poppyTableStatements(): string[];
|
|
161
|
+
/** Create the Poppy tables if missing. Idempotent. */
|
|
162
|
+
export declare function ensurePoppyTables(db: LucidRawRunner): Promise<void>;
|
|
163
|
+
export interface LucidPoppyStoreOptions {
|
|
164
|
+
/** Create the tables on first use (`CREATE TABLE IF NOT EXISTS`). Default `true`. */
|
|
165
|
+
autoCreateTables?: boolean;
|
|
166
|
+
}
|
|
167
|
+
/** {@link PoppyStore} over Lucid (any of SQLite / Postgres / MySQL). */
|
|
168
|
+
export declare class LucidPoppyStore implements PoppyStore {
|
|
169
|
+
#private;
|
|
170
|
+
private readonly db;
|
|
171
|
+
constructor(db: LucidClientLike & LucidRawRunner, options?: LucidPoppyStoreOptions);
|
|
172
|
+
/** Provision the tables (once). A failure is retried on the next call. */
|
|
173
|
+
ready(): Promise<void>;
|
|
174
|
+
createConversation(record: PoppyConversationRecord): Promise<void>;
|
|
175
|
+
getConversation(id: string): Promise<PoppyConversationRecord | null>;
|
|
176
|
+
updateConversation(id: string, patch: PoppyConversationPatch): Promise<void>;
|
|
177
|
+
appendEvent(conversationId: string, body: PoppyEventBody, options?: {
|
|
178
|
+
key?: string;
|
|
179
|
+
}): Promise<{
|
|
180
|
+
event: {
|
|
181
|
+
seq: number;
|
|
182
|
+
event: PoppyEvent;
|
|
183
|
+
};
|
|
184
|
+
created: boolean;
|
|
185
|
+
}>;
|
|
186
|
+
listEvents(conversationId: string, afterSeq: number, limit: number): Promise<{
|
|
187
|
+
seq: number;
|
|
188
|
+
event: PoppyEvent;
|
|
189
|
+
}[]>;
|
|
190
|
+
findEventSeq(conversationId: string, eventId: string): Promise<number | null>;
|
|
191
|
+
pruneEvents(conversationId: string, before: number): Promise<void>;
|
|
192
|
+
claimMessage(owner: string, messageId: string, receipt: PoppyMessageReceipt): Promise<{
|
|
193
|
+
status: 'claimed';
|
|
194
|
+
fingerprint?: never;
|
|
195
|
+
conversationId?: never;
|
|
196
|
+
response?: never;
|
|
197
|
+
} | {
|
|
198
|
+
status: 'existing';
|
|
199
|
+
fingerprint: string;
|
|
200
|
+
conversationId: string;
|
|
201
|
+
response: PoppyMessageReceipt['response'];
|
|
202
|
+
}>;
|
|
203
|
+
releaseMessage(owner: string, messageId: string): Promise<void>;
|
|
204
|
+
leaseReader(conversationId: string, holder: string, ttlMs: number): Promise<boolean>;
|
|
205
|
+
releaseReader(conversationId: string, holder: string): Promise<void>;
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/poppy/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { forDialect } from '../stores/lucid-schema.js';
|
|
3
|
+
import { isMySql } from '../stores/sql-dialect.js';
|
|
4
|
+
/** `evt_<seq in base 36>_<random>`: unique, and its position survives the event being pruned. */
|
|
5
|
+
export function newEventId(seq) {
|
|
6
|
+
return `evt_${seq.toString(36)}_${randomBytes(6).toString('base64url')}`;
|
|
7
|
+
}
|
|
8
|
+
/** The position an event id was minted at, or `null` for an id this surface never minted. */
|
|
9
|
+
export function eventIdSeq(id) {
|
|
10
|
+
const match = /^evt_([0-9a-z]{1,11})_[A-Za-z0-9_-]+$/.exec(id);
|
|
11
|
+
if (!match?.[1])
|
|
12
|
+
return null;
|
|
13
|
+
const seq = Number.parseInt(match[1], 36);
|
|
14
|
+
return Number.isSafeInteger(seq) && seq > 0 ? seq : null;
|
|
15
|
+
}
|
|
16
|
+
function toEvent(seq, body, now) {
|
|
17
|
+
return {
|
|
18
|
+
seq,
|
|
19
|
+
event: { id: newEventId(seq), created_at: new Date(now).toISOString(), ...body },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// ── In memory ─────────────────────────────────────────────────────────────────
|
|
23
|
+
/** Process-local {@link PoppyStore} — tests and single-instance dev. */
|
|
24
|
+
export class InMemoryPoppyStore {
|
|
25
|
+
#conversations = new Map();
|
|
26
|
+
#events = new Map();
|
|
27
|
+
#messages = new Map();
|
|
28
|
+
#leases = new Map();
|
|
29
|
+
async createConversation(record) {
|
|
30
|
+
this.#conversations.set(record.id, structuredClone(record));
|
|
31
|
+
}
|
|
32
|
+
async getConversation(id) {
|
|
33
|
+
const found = this.#conversations.get(id);
|
|
34
|
+
return found ? structuredClone(found) : null;
|
|
35
|
+
}
|
|
36
|
+
async updateConversation(id, patch) {
|
|
37
|
+
const found = this.#conversations.get(id);
|
|
38
|
+
if (!found)
|
|
39
|
+
return;
|
|
40
|
+
Object.assign(found, structuredClone(patch), { updatedAt: Date.now() });
|
|
41
|
+
}
|
|
42
|
+
async appendEvent(conversationId, body, options = {}) {
|
|
43
|
+
const list = this.#events.get(conversationId) ?? [];
|
|
44
|
+
this.#events.set(conversationId, list);
|
|
45
|
+
if (options.key !== undefined) {
|
|
46
|
+
const existing = list.find((entry) => entry.key === options.key);
|
|
47
|
+
if (existing) {
|
|
48
|
+
return { event: { seq: existing.seq, event: existing.event }, created: false };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const conversation = this.#conversations.get(conversationId);
|
|
52
|
+
const last = list.at(-1)?.seq ?? conversation?.prunedSeq ?? 0;
|
|
53
|
+
const stored = toEvent(last + 1, body, Date.now());
|
|
54
|
+
list.push({ ...stored, key: options.key ?? null });
|
|
55
|
+
return { event: structuredClone(stored), created: true };
|
|
56
|
+
}
|
|
57
|
+
async listEvents(conversationId, afterSeq, limit) {
|
|
58
|
+
return (this.#events.get(conversationId) ?? [])
|
|
59
|
+
.filter((entry) => entry.seq > afterSeq)
|
|
60
|
+
.slice(0, limit)
|
|
61
|
+
.map((entry) => structuredClone({ seq: entry.seq, event: entry.event }));
|
|
62
|
+
}
|
|
63
|
+
async findEventSeq(conversationId, eventId) {
|
|
64
|
+
return ((this.#events.get(conversationId) ?? []).find((entry) => entry.event.id === eventId)?.seq ??
|
|
65
|
+
null);
|
|
66
|
+
}
|
|
67
|
+
async pruneEvents(conversationId, before) {
|
|
68
|
+
const list = this.#events.get(conversationId) ?? [];
|
|
69
|
+
const cutoff = new Date(before).toISOString();
|
|
70
|
+
const last = list.at(-1);
|
|
71
|
+
const keep = list.filter((entry) => entry === last || entry.event.created_at >= cutoff);
|
|
72
|
+
const dropped = list.filter((entry) => !keep.includes(entry));
|
|
73
|
+
if (dropped.length === 0)
|
|
74
|
+
return;
|
|
75
|
+
this.#events.set(conversationId, keep);
|
|
76
|
+
const conversation = this.#conversations.get(conversationId);
|
|
77
|
+
if (conversation) {
|
|
78
|
+
conversation.prunedSeq = Math.max(conversation.prunedSeq, ...dropped.map((e) => e.seq));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async claimMessage(owner, messageId, receipt) {
|
|
82
|
+
const key = `${owner}\n${messageId}`;
|
|
83
|
+
const found = this.#messages.get(key);
|
|
84
|
+
if (found)
|
|
85
|
+
return { status: 'existing', ...structuredClone(found) };
|
|
86
|
+
this.#messages.set(key, structuredClone(receipt));
|
|
87
|
+
return { status: 'claimed' };
|
|
88
|
+
}
|
|
89
|
+
async releaseMessage(owner, messageId) {
|
|
90
|
+
this.#messages.delete(`${owner}\n${messageId}`);
|
|
91
|
+
}
|
|
92
|
+
async leaseReader(conversationId, holder, ttlMs) {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const lease = this.#leases.get(conversationId);
|
|
95
|
+
if (lease && lease.holder !== holder && lease.until > now)
|
|
96
|
+
return false;
|
|
97
|
+
this.#leases.set(conversationId, { holder, until: now + ttlMs });
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
async releaseReader(conversationId, holder) {
|
|
101
|
+
if (this.#leases.get(conversationId)?.holder === holder)
|
|
102
|
+
this.#leases.delete(conversationId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// ── Lucid ─────────────────────────────────────────────────────────────────────
|
|
106
|
+
export const POPPY_TABLES = {
|
|
107
|
+
conversations: 'agent_poppy_conversation',
|
|
108
|
+
events: 'agent_poppy_event',
|
|
109
|
+
messages: 'agent_poppy_message',
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* `CREATE TABLE IF NOT EXISTS` DDL for the Poppy tables — the agent tables' portable dialect
|
|
113
|
+
* (quoted identifiers, epoch-ms `BIGINT`, `TEXT` JSON; `forDialect` adapts it to MySQL). An event's
|
|
114
|
+
* primary key is its position, so two appends racing for one position cannot both win; a message's
|
|
115
|
+
* is its (owner, id), the idempotency lock of §7.3.
|
|
116
|
+
*/
|
|
117
|
+
export function poppyTableStatements() {
|
|
118
|
+
const t = POPPY_TABLES;
|
|
119
|
+
return [
|
|
120
|
+
`CREATE TABLE IF NOT EXISTS "${t.conversations}" (
|
|
121
|
+
"id" VARCHAR(255) PRIMARY KEY NOT NULL,
|
|
122
|
+
"client_id" VARCHAR(2048) NOT NULL,
|
|
123
|
+
"user_id" VARCHAR(255) NOT NULL,
|
|
124
|
+
"account_ref" VARCHAR(255) NULL,
|
|
125
|
+
"agent_name" VARCHAR(255) NOT NULL,
|
|
126
|
+
"thread_id" VARCHAR(255) NULL,
|
|
127
|
+
"parent_id" VARCHAR(255) NULL,
|
|
128
|
+
"open_direct_id" VARCHAR(255) NULL,
|
|
129
|
+
"status" VARCHAR(16) NOT NULL,
|
|
130
|
+
"responder" VARCHAR(16) NOT NULL,
|
|
131
|
+
"context" TEXT NULL,
|
|
132
|
+
"active_run_id" VARCHAR(255) NULL,
|
|
133
|
+
"turn_seq" BIGINT NOT NULL,
|
|
134
|
+
"grant_info" TEXT NULL,
|
|
135
|
+
"reader_holder" VARCHAR(255) NULL,
|
|
136
|
+
"reader_until" BIGINT NULL,
|
|
137
|
+
"pruned_seq" BIGINT NOT NULL,
|
|
138
|
+
"created_at" BIGINT NOT NULL,
|
|
139
|
+
"updated_at" BIGINT NOT NULL
|
|
140
|
+
)`,
|
|
141
|
+
`CREATE TABLE IF NOT EXISTS "${t.events}" (
|
|
142
|
+
"conversation_id" VARCHAR(255) NOT NULL,
|
|
143
|
+
"seq" BIGINT NOT NULL,
|
|
144
|
+
"id" VARCHAR(64) NOT NULL,
|
|
145
|
+
"type" VARCHAR(64) NOT NULL,
|
|
146
|
+
"event" TEXT NOT NULL,
|
|
147
|
+
"event_key" VARCHAR(255) NULL,
|
|
148
|
+
"created_at" BIGINT NOT NULL,
|
|
149
|
+
PRIMARY KEY ("conversation_id", "seq")
|
|
150
|
+
)`,
|
|
151
|
+
`CREATE TABLE IF NOT EXISTS "${t.messages}" (
|
|
152
|
+
"owner" VARCHAR(64) NOT NULL,
|
|
153
|
+
"message_id" VARCHAR(256) NOT NULL,
|
|
154
|
+
"fingerprint" VARCHAR(64) NOT NULL,
|
|
155
|
+
"conversation_id" VARCHAR(255) NOT NULL,
|
|
156
|
+
"response" TEXT NOT NULL,
|
|
157
|
+
"created_at" BIGINT NOT NULL,
|
|
158
|
+
PRIMARY KEY ("owner", "message_id")
|
|
159
|
+
)`,
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
/** Create the Poppy tables if missing. Idempotent. */
|
|
163
|
+
export async function ensurePoppyTables(db) {
|
|
164
|
+
const mysql = isMySql(db);
|
|
165
|
+
for (const statement of poppyTableStatements()) {
|
|
166
|
+
await db.rawQuery(forDialect(statement, mysql));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function toInt(value) {
|
|
170
|
+
if (typeof value === 'number')
|
|
171
|
+
return value;
|
|
172
|
+
if (typeof value === 'bigint')
|
|
173
|
+
return Number(value);
|
|
174
|
+
if (typeof value === 'string')
|
|
175
|
+
return Number.parseInt(value, 10) || 0;
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
const nullable = (value) => (value == null ? null : String(value));
|
|
179
|
+
const PATCH_COLUMNS = {
|
|
180
|
+
accountRef: 'account_ref',
|
|
181
|
+
threadId: 'thread_id',
|
|
182
|
+
openDirectId: 'open_direct_id',
|
|
183
|
+
status: 'status',
|
|
184
|
+
responder: 'responder',
|
|
185
|
+
context: 'context',
|
|
186
|
+
activeRunId: 'active_run_id',
|
|
187
|
+
turnSeq: 'turn_seq',
|
|
188
|
+
grant: 'grant_info',
|
|
189
|
+
prunedSeq: 'pruned_seq',
|
|
190
|
+
};
|
|
191
|
+
/** {@link PoppyStore} over Lucid (any of SQLite / Postgres / MySQL). */
|
|
192
|
+
export class LucidPoppyStore {
|
|
193
|
+
db;
|
|
194
|
+
#ready = null;
|
|
195
|
+
#autoCreate;
|
|
196
|
+
constructor(db, options = {}) {
|
|
197
|
+
this.db = db;
|
|
198
|
+
this.#autoCreate = options.autoCreateTables ?? true;
|
|
199
|
+
}
|
|
200
|
+
/** Provision the tables (once). A failure is retried on the next call. */
|
|
201
|
+
ready() {
|
|
202
|
+
if (!this.#autoCreate)
|
|
203
|
+
return Promise.resolve();
|
|
204
|
+
if (this.#ready === null) {
|
|
205
|
+
this.#ready = ensurePoppyTables(this.db);
|
|
206
|
+
this.#ready.catch(() => {
|
|
207
|
+
this.#ready = null;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return this.#ready;
|
|
211
|
+
}
|
|
212
|
+
async createConversation(record) {
|
|
213
|
+
await this.ready();
|
|
214
|
+
await this.db.table(POPPY_TABLES.conversations).insert({
|
|
215
|
+
id: record.id,
|
|
216
|
+
client_id: record.clientId,
|
|
217
|
+
user_id: record.userId,
|
|
218
|
+
account_ref: record.accountRef,
|
|
219
|
+
agent_name: record.agentName,
|
|
220
|
+
thread_id: record.threadId,
|
|
221
|
+
parent_id: record.parentId,
|
|
222
|
+
open_direct_id: record.openDirectId,
|
|
223
|
+
status: record.status,
|
|
224
|
+
responder: record.responder,
|
|
225
|
+
context: JSON.stringify(record.context),
|
|
226
|
+
active_run_id: record.activeRunId,
|
|
227
|
+
turn_seq: record.turnSeq,
|
|
228
|
+
grant_info: record.grant === null ? null : JSON.stringify(record.grant),
|
|
229
|
+
reader_holder: null,
|
|
230
|
+
reader_until: null,
|
|
231
|
+
pruned_seq: record.prunedSeq,
|
|
232
|
+
created_at: record.createdAt,
|
|
233
|
+
updated_at: record.updatedAt,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async getConversation(id) {
|
|
237
|
+
await this.ready();
|
|
238
|
+
const row = await this.db.from(POPPY_TABLES.conversations).where('id', id).first();
|
|
239
|
+
if (!row)
|
|
240
|
+
return null;
|
|
241
|
+
return {
|
|
242
|
+
id: String(row.id),
|
|
243
|
+
clientId: String(row.client_id),
|
|
244
|
+
userId: String(row.user_id),
|
|
245
|
+
accountRef: nullable(row.account_ref),
|
|
246
|
+
agentName: String(row.agent_name),
|
|
247
|
+
threadId: nullable(row.thread_id),
|
|
248
|
+
parentId: nullable(row.parent_id),
|
|
249
|
+
openDirectId: nullable(row.open_direct_id),
|
|
250
|
+
status: String(row.status),
|
|
251
|
+
responder: String(row.responder),
|
|
252
|
+
context: row.context == null ? {} : JSON.parse(String(row.context)),
|
|
253
|
+
activeRunId: nullable(row.active_run_id),
|
|
254
|
+
turnSeq: toInt(row.turn_seq),
|
|
255
|
+
grant: row.grant_info == null ? null : JSON.parse(String(row.grant_info)),
|
|
256
|
+
prunedSeq: toInt(row.pruned_seq),
|
|
257
|
+
createdAt: toInt(row.created_at),
|
|
258
|
+
updatedAt: toInt(row.updated_at),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async updateConversation(id, patch) {
|
|
262
|
+
await this.ready();
|
|
263
|
+
const row = { updated_at: Date.now() };
|
|
264
|
+
for (const [key, column] of Object.entries(PATCH_COLUMNS)) {
|
|
265
|
+
const value = patch[key];
|
|
266
|
+
if (value === undefined)
|
|
267
|
+
continue;
|
|
268
|
+
row[column] =
|
|
269
|
+
key === 'context' || (key === 'grant' && value !== null) ? JSON.stringify(value) : value;
|
|
270
|
+
}
|
|
271
|
+
await this.db.from(POPPY_TABLES.conversations).where('id', id).update(row);
|
|
272
|
+
}
|
|
273
|
+
async appendEvent(conversationId, body, options = {}) {
|
|
274
|
+
await this.ready();
|
|
275
|
+
for (let attempt = 0;; attempt++) {
|
|
276
|
+
if (options.key !== undefined) {
|
|
277
|
+
const existing = await this.db
|
|
278
|
+
.from(POPPY_TABLES.events)
|
|
279
|
+
.where('conversation_id', conversationId)
|
|
280
|
+
.where('event_key', options.key)
|
|
281
|
+
.first();
|
|
282
|
+
if (existing) {
|
|
283
|
+
return {
|
|
284
|
+
event: {
|
|
285
|
+
seq: toInt(existing.seq),
|
|
286
|
+
event: JSON.parse(String(existing.event)),
|
|
287
|
+
},
|
|
288
|
+
created: false,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const last = await this.db
|
|
293
|
+
.from(POPPY_TABLES.events)
|
|
294
|
+
.where('conversation_id', conversationId)
|
|
295
|
+
.orderBy('seq', 'desc')
|
|
296
|
+
.limit(1)
|
|
297
|
+
.first();
|
|
298
|
+
const floor = last === null
|
|
299
|
+
? ((await this.getConversation(conversationId))?.prunedSeq ?? 0)
|
|
300
|
+
: toInt(last.seq);
|
|
301
|
+
const now = Date.now();
|
|
302
|
+
const stored = toEvent(floor + 1, body, now);
|
|
303
|
+
try {
|
|
304
|
+
await this.db.table(POPPY_TABLES.events).insert({
|
|
305
|
+
conversation_id: conversationId,
|
|
306
|
+
seq: stored.seq,
|
|
307
|
+
id: stored.event.id,
|
|
308
|
+
type: body.type,
|
|
309
|
+
event: JSON.stringify(stored.event),
|
|
310
|
+
event_key: options.key ?? null,
|
|
311
|
+
created_at: now,
|
|
312
|
+
});
|
|
313
|
+
return { event: stored, created: true };
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
// Another append took this position first: number this one after it.
|
|
317
|
+
if (attempt >= 20)
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async listEvents(conversationId, afterSeq, limit) {
|
|
323
|
+
await this.ready();
|
|
324
|
+
const rows = await this.db
|
|
325
|
+
.from(POPPY_TABLES.events)
|
|
326
|
+
.where('conversation_id', conversationId)
|
|
327
|
+
.where('seq', '>', afterSeq)
|
|
328
|
+
.orderBy('seq', 'asc')
|
|
329
|
+
.limit(limit)
|
|
330
|
+
.select('seq', 'event');
|
|
331
|
+
return rows.map((row) => ({
|
|
332
|
+
seq: toInt(row.seq),
|
|
333
|
+
event: JSON.parse(String(row.event)),
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
async findEventSeq(conversationId, eventId) {
|
|
337
|
+
await this.ready();
|
|
338
|
+
const row = await this.db
|
|
339
|
+
.from(POPPY_TABLES.events)
|
|
340
|
+
.where('conversation_id', conversationId)
|
|
341
|
+
.where('id', eventId)
|
|
342
|
+
.first();
|
|
343
|
+
return row ? toInt(row.seq) : null;
|
|
344
|
+
}
|
|
345
|
+
async pruneEvents(conversationId, before) {
|
|
346
|
+
await this.ready();
|
|
347
|
+
const last = await this.db
|
|
348
|
+
.from(POPPY_TABLES.events)
|
|
349
|
+
.where('conversation_id', conversationId)
|
|
350
|
+
.orderBy('seq', 'desc')
|
|
351
|
+
.limit(1)
|
|
352
|
+
.first();
|
|
353
|
+
if (!last)
|
|
354
|
+
return;
|
|
355
|
+
const dropped = await this.db
|
|
356
|
+
.from(POPPY_TABLES.events)
|
|
357
|
+
.where('conversation_id', conversationId)
|
|
358
|
+
.where('created_at', '<', before)
|
|
359
|
+
.where('seq', '<', toInt(last.seq))
|
|
360
|
+
.orderBy('seq', 'desc')
|
|
361
|
+
.limit(1)
|
|
362
|
+
.first();
|
|
363
|
+
if (!dropped)
|
|
364
|
+
return;
|
|
365
|
+
const upTo = toInt(dropped.seq);
|
|
366
|
+
await this.db
|
|
367
|
+
.from(POPPY_TABLES.events)
|
|
368
|
+
.where('conversation_id', conversationId)
|
|
369
|
+
.where('seq', '<=', upTo)
|
|
370
|
+
.delete();
|
|
371
|
+
const conversation = await this.getConversation(conversationId);
|
|
372
|
+
if (conversation && conversation.prunedSeq < upTo) {
|
|
373
|
+
await this.updateConversation(conversationId, { prunedSeq: upTo });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async claimMessage(owner, messageId, receipt) {
|
|
377
|
+
await this.ready();
|
|
378
|
+
try {
|
|
379
|
+
await this.db.table(POPPY_TABLES.messages).insert({
|
|
380
|
+
owner,
|
|
381
|
+
message_id: messageId,
|
|
382
|
+
fingerprint: receipt.fingerprint,
|
|
383
|
+
conversation_id: receipt.conversationId,
|
|
384
|
+
response: JSON.stringify(receipt.response),
|
|
385
|
+
created_at: Date.now(),
|
|
386
|
+
});
|
|
387
|
+
return { status: 'claimed' };
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
const row = await this.db
|
|
391
|
+
.from(POPPY_TABLES.messages)
|
|
392
|
+
.where('owner', owner)
|
|
393
|
+
.where('message_id', messageId)
|
|
394
|
+
.first();
|
|
395
|
+
if (!row)
|
|
396
|
+
throw error;
|
|
397
|
+
return {
|
|
398
|
+
status: 'existing',
|
|
399
|
+
fingerprint: String(row.fingerprint),
|
|
400
|
+
conversationId: String(row.conversation_id),
|
|
401
|
+
response: JSON.parse(String(row.response)),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async releaseMessage(owner, messageId) {
|
|
406
|
+
await this.ready();
|
|
407
|
+
await this.db
|
|
408
|
+
.from(POPPY_TABLES.messages)
|
|
409
|
+
.where('owner', owner)
|
|
410
|
+
.where('message_id', messageId)
|
|
411
|
+
.delete();
|
|
412
|
+
}
|
|
413
|
+
async leaseReader(conversationId, holder, ttlMs) {
|
|
414
|
+
await this.ready();
|
|
415
|
+
const now = Date.now();
|
|
416
|
+
const row = await this.db.from(POPPY_TABLES.conversations).where('id', conversationId).first();
|
|
417
|
+
if (!row)
|
|
418
|
+
return false;
|
|
419
|
+
const current = nullable(row.reader_holder);
|
|
420
|
+
if (current !== null && current !== holder && toInt(row.reader_until) > now)
|
|
421
|
+
return false;
|
|
422
|
+
// Compare-and-set on what was read: of two takers, the second matches nothing.
|
|
423
|
+
let query = this.db.from(POPPY_TABLES.conversations).where('id', conversationId);
|
|
424
|
+
query =
|
|
425
|
+
current === null
|
|
426
|
+
? query.whereNull('reader_holder')
|
|
427
|
+
: query.where('reader_holder', current).where('reader_until', toInt(row.reader_until));
|
|
428
|
+
await query.update({ reader_holder: holder, reader_until: now + ttlMs });
|
|
429
|
+
const after = await this.db
|
|
430
|
+
.from(POPPY_TABLES.conversations)
|
|
431
|
+
.where('id', conversationId)
|
|
432
|
+
.first();
|
|
433
|
+
return nullable(after?.reader_holder) === holder;
|
|
434
|
+
}
|
|
435
|
+
async releaseReader(conversationId, holder) {
|
|
436
|
+
await this.ready();
|
|
437
|
+
await this.db
|
|
438
|
+
.from(POPPY_TABLES.conversations)
|
|
439
|
+
.where('id', conversationId)
|
|
440
|
+
.where('reader_holder', holder)
|
|
441
|
+
.update({ reader_holder: null, reader_until: null });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
//# sourceMappingURL=store.js.map
|