@nowledge/openclaw-nowledge-mem 0.8.36 → 0.8.39
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 +20 -0
- package/README.md +4 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +21 -10
- package/src/generated/plugin-core/acknowledgement.d.ts +6 -0
- package/src/generated/plugin-core/index.d.ts +3 -0
- package/src/generated/plugin-core/index.js +92 -0
- package/src/generated/plugin-core/manifest.json +22 -0
- package/src/generated/plugin-core/package.json +4 -0
- package/src/generated/plugin-core/thread-delta.d.ts +17 -0
- package/src/generated/plugin-core/timeout.d.ts +1 -0
- package/src/hooks/capture.js +2 -7
- package/src/session-delta.js +32 -45
- package/src/thread-sync-timeout.js +1 -14
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to the Nowledge Mem OpenClaw plugin will be documented in th
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [0.8.39] - 2026-10-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Schedule automatic distillation by stored thread ID, without the inline content removed in Mem 0.10.7.
|
|
12
|
+
- Report background task acceptance instead of a synchronous memory count, and reject missing scheduling acknowledgements or an unavailable background scheduler.
|
|
13
|
+
|
|
14
|
+
## [0.8.38] - 2026-10-17
|
|
15
|
+
|
|
16
|
+
- Refresh the embedded core 0.2.0 content manifest for Alma adoption; runtime behavior is unchanged.
|
|
17
|
+
|
|
18
|
+
## [0.8.37] - 2026-10-16
|
|
19
|
+
|
|
20
|
+
- Embed shared TypeScript core 0.2.0, including the shared prefix-hashing API.
|
|
21
|
+
- Reuse shared persistence acknowledgements and sync timeouts while retaining
|
|
22
|
+
OpenClaw message projection, compaction-shrink handling, legacy cursors, and
|
|
23
|
+
response-shape normalization. Reject unsafe acknowledgement counts.
|
|
24
|
+
- Retain embedded-core receipts for both npm and ClawHub artifacts.
|
|
25
|
+
|
|
26
|
+
|
|
7
27
|
## [0.8.36] - 2026-10-11
|
|
8
28
|
|
|
9
29
|
### Fixed
|
package/README.md
CHANGED
|
@@ -203,7 +203,7 @@ The behavioral skill and always-on hook nudge the agent to **search before answe
|
|
|
203
203
|
|
|
204
204
|
### Session Lifecycle (Automatic Capture)
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
Conversations are captured during the session lifecycle. After positive triage, the plugin schedules background distillation of the saved thread. Task acceptance is logged immediately; extracted memories appear after the backend finishes the task. No user action needed.
|
|
207
207
|
|
|
208
208
|
```mermaid
|
|
209
209
|
flowchart TD
|
|
@@ -216,7 +216,8 @@ flowchart TD
|
|
|
216
216
|
C --> F{"sessionDigest enabled+ enough messages+ cooldown passed?"}
|
|
217
217
|
F -->|"Yes"| G["LLM triage (~100 tokens):worth distilling?"]
|
|
218
218
|
F -->|"No"| H["Done - thread saved"]
|
|
219
|
-
G -->|"Decisions, insights,preferences found"| I["
|
|
219
|
+
G -->|"Decisions, insights,preferences found"| I["Schedule background distillation by thread ID"]
|
|
220
|
+
I --> J["Backend extracts structured memories(with sourceThreadId)"]
|
|
220
221
|
G -->|"Routine chat"| H
|
|
221
222
|
|
|
222
223
|
D --> H
|
|
@@ -226,7 +227,7 @@ flowchart TD
|
|
|
226
227
|
**Key points:**
|
|
227
228
|
- Thread capture is unconditional: every conversation is saved and searchable via `nowledge_mem_thread_search`
|
|
228
229
|
- Thread sync is incremental: the plugin preserves the real transcript, appends only the unsynced tail, and requires Mem's checkpointed acknowledgement before the local cursor advances
|
|
229
|
-
-
|
|
230
|
+
- Distillation is scheduled from `agent_end` or Context Engine `afterTurn`, not during compaction/reset checkpoints
|
|
230
231
|
- Distilled memories carry `sourceThreadId`, linking them back to the source conversation
|
|
231
232
|
- Cooldown (`digestMinInterval`, default 300s) prevents burst distillation
|
|
232
233
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-nowledge-mem",
|
|
3
3
|
"name": "Nowledge Mem",
|
|
4
4
|
"description": "Cross-tool knowledge graph memory for OpenClaw with startup context, graph search, and session distillation.",
|
|
5
|
-
"version": "0.8.
|
|
5
|
+
"version": "0.8.39",
|
|
6
6
|
"kind": ["memory", "context-engine"],
|
|
7
7
|
"contracts": {
|
|
8
8
|
"tools": [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowledge/openclaw-nowledge-mem",
|
|
3
3
|
"displayName": "Nowledge Mem for OpenClaw",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.39",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Nowledge Mem memory plugin for OpenClaw, local-first personal knowledge base",
|
|
7
7
|
"author": {
|
package/src/client.js
CHANGED
|
@@ -849,25 +849,36 @@ export class NowledgeMemClient {
|
|
|
849
849
|
}
|
|
850
850
|
|
|
851
851
|
/**
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
852
|
+
* Schedule background distillation of an already stored conversation thread.
|
|
853
|
+
* Mem 0.10.7+ no longer accepts inline content for distillation.
|
|
854
|
+
* The acknowledgement reports task acceptance, not completed memories.
|
|
855
855
|
*
|
|
856
|
-
* @param {{ threadId: string, title?: string
|
|
856
|
+
* @param {{ threadId: string, title?: string }} params
|
|
857
857
|
*/
|
|
858
|
-
async distillThread({ threadId, title
|
|
859
|
-
|
|
858
|
+
async distillThread({ threadId, title }) {
|
|
859
|
+
const normalizedThreadId = String(threadId || "").trim();
|
|
860
|
+
if (!normalizedThreadId) throw new Error("distillThread requires threadId");
|
|
861
|
+
const result = await this.apiJson(
|
|
860
862
|
"POST",
|
|
861
|
-
"/memories/distill",
|
|
863
|
+
"/memories/distill/schedule",
|
|
862
864
|
{
|
|
863
|
-
thread_id:
|
|
865
|
+
thread_id: normalizedThreadId,
|
|
864
866
|
thread_title: title || "",
|
|
865
|
-
thread_content: String(content || ""),
|
|
866
867
|
distillation_type: "simple_llm",
|
|
867
868
|
extraction_level: "swift",
|
|
868
869
|
},
|
|
869
|
-
60_000, // 60s timeout for LLM distillation
|
|
870
870
|
);
|
|
871
|
+
if (
|
|
872
|
+
result?.success !== true ||
|
|
873
|
+
typeof result.task_id !== "string" ||
|
|
874
|
+
!result.task_id.trim()
|
|
875
|
+
) {
|
|
876
|
+
throw new Error("Distillation scheduling was not acknowledged with a task ID");
|
|
877
|
+
}
|
|
878
|
+
if (result.enqueued === false) {
|
|
879
|
+
throw new Error("Distillation background scheduler is unavailable");
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
871
882
|
}
|
|
872
883
|
|
|
873
884
|
// ── Thread search ────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function isThreadAppendAck(data: unknown): boolean;
|
|
2
|
+
export declare function isCheckpointedAppendAck(data: unknown): boolean;
|
|
3
|
+
export declare function appendAcknowledgedRemoteCount(data: unknown): number | undefined;
|
|
4
|
+
export declare function isThreadCreateAck(data: unknown, expectedThreadId: string): boolean;
|
|
5
|
+
export declare function createAcknowledgedRemoteCount(data: unknown, expectedThreadId: string): number | undefined;
|
|
6
|
+
export declare function isCheckpointConflictResponse(data: unknown): boolean;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Generated from shared/plugin-core. Run npm --prefix shared/plugin-core run build.
|
|
2
|
+
|
|
3
|
+
// src/thread-delta.ts
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
function stableMessageFingerprint(message) {
|
|
6
|
+
return JSON.stringify(message);
|
|
7
|
+
}
|
|
8
|
+
function prefixFingerprint(messages, end, messageFingerprint) {
|
|
9
|
+
const hash = createHash("sha256");
|
|
10
|
+
for (const message of messages.slice(0, end)) {
|
|
11
|
+
const value = messageFingerprint(message);
|
|
12
|
+
hash.update(String(Buffer.byteLength(value)));
|
|
13
|
+
hash.update(":");
|
|
14
|
+
hash.update(value);
|
|
15
|
+
}
|
|
16
|
+
return hash.digest("hex");
|
|
17
|
+
}
|
|
18
|
+
function selectAcknowledgedDelta(messages, cursor, externalId, messageFingerprint = stableMessageFingerprint) {
|
|
19
|
+
let start = cursor?.count ?? 0;
|
|
20
|
+
const validCount = Number.isSafeInteger(start) && start >= 0 && start <= messages.length;
|
|
21
|
+
const prefixMatches = validCount && (start === 0 || externalId(messages[start - 1]) === cursor?.lastExternalId && prefixFingerprint(messages, start, messageFingerprint) === cursor?.prefixFingerprint);
|
|
22
|
+
const reset = !prefixMatches;
|
|
23
|
+
if (reset) start = 0;
|
|
24
|
+
const end = messages.length;
|
|
25
|
+
return {
|
|
26
|
+
start,
|
|
27
|
+
end,
|
|
28
|
+
messages: messages.slice(start),
|
|
29
|
+
next: {
|
|
30
|
+
count: end,
|
|
31
|
+
remoteCount: cursor?.remoteCount ?? end,
|
|
32
|
+
...end > 0 ? { lastExternalId: externalId(messages[end - 1]) } : {},
|
|
33
|
+
prefixFingerprint: prefixFingerprint(messages, end, messageFingerprint)
|
|
34
|
+
},
|
|
35
|
+
reset
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/acknowledgement.ts
|
|
40
|
+
function record(value) {
|
|
41
|
+
return typeof value === "object" && value !== null;
|
|
42
|
+
}
|
|
43
|
+
function messageCount(value) {
|
|
44
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
45
|
+
}
|
|
46
|
+
function isThreadAppendAck(data) {
|
|
47
|
+
return record(data) && data.success === true && messageCount(data.messages_added) && messageCount(data.total_messages);
|
|
48
|
+
}
|
|
49
|
+
function isCheckpointedAppendAck(data) {
|
|
50
|
+
return isThreadAppendAck(data) && data.append_mode === "checkpointed";
|
|
51
|
+
}
|
|
52
|
+
function appendAcknowledgedRemoteCount(data) {
|
|
53
|
+
return isThreadAppendAck(data) ? data.total_messages : void 0;
|
|
54
|
+
}
|
|
55
|
+
function isThreadCreateAck(data, expectedThreadId) {
|
|
56
|
+
return record(data) && record(data.thread) && data.thread.thread_id === expectedThreadId;
|
|
57
|
+
}
|
|
58
|
+
function createAcknowledgedRemoteCount(data, expectedThreadId) {
|
|
59
|
+
if (!isThreadCreateAck(data, expectedThreadId)) return void 0;
|
|
60
|
+
const result = data;
|
|
61
|
+
if (result.thread.message_count !== void 0) {
|
|
62
|
+
return messageCount(result.thread.message_count) ? result.thread.message_count : void 0;
|
|
63
|
+
}
|
|
64
|
+
return Array.isArray(result.messages) ? result.messages.length : void 0;
|
|
65
|
+
}
|
|
66
|
+
function isCheckpointConflictResponse(data) {
|
|
67
|
+
return record(data) && data.error_code === "checkpoint_conflict";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/timeout.ts
|
|
71
|
+
var DEFAULT_THREAD_SYNC_TIMEOUT_MS = 12e4;
|
|
72
|
+
var MIN_THREAD_SYNC_TIMEOUT_MS = 1e3;
|
|
73
|
+
var MAX_THREAD_SYNC_TIMEOUT_MS = 30 * 6e4;
|
|
74
|
+
function resolveThreadSyncTimeoutMs(raw) {
|
|
75
|
+
const parsed = Number(raw);
|
|
76
|
+
if (!raw?.trim() || !Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
77
|
+
return DEFAULT_THREAD_SYNC_TIMEOUT_MS;
|
|
78
|
+
}
|
|
79
|
+
return Math.min(MAX_THREAD_SYNC_TIMEOUT_MS, Math.max(MIN_THREAD_SYNC_TIMEOUT_MS, parsed));
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
appendAcknowledgedRemoteCount,
|
|
83
|
+
createAcknowledgedRemoteCount,
|
|
84
|
+
isCheckpointConflictResponse,
|
|
85
|
+
isCheckpointedAppendAck,
|
|
86
|
+
isThreadAppendAck,
|
|
87
|
+
isThreadCreateAck,
|
|
88
|
+
prefixFingerprint,
|
|
89
|
+
resolveThreadSyncTimeoutMs,
|
|
90
|
+
selectAcknowledgedDelta,
|
|
91
|
+
stableMessageFingerprint
|
|
92
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"core": {
|
|
4
|
+
"name": "@nowledge/plugin-core",
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"sourceDigest": "f06362cae4cdb5cd24a1eab4420860d41ab707f1558a25b779fcb3d74c651c56"
|
|
7
|
+
},
|
|
8
|
+
"build": {
|
|
9
|
+
"target": "es2022",
|
|
10
|
+
"format": "esm",
|
|
11
|
+
"esbuild": "0.25.9",
|
|
12
|
+
"typescript": "5.6.3"
|
|
13
|
+
},
|
|
14
|
+
"files": {
|
|
15
|
+
"acknowledgement.d.ts": "ffd0fd090f289c4deafba41d9faa83fb7f4a6070bd53588de5d1a947b076f65a",
|
|
16
|
+
"index.d.ts": "6e7965f903a043a34c3dd454f909e878134c1bf451ec7ff3c3026f035f1aaee0",
|
|
17
|
+
"index.js": "0e25a05abe9f9282dc6a7d3cc64a3da872c1e5192908308ee5086c0465f6c032",
|
|
18
|
+
"package.json": "609158e6c5fbc237939fa3ddf7faab80ab690bdc0c8d584414a885130103c4e8",
|
|
19
|
+
"thread-delta.d.ts": "83f31e3334086f6fdfe5e6e1b1c4b1a994bb6d49fda2fdedf450d14f4ec18cba",
|
|
20
|
+
"timeout.d.ts": "85bfd4185e9d0b22073250b5cbe19da38613d9af9fbc2022403ebb527dad9f8f"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type AcknowledgedCursor = {
|
|
2
|
+
count: number;
|
|
3
|
+
remoteCount: number;
|
|
4
|
+
lastExternalId?: string;
|
|
5
|
+
prefixFingerprint: string;
|
|
6
|
+
};
|
|
7
|
+
export type AcknowledgedDelta<T> = {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
messages: T[];
|
|
11
|
+
next: AcknowledgedCursor;
|
|
12
|
+
reset: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare function stableMessageFingerprint(message: unknown): string;
|
|
15
|
+
export declare function prefixFingerprint<T>(messages: readonly T[], end: number, messageFingerprint: (message: T) => string): string;
|
|
16
|
+
/** The returned cursor is a candidate; only persistence acknowledgement commits it. */
|
|
17
|
+
export declare function selectAcknowledgedDelta<T>(messages: readonly T[], cursor: AcknowledgedCursor | undefined, externalId: (message: T) => string, messageFingerprint?: (message: T) => string): AcknowledgedDelta<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveThreadSyncTimeoutMs(raw: string | undefined): number;
|
package/src/hooks/capture.js
CHANGED
|
@@ -742,7 +742,7 @@ async function persistMessages({
|
|
|
742
742
|
|
|
743
743
|
/**
|
|
744
744
|
* Build a plain-text conversation string from normalized messages.
|
|
745
|
-
* Used as input for triage
|
|
745
|
+
* Used as input for triage; distillation reads the stored thread.
|
|
746
746
|
*
|
|
747
747
|
* Per-message content is capped at MAX_DISTILL_MESSAGE_CHARS and the
|
|
748
748
|
* total output at MAX_CONVERSATION_CHARS to keep LLM API payloads
|
|
@@ -823,15 +823,10 @@ export async function triageAndDistill({
|
|
|
823
823
|
const distillResult = await client.distillThread({
|
|
824
824
|
threadId: captureResult.threadId,
|
|
825
825
|
title: buildThreadTitle(ctx, captureResult.normalized),
|
|
826
|
-
content: conversationText,
|
|
827
826
|
});
|
|
828
827
|
|
|
829
|
-
const count =
|
|
830
|
-
distillResult?.memories_created ??
|
|
831
|
-
distillResult?.created_memories?.length ??
|
|
832
|
-
0;
|
|
833
828
|
logger.info(
|
|
834
|
-
`capture:
|
|
829
|
+
`capture: scheduled background distillation for ${captureResult.threadId} (task ${distillResult.task_id})`,
|
|
835
830
|
);
|
|
836
831
|
} catch (err) {
|
|
837
832
|
const message = err instanceof Error ? err.message : String(err);
|
package/src/session-delta.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
createAcknowledgedRemoteCount,
|
|
4
|
+
prefixFingerprint as corePrefixFingerprint,
|
|
5
|
+
} from "./generated/plugin-core/index.js";
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
isCheckpointConflictResponse,
|
|
9
|
+
isCheckpointedAppendAck,
|
|
10
|
+
isThreadAppendAck,
|
|
11
|
+
} from "./generated/plugin-core/index.js";
|
|
2
12
|
|
|
3
13
|
export function sessionSyncLaneKey(threadId, apiUrl, apiKey, spaceId) {
|
|
4
14
|
const destination = createHash("sha256")
|
|
@@ -24,54 +34,27 @@ export function prefixFingerprint(
|
|
|
24
34
|
end,
|
|
25
35
|
messageFingerprint = stableMessageFingerprint,
|
|
26
36
|
) {
|
|
27
|
-
|
|
28
|
-
for (const message of messages.slice(0, end)) {
|
|
29
|
-
const value = messageFingerprint(message);
|
|
30
|
-
hash.update(String(Buffer.byteLength(value)));
|
|
31
|
-
hash.update(":");
|
|
32
|
-
hash.update(value);
|
|
33
|
-
}
|
|
34
|
-
return hash.digest("hex");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function isThreadAppendAck(data) {
|
|
38
|
-
return (
|
|
39
|
-
data !== null &&
|
|
40
|
-
typeof data === "object" &&
|
|
41
|
-
data.success === true &&
|
|
42
|
-
Number.isInteger(data.messages_added) &&
|
|
43
|
-
data.messages_added >= 0 &&
|
|
44
|
-
Number.isInteger(data.total_messages) &&
|
|
45
|
-
data.total_messages >= 0
|
|
46
|
-
);
|
|
37
|
+
return corePrefixFingerprint(messages, end, messageFingerprint);
|
|
47
38
|
}
|
|
48
39
|
|
|
49
40
|
export function threadCreateAck(data, expectedThreadId) {
|
|
50
41
|
if (data === null || typeof data !== "object") return null;
|
|
51
42
|
const thread =
|
|
52
|
-
data.thread !== null && typeof data.thread === "object"
|
|
43
|
+
data.thread !== null && typeof data.thread === "object"
|
|
44
|
+
? data.thread
|
|
45
|
+
: data;
|
|
53
46
|
const threadId = String(thread.thread_id ?? "").trim();
|
|
54
|
-
const totalMessages =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return { threadId, totalMessages };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function isCheckpointedAppendAck(data) {
|
|
66
|
-
return isThreadAppendAck(data) && data.append_mode === "checkpointed";
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function isCheckpointConflictResponse(data) {
|
|
70
|
-
return (
|
|
71
|
-
data !== null &&
|
|
72
|
-
typeof data === "object" &&
|
|
73
|
-
data.error_code === "checkpoint_conflict"
|
|
47
|
+
const totalMessages = createAcknowledgedRemoteCount(
|
|
48
|
+
{
|
|
49
|
+
thread: {
|
|
50
|
+
thread_id: threadId,
|
|
51
|
+
message_count: thread.message_count ?? thread.total_messages,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
String(expectedThreadId || "").trim(),
|
|
74
55
|
);
|
|
56
|
+
if (totalMessages === undefined) return null;
|
|
57
|
+
return { threadId, totalMessages };
|
|
75
58
|
}
|
|
76
59
|
|
|
77
60
|
export function selectSnapshotDelta(
|
|
@@ -102,9 +85,7 @@ export function selectSnapshotDelta(
|
|
|
102
85
|
};
|
|
103
86
|
}
|
|
104
87
|
const acknowledgedPrefix =
|
|
105
|
-
start >= 0
|
|
106
|
-
? prefixFingerprint(messages, start, messageFingerprint)
|
|
107
|
-
: "";
|
|
88
|
+
start >= 0 ? prefixFingerprint(messages, start, messageFingerprint) : "";
|
|
108
89
|
const fingerprintTrusted = Boolean(cursor?.prefixFingerprint);
|
|
109
90
|
const externalIdTrusted = Boolean(cursor?.lastExternalId);
|
|
110
91
|
if (
|
|
@@ -134,7 +115,13 @@ export function selectSnapshotDelta(
|
|
|
134
115
|
};
|
|
135
116
|
}
|
|
136
117
|
|
|
137
|
-
export function contentBoundIdempotencyKey(
|
|
118
|
+
export function contentBoundIdempotencyKey(
|
|
119
|
+
prefix,
|
|
120
|
+
threadId,
|
|
121
|
+
start,
|
|
122
|
+
end,
|
|
123
|
+
fingerprint,
|
|
124
|
+
) {
|
|
138
125
|
return `${prefix}:${threadId}:${start}-${end}:${fingerprint}`;
|
|
139
126
|
}
|
|
140
127
|
|
|
@@ -1,14 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const MIN_THREAD_SYNC_TIMEOUT_MS = 1_000;
|
|
3
|
-
const MAX_THREAD_SYNC_TIMEOUT_MS = 30 * 60_000;
|
|
4
|
-
|
|
5
|
-
export function resolveThreadSyncTimeoutMs(raw) {
|
|
6
|
-
const parsed = Number(raw);
|
|
7
|
-
if (!String(raw ?? "").trim() || !Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
8
|
-
return DEFAULT_THREAD_SYNC_TIMEOUT_MS;
|
|
9
|
-
}
|
|
10
|
-
return Math.min(
|
|
11
|
-
MAX_THREAD_SYNC_TIMEOUT_MS,
|
|
12
|
-
Math.max(MIN_THREAD_SYNC_TIMEOUT_MS, parsed),
|
|
13
|
-
);
|
|
14
|
-
}
|
|
1
|
+
export { resolveThreadSyncTimeoutMs } from "./generated/plugin-core/index.js";
|