@animalabs/agent-framework 0.26.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -7
- package/dist/src/agent.d.ts +263 -22
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +531 -54
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +1 -2
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +33 -0
- package/dist/src/auto-continue.d.ts.map +1 -0
- package/dist/src/auto-continue.js +35 -0
- package/dist/src/auto-continue.js.map +1 -0
- package/dist/src/code-execution/isolate/scripts.d.ts +11 -0
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +5 -1
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/framework.d.ts +829 -156
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3906 -1008
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +309 -46
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +2117 -395
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +2 -2
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/recipe-reconcile.d.ts +103 -0
- package/dist/src/gate/recipe-reconcile.d.ts.map +1 -0
- package/dist/src/gate/recipe-reconcile.js +325 -0
- package/dist/src/gate/recipe-reconcile.js.map +1 -0
- package/dist/src/gate/types.d.ts +68 -8
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +433 -113
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +1152 -434
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +16 -8
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +29 -15
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +14 -2
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts +7 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +32 -7
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +5 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +4 -0
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +14 -2
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +73 -28
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/prose-grammar.d.ts +4 -0
- package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
- package/dist/src/mcpl/prose-grammar.js +6 -0
- package/dist/src/mcpl/prose-grammar.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +3 -0
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +20 -2
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +33 -11
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +63 -42
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +177 -145
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +380 -46
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +737 -78
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +82 -3
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +132 -7
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.d.ts +11 -0
- package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.js +21 -0
- package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts +259 -0
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -0
- package/dist/src/mcpl/tool-presence.js +628 -0
- package/dist/src/mcpl/tool-presence.js.map +1 -0
- package/dist/src/mcpl/tool-result-normalize.d.ts +9 -3
- package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
- package/dist/src/mcpl/tool-result-normalize.js +14 -6
- package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +39 -0
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -0
- package/dist/src/mcpl/tool-signature.js +292 -0
- package/dist/src/mcpl/tool-signature.js.map +1 -0
- package/dist/src/mcpl/toolset-host.d.ts +200 -0
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -0
- package/dist/src/mcpl/toolset-host.js +621 -0
- package/dist/src/mcpl/toolset-host.js.map +1 -0
- package/dist/src/mcpl/toolsets-module.d.ts +80 -0
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -0
- package/dist/src/mcpl/toolsets-module.js +431 -0
- package/dist/src/mcpl/toolsets-module.js.map +1 -0
- package/dist/src/mcpl/transport.d.ts +2 -2
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +3 -2
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +21 -1
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/mcpl/visible-content.d.ts +7 -9
- package/dist/src/mcpl/visible-content.d.ts.map +1 -1
- package/dist/src/mcpl/visible-content.js +21 -9
- package/dist/src/mcpl/visible-content.js.map +1 -1
- package/dist/src/module-registry.d.ts +23 -8
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +68 -15
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.js +1 -1
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +2 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +51 -17
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +108 -6
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +273 -57
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts +181 -0
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -0
- package/dist/src/modules/workspace/direct-write.js +384 -0
- package/dist/src/modules/workspace/direct-write.js.map +1 -0
- package/dist/src/modules/workspace/edit-text.d.ts +26 -12
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/edit-text.js +58 -28
- package/dist/src/modules/workspace/edit-text.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +10 -0
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +1 -1
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +246 -27
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +1216 -236
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/read-page.d.ts +41 -0
- package/dist/src/modules/workspace/read-page.d.ts.map +1 -0
- package/dist/src/modules/workspace/read-page.js +213 -0
- package/dist/src/modules/workspace/read-page.js.map +1 -0
- package/dist/src/modules/workspace/result-text.d.ts +67 -0
- package/dist/src/modules/workspace/result-text.d.ts.map +1 -0
- package/dist/src/modules/workspace/result-text.js +94 -0
- package/dist/src/modules/workspace/result-text.js.map +1 -0
- package/dist/src/modules/workspace/text.d.ts +29 -12
- package/dist/src/modules/workspace/text.d.ts.map +1 -1
- package/dist/src/modules/workspace/text.js +35 -33
- package/dist/src/modules/workspace/text.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +41 -17
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/sent-rows.d.ts +59 -0
- package/dist/src/sent-rows.d.ts.map +1 -0
- package/dist/src/sent-rows.js +145 -0
- package/dist/src/sent-rows.js.map +1 -0
- package/dist/src/silent-heartbeat.d.ts +17 -10
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stable-hash.d.ts +3 -0
- package/dist/src/stable-hash.d.ts.map +1 -0
- package/dist/src/stable-hash.js +15 -0
- package/dist/src/stable-hash.js.map +1 -0
- package/dist/src/stored-injections.d.ts +99 -0
- package/dist/src/stored-injections.d.ts.map +1 -0
- package/dist/src/stored-injections.js +106 -0
- package/dist/src/stored-injections.js.map +1 -0
- package/dist/src/text-as-stored.d.ts +4 -3
- package/dist/src/text-as-stored.d.ts.map +1 -1
- package/dist/src/text-as-stored.js +4 -3
- package/dist/src/text-as-stored.js.map +1 -1
- package/dist/src/thinking-drops.d.ts +81 -0
- package/dist/src/thinking-drops.d.ts.map +1 -0
- package/dist/src/thinking-drops.js +103 -0
- package/dist/src/thinking-drops.js.map +1 -0
- package/dist/src/tool-image-ledger.d.ts +9 -8
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +9 -8
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +3 -1
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +4 -2
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +21 -6
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +186 -21
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +428 -89
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +17 -9
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +84 -37
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +186 -7
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +19 -2
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +24 -3
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +90 -7
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +57 -2
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/agent.ts +593 -60
- package/src/api/server.ts +1 -2
- package/src/auto-continue.ts +42 -0
- package/src/code-execution/isolate/scripts.ts +11 -3
- package/src/framework.ts +4004 -1026
- package/src/gate/event-gate.ts +2315 -389
- package/src/gate/index.ts +2 -1
- package/src/gate/recipe-reconcile.ts +355 -0
- package/src/gate/types.ts +70 -8
- package/src/index.ts +7 -4
- package/src/mcpl/channel-registry.ts +1220 -467
- package/src/mcpl/conversation-router.ts +34 -16
- package/src/mcpl/convert-block.ts +14 -1
- package/src/mcpl/hook-orchestrator.ts +33 -6
- package/src/mcpl/index.ts +6 -1
- package/src/mcpl/inference-router.ts +79 -34
- package/src/mcpl/prose-grammar.ts +7 -0
- package/src/mcpl/protocol-family.ts +2 -0
- package/src/mcpl/push-coalescer.ts +51 -13
- package/src/mcpl/reference-fetcher.ts +201 -162
- package/src/mcpl/references.ts +852 -78
- package/src/mcpl/server-connection.ts +145 -7
- package/src/mcpl/tool-lifecycle.ts +21 -0
- package/src/mcpl/tool-presence.ts +650 -0
- package/src/mcpl/tool-result-normalize.ts +18 -6
- package/src/mcpl/tool-signature.ts +285 -0
- package/src/mcpl/toolset-host.ts +637 -0
- package/src/mcpl/toolsets-module.ts +451 -0
- package/src/mcpl/transport.ts +3 -2
- package/src/mcpl/types.ts +22 -1
- package/src/mcpl/visible-content.ts +22 -9
- package/src/module-registry.ts +77 -24
- package/src/modules/discord/index.ts +1 -1
- package/src/modules/history/index.ts +48 -18
- package/src/modules/history/semantic.ts +308 -54
- package/src/modules/workspace/direct-write.ts +425 -0
- package/src/modules/workspace/edit-text.ts +82 -35
- package/src/modules/workspace/effects.ts +1 -1
- package/src/modules/workspace/index.ts +1274 -241
- package/src/modules/workspace/read-page.ts +222 -0
- package/src/modules/workspace/result-text.ts +119 -0
- package/src/modules/workspace/text.ts +58 -32
- package/src/modules/workspace/types.ts +41 -17
- package/src/sent-rows.ts +162 -0
- package/src/silent-heartbeat.ts +17 -10
- package/src/stable-hash.ts +14 -0
- package/src/stored-injections.ts +131 -0
- package/src/text-as-stored.ts +4 -3
- package/src/thinking-drops.ts +141 -0
- package/src/tool-image-ledger.ts +9 -8
- package/src/tool-presentation.ts +3 -1
- package/src/tool-result-guard.ts +19 -6
- package/src/tool-result-history.ts +488 -88
- package/src/tune-out/coordinator.ts +80 -37
- package/src/types/agent.ts +189 -7
- package/src/types/events.ts +19 -2
- package/src/types/framework.ts +21 -3
- package/src/types/module.ts +86 -7
- package/src/types/trace.ts +58 -2
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
* so a message stamped slightly out of order is still picked up; the
|
|
13
13
|
* service dedups by id + text hash, so re-sending is free. Summaries are
|
|
14
14
|
* watermarked by `createdMs` (a fresh L3 spans months of old timestamps —
|
|
15
|
-
* its creation time is the only monotonic signal).
|
|
15
|
+
* its creation time is the only monotonic signal). Both watermarks are
|
|
16
|
+
* kept in the module's state, which follows the store's branches, so each
|
|
17
|
+
* branch sends its own messages and summaries. A branch made at an earlier
|
|
18
|
+
* point, as an undo makes one, mints summary ids its parent already used,
|
|
19
|
+
* so a summary is keyed `sum:<id>@<created>`.
|
|
16
20
|
*
|
|
17
21
|
* What gets embedded per message: text blocks verbatim, plus the string
|
|
18
22
|
* arguments of the agent's private-prose tools (think / journal / skip_reply
|
|
@@ -32,6 +36,7 @@ import type { ContextManager, StoredMessage } from '@animalabs/context-manager';
|
|
|
32
36
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
33
37
|
import { safeSlice } from '../../safe-slice.js';
|
|
34
38
|
import { SEND_TOOLS, bareToolName } from '../../send-tools.js';
|
|
39
|
+
import { storedInjectionMarkOf } from '../../stored-injections.js';
|
|
35
40
|
|
|
36
41
|
export interface SemanticIndexConfig {
|
|
37
42
|
/** Base URL of the embed-service, e.g. `http://100.90.161.34:8804`. */
|
|
@@ -46,9 +51,30 @@ export interface SemanticIndexConfig {
|
|
|
46
51
|
syncIntervalMs?: number;
|
|
47
52
|
/** Items per upsert request. Default 128, max 256 (service limit). */
|
|
48
53
|
syncBatch?: number;
|
|
49
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* The most items one background tick's pass writes to the index: what the
|
|
56
|
+
* service reports each upsert inserted or updated, with each queued edit
|
|
57
|
+
* counted as one. Deletes aren't counted: a pass sends its queued removals
|
|
58
|
+
* whatever the budget, and deletes the items its summaries had from before
|
|
59
|
+
* they were keyed by creation time as it sends them.
|
|
60
|
+
*
|
|
61
|
+
* A pass also re-sends items the service already holds unchanged. They
|
|
62
|
+
* spend no budget however many there are, and each costs the service a
|
|
63
|
+
* check and a metadata write, with no embedding. They're the overlap
|
|
64
|
+
* behind the message cursor, the summaries since the summary cursor while
|
|
65
|
+
* a delete of their old items keeps failing, and, once on a branch with
|
|
66
|
+
* no cursor yet, as after upgrading to keys with the creation time, the
|
|
67
|
+
* summaries it shares with other branches. A tick that finds a pass
|
|
68
|
+
* already running, such as a search's catch-up, starts none of its own.
|
|
69
|
+
* Default 1024.
|
|
70
|
+
*/
|
|
50
71
|
maxSyncPerTick?: number;
|
|
51
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* The most items a pre-search catch-up writes to the index before
|
|
74
|
+
* searching anyway, counted as for `maxSyncPerTick`. A search that finds a
|
|
75
|
+
* pass already running, such as a tick's, waits for that pass instead,
|
|
76
|
+
* under that pass's own budget. Default 256.
|
|
77
|
+
*/
|
|
52
78
|
maxSyncBeforeSearch?: number;
|
|
53
79
|
/** Re-scan window behind the message watermark, ms. Default 10 min. */
|
|
54
80
|
overlapMs?: number;
|
|
@@ -240,7 +266,12 @@ export class SemanticIndexClient {
|
|
|
240
266
|
}
|
|
241
267
|
|
|
242
268
|
async upsert(items: IndexItem[]): Promise<{ inserted: number; updated: number; unchanged: number; count: number }> {
|
|
243
|
-
return this.call('POST', `/v1/index/${this.ns}/upsert`, { items }, {
|
|
269
|
+
return this.call('POST', `/v1/index/${this.ns}/upsert`, { items }, {
|
|
270
|
+
// A pass's budget spends the counts, so an answer without them isn't
|
|
271
|
+
// taken as one.
|
|
272
|
+
what: 'counts of the items inserted and updated', is: (json) => isObject(json)
|
|
273
|
+
&& Number.isFinite((json as { inserted?: unknown }).inserted) && Number.isFinite((json as { updated?: unknown }).updated),
|
|
274
|
+
});
|
|
244
275
|
}
|
|
245
276
|
|
|
246
277
|
async delete(ids: string[]): Promise<{ deleted: number }> {
|
|
@@ -307,6 +338,10 @@ function authorOf(msg: StoredMessage): string | null {
|
|
|
307
338
|
}
|
|
308
339
|
|
|
309
340
|
export function messageToItem(msg: StoredMessage, cfg: { includePrivateTools?: boolean; maxChars?: number }): IndexItem | null {
|
|
341
|
+
// A stored context injection (stored-injections.ts) is a source's context,
|
|
342
|
+
// such as retrieval's lessons, not something that happened: indexed, it
|
|
343
|
+
// would come back to a search as history.
|
|
344
|
+
if (storedInjectionMarkOf(msg.metadata)) return null;
|
|
310
345
|
const text = messageIndexText(msg, cfg.includePrivateTools ?? true);
|
|
311
346
|
if (!text) return null;
|
|
312
347
|
const tsMs = msg.timestamp.getTime();
|
|
@@ -339,10 +374,37 @@ export function summaryIndexText(s: { content: string }, cfg: { maxChars?: numbe
|
|
|
339
374
|
return safeSlice(s.content.trim(), 0, cfg.maxChars ?? 32_000);
|
|
340
375
|
}
|
|
341
376
|
|
|
377
|
+
/**
|
|
378
|
+
* A summary's index key, `sum:<id>@<created>`. A branch made at an earlier
|
|
379
|
+
* point mints ids its parent already used, so the id alone names two
|
|
380
|
+
* summaries; its creation time, set when it was minted and kept by a branch
|
|
381
|
+
* that inherits it, tells them apart.
|
|
382
|
+
*/
|
|
383
|
+
export function summaryKey(id: string, createdMs: number): string {
|
|
384
|
+
return `sum:${id}@${createdMs}`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* A `sum:` key read back: its summary id, and its creation time, or null for
|
|
389
|
+
* a key in the form written before summaries were keyed by it (`sum:<id>`).
|
|
390
|
+
* Null for a key that isn't a summary's. A finite number written as a string
|
|
391
|
+
* reads back as the same number, so the time compares equal to the
|
|
392
|
+
* summary's own.
|
|
393
|
+
*/
|
|
394
|
+
export function parseSummaryKey(key: string): { id: string; createdMs: number | null } | null {
|
|
395
|
+
if (!key.startsWith('sum:')) return null;
|
|
396
|
+
const rest = key.slice(4);
|
|
397
|
+
const at = rest.lastIndexOf('@');
|
|
398
|
+
if (at > 0 && /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/.test(rest.slice(at + 1))) {
|
|
399
|
+
return { id: rest.slice(0, at), createdMs: Number(rest.slice(at + 1)) };
|
|
400
|
+
}
|
|
401
|
+
return rest ? { id: rest, createdMs: null } : null;
|
|
402
|
+
}
|
|
403
|
+
|
|
342
404
|
export function summaryToItem(s: SummaryLike, cfg: { maxChars?: number }): IndexItem | null {
|
|
343
405
|
if (!s.content.trim()) return null;
|
|
344
406
|
return {
|
|
345
|
-
id:
|
|
407
|
+
id: summaryKey(s.id, s.createdMs),
|
|
346
408
|
text: summaryIndexText(s, cfg),
|
|
347
409
|
ts: s.startMs / 1000,
|
|
348
410
|
kind: 'summary',
|
|
@@ -355,12 +417,36 @@ export function summaryToItem(s: SummaryLike, cfg: { maxChars?: number }): Index
|
|
|
355
417
|
/** Edits/removals not yet applied to the index (persisted by the host across restarts). */
|
|
356
418
|
export interface PendingChanges { edited: string[]; removed: string[] }
|
|
357
419
|
|
|
420
|
+
/**
|
|
421
|
+
* A branch's sync cursors for one index: the creation time through which its
|
|
422
|
+
* summaries are sent under their keys, and the time through which its
|
|
423
|
+
* messages are sent. Null where nothing is sent yet.
|
|
424
|
+
*/
|
|
425
|
+
export interface SyncCursors { namespace: string; summaries: number | null; messages: number | null }
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Where the sync cursors are kept. HistoryModule keeps them in module state,
|
|
429
|
+
* which the store's branches carry, so a branch made at an earlier point
|
|
430
|
+
* starts from the cursors as they were there, and another branch's passes
|
|
431
|
+
* never move them.
|
|
432
|
+
*/
|
|
433
|
+
export interface SyncCursorStore {
|
|
434
|
+
read(): SyncCursors | null;
|
|
435
|
+
write(cursors: SyncCursors): void;
|
|
436
|
+
}
|
|
437
|
+
|
|
358
438
|
export interface SyncReport {
|
|
359
439
|
pushed: number;
|
|
360
440
|
inserted: number;
|
|
361
441
|
updated: number;
|
|
362
442
|
unchanged: number;
|
|
363
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* True when items may be left to send: the pass hit its item cap before
|
|
445
|
+
* the end of the store or of the queued edits, a delete of summaries' old
|
|
446
|
+
* items failed, it failed or was backing off, or the store left the branch
|
|
447
|
+
* it read during it. An edit or removal that a failed send queued again
|
|
448
|
+
* doesn't set it; the next pass sends it.
|
|
449
|
+
*/
|
|
364
450
|
more: boolean;
|
|
365
451
|
messagesScanned: number;
|
|
366
452
|
summariesScanned: number;
|
|
@@ -368,6 +454,8 @@ export interface SyncReport {
|
|
|
368
454
|
|
|
369
455
|
export class SemanticIndexer {
|
|
370
456
|
private inFlight: Promise<SyncReport> | null = null;
|
|
457
|
+
/** The branch each pass read, by its report, for a search that joined it. */
|
|
458
|
+
private readonly passBranch = new WeakMap<SyncReport, string>();
|
|
371
459
|
private consecutiveFailures = 0;
|
|
372
460
|
private backoffUntil = 0;
|
|
373
461
|
lastError: string | null = null;
|
|
@@ -411,6 +499,56 @@ export class SemanticIndexer {
|
|
|
411
499
|
private deleteUnsupported = false;
|
|
412
500
|
/** Called whenever the pending edit/remove sets change. */
|
|
413
501
|
onPendingChange: ((p: PendingChanges) => void) | null = null;
|
|
502
|
+
/**
|
|
503
|
+
* Where the sync cursors are kept, set by the host. Without one, as
|
|
504
|
+
* before the host wires module state or after it unwires it, this indexer
|
|
505
|
+
* goes on from the cursors it last wrote for each branch, which it keeps
|
|
506
|
+
* in memory. A branch it wrote none for starts with none.
|
|
507
|
+
*/
|
|
508
|
+
cursorStore: SyncCursorStore | null = null;
|
|
509
|
+
/** The cursors this indexer last wrote, by branch id ('' for a store without branches). */
|
|
510
|
+
private readonly ownCursors = new Map<string, SyncCursors>();
|
|
511
|
+
|
|
512
|
+
/** The store's current branch, by id ('' for a store without branches). */
|
|
513
|
+
branchId(): string {
|
|
514
|
+
return (this.cm as { currentBranch?: () => { id: string } | undefined }).currentBranch?.()?.id ?? '';
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* The current branch's cursors for this indexer's index. Cursors kept for
|
|
519
|
+
* another namespace, as before the host's index setting changed, are none
|
|
520
|
+
* here.
|
|
521
|
+
*/
|
|
522
|
+
private syncCursors(): { summaries: number | null; messages: number | null } {
|
|
523
|
+
const c = this.cursorStore ? this.cursorStore.read() : this.ownCursors.get(this.branchId()) ?? null;
|
|
524
|
+
return c && c.namespace === this.cfg.namespace ? c : { summaries: null, messages: null };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Writes the current branch's cursors, which must be `branch`'s. */
|
|
528
|
+
private setSyncCursors(c: { summaries: number | null; messages: number | null }, branch: string): void {
|
|
529
|
+
const cursors: SyncCursors = { namespace: this.cfg.namespace, summaries: c.summaries, messages: c.messages };
|
|
530
|
+
this.ownCursors.set(branch, cursors);
|
|
531
|
+
this.cursorStore?.write(cursors);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Deletes ids from the index. A 404 in a pass whose /stats answered with
|
|
536
|
+
* stats shows the index is made, so it means a service build without the
|
|
537
|
+
* route: that sets deleteUnsupported and answers the 404's message. Null
|
|
538
|
+
* when the ids are deleted; any other failure throws.
|
|
539
|
+
*/
|
|
540
|
+
private async deleteIds(ids: string[], stats: IndexStats | null): Promise<string | null> {
|
|
541
|
+
try {
|
|
542
|
+
await this.client.delete(ids);
|
|
543
|
+
return null;
|
|
544
|
+
} catch (e) {
|
|
545
|
+
if (e instanceof EmbedServiceError && e.status === 404 && stats !== null) {
|
|
546
|
+
this.deleteUnsupported = true;
|
|
547
|
+
return e.message;
|
|
548
|
+
}
|
|
549
|
+
throw e;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
414
552
|
|
|
415
553
|
pendingSnapshot(): PendingChanges { return { edited: [...this.editedIds], removed: [...this.removedIds] }; }
|
|
416
554
|
|
|
@@ -444,9 +582,15 @@ export class SemanticIndexer {
|
|
|
444
582
|
|
|
445
583
|
get backingOff(): boolean { return Date.now() < this.backoffUntil; }
|
|
446
584
|
|
|
585
|
+
/** The branch the pass that gave this report read, or null if it read none. */
|
|
586
|
+
passedOn(report: SyncReport): string | null { return this.passBranch.get(report) ?? null; }
|
|
587
|
+
|
|
447
588
|
/**
|
|
448
|
-
* Push what the index is missing,
|
|
449
|
-
*
|
|
589
|
+
* Push what the index is missing, writing at most `maxItems` items by the
|
|
590
|
+
* service's own count (edits one each), and stopping where that runs out,
|
|
591
|
+
* inside a millisecond or a message page too.
|
|
592
|
+
* Coalesces: a call while a sync is already running returns that run's
|
|
593
|
+
* promise instead of racing it.
|
|
450
594
|
*/
|
|
451
595
|
catchUp(maxItems: number): Promise<SyncReport> {
|
|
452
596
|
if (this.inFlight) return this.inFlight;
|
|
@@ -465,11 +609,10 @@ export class SemanticIndexer {
|
|
|
465
609
|
let notStats: EmbedServiceError | null = null;
|
|
466
610
|
try {
|
|
467
611
|
// A 2xx that isn't stats, such as a web page from a wrong route, gives no
|
|
468
|
-
// watermark
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
// answer.
|
|
612
|
+
// service watermark, and lastError names the answer. A branch with no
|
|
613
|
+
// message cursor of its own then walks its messages from the beginning,
|
|
614
|
+
// as for a namespace that doesn't exist yet, so a pass whose budget
|
|
615
|
+
// runs out fails on that answer.
|
|
473
616
|
let stats: IndexStats | null = null;
|
|
474
617
|
try {
|
|
475
618
|
stats = await this.client.stats();
|
|
@@ -480,16 +623,66 @@ export class SemanticIndexer {
|
|
|
480
623
|
// /stats answered 404, which stats() reads as a namespace that doesn't
|
|
481
624
|
// exist: there's no index to remove from.
|
|
482
625
|
const noIndex = stats === null && notStats === null;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
626
|
+
// This branch's cursors, read at each pass so a switch between passes is
|
|
627
|
+
// seen. They're written only when every read this pass made of the
|
|
628
|
+
// store was on the branch read here, so one branch's pass can't move
|
|
629
|
+
// another's cursor past what that branch hasn't sent. A branch with no
|
|
630
|
+
// message cursor starts from the service's watermark, as before cursors
|
|
631
|
+
// were kept per branch, so an upgrade doesn't walk every history again.
|
|
632
|
+
const branch = this.branchId();
|
|
633
|
+
this.passBranch.set(report, branch);
|
|
634
|
+
// Set when a read of the store finds it on another branch.
|
|
635
|
+
let strayed = false;
|
|
636
|
+
// Set when a delete of summaries' old items fails.
|
|
637
|
+
let oldItemsOwed = false;
|
|
638
|
+
const reading = (): void => { if (this.branchId() !== branch) strayed = true; };
|
|
639
|
+
let kept = this.syncCursors();
|
|
640
|
+
// A namespace that doesn't exist holds nothing of this branch's, as after
|
|
641
|
+
// an index is deleted, so the cursors start over, at once, so a pass
|
|
642
|
+
// that fails partway through sending everything again still finds them
|
|
643
|
+
// over.
|
|
644
|
+
if (noIndex && (kept.summaries !== null || kept.messages !== null)) {
|
|
645
|
+
kept = { summaries: null, messages: null };
|
|
646
|
+
this.setSyncCursors(kept, branch);
|
|
647
|
+
}
|
|
648
|
+
// A branch's cursor is its own while the index holds what it sent, and
|
|
649
|
+
// the index's when it doesn't. Every branch's items are in the one
|
|
650
|
+
// index, so an index whose newest item of a kind is older than the
|
|
651
|
+
// cursor may have lost what this branch sent, as a restore from an
|
|
652
|
+
// older backup leaves it, and the pass walks from the index's newest,
|
|
653
|
+
// as the service's watermark did before cursors were kept. Nothing is
|
|
654
|
+
// lost when the newest the branch walked had nothing to index, such as
|
|
655
|
+
// a summary that's blank once trimmed, or was removed since, and then
|
|
656
|
+
// the pass re-sends what lies between, which the service finds
|
|
657
|
+
// unchanged. A kind the stats don't list has no items left.
|
|
658
|
+
const held = (cursor: number | null, kind: string): number | null => {
|
|
659
|
+
if (cursor === null || stats === null) return cursor;
|
|
660
|
+
const newest = stats.by_kind[kind]?.max_cursor ?? null;
|
|
661
|
+
return newest === null ? null : Math.min(cursor, newest);
|
|
662
|
+
};
|
|
663
|
+
const sumCursor = held(kept.summaries, 'summary');
|
|
664
|
+
const msgWm = held(kept.messages, 'message') ?? stats?.by_kind.message?.max_cursor ?? null;
|
|
665
|
+
let sumNext = sumCursor;
|
|
666
|
+
let msgThrough: number | null = null;
|
|
667
|
+
// What the pass may still write. Edits spend one each; summaries and
|
|
668
|
+
// messages spend what the service says each upsert wrote (inserted or
|
|
669
|
+
// updated), so a re-send it finds unchanged, such as the overlap's
|
|
670
|
+
// behind the message cursor, spends no budget and can't freeze a pass.
|
|
671
|
+
// A budget of 1.5 writes one item. One that isn't a number is no limit,
|
|
672
|
+
// as it was before the budget counted writes, and chunks stay a batch.
|
|
673
|
+
const floored = Math.floor(maxItems);
|
|
674
|
+
let budget = Number.isNaN(floored) ? Infinity : floored;
|
|
486
675
|
let pending: IndexItem[] = [];
|
|
487
676
|
const flush = async (): Promise<void> => {
|
|
488
677
|
if (pending.length === 0) return;
|
|
489
678
|
const r = await this.client.upsert(pending);
|
|
490
679
|
report.pushed += pending.length; report.inserted += r.inserted; report.updated += r.updated; report.unchanged += r.unchanged;
|
|
680
|
+
budget -= r.inserted + r.updated;
|
|
491
681
|
pending = [];
|
|
492
682
|
};
|
|
683
|
+
/** Pending items are sent once they could write all the budget left,
|
|
684
|
+
* so a pass never writes more than its budget. */
|
|
685
|
+
const full = (): boolean => pending.length >= Math.min(batchSize, budget);
|
|
493
686
|
|
|
494
687
|
// Edits and removals first: they never come back round in the time walk.
|
|
495
688
|
// This phase fails on its own: a failure re-queues its ids, is logged
|
|
@@ -530,33 +723,29 @@ export class SemanticIndexer {
|
|
|
530
723
|
// search-time checks (on-branch, still indexable) keep them out of
|
|
531
724
|
// results.
|
|
532
725
|
if (toDelete.length > 0 && !noIndex && !this.deleteUnsupported) {
|
|
726
|
+
// What the delete held, by kind, for a warning.
|
|
727
|
+
const what = [
|
|
728
|
+
removed.length > 0 ? `${removed.length} removal(s)` : null,
|
|
729
|
+
blanked.length > 0 ? `${blanked.length} edit(s) down to nothing indexable` : null,
|
|
730
|
+
].filter((s): s is string => s !== null).join(' and ');
|
|
533
731
|
try {
|
|
534
|
-
await this.
|
|
535
|
-
|
|
536
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
537
|
-
// What the delete held, by kind, for the warning.
|
|
538
|
-
const what = [
|
|
539
|
-
removed.length > 0 ? `${removed.length} removal(s)` : null,
|
|
540
|
-
blanked.length > 0 ? `${blanked.length} edit(s) down to nothing indexable` : null,
|
|
541
|
-
].filter((s): s is string => s !== null).join(' and ');
|
|
542
|
-
if (e instanceof EmbedServiceError && e.status === 404 && stats !== null) {
|
|
543
|
-
// This pass's /stats answered with stats, so the index is made
|
|
544
|
-
// and the 404 is the route's: a service build without /delete.
|
|
545
|
-
this.deleteUnsupported = true;
|
|
732
|
+
const missing = await this.deleteIds(toDelete, stats);
|
|
733
|
+
if (missing !== null) {
|
|
546
734
|
this.warn(
|
|
547
735
|
`/delete answered 404, so the service lacks that route; dropping ${what}. Later removals and edits `
|
|
548
736
|
+ `down to nothing indexable are dropped too, until the host restarts or binds the history module `
|
|
549
|
-
+ `again: ${
|
|
737
|
+
+ `again: ${missing}`,
|
|
550
738
|
);
|
|
551
|
-
} else {
|
|
552
|
-
// Any other failure, and a 404 in a pass whose /stats answered
|
|
553
|
-
// with something that isn't stats, which doesn't show the index
|
|
554
|
-
// is made: re-queued, for a later pass.
|
|
555
|
-
for (const id of removed) this.removedIds.add(id);
|
|
556
|
-
for (const id of blanked) if (!this.removedIds.has(id)) this.editedIds.add(id);
|
|
557
|
-
phaseErrors.push(msg);
|
|
558
|
-
this.warn(`delete failed, ${what} re-queued: ${msg}`);
|
|
559
739
|
}
|
|
740
|
+
} catch (e) {
|
|
741
|
+
// Any other failure, and a 404 in a pass whose /stats answered
|
|
742
|
+
// with something that isn't stats, which doesn't show the index
|
|
743
|
+
// is made: re-queued, for a later pass.
|
|
744
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
745
|
+
for (const id of removed) this.removedIds.add(id);
|
|
746
|
+
for (const id of blanked) if (!this.removedIds.has(id)) this.editedIds.add(id);
|
|
747
|
+
phaseErrors.push(msg);
|
|
748
|
+
this.warn(`delete failed, ${what} re-queued: ${msg}`);
|
|
560
749
|
}
|
|
561
750
|
}
|
|
562
751
|
try {
|
|
@@ -577,21 +766,67 @@ export class SemanticIndexer {
|
|
|
577
766
|
// Summaries before messages: they are few, and a message backlog that
|
|
578
767
|
// exceeds every tick's budget must not keep new summaries out forever.
|
|
579
768
|
{
|
|
769
|
+
const cursor = sumCursor;
|
|
770
|
+
reading();
|
|
580
771
|
const all = this.cm.getSummariesInRange({ fromMs: 0, toMs: Number.MAX_SAFE_INTEGER }) as SummaryLike[];
|
|
581
772
|
report.summariesScanned = all.length;
|
|
582
|
-
// `>=`:
|
|
583
|
-
//
|
|
584
|
-
//
|
|
585
|
-
|
|
773
|
+
// `>=`: a summary minted in the cursor's millisecond after the pass
|
|
774
|
+
// that wrote it read the store is sent by the next one. Re-sending
|
|
775
|
+
// the others there is a dedup no-op for the service and costs no
|
|
776
|
+
// budget.
|
|
777
|
+
const fresh = all.filter((s) => cursor === null || s.createdMs >= cursor).sort((a, b) => a.createdMs - b.createdMs);
|
|
778
|
+
// Through which creation time this pass sent every summary it read.
|
|
779
|
+
// A pass whose budget runs out inside a millisecond leaves the cursor
|
|
780
|
+
// at the one before, so the next pass sends the rest of that
|
|
781
|
+
// millisecond, re-sending what this one wrote of it at no cost, and
|
|
782
|
+
// counts all of it as new for the old keys below.
|
|
783
|
+
let through: number | null = null;
|
|
784
|
+
// The millisecond of the summaries being sent.
|
|
785
|
+
let at: number | null = null;
|
|
786
|
+
let finished = true;
|
|
787
|
+
// Each newly sent summary's key in the form written before keys
|
|
788
|
+
// carried the creation time, for deleting once its new item is in.
|
|
789
|
+
const oldKeys: string[] = [];
|
|
586
790
|
for (const s of fresh) {
|
|
587
|
-
if (
|
|
791
|
+
if (s.createdMs !== at) { through = at; at = s.createdMs; }
|
|
792
|
+
if (budget <= 0) { report.more = true; finished = false; break; }
|
|
588
793
|
const item = summaryToItem(s, this.cfg);
|
|
589
794
|
if (!item) continue;
|
|
590
795
|
pending.push(item);
|
|
591
|
-
if (
|
|
592
|
-
if (
|
|
796
|
+
if (cursor === null || s.createdMs > cursor) oldKeys.push(`sum:${s.id}`);
|
|
797
|
+
if (full()) await flush();
|
|
593
798
|
}
|
|
594
799
|
await flush();
|
|
800
|
+
if (finished) through = at;
|
|
801
|
+
// The index's old items go once their summaries' new ones are in, a
|
|
802
|
+
// batch of ids to a request. On a service without /delete they stay,
|
|
803
|
+
// and search leaves them out. A failed delete leaves the cursor, so
|
|
804
|
+
// the next pass sends those summaries again, which the service finds
|
|
805
|
+
// unchanged, and deletes again.
|
|
806
|
+
let deleted = true;
|
|
807
|
+
if (oldKeys.length > 0 && !noIndex && !this.deleteUnsupported) {
|
|
808
|
+
try {
|
|
809
|
+
let missing: string | null = null;
|
|
810
|
+
for (let i = 0; i < oldKeys.length && missing === null; i += batchSize) {
|
|
811
|
+
missing = await this.deleteIds(oldKeys.slice(i, i + batchSize), stats);
|
|
812
|
+
}
|
|
813
|
+
if (missing !== null) {
|
|
814
|
+
this.warn(
|
|
815
|
+
`/delete answered 404, so the service lacks that route; any items these ${oldKeys.length} summaries had `
|
|
816
|
+
+ `from before keys carried the creation time stay, and search leaves them out. Later removals and edits `
|
|
817
|
+
+ `down to nothing indexable are dropped too, until the host restarts or binds the history module `
|
|
818
|
+
+ `again: ${missing}`,
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
} catch (e) {
|
|
822
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
823
|
+
deleted = false;
|
|
824
|
+
oldItemsOwed = true;
|
|
825
|
+
phaseErrors.push(msg);
|
|
826
|
+
this.warn(`deleting ${oldKeys.length} summaries' items from before they were keyed by creation time failed; the next pass tries again: ${msg}`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (deleted && through !== null) sumNext = through;
|
|
595
830
|
}
|
|
596
831
|
|
|
597
832
|
// Messages: walk forward from (watermark - overlap) via the time index.
|
|
@@ -606,32 +841,51 @@ export class SemanticIndexer {
|
|
|
606
841
|
// of more than a page within one millisecond is walked, not jumped.
|
|
607
842
|
let offset = 0;
|
|
608
843
|
const pageSize = 256;
|
|
609
|
-
|
|
844
|
+
// The walk starts with the overlap's re-sends, which spend no budget, but
|
|
845
|
+
// like every chunk go out no bigger than the budget left, so a pass
|
|
846
|
+
// whose summaries left little sends them a few at a time.
|
|
847
|
+
for (let stopped = false; !stopped;) {
|
|
610
848
|
if (budget <= 0) { report.more = true; break; }
|
|
849
|
+
reading();
|
|
611
850
|
const page = this.cm.queryMessagesByTime({ fromMs, offset, limit: pageSize });
|
|
612
851
|
const msgs = page.messages;
|
|
613
852
|
report.messagesScanned += msgs.length;
|
|
614
853
|
for (const m of msgs) {
|
|
854
|
+
// Only what the service writes spends the budget (flush). The
|
|
855
|
+
// overlap re-walk behind the watermark is a dedup no-op for the
|
|
856
|
+
// service, and it must be one for the budget too: when the overlap
|
|
857
|
+
// window held more messages than a tick's budget, every tick spent
|
|
858
|
+
// it all re-sending the same indexed items and never reached the
|
|
859
|
+
// first new one — the index froze at the watermark while reporting
|
|
860
|
+
// merely `more: true`. A pass stops inside a page once the budget
|
|
861
|
+
// is spent; the next one's overlap sends the rest of the
|
|
862
|
+
// millisecond it stopped in.
|
|
863
|
+
if (budget <= 0) { report.more = true; stopped = true; break; }
|
|
615
864
|
const item = messageToItem(m, this.cfg);
|
|
616
865
|
if (!item) continue;
|
|
617
866
|
pending.push(item);
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
// for the budget too: when the overlap window held more messages
|
|
621
|
-
// than a tick's budget, every tick spent it all re-sending the same
|
|
622
|
-
// indexed items and never reached the first new one — the index
|
|
623
|
-
// froze at the watermark while reporting merely `more: true`.
|
|
624
|
-
if (msgWm === null || item.cursor === undefined || item.cursor > msgWm) budget--;
|
|
625
|
-
if (pending.length >= batchSize) await flush();
|
|
867
|
+
if (item.cursor !== undefined && (msgThrough === null || item.cursor > msgThrough)) msgThrough = item.cursor;
|
|
868
|
+
if (full()) await flush();
|
|
626
869
|
}
|
|
627
|
-
if (msgs.length < pageSize) break;
|
|
870
|
+
if (stopped || msgs.length < pageSize) break;
|
|
628
871
|
const lastMs = msgs[msgs.length - 1]!.timestamp.getTime();
|
|
629
872
|
const atLast = msgs.filter((m) => m.timestamp.getTime() === lastMs).length;
|
|
630
873
|
offset = lastMs === fromMs ? offset + atLast : atLast;
|
|
631
874
|
fromMs = lastMs;
|
|
632
875
|
}
|
|
633
876
|
await flush();
|
|
877
|
+
// Every send of this pass is in, so its cursors stand, unless the store
|
|
878
|
+
// left the branch the pass read during it: what it read may then be
|
|
879
|
+
// another branch's. A pass that moves neither cursor writes no state.
|
|
880
|
+
const msgNext = msgThrough ?? msgWm;
|
|
881
|
+
const left = strayed || this.branchId() !== branch;
|
|
882
|
+
if ((sumNext !== kept.summaries || msgNext !== kept.messages) && !left) {
|
|
883
|
+
this.setSyncCursors({ summaries: sumNext, messages: msgNext }, branch);
|
|
884
|
+
}
|
|
634
885
|
if (notStats && report.more) throw notStats;
|
|
886
|
+
// The current branch may then have items this pass didn't send, and
|
|
887
|
+
// old items a failed delete left are still to go.
|
|
888
|
+
if (left || oldItemsOwed) report.more = true;
|
|
635
889
|
this.consecutiveFailures = 0; this.lastError = named(notStats?.message ?? null, ...phaseErrors); this.lastSyncAt = Date.now();
|
|
636
890
|
return report;
|
|
637
891
|
} catch (e) {
|