@contractspec/integration.runtime 2.10.0 → 3.0.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/dist/channel/dispatcher.d.ts +37 -0
- package/dist/channel/dispatcher.js +130 -0
- package/dist/channel/dispatcher.test.d.ts +1 -0
- package/dist/channel/github.d.ts +47 -0
- package/dist/channel/github.js +58 -0
- package/dist/channel/github.test.d.ts +1 -0
- package/dist/channel/index.d.ts +14 -0
- package/dist/channel/index.js +1420 -0
- package/dist/channel/memory-store.d.ts +28 -0
- package/dist/channel/memory-store.js +223 -0
- package/dist/channel/policy.d.ts +19 -0
- package/dist/channel/policy.js +110 -0
- package/dist/channel/policy.test.d.ts +1 -0
- package/dist/channel/postgres-queries.d.ts +11 -0
- package/dist/channel/postgres-queries.js +222 -0
- package/dist/channel/postgres-schema.d.ts +1 -0
- package/dist/channel/postgres-schema.js +94 -0
- package/dist/channel/postgres-store.d.ts +21 -0
- package/dist/channel/postgres-store.js +498 -0
- package/dist/channel/postgres-store.test.d.ts +1 -0
- package/dist/channel/replay-fixtures.d.ts +8 -0
- package/dist/channel/replay-fixtures.js +31 -0
- package/dist/channel/replay.test.d.ts +1 -0
- package/dist/channel/service.d.ts +26 -0
- package/dist/channel/service.js +287 -0
- package/dist/channel/service.test.d.ts +1 -0
- package/dist/channel/slack.d.ts +42 -0
- package/dist/channel/slack.js +82 -0
- package/dist/channel/slack.test.d.ts +1 -0
- package/dist/channel/store.d.ts +83 -0
- package/dist/channel/store.js +1 -0
- package/dist/channel/telemetry.d.ts +17 -0
- package/dist/channel/telemetry.js +1 -0
- package/dist/channel/types.d.ts +111 -0
- package/dist/channel/types.js +1 -0
- package/dist/channel/whatsapp-meta.d.ts +55 -0
- package/dist/channel/whatsapp-meta.js +66 -0
- package/dist/channel/whatsapp-meta.test.d.ts +1 -0
- package/dist/channel/whatsapp-twilio.d.ts +20 -0
- package/dist/channel/whatsapp-twilio.js +61 -0
- package/dist/channel/whatsapp-twilio.test.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1418 -1
- package/dist/node/channel/dispatcher.js +129 -0
- package/dist/node/channel/github.js +57 -0
- package/dist/node/channel/index.js +1419 -0
- package/dist/node/channel/memory-store.js +222 -0
- package/dist/node/channel/policy.js +109 -0
- package/dist/node/channel/postgres-queries.js +221 -0
- package/dist/node/channel/postgres-schema.js +93 -0
- package/dist/node/channel/postgres-store.js +497 -0
- package/dist/node/channel/replay-fixtures.js +30 -0
- package/dist/node/channel/service.js +286 -0
- package/dist/node/channel/slack.js +81 -0
- package/dist/node/channel/store.js +0 -0
- package/dist/node/channel/telemetry.js +0 -0
- package/dist/node/channel/types.js +0 -0
- package/dist/node/channel/whatsapp-meta.js +65 -0
- package/dist/node/channel/whatsapp-twilio.js +60 -0
- package/dist/node/index.js +1418 -1
- package/dist/runtime.health.test.d.ts +1 -0
- package/package.json +213 -6
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ChannelDecisionRecord, ChannelDeliveryAttemptRecord, ChannelEventReceiptRecord, ChannelOutboxActionRecord, ChannelThreadRecord } from './types';
|
|
2
|
+
import type { ChannelRuntimeStore, ClaimEventReceiptInput, ClaimEventReceiptResult, EnqueueOutboxActionInput, EnqueueOutboxActionResult, MarkOutboxDeadLetterInput, MarkOutboxRetryInput, RecordDeliveryAttemptInput, SaveDecisionInput, UpsertThreadInput } from './store';
|
|
3
|
+
export declare class InMemoryChannelRuntimeStore implements ChannelRuntimeStore {
|
|
4
|
+
readonly receipts: Map<string, ChannelEventReceiptRecord>;
|
|
5
|
+
readonly threads: Map<string, ChannelThreadRecord>;
|
|
6
|
+
readonly decisions: Map<string, ChannelDecisionRecord>;
|
|
7
|
+
readonly outbox: Map<string, ChannelOutboxActionRecord>;
|
|
8
|
+
readonly deliveryAttempts: Map<string, ChannelDeliveryAttemptRecord>;
|
|
9
|
+
private readonly receiptKeyToId;
|
|
10
|
+
private readonly threadKeyToId;
|
|
11
|
+
private readonly outboxKeyToId;
|
|
12
|
+
private deliveryAttemptSequence;
|
|
13
|
+
claimEventReceipt(input: ClaimEventReceiptInput): Promise<ClaimEventReceiptResult>;
|
|
14
|
+
updateReceiptStatus(receiptId: string, status: ChannelEventReceiptRecord['status'], error?: {
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
upsertThread(input: UpsertThreadInput): Promise<ChannelThreadRecord>;
|
|
19
|
+
saveDecision(input: SaveDecisionInput): Promise<ChannelDecisionRecord>;
|
|
20
|
+
enqueueOutboxAction(input: EnqueueOutboxActionInput): Promise<EnqueueOutboxActionResult>;
|
|
21
|
+
claimPendingOutboxActions(limit: number, now?: Date): Promise<ChannelOutboxActionRecord[]>;
|
|
22
|
+
recordDeliveryAttempt(input: RecordDeliveryAttemptInput): Promise<ChannelDeliveryAttemptRecord>;
|
|
23
|
+
markOutboxSent(actionId: string, providerMessageId?: string): Promise<void>;
|
|
24
|
+
markOutboxRetry(input: MarkOutboxRetryInput): Promise<void>;
|
|
25
|
+
markOutboxDeadLetter(input: MarkOutboxDeadLetterInput): Promise<void>;
|
|
26
|
+
private receiptKey;
|
|
27
|
+
private threadKey;
|
|
28
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/channel/memory-store.ts
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
|
|
5
|
+
class InMemoryChannelRuntimeStore {
|
|
6
|
+
receipts = new Map;
|
|
7
|
+
threads = new Map;
|
|
8
|
+
decisions = new Map;
|
|
9
|
+
outbox = new Map;
|
|
10
|
+
deliveryAttempts = new Map;
|
|
11
|
+
receiptKeyToId = new Map;
|
|
12
|
+
threadKeyToId = new Map;
|
|
13
|
+
outboxKeyToId = new Map;
|
|
14
|
+
deliveryAttemptSequence = 0;
|
|
15
|
+
async claimEventReceipt(input) {
|
|
16
|
+
const key = this.receiptKey(input);
|
|
17
|
+
const existingId = this.receiptKeyToId.get(key);
|
|
18
|
+
if (existingId) {
|
|
19
|
+
const existing = this.receipts.get(existingId);
|
|
20
|
+
if (existing) {
|
|
21
|
+
existing.lastSeenAt = new Date;
|
|
22
|
+
this.receipts.set(existing.id, existing);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
receiptId: existingId,
|
|
26
|
+
duplicate: true
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const id = randomUUID();
|
|
30
|
+
const now = new Date;
|
|
31
|
+
this.receipts.set(id, {
|
|
32
|
+
id,
|
|
33
|
+
workspaceId: input.workspaceId,
|
|
34
|
+
providerKey: input.providerKey,
|
|
35
|
+
externalEventId: input.externalEventId,
|
|
36
|
+
eventType: input.eventType,
|
|
37
|
+
status: "accepted",
|
|
38
|
+
signatureValid: input.signatureValid,
|
|
39
|
+
payloadHash: input.payloadHash,
|
|
40
|
+
traceId: input.traceId,
|
|
41
|
+
firstSeenAt: now,
|
|
42
|
+
lastSeenAt: now
|
|
43
|
+
});
|
|
44
|
+
this.receiptKeyToId.set(key, id);
|
|
45
|
+
return { receiptId: id, duplicate: false };
|
|
46
|
+
}
|
|
47
|
+
async updateReceiptStatus(receiptId, status, error) {
|
|
48
|
+
const receipt = this.receipts.get(receiptId);
|
|
49
|
+
if (!receipt) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
receipt.status = status;
|
|
53
|
+
receipt.lastSeenAt = new Date;
|
|
54
|
+
if (status === "processed") {
|
|
55
|
+
receipt.processedAt = new Date;
|
|
56
|
+
}
|
|
57
|
+
receipt.errorCode = error?.code;
|
|
58
|
+
receipt.errorMessage = error?.message;
|
|
59
|
+
this.receipts.set(receiptId, receipt);
|
|
60
|
+
}
|
|
61
|
+
async upsertThread(input) {
|
|
62
|
+
const key = this.threadKey(input);
|
|
63
|
+
const existingId = this.threadKeyToId.get(key);
|
|
64
|
+
if (existingId) {
|
|
65
|
+
const existing = this.threads.get(existingId);
|
|
66
|
+
if (!existing) {
|
|
67
|
+
throw new Error("Corrupted thread state");
|
|
68
|
+
}
|
|
69
|
+
existing.externalChannelId = input.externalChannelId ?? existing.externalChannelId;
|
|
70
|
+
existing.externalUserId = input.externalUserId ?? existing.externalUserId;
|
|
71
|
+
existing.lastProviderEventAt = input.occurredAt ?? existing.lastProviderEventAt;
|
|
72
|
+
existing.updatedAt = new Date;
|
|
73
|
+
if (input.state) {
|
|
74
|
+
existing.state = {
|
|
75
|
+
...existing.state,
|
|
76
|
+
...input.state
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
this.threads.set(existing.id, existing);
|
|
80
|
+
return existing;
|
|
81
|
+
}
|
|
82
|
+
const id = randomUUID();
|
|
83
|
+
const now = new Date;
|
|
84
|
+
const record = {
|
|
85
|
+
id,
|
|
86
|
+
workspaceId: input.workspaceId,
|
|
87
|
+
providerKey: input.providerKey,
|
|
88
|
+
externalThreadId: input.externalThreadId,
|
|
89
|
+
externalChannelId: input.externalChannelId,
|
|
90
|
+
externalUserId: input.externalUserId,
|
|
91
|
+
state: input.state ?? {},
|
|
92
|
+
lastProviderEventAt: input.occurredAt,
|
|
93
|
+
createdAt: now,
|
|
94
|
+
updatedAt: now
|
|
95
|
+
};
|
|
96
|
+
this.threads.set(id, record);
|
|
97
|
+
this.threadKeyToId.set(key, id);
|
|
98
|
+
return record;
|
|
99
|
+
}
|
|
100
|
+
async saveDecision(input) {
|
|
101
|
+
const id = randomUUID();
|
|
102
|
+
const record = {
|
|
103
|
+
id,
|
|
104
|
+
receiptId: input.receiptId,
|
|
105
|
+
threadId: input.threadId,
|
|
106
|
+
policyMode: input.policyMode,
|
|
107
|
+
riskTier: input.riskTier,
|
|
108
|
+
confidence: input.confidence,
|
|
109
|
+
modelName: input.modelName,
|
|
110
|
+
promptVersion: input.promptVersion,
|
|
111
|
+
policyVersion: input.policyVersion,
|
|
112
|
+
toolTrace: input.toolTrace ?? [],
|
|
113
|
+
actionPlan: input.actionPlan,
|
|
114
|
+
requiresApproval: input.requiresApproval,
|
|
115
|
+
createdAt: new Date
|
|
116
|
+
};
|
|
117
|
+
this.decisions.set(id, record);
|
|
118
|
+
return record;
|
|
119
|
+
}
|
|
120
|
+
async enqueueOutboxAction(input) {
|
|
121
|
+
const existingId = this.outboxKeyToId.get(input.idempotencyKey);
|
|
122
|
+
if (existingId) {
|
|
123
|
+
return {
|
|
124
|
+
actionId: existingId,
|
|
125
|
+
duplicate: true
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const id = randomUUID();
|
|
129
|
+
const now = new Date;
|
|
130
|
+
this.outbox.set(id, {
|
|
131
|
+
id,
|
|
132
|
+
workspaceId: input.workspaceId,
|
|
133
|
+
providerKey: input.providerKey,
|
|
134
|
+
decisionId: input.decisionId,
|
|
135
|
+
threadId: input.threadId,
|
|
136
|
+
actionType: input.actionType,
|
|
137
|
+
idempotencyKey: input.idempotencyKey,
|
|
138
|
+
target: input.target,
|
|
139
|
+
payload: input.payload,
|
|
140
|
+
status: "pending",
|
|
141
|
+
attemptCount: 0,
|
|
142
|
+
nextAttemptAt: now,
|
|
143
|
+
createdAt: now,
|
|
144
|
+
updatedAt: now
|
|
145
|
+
});
|
|
146
|
+
this.outboxKeyToId.set(input.idempotencyKey, id);
|
|
147
|
+
return {
|
|
148
|
+
actionId: id,
|
|
149
|
+
duplicate: false
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async claimPendingOutboxActions(limit, now = new Date) {
|
|
153
|
+
const items = Array.from(this.outbox.values()).filter((item) => (item.status === "pending" || item.status === "retryable") && item.nextAttemptAt.getTime() <= now.getTime()).sort((a, b) => a.nextAttemptAt.getTime() - b.nextAttemptAt.getTime()).slice(0, Math.max(1, limit));
|
|
154
|
+
const claimed = [];
|
|
155
|
+
for (const item of items) {
|
|
156
|
+
const updated = {
|
|
157
|
+
...item,
|
|
158
|
+
status: "sending",
|
|
159
|
+
attemptCount: item.attemptCount + 1,
|
|
160
|
+
updatedAt: new Date
|
|
161
|
+
};
|
|
162
|
+
this.outbox.set(updated.id, updated);
|
|
163
|
+
claimed.push(updated);
|
|
164
|
+
}
|
|
165
|
+
return claimed;
|
|
166
|
+
}
|
|
167
|
+
async recordDeliveryAttempt(input) {
|
|
168
|
+
this.deliveryAttemptSequence += 1;
|
|
169
|
+
const record = {
|
|
170
|
+
id: this.deliveryAttemptSequence,
|
|
171
|
+
actionId: input.actionId,
|
|
172
|
+
attempt: input.attempt,
|
|
173
|
+
responseStatus: input.responseStatus,
|
|
174
|
+
responseBody: input.responseBody,
|
|
175
|
+
latencyMs: input.latencyMs,
|
|
176
|
+
createdAt: new Date
|
|
177
|
+
};
|
|
178
|
+
this.deliveryAttempts.set(`${input.actionId}:${input.attempt}`, record);
|
|
179
|
+
return record;
|
|
180
|
+
}
|
|
181
|
+
async markOutboxSent(actionId, providerMessageId) {
|
|
182
|
+
const item = this.outbox.get(actionId);
|
|
183
|
+
if (!item)
|
|
184
|
+
return;
|
|
185
|
+
item.status = "sent";
|
|
186
|
+
item.providerMessageId = providerMessageId;
|
|
187
|
+
item.sentAt = new Date;
|
|
188
|
+
item.lastErrorCode = undefined;
|
|
189
|
+
item.lastErrorMessage = undefined;
|
|
190
|
+
item.updatedAt = new Date;
|
|
191
|
+
this.outbox.set(actionId, item);
|
|
192
|
+
}
|
|
193
|
+
async markOutboxRetry(input) {
|
|
194
|
+
const item = this.outbox.get(input.actionId);
|
|
195
|
+
if (!item)
|
|
196
|
+
return;
|
|
197
|
+
item.status = "retryable";
|
|
198
|
+
item.nextAttemptAt = input.nextAttemptAt;
|
|
199
|
+
item.lastErrorCode = input.lastErrorCode;
|
|
200
|
+
item.lastErrorMessage = input.lastErrorMessage;
|
|
201
|
+
item.updatedAt = new Date;
|
|
202
|
+
this.outbox.set(input.actionId, item);
|
|
203
|
+
}
|
|
204
|
+
async markOutboxDeadLetter(input) {
|
|
205
|
+
const item = this.outbox.get(input.actionId);
|
|
206
|
+
if (!item)
|
|
207
|
+
return;
|
|
208
|
+
item.status = "dead_letter";
|
|
209
|
+
item.lastErrorCode = input.lastErrorCode;
|
|
210
|
+
item.lastErrorMessage = input.lastErrorMessage;
|
|
211
|
+
item.updatedAt = new Date;
|
|
212
|
+
this.outbox.set(input.actionId, item);
|
|
213
|
+
}
|
|
214
|
+
receiptKey(input) {
|
|
215
|
+
return `${input.workspaceId}:${input.providerKey}:${input.externalEventId}`;
|
|
216
|
+
}
|
|
217
|
+
threadKey(input) {
|
|
218
|
+
return `${input.workspaceId}:${input.providerKey}:${input.externalThreadId}`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
InMemoryChannelRuntimeStore
|
|
223
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ChannelInboundEvent, ChannelPolicyDecision } from './types';
|
|
2
|
+
export interface MessagingPolicyConfig {
|
|
3
|
+
autoResolveMinConfidence: number;
|
|
4
|
+
assistMinConfidence: number;
|
|
5
|
+
blockedSignals: string[];
|
|
6
|
+
highRiskSignals: string[];
|
|
7
|
+
mediumRiskSignals: string[];
|
|
8
|
+
safeAckTemplate: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const DEFAULT_MESSAGING_POLICY_CONFIG: MessagingPolicyConfig;
|
|
11
|
+
export interface PolicyEvaluationInput {
|
|
12
|
+
event: ChannelInboundEvent;
|
|
13
|
+
}
|
|
14
|
+
export declare class MessagingPolicyEngine {
|
|
15
|
+
private readonly config;
|
|
16
|
+
constructor(config?: Partial<MessagingPolicyConfig>);
|
|
17
|
+
evaluate(input: PolicyEvaluationInput): ChannelPolicyDecision;
|
|
18
|
+
private defaultResponseText;
|
|
19
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/channel/policy.ts
|
|
3
|
+
var DEFAULT_MESSAGING_POLICY_CONFIG = {
|
|
4
|
+
autoResolveMinConfidence: 0.85,
|
|
5
|
+
assistMinConfidence: 0.65,
|
|
6
|
+
blockedSignals: [
|
|
7
|
+
"ignore previous instructions",
|
|
8
|
+
"reveal secret",
|
|
9
|
+
"api key",
|
|
10
|
+
"password",
|
|
11
|
+
"token",
|
|
12
|
+
"drop table",
|
|
13
|
+
"delete repository"
|
|
14
|
+
],
|
|
15
|
+
highRiskSignals: [
|
|
16
|
+
"refund",
|
|
17
|
+
"delete account",
|
|
18
|
+
"cancel subscription",
|
|
19
|
+
"permission",
|
|
20
|
+
"admin access",
|
|
21
|
+
"wire transfer",
|
|
22
|
+
"bank account"
|
|
23
|
+
],
|
|
24
|
+
mediumRiskSignals: [
|
|
25
|
+
"urgent",
|
|
26
|
+
"legal",
|
|
27
|
+
"compliance",
|
|
28
|
+
"frustrated",
|
|
29
|
+
"escalate",
|
|
30
|
+
"outage"
|
|
31
|
+
],
|
|
32
|
+
safeAckTemplate: "Thanks for your message. We received it and are preparing the next step."
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
class MessagingPolicyEngine {
|
|
36
|
+
config;
|
|
37
|
+
constructor(config) {
|
|
38
|
+
this.config = {
|
|
39
|
+
...DEFAULT_MESSAGING_POLICY_CONFIG,
|
|
40
|
+
...config ?? {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
evaluate(input) {
|
|
44
|
+
const text = (input.event.message?.text ?? "").toLowerCase();
|
|
45
|
+
if (containsAny(text, this.config.blockedSignals)) {
|
|
46
|
+
return {
|
|
47
|
+
confidence: 0.2,
|
|
48
|
+
riskTier: "blocked",
|
|
49
|
+
verdict: "blocked",
|
|
50
|
+
reasons: ["blocked_signal_detected"],
|
|
51
|
+
responseText: this.config.safeAckTemplate,
|
|
52
|
+
requiresApproval: true
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (containsAny(text, this.config.highRiskSignals)) {
|
|
56
|
+
return {
|
|
57
|
+
confidence: 0.55,
|
|
58
|
+
riskTier: "high",
|
|
59
|
+
verdict: "assist",
|
|
60
|
+
reasons: ["high_risk_topic_detected"],
|
|
61
|
+
responseText: this.config.safeAckTemplate,
|
|
62
|
+
requiresApproval: true
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const mediumRiskDetected = containsAny(text, this.config.mediumRiskSignals);
|
|
66
|
+
const confidence = mediumRiskDetected ? 0.74 : 0.92;
|
|
67
|
+
const riskTier = mediumRiskDetected ? "medium" : "low";
|
|
68
|
+
if (confidence >= this.config.autoResolveMinConfidence && riskTier === "low") {
|
|
69
|
+
return {
|
|
70
|
+
confidence,
|
|
71
|
+
riskTier,
|
|
72
|
+
verdict: "autonomous",
|
|
73
|
+
reasons: ["low_risk_high_confidence"],
|
|
74
|
+
responseText: this.defaultResponseText(input.event),
|
|
75
|
+
requiresApproval: false
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (confidence >= this.config.assistMinConfidence) {
|
|
79
|
+
return {
|
|
80
|
+
confidence,
|
|
81
|
+
riskTier,
|
|
82
|
+
verdict: "assist",
|
|
83
|
+
reasons: ["needs_human_review"],
|
|
84
|
+
responseText: this.config.safeAckTemplate,
|
|
85
|
+
requiresApproval: true
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
confidence,
|
|
90
|
+
riskTier: "blocked",
|
|
91
|
+
verdict: "blocked",
|
|
92
|
+
reasons: ["low_confidence"],
|
|
93
|
+
responseText: this.config.safeAckTemplate,
|
|
94
|
+
requiresApproval: true
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
defaultResponseText(event) {
|
|
98
|
+
if (!event.message?.text) {
|
|
99
|
+
return this.config.safeAckTemplate;
|
|
100
|
+
}
|
|
101
|
+
return `Acknowledged: ${event.message.text.slice(0, 240)}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function containsAny(text, candidates) {
|
|
105
|
+
return candidates.some((candidate) => text.includes(candidate));
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
MessagingPolicyEngine,
|
|
109
|
+
DEFAULT_MESSAGING_POLICY_CONFIG
|
|
110
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const CLAIM_EVENT_RECEIPT_SQL = "\nwith inserted as (\n insert into channel_event_receipts (\n id,\n workspace_id,\n provider_key,\n external_event_id,\n event_type,\n status,\n signature_valid,\n payload_hash,\n trace_id\n )\n values ($1, $2, $3, $4, $5, 'accepted', $6, $7, $8)\n on conflict (workspace_id, provider_key, external_event_id)\n do nothing\n returning id\n)\nselect id, true as inserted from inserted\nunion all\nselect id, false as inserted\nfrom channel_event_receipts\nwhere workspace_id = $2\n and provider_key = $3\n and external_event_id = $4\nlimit 1\n";
|
|
2
|
+
export declare const MARK_RECEIPT_DUPLICATE_SQL = "\nupdate channel_event_receipts\nset last_seen_at = now(), status = 'duplicate'\nwhere id = $1\n";
|
|
3
|
+
export declare const UPDATE_RECEIPT_STATUS_SQL = "\nupdate channel_event_receipts\nset\n status = $2,\n error_code = $3,\n error_message = $4,\n last_seen_at = now(),\n processed_at = case when $2 = 'processed' then now() else processed_at end\nwhere id = $1\n";
|
|
4
|
+
export declare const UPSERT_THREAD_SQL = "\ninsert into channel_threads (\n id,\n workspace_id,\n provider_key,\n external_thread_id,\n external_channel_id,\n external_user_id,\n state,\n last_provider_event_ts\n)\nvalues ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)\non conflict (workspace_id, provider_key, external_thread_id)\ndo update set\n external_channel_id = coalesce(excluded.external_channel_id, channel_threads.external_channel_id),\n external_user_id = coalesce(excluded.external_user_id, channel_threads.external_user_id),\n state = channel_threads.state || excluded.state,\n last_provider_event_ts = coalesce(excluded.last_provider_event_ts, channel_threads.last_provider_event_ts),\n updated_at = now()\nreturning\n id,\n workspace_id,\n provider_key,\n external_thread_id,\n external_channel_id,\n external_user_id,\n state,\n last_provider_event_ts,\n created_at,\n updated_at\n";
|
|
5
|
+
export declare const INSERT_DECISION_SQL = "\ninsert into channel_ai_decisions (\n id,\n receipt_id,\n thread_id,\n policy_mode,\n risk_tier,\n confidence,\n model_name,\n prompt_version,\n policy_version,\n tool_trace,\n action_plan,\n requires_approval\n)\nvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12)\n";
|
|
6
|
+
export declare const ENQUEUE_OUTBOX_SQL = "\nwith inserted as (\n insert into channel_outbox_actions (\n id,\n workspace_id,\n provider_key,\n decision_id,\n thread_id,\n action_type,\n idempotency_key,\n target,\n payload,\n status\n )\n values ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb, 'pending')\n on conflict (idempotency_key)\n do nothing\n returning id\n)\nselect id, true as inserted from inserted\nunion all\nselect id, false as inserted\nfrom channel_outbox_actions\nwhere idempotency_key = $7\nlimit 1\n";
|
|
7
|
+
export declare const CLAIM_PENDING_OUTBOX_SQL = "\nwith candidates as (\n select id\n from channel_outbox_actions\n where status in ('pending', 'retryable')\n and next_attempt_at <= $2\n order by next_attempt_at asc\n limit $1\n for update skip locked\n)\nupdate channel_outbox_actions as actions\nset\n status = 'sending',\n attempt_count = actions.attempt_count + 1,\n updated_at = now()\nfrom candidates\nwhere actions.id = candidates.id\nreturning\n actions.id,\n actions.workspace_id,\n actions.provider_key,\n actions.decision_id,\n actions.thread_id,\n actions.action_type,\n actions.idempotency_key,\n actions.target,\n actions.payload,\n actions.status,\n actions.attempt_count,\n actions.next_attempt_at,\n actions.provider_message_id,\n actions.last_error_code,\n actions.last_error_message,\n actions.created_at,\n actions.updated_at,\n actions.sent_at\n";
|
|
8
|
+
export declare const INSERT_DELIVERY_ATTEMPT_SQL = "\ninsert into channel_delivery_attempts (\n action_id,\n attempt,\n response_status,\n response_body,\n latency_ms\n)\nvalues ($1, $2, $3, $4, $5)\non conflict (action_id, attempt)\ndo update set\n response_status = excluded.response_status,\n response_body = excluded.response_body,\n latency_ms = excluded.latency_ms,\n created_at = now()\nreturning\n id,\n action_id,\n attempt,\n response_status,\n response_body,\n latency_ms,\n created_at\n";
|
|
9
|
+
export declare const MARK_OUTBOX_SENT_SQL = "\nupdate channel_outbox_actions\nset\n status = 'sent',\n provider_message_id = $2,\n sent_at = now(),\n updated_at = now(),\n last_error_code = null,\n last_error_message = null\nwhere id = $1\n";
|
|
10
|
+
export declare const MARK_OUTBOX_RETRY_SQL = "\nupdate channel_outbox_actions\nset\n status = 'retryable',\n next_attempt_at = $2,\n last_error_code = $3,\n last_error_message = $4,\n updated_at = now()\nwhere id = $1\n";
|
|
11
|
+
export declare const MARK_OUTBOX_DEAD_LETTER_SQL = "\nupdate channel_outbox_actions\nset\n status = 'dead_letter',\n last_error_code = $2,\n last_error_message = $3,\n updated_at = now()\nwhere id = $1\n";
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/channel/postgres-queries.ts
|
|
3
|
+
var CLAIM_EVENT_RECEIPT_SQL = `
|
|
4
|
+
with inserted as (
|
|
5
|
+
insert into channel_event_receipts (
|
|
6
|
+
id,
|
|
7
|
+
workspace_id,
|
|
8
|
+
provider_key,
|
|
9
|
+
external_event_id,
|
|
10
|
+
event_type,
|
|
11
|
+
status,
|
|
12
|
+
signature_valid,
|
|
13
|
+
payload_hash,
|
|
14
|
+
trace_id
|
|
15
|
+
)
|
|
16
|
+
values ($1, $2, $3, $4, $5, 'accepted', $6, $7, $8)
|
|
17
|
+
on conflict (workspace_id, provider_key, external_event_id)
|
|
18
|
+
do nothing
|
|
19
|
+
returning id
|
|
20
|
+
)
|
|
21
|
+
select id, true as inserted from inserted
|
|
22
|
+
union all
|
|
23
|
+
select id, false as inserted
|
|
24
|
+
from channel_event_receipts
|
|
25
|
+
where workspace_id = $2
|
|
26
|
+
and provider_key = $3
|
|
27
|
+
and external_event_id = $4
|
|
28
|
+
limit 1
|
|
29
|
+
`;
|
|
30
|
+
var MARK_RECEIPT_DUPLICATE_SQL = `
|
|
31
|
+
update channel_event_receipts
|
|
32
|
+
set last_seen_at = now(), status = 'duplicate'
|
|
33
|
+
where id = $1
|
|
34
|
+
`;
|
|
35
|
+
var UPDATE_RECEIPT_STATUS_SQL = `
|
|
36
|
+
update channel_event_receipts
|
|
37
|
+
set
|
|
38
|
+
status = $2,
|
|
39
|
+
error_code = $3,
|
|
40
|
+
error_message = $4,
|
|
41
|
+
last_seen_at = now(),
|
|
42
|
+
processed_at = case when $2 = 'processed' then now() else processed_at end
|
|
43
|
+
where id = $1
|
|
44
|
+
`;
|
|
45
|
+
var UPSERT_THREAD_SQL = `
|
|
46
|
+
insert into channel_threads (
|
|
47
|
+
id,
|
|
48
|
+
workspace_id,
|
|
49
|
+
provider_key,
|
|
50
|
+
external_thread_id,
|
|
51
|
+
external_channel_id,
|
|
52
|
+
external_user_id,
|
|
53
|
+
state,
|
|
54
|
+
last_provider_event_ts
|
|
55
|
+
)
|
|
56
|
+
values ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)
|
|
57
|
+
on conflict (workspace_id, provider_key, external_thread_id)
|
|
58
|
+
do update set
|
|
59
|
+
external_channel_id = coalesce(excluded.external_channel_id, channel_threads.external_channel_id),
|
|
60
|
+
external_user_id = coalesce(excluded.external_user_id, channel_threads.external_user_id),
|
|
61
|
+
state = channel_threads.state || excluded.state,
|
|
62
|
+
last_provider_event_ts = coalesce(excluded.last_provider_event_ts, channel_threads.last_provider_event_ts),
|
|
63
|
+
updated_at = now()
|
|
64
|
+
returning
|
|
65
|
+
id,
|
|
66
|
+
workspace_id,
|
|
67
|
+
provider_key,
|
|
68
|
+
external_thread_id,
|
|
69
|
+
external_channel_id,
|
|
70
|
+
external_user_id,
|
|
71
|
+
state,
|
|
72
|
+
last_provider_event_ts,
|
|
73
|
+
created_at,
|
|
74
|
+
updated_at
|
|
75
|
+
`;
|
|
76
|
+
var INSERT_DECISION_SQL = `
|
|
77
|
+
insert into channel_ai_decisions (
|
|
78
|
+
id,
|
|
79
|
+
receipt_id,
|
|
80
|
+
thread_id,
|
|
81
|
+
policy_mode,
|
|
82
|
+
risk_tier,
|
|
83
|
+
confidence,
|
|
84
|
+
model_name,
|
|
85
|
+
prompt_version,
|
|
86
|
+
policy_version,
|
|
87
|
+
tool_trace,
|
|
88
|
+
action_plan,
|
|
89
|
+
requires_approval
|
|
90
|
+
)
|
|
91
|
+
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11::jsonb, $12)
|
|
92
|
+
`;
|
|
93
|
+
var ENQUEUE_OUTBOX_SQL = `
|
|
94
|
+
with inserted as (
|
|
95
|
+
insert into channel_outbox_actions (
|
|
96
|
+
id,
|
|
97
|
+
workspace_id,
|
|
98
|
+
provider_key,
|
|
99
|
+
decision_id,
|
|
100
|
+
thread_id,
|
|
101
|
+
action_type,
|
|
102
|
+
idempotency_key,
|
|
103
|
+
target,
|
|
104
|
+
payload,
|
|
105
|
+
status
|
|
106
|
+
)
|
|
107
|
+
values ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb, 'pending')
|
|
108
|
+
on conflict (idempotency_key)
|
|
109
|
+
do nothing
|
|
110
|
+
returning id
|
|
111
|
+
)
|
|
112
|
+
select id, true as inserted from inserted
|
|
113
|
+
union all
|
|
114
|
+
select id, false as inserted
|
|
115
|
+
from channel_outbox_actions
|
|
116
|
+
where idempotency_key = $7
|
|
117
|
+
limit 1
|
|
118
|
+
`;
|
|
119
|
+
var CLAIM_PENDING_OUTBOX_SQL = `
|
|
120
|
+
with candidates as (
|
|
121
|
+
select id
|
|
122
|
+
from channel_outbox_actions
|
|
123
|
+
where status in ('pending', 'retryable')
|
|
124
|
+
and next_attempt_at <= $2
|
|
125
|
+
order by next_attempt_at asc
|
|
126
|
+
limit $1
|
|
127
|
+
for update skip locked
|
|
128
|
+
)
|
|
129
|
+
update channel_outbox_actions as actions
|
|
130
|
+
set
|
|
131
|
+
status = 'sending',
|
|
132
|
+
attempt_count = actions.attempt_count + 1,
|
|
133
|
+
updated_at = now()
|
|
134
|
+
from candidates
|
|
135
|
+
where actions.id = candidates.id
|
|
136
|
+
returning
|
|
137
|
+
actions.id,
|
|
138
|
+
actions.workspace_id,
|
|
139
|
+
actions.provider_key,
|
|
140
|
+
actions.decision_id,
|
|
141
|
+
actions.thread_id,
|
|
142
|
+
actions.action_type,
|
|
143
|
+
actions.idempotency_key,
|
|
144
|
+
actions.target,
|
|
145
|
+
actions.payload,
|
|
146
|
+
actions.status,
|
|
147
|
+
actions.attempt_count,
|
|
148
|
+
actions.next_attempt_at,
|
|
149
|
+
actions.provider_message_id,
|
|
150
|
+
actions.last_error_code,
|
|
151
|
+
actions.last_error_message,
|
|
152
|
+
actions.created_at,
|
|
153
|
+
actions.updated_at,
|
|
154
|
+
actions.sent_at
|
|
155
|
+
`;
|
|
156
|
+
var INSERT_DELIVERY_ATTEMPT_SQL = `
|
|
157
|
+
insert into channel_delivery_attempts (
|
|
158
|
+
action_id,
|
|
159
|
+
attempt,
|
|
160
|
+
response_status,
|
|
161
|
+
response_body,
|
|
162
|
+
latency_ms
|
|
163
|
+
)
|
|
164
|
+
values ($1, $2, $3, $4, $5)
|
|
165
|
+
on conflict (action_id, attempt)
|
|
166
|
+
do update set
|
|
167
|
+
response_status = excluded.response_status,
|
|
168
|
+
response_body = excluded.response_body,
|
|
169
|
+
latency_ms = excluded.latency_ms,
|
|
170
|
+
created_at = now()
|
|
171
|
+
returning
|
|
172
|
+
id,
|
|
173
|
+
action_id,
|
|
174
|
+
attempt,
|
|
175
|
+
response_status,
|
|
176
|
+
response_body,
|
|
177
|
+
latency_ms,
|
|
178
|
+
created_at
|
|
179
|
+
`;
|
|
180
|
+
var MARK_OUTBOX_SENT_SQL = `
|
|
181
|
+
update channel_outbox_actions
|
|
182
|
+
set
|
|
183
|
+
status = 'sent',
|
|
184
|
+
provider_message_id = $2,
|
|
185
|
+
sent_at = now(),
|
|
186
|
+
updated_at = now(),
|
|
187
|
+
last_error_code = null,
|
|
188
|
+
last_error_message = null
|
|
189
|
+
where id = $1
|
|
190
|
+
`;
|
|
191
|
+
var MARK_OUTBOX_RETRY_SQL = `
|
|
192
|
+
update channel_outbox_actions
|
|
193
|
+
set
|
|
194
|
+
status = 'retryable',
|
|
195
|
+
next_attempt_at = $2,
|
|
196
|
+
last_error_code = $3,
|
|
197
|
+
last_error_message = $4,
|
|
198
|
+
updated_at = now()
|
|
199
|
+
where id = $1
|
|
200
|
+
`;
|
|
201
|
+
var MARK_OUTBOX_DEAD_LETTER_SQL = `
|
|
202
|
+
update channel_outbox_actions
|
|
203
|
+
set
|
|
204
|
+
status = 'dead_letter',
|
|
205
|
+
last_error_code = $2,
|
|
206
|
+
last_error_message = $3,
|
|
207
|
+
updated_at = now()
|
|
208
|
+
where id = $1
|
|
209
|
+
`;
|
|
210
|
+
export {
|
|
211
|
+
UPSERT_THREAD_SQL,
|
|
212
|
+
UPDATE_RECEIPT_STATUS_SQL,
|
|
213
|
+
MARK_RECEIPT_DUPLICATE_SQL,
|
|
214
|
+
MARK_OUTBOX_SENT_SQL,
|
|
215
|
+
MARK_OUTBOX_RETRY_SQL,
|
|
216
|
+
MARK_OUTBOX_DEAD_LETTER_SQL,
|
|
217
|
+
INSERT_DELIVERY_ATTEMPT_SQL,
|
|
218
|
+
INSERT_DECISION_SQL,
|
|
219
|
+
ENQUEUE_OUTBOX_SQL,
|
|
220
|
+
CLAIM_PENDING_OUTBOX_SQL,
|
|
221
|
+
CLAIM_EVENT_RECEIPT_SQL
|
|
222
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CHANNEL_RUNTIME_SCHEMA_STATEMENTS: readonly string[];
|