@ekairos/thread 1.21.89-beta.0 → 1.21.90-beta.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/schema.js
CHANGED
|
@@ -13,6 +13,7 @@ export const threadDomain = domain("thread")
|
|
|
13
13
|
thread_contexts: i.entity({
|
|
14
14
|
createdAt: i.date(),
|
|
15
15
|
updatedAt: i.date().optional(),
|
|
16
|
+
// Required for prefix/history queries that use `$like` over context keys.
|
|
16
17
|
key: i.string().optional().indexed().unique(),
|
|
17
18
|
status: i.string().optional().indexed(), // open | streaming | closed
|
|
18
19
|
content: i.any().optional(),
|
|
@@ -74,6 +74,39 @@ function inferDirection(item) {
|
|
|
74
74
|
}
|
|
75
75
|
return undefined;
|
|
76
76
|
}
|
|
77
|
+
function shouldDebugThreadStoreSteps() {
|
|
78
|
+
return (process.env.EKAIROS_THREAD_DEBUG === "1" ||
|
|
79
|
+
process.env.PLAYWRIGHT_TEST === "1");
|
|
80
|
+
}
|
|
81
|
+
function summarizeStepError(error) {
|
|
82
|
+
const err = error;
|
|
83
|
+
return {
|
|
84
|
+
name: err?.name,
|
|
85
|
+
message: err?.message,
|
|
86
|
+
code: err?.code,
|
|
87
|
+
status: err?.status ?? err?.statusCode,
|
|
88
|
+
details: err?.details ?? err?.body ?? err?.response ?? err?.data,
|
|
89
|
+
stack: typeof err?.stack === "string"
|
|
90
|
+
? err.stack.slice(0, 1500)
|
|
91
|
+
: undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function summarizeContextIdentifierForLog(identifier) {
|
|
95
|
+
return {
|
|
96
|
+
id: identifier && typeof identifier.id === "string"
|
|
97
|
+
? String(identifier.id)
|
|
98
|
+
: undefined,
|
|
99
|
+
key: identifier && typeof identifier.key === "string"
|
|
100
|
+
? String(identifier.key)
|
|
101
|
+
: undefined,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function logStepDebug(message, payload) {
|
|
105
|
+
if (!shouldDebugThreadStoreSteps())
|
|
106
|
+
return;
|
|
107
|
+
// eslint-disable-next-line no-console
|
|
108
|
+
console.error(`[thread][store.steps] ${message}`, payload);
|
|
109
|
+
}
|
|
77
110
|
/**
|
|
78
111
|
* Initializes/ensures the story context exists and emits a single `data-context-id` chunk.
|
|
79
112
|
*
|
|
@@ -174,13 +207,54 @@ export async function saveTriggerAndCreateExecution(params) {
|
|
|
174
207
|
const { getThreadRuntime } = await import("@ekairos/thread/runtime");
|
|
175
208
|
const runtime = await getThreadRuntime(params.env);
|
|
176
209
|
const { store, db } = runtime;
|
|
177
|
-
|
|
210
|
+
logStepDebug("saveTriggerAndCreateExecution:start", {
|
|
211
|
+
contextIdentifier: summarizeContextIdentifierForLog(params.contextIdentifier),
|
|
212
|
+
triggerEventId: params.triggerEvent?.id,
|
|
213
|
+
triggerEventType: params.triggerEvent?.type,
|
|
214
|
+
triggerEventChannel: params.triggerEvent?.channel,
|
|
215
|
+
triggerEventCreatedAt: params.triggerEvent?.createdAt,
|
|
216
|
+
});
|
|
217
|
+
let saved;
|
|
218
|
+
try {
|
|
219
|
+
saved = await store.saveItem(params.contextIdentifier, params.triggerEvent);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
logStepDebug("saveTriggerAndCreateExecution:saveItem:error", {
|
|
223
|
+
contextIdentifier: summarizeContextIdentifierForLog(params.contextIdentifier),
|
|
224
|
+
triggerEventId: params.triggerEvent?.id,
|
|
225
|
+
error: summarizeStepError(error),
|
|
226
|
+
});
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
178
229
|
const uuid = globalThis.crypto?.randomUUID?.();
|
|
179
230
|
const reactionEventId = typeof uuid === "string"
|
|
180
231
|
? uuid
|
|
181
232
|
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
182
|
-
|
|
183
|
-
|
|
233
|
+
try {
|
|
234
|
+
await store.updateContextStatus(params.contextIdentifier, "streaming");
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
logStepDebug("saveTriggerAndCreateExecution:updateContextStatus:error", {
|
|
238
|
+
contextIdentifier: summarizeContextIdentifierForLog(params.contextIdentifier),
|
|
239
|
+
triggerEventId: saved.id,
|
|
240
|
+
reactionEventId,
|
|
241
|
+
error: summarizeStepError(error),
|
|
242
|
+
});
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
let execution;
|
|
246
|
+
try {
|
|
247
|
+
execution = await store.createExecution(params.contextIdentifier, saved.id, reactionEventId);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
logStepDebug("saveTriggerAndCreateExecution:createExecution:error", {
|
|
251
|
+
contextIdentifier: summarizeContextIdentifierForLog(params.contextIdentifier),
|
|
252
|
+
triggerEventId: saved.id,
|
|
253
|
+
reactionEventId,
|
|
254
|
+
error: summarizeStepError(error),
|
|
255
|
+
});
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
184
258
|
const { runId, meta } = await resolveWorkflowRunId({
|
|
185
259
|
env: params.env,
|
|
186
260
|
db,
|
|
@@ -14,6 +14,7 @@ export type InstantStoreDb = any;
|
|
|
14
14
|
export declare class InstantStore implements ThreadStore {
|
|
15
15
|
private db;
|
|
16
16
|
constructor(db: InstantStoreDb);
|
|
17
|
+
private syncLegacyThreadRowBestEffort;
|
|
17
18
|
private ensureContextThreadLink;
|
|
18
19
|
private normalizeThread;
|
|
19
20
|
private normalizeContext;
|
|
@@ -4,10 +4,92 @@ import { convertItemsToModelMessages } from "../thread.events.js";
|
|
|
4
4
|
export { parseAndStoreDocument } from "./instant.document-parser.js";
|
|
5
5
|
import { expandEventsWithInstantDocuments } from "./instant.documents.js";
|
|
6
6
|
export { coerceDocumentTextPages, expandEventsWithInstantDocuments, } from "./instant.documents.js";
|
|
7
|
+
function shouldDebugInstantStore() {
|
|
8
|
+
return (process.env.EKAIROS_THREAD_DEBUG === "1" ||
|
|
9
|
+
process.env.PLAYWRIGHT_TEST === "1");
|
|
10
|
+
}
|
|
11
|
+
function clipText(value, max = 500) {
|
|
12
|
+
if (value.length <= max)
|
|
13
|
+
return value;
|
|
14
|
+
return `${value.slice(0, max)}...<truncated:${value.length - max}>`;
|
|
15
|
+
}
|
|
16
|
+
function simplifyForLog(value, depth = 0) {
|
|
17
|
+
if (value === null || value === undefined)
|
|
18
|
+
return value;
|
|
19
|
+
if (typeof value === "string")
|
|
20
|
+
return clipText(value);
|
|
21
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
22
|
+
return value;
|
|
23
|
+
if (value instanceof Date)
|
|
24
|
+
return value.toISOString();
|
|
25
|
+
if (depth >= 2)
|
|
26
|
+
return "[max-depth]";
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
const maxItems = 8;
|
|
29
|
+
const items = value
|
|
30
|
+
.slice(0, maxItems)
|
|
31
|
+
.map((item) => simplifyForLog(item, depth + 1));
|
|
32
|
+
if (value.length > maxItems) {
|
|
33
|
+
items.push(`...+${value.length - maxItems} more`);
|
|
34
|
+
}
|
|
35
|
+
return items;
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "object") {
|
|
38
|
+
const entries = Object.entries(value);
|
|
39
|
+
const maxEntries = 16;
|
|
40
|
+
const out = {};
|
|
41
|
+
for (const [key, entryValue] of entries.slice(0, maxEntries)) {
|
|
42
|
+
out[key] = simplifyForLog(entryValue, depth + 1);
|
|
43
|
+
}
|
|
44
|
+
if (entries.length > maxEntries) {
|
|
45
|
+
out.__truncatedKeys = entries.length - maxEntries;
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
return String(value);
|
|
50
|
+
}
|
|
51
|
+
function summarizeError(error) {
|
|
52
|
+
const err = error;
|
|
53
|
+
return {
|
|
54
|
+
name: err?.name,
|
|
55
|
+
message: err?.message,
|
|
56
|
+
code: err?.code,
|
|
57
|
+
status: err?.status ?? err?.statusCode,
|
|
58
|
+
details: simplifyForLog(err?.details ?? err?.body ?? err?.response ?? err?.data),
|
|
59
|
+
stack: typeof err?.stack === "string"
|
|
60
|
+
? clipText(err.stack, 1500)
|
|
61
|
+
: undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function logInstantTransactFailure(params) {
|
|
65
|
+
if (!shouldDebugInstantStore())
|
|
66
|
+
return;
|
|
67
|
+
const payload = {
|
|
68
|
+
action: params.action,
|
|
69
|
+
meta: simplifyForLog(params.meta ?? {}),
|
|
70
|
+
txs: simplifyForLog(params.txs),
|
|
71
|
+
error: summarizeError(params.error),
|
|
72
|
+
};
|
|
73
|
+
// eslint-disable-next-line no-console
|
|
74
|
+
console.error("[thread][instant.store] transact failed", payload);
|
|
75
|
+
}
|
|
7
76
|
export class InstantStore {
|
|
8
77
|
constructor(db) {
|
|
9
78
|
this.db = db;
|
|
10
79
|
}
|
|
80
|
+
async syncLegacyThreadRowBestEffort(threadId) {
|
|
81
|
+
if (!threadId)
|
|
82
|
+
return;
|
|
83
|
+
try {
|
|
84
|
+
await this.db.transact([
|
|
85
|
+
this.db.tx.thread[threadId].update({}),
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Legacy `thread` entity does not exist on all apps.
|
|
90
|
+
// This is a best-effort compatibility write.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
11
93
|
async ensureContextThreadLink(context, contextIdentifier) {
|
|
12
94
|
if (context.threadId)
|
|
13
95
|
return context;
|
|
@@ -15,6 +97,7 @@ export class InstantStore {
|
|
|
15
97
|
context.key ??
|
|
16
98
|
(context.id ? String(context.id) : null);
|
|
17
99
|
const thread = await this.createThread(fallbackKey ? { key: String(fallbackKey) } : null, context.id ? String(context.id) : undefined);
|
|
100
|
+
await this.syncLegacyThreadRowBestEffort(thread.id);
|
|
18
101
|
const txs = [this.db.tx.thread_contexts[context.id].link({ thread: thread.id })];
|
|
19
102
|
if (!context.key && thread.key) {
|
|
20
103
|
txs.push(this.db.tx.thread_contexts[context.id].update({
|
|
@@ -56,6 +139,7 @@ export class InstantStore {
|
|
|
56
139
|
};
|
|
57
140
|
}
|
|
58
141
|
async getThreadByContextId(contextId) {
|
|
142
|
+
let contextKey = null;
|
|
59
143
|
try {
|
|
60
144
|
const res = await this.db.query({
|
|
61
145
|
thread_contexts: {
|
|
@@ -64,13 +148,27 @@ export class InstantStore {
|
|
|
64
148
|
},
|
|
65
149
|
});
|
|
66
150
|
const ctx = res?.thread_contexts?.[0];
|
|
151
|
+
contextKey = typeof ctx?.key === "string" ? ctx.key : null;
|
|
67
152
|
const threadNode = Array.isArray(ctx?.thread) ? ctx.thread[0] : ctx?.thread;
|
|
68
|
-
if (threadNode)
|
|
69
|
-
|
|
153
|
+
if (threadNode) {
|
|
154
|
+
const normalized = this.normalizeThread(threadNode);
|
|
155
|
+
if (normalized.id)
|
|
156
|
+
return normalized;
|
|
157
|
+
}
|
|
70
158
|
}
|
|
71
159
|
catch {
|
|
72
160
|
// fallback below
|
|
73
161
|
}
|
|
162
|
+
if (contextKey) {
|
|
163
|
+
try {
|
|
164
|
+
const byKey = await this.getThread({ key: contextKey });
|
|
165
|
+
if (byKey?.id)
|
|
166
|
+
return byKey;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// continue fallback chain
|
|
170
|
+
}
|
|
171
|
+
}
|
|
74
172
|
try {
|
|
75
173
|
const res = await this.db.query({
|
|
76
174
|
thread_threads: {
|
|
@@ -81,7 +179,23 @@ export class InstantStore {
|
|
|
81
179
|
return row ? this.normalizeThread(row) : null;
|
|
82
180
|
}
|
|
83
181
|
catch {
|
|
84
|
-
|
|
182
|
+
try {
|
|
183
|
+
const executionFallback = await this.db.query({
|
|
184
|
+
thread_executions: {
|
|
185
|
+
$: { where: { "context.id": contextId }, limit: 1 },
|
|
186
|
+
thread: {},
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
const executionRow = executionFallback?.thread_executions?.[0];
|
|
190
|
+
const executionThread = Array.isArray(executionRow?.thread)
|
|
191
|
+
? executionRow.thread[0]
|
|
192
|
+
: executionRow?.thread;
|
|
193
|
+
const normalized = executionThread ? this.normalizeThread(executionThread) : null;
|
|
194
|
+
return normalized?.id ? normalized : null;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
85
199
|
}
|
|
86
200
|
}
|
|
87
201
|
async getContextByThreadId(threadId) {
|
|
@@ -142,6 +256,7 @@ export class InstantStore {
|
|
|
142
256
|
key,
|
|
143
257
|
}),
|
|
144
258
|
]);
|
|
259
|
+
await this.syncLegacyThreadRowBestEffort(threadId);
|
|
145
260
|
const thread = await this.getThread({ id: threadId });
|
|
146
261
|
if (!thread) {
|
|
147
262
|
throw new Error("InstantStore: failed to create thread");
|
|
@@ -333,7 +448,26 @@ export class InstantStore {
|
|
|
333
448
|
];
|
|
334
449
|
txs.push(this.db.tx.thread_items[event.id].link({ context: context.id }));
|
|
335
450
|
txs.push(this.db.tx.thread_items[event.id].link({ thread: thread.id }));
|
|
336
|
-
|
|
451
|
+
try {
|
|
452
|
+
await this.db.transact(txs);
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
logInstantTransactFailure({
|
|
456
|
+
action: "saveItem",
|
|
457
|
+
meta: {
|
|
458
|
+
contextIdentifier: simplifyForLog(contextIdentifier),
|
|
459
|
+
contextId: context.id,
|
|
460
|
+
threadId: thread.id,
|
|
461
|
+
eventId: event?.id,
|
|
462
|
+
eventType: event?.type,
|
|
463
|
+
eventChannel: event?.channel,
|
|
464
|
+
eventCreatedAt: event?.createdAt,
|
|
465
|
+
},
|
|
466
|
+
txs,
|
|
467
|
+
error,
|
|
468
|
+
});
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
337
471
|
const persisted = await this.getItem(event.id);
|
|
338
472
|
if (!persisted)
|
|
339
473
|
throw new Error("InstantStore: failed to read event after save");
|
|
@@ -414,7 +548,25 @@ export class InstantStore {
|
|
|
414
548
|
status: "streaming",
|
|
415
549
|
updatedAt: new Date(),
|
|
416
550
|
}));
|
|
417
|
-
|
|
551
|
+
try {
|
|
552
|
+
await this.db.transact(txs);
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
logInstantTransactFailure({
|
|
556
|
+
action: "createExecution",
|
|
557
|
+
meta: {
|
|
558
|
+
contextIdentifier: simplifyForLog(contextIdentifier),
|
|
559
|
+
contextId: context.id,
|
|
560
|
+
threadId: thread.id,
|
|
561
|
+
executionId,
|
|
562
|
+
triggerEventId,
|
|
563
|
+
reactionEventId,
|
|
564
|
+
},
|
|
565
|
+
txs,
|
|
566
|
+
error,
|
|
567
|
+
});
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
418
570
|
return { id: executionId };
|
|
419
571
|
}
|
|
420
572
|
async completeExecution(contextIdentifier, executionId, status) {
|
|
@@ -442,7 +594,23 @@ export class InstantStore {
|
|
|
442
594
|
}),
|
|
443
595
|
];
|
|
444
596
|
txs.push(this.db.tx.thread_steps[stepId].link({ execution: params.executionId }));
|
|
445
|
-
|
|
597
|
+
try {
|
|
598
|
+
await this.db.transact(txs);
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
logInstantTransactFailure({
|
|
602
|
+
action: "createStep",
|
|
603
|
+
meta: {
|
|
604
|
+
executionId: params.executionId,
|
|
605
|
+
iteration: params.iteration,
|
|
606
|
+
stepId,
|
|
607
|
+
eventId,
|
|
608
|
+
},
|
|
609
|
+
txs,
|
|
610
|
+
error,
|
|
611
|
+
});
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
446
614
|
return { id: stepId, eventId };
|
|
447
615
|
}
|
|
448
616
|
async updateStep(stepId, patch) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekairos/thread",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.90-beta.0",
|
|
4
4
|
"description": "Pulzar Thread - Workflow-based AI Threads",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
},
|
|
111
111
|
"dependencies": {
|
|
112
112
|
"@ai-sdk/openai": "^2.0.52",
|
|
113
|
-
"@ekairos/domain": "^1.21.
|
|
113
|
+
"@ekairos/domain": "^1.21.90-beta.0",
|
|
114
114
|
"@instantdb/admin": "0.22.126",
|
|
115
115
|
"@instantdb/core": "0.22.126",
|
|
116
116
|
"@vercel/mcp-adapter": "^1.0.0",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"jose": "^6.1.3",
|
|
121
121
|
"llamaindex": "^0.12.0",
|
|
122
122
|
"react": "^19.2.0",
|
|
123
|
-
"workflow": "4.1.0-beta.
|
|
123
|
+
"workflow": "4.1.0-beta.55",
|
|
124
124
|
"xmlbuilder2": "^3.1.1",
|
|
125
125
|
"zod": "^4.1.8"
|
|
126
126
|
},
|