@securecoms/agent-bridge 0.28.2 → 0.29.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 +176 -79
- package/dist/bin.js +2720 -133
- package/dist/bridge-agent.d.ts +184 -1
- package/dist/config.d.ts +2 -0
- package/dist/index.js +2694 -107
- package/dist/memory/note-frames.d.ts +1 -1
- package/dist/memory/unified/chunk.d.ts +12 -0
- package/dist/memory/unified/config.d.ts +7 -0
- package/dist/memory/unified/consumer-unified.d.ts +21 -0
- package/dist/memory/unified/escape.d.ts +23 -0
- package/dist/memory/unified/index-key.d.ts +8 -0
- package/dist/memory/unified/item-book.d.ts +70 -0
- package/dist/memory/unified/item-reembed.d.ts +20 -0
- package/dist/memory/unified/migrate.d.ts +68 -0
- package/dist/memory/unified/owner-scope.d.ts +48 -0
- package/dist/memory/unified/pin-rules.d.ts +18 -0
- package/dist/memory/unified/ranking-client.d.ts +48 -0
- package/dist/memory/unified/ranking-core-constants.d.ts +8 -0
- package/dist/memory/unified/ranking-core.d.ts +117 -0
- package/dist/memory/unified/ranking-inprocess.d.ts +3 -0
- package/dist/memory/unified/ranking-worker.d.ts +1 -0
- package/dist/memory/unified/read-book.d.ts +80 -0
- package/dist/memory/unified/switch.d.ts +8 -0
- package/dist/memory/unified/text.d.ts +15 -0
- package/dist/memory/unified/worker-protocol.d.ts +102 -0
- package/dist/memory-worker.js +583 -0
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +3 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* is ignored.
|
|
9
9
|
*/
|
|
10
10
|
/** Spec §8.2, §18.1: content-free refusal classes, the only thing a refusal says. */
|
|
11
|
-
export declare const REASON_CLASSES: readonly ["mode", "invalid", "message", "audience", "provenance", "secret", "cap", "cleared", "store", "budget", "purged"];
|
|
11
|
+
export declare const REASON_CLASSES: readonly ["mode", "invalid", "message", "audience", "provenance", "secret", "cap", "cleared", "store", "budget", "purged", "not_owner_words"];
|
|
12
12
|
export type ReasonClass = (typeof REASON_CLASSES)[number];
|
|
13
13
|
export interface MemoryNoteFrame {
|
|
14
14
|
request_id: string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const CHUNK_TARGET = 600;
|
|
2
|
+
export declare const CHUNK_CAP = 800;
|
|
3
|
+
export declare const CHUNK_OVERLAP = 100;
|
|
4
|
+
export declare function chunkText(text: string): string[];
|
|
5
|
+
/** M-2 then R-U15, in that order: the whole text is scanned once; any hit drops the whole message. */
|
|
6
|
+
export declare function prepareMessageText(text: string): {
|
|
7
|
+
ok: true;
|
|
8
|
+
chunks: string[];
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
reason: "secret" | "empty";
|
|
12
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec section 8.1 step 8, U6: unified memory attaches at delivery to the frame copy only, for a runtime socket that
|
|
3
|
+
* declared memory_unified and never claimed a harness. The buffer, the sealed runtime_inbox and `inflight` keep the
|
|
4
|
+
* original, so a re-buffered message carries none to an HTTP or harness consumer. The owner predicate and the owner scope
|
|
5
|
+
* are re-checked in the source (`BridgeAgent.unifiedForDelivery`) on the ORIGINAL message's frozen authorization (H-4).
|
|
6
|
+
*/
|
|
7
|
+
import type { BridgeMessage } from "../../bridge-agent.js";
|
|
8
|
+
import type { UnifiedFrameItem } from "./read-book.js";
|
|
9
|
+
export declare function withConsumerUnified(out: BridgeMessage, original: BridgeMessage, session: {
|
|
10
|
+
consumer: "runtime" | "unknown";
|
|
11
|
+
harnessClaimed?: boolean;
|
|
12
|
+
unifiedConsumer: boolean;
|
|
13
|
+
}, src: {
|
|
14
|
+
unifiedForDelivery(m: BridgeMessage): {
|
|
15
|
+
items: UnifiedFrameItem[];
|
|
16
|
+
pinnedMore: number;
|
|
17
|
+
} | undefined;
|
|
18
|
+
}): {
|
|
19
|
+
message: BridgeMessage;
|
|
20
|
+
shown: ReadonlySet<number>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified memory (spec \u00A710.3, R-U26, M-4, C-1): the escape the bridge applies to the text it quotes in its own lines
|
|
3
|
+
* (`Pinned:`, `Forgot:`). The runtime renders every block with its own copy of the same function (parity, plan Task 16),
|
|
4
|
+
* so this file is self-contained. Line breaks and other control characters become a space (the runtime's CONTROL_CHARS
|
|
5
|
+
* rule, memory.ts:16) so a quoted text can never open a new line; then the remaining C-1 classes are stripped; then the
|
|
6
|
+
* text is capped to `max` code points; only then are `"` and `\` backslash-escaped (ruling C21). Capping before the
|
|
7
|
+
* escape means every backslash in the output comes from a whole pair or a whole `\"`, so the output never ends in an
|
|
8
|
+
* unpaired backslash that would escape the closing quote. The escaped output can be up to twice `max` long.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ITEM_LINE_MAX = 1000;
|
|
11
|
+
export declare const LABEL_MAX = 80;
|
|
12
|
+
export declare function escapeQuoted(s: string, max: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* Plan Task 30 (I-4 overruled): the CONTROL_TO_SPACE and C1_STRIP steps of escapeQuoted only, with no quote escaping and
|
|
15
|
+
* no cap. A migrated 7a note loses its invisible characters and is never refused for them.
|
|
16
|
+
*/
|
|
17
|
+
export declare function stripInvisible(s: string): string;
|
|
18
|
+
/** \u00A710.3 "At ingest": every label field loses its control characters before it is stored. */
|
|
19
|
+
export declare function stripLabelAtIngest(s: string): string;
|
|
20
|
+
export declare function pinnedLine(pin: string): string;
|
|
21
|
+
export declare function forgotLine(text: string): string;
|
|
22
|
+
/** OQ-P2: true for exactly the shapes pinnedLine and forgotLine produce (single line, since the escape removes line breaks). Such a line is never saved as an agent reply. */
|
|
23
|
+
export declare function isBridgeMemoryLine(text: string): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const MEMORY_INDEX_INFO = "securecoms/agent-memory-index/v1";
|
|
2
|
+
export declare const QUERY_MACS_MAX = 64;
|
|
3
|
+
export declare function deriveMemoryIndexKey(seed: Uint8Array): Buffer;
|
|
4
|
+
/** HMAC-SHA-256 truncated to 16 bytes. */
|
|
5
|
+
export declare function tokenMac(key: Uint8Array, token: string): Uint8Array;
|
|
6
|
+
export declare function itemTokenMacs(key: Uint8Array, text: string): Uint8Array[];
|
|
7
|
+
/** The query's tokens (stop words dropped) and their L-3 expansions, de-duplicated, capped at `max` MACs. */
|
|
8
|
+
export declare function queryTokenMacs(key: Uint8Array, text: string, max?: number): Uint8Array[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified memory (spec section 7.7; U9, U11, U16, L-1, R2-L-3): the ONLY caller of SqliteMlsStateStore.insertItems. Steps:
|
|
3
|
+
* the caller has captured U11 synchronously; here 4 (secret filter on the full text), 5 (chunk), 6 (seal, embed, MAC,
|
|
4
|
+
* outside the mutex), 7 (commit under the mutex: the trust decision, then one transaction), 8 (idempotent key; owner
|
|
5
|
+
* replaces reference, never the reverse). Logs are content-free (U17).
|
|
6
|
+
*/
|
|
7
|
+
import type { ItemCapture, ItemKind, ItemSource, ItemTrust, ScopeKind, SqliteMlsStateStore } from "@agentvault/connect-mls-store";
|
|
8
|
+
import { type ServiceLog } from "../../service-log.js";
|
|
9
|
+
import type { Embedder } from "../embedder/client.js";
|
|
10
|
+
import type { RankingClient } from "./ranking-client.js";
|
|
11
|
+
export type ItemSkip = "secret" | "empty" | "cleared" | "room_cleared" | "disabled" | "purged" | "duplicate" | "changed" | "pin_cap" | "quota" | "invalid" | "store" | "mode" | "message";
|
|
12
|
+
export interface ItemWriteRequest {
|
|
13
|
+
scopeKind: ScopeKind;
|
|
14
|
+
scopeId: string;
|
|
15
|
+
channelId: string;
|
|
16
|
+
kind: ItemKind;
|
|
17
|
+
originId: string;
|
|
18
|
+
originAt: number;
|
|
19
|
+
source: ItemSource;
|
|
20
|
+
trust: ItemTrust;
|
|
21
|
+
text: string;
|
|
22
|
+
expect: ItemCapture;
|
|
23
|
+
sourceRef?: string;
|
|
24
|
+
vec?: Float32Array | null;
|
|
25
|
+
vecModelId?: string | null;
|
|
26
|
+
/** Pins and migrated notes: one item, never chunked. */
|
|
27
|
+
noChunk?: true;
|
|
28
|
+
/** Task 30: a migrated source row changed since the marker: replace the item with the same source_ref, same trust. */
|
|
29
|
+
refresh?: true;
|
|
30
|
+
/** R-U19, migration only (Task 30). */
|
|
31
|
+
capExempt?: true;
|
|
32
|
+
/** Plan audit HIGH-2: embed nothing inline; every part goes pending to ItemReembed (Task 30's migration). */
|
|
33
|
+
pendingOnly?: true;
|
|
34
|
+
/** Task 28 (pins): the caller's last W1/W2 re-check, evaluated FIRST inside the memory mutex; a veto writes nothing. */
|
|
35
|
+
precommit?: () => "mode" | "message" | null;
|
|
36
|
+
}
|
|
37
|
+
export interface ItemBookDeps {
|
|
38
|
+
store: Pick<SqliteMlsStateStore, "reserveItemIds" | "sealItemRecord" | "sealItemVec" | "insertItems" | "itemByOrigin" | "openItemRecord" | "evictBatch">;
|
|
39
|
+
embedder: Embedder;
|
|
40
|
+
modelId: string;
|
|
41
|
+
indexKey: Uint8Array;
|
|
42
|
+
exclusive: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
43
|
+
ranking: Pick<RankingClient, "added" | "removed">;
|
|
44
|
+
/** R-U11: false on win32 while WINDOWS_SEMANTIC_ENABLED is false: no embed job runs. */
|
|
45
|
+
embedOn: () => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Plan audit HIGH-2: true while any call heartbeat of this OS user is live (the 7b E1.4 pause ItemReembed already
|
|
48
|
+
* respects). While true the write path embeds nothing inline; every part goes pending.
|
|
49
|
+
*/
|
|
50
|
+
paused: () => boolean;
|
|
51
|
+
now?: () => number;
|
|
52
|
+
log?: ServiceLog;
|
|
53
|
+
}
|
|
54
|
+
export declare class ItemBook {
|
|
55
|
+
private readonly d;
|
|
56
|
+
private evicting;
|
|
57
|
+
constructor(d: ItemBookDeps);
|
|
58
|
+
private skip;
|
|
59
|
+
commit(r: ItemWriteRequest): Promise<{
|
|
60
|
+
ok: true;
|
|
61
|
+
itemIds: number[];
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
reason: ItemSkip;
|
|
65
|
+
}>;
|
|
66
|
+
/** A post-commit hot-set notification: a throw is logged (content-free) and swallowed, never turned into a write failure. */
|
|
67
|
+
private rankingNote;
|
|
68
|
+
/** H-7: batches of at most EVICT_BATCH_MAX rows, a yield between batches, one loop at a time. */
|
|
69
|
+
evictLoop(): Promise<void>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Unified memory (spec section 7.7 step 6, 7b section 8): pending items are embedded in the background, 16 per tick, paused during calls, never on Windows (R-U11). */
|
|
2
|
+
import type { SqliteMlsStateStore } from "@agentvault/connect-mls-store";
|
|
3
|
+
import type { Embedder } from "../embedder/client.js";
|
|
4
|
+
import { type ServiceLog } from "../../service-log.js";
|
|
5
|
+
import type { RankingClient } from "./ranking-client.js";
|
|
6
|
+
export declare class ItemReembed {
|
|
7
|
+
private readonly d;
|
|
8
|
+
private running;
|
|
9
|
+
constructor(d: {
|
|
10
|
+
ranking: Pick<RankingClient, "pending" | "added">;
|
|
11
|
+
store: Pick<SqliteMlsStateStore, "itemCapture" | "sealItemVec" | "updateItemVec">;
|
|
12
|
+
embedder: Embedder;
|
|
13
|
+
exclusive: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
14
|
+
modelId: string;
|
|
15
|
+
paused: () => boolean;
|
|
16
|
+
embedOn: () => boolean;
|
|
17
|
+
log?: ServiceLog;
|
|
18
|
+
});
|
|
19
|
+
tick(): Promise<number>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified memory (spec 2026-10-10 section 14.1, section 14.5; R-U12, M-1, L-1, H-5, H-B; plan audit HIGH-1, HIGH-2, M-4):
|
|
3
|
+
* migrate, per conversation, 7a notes (pins), 7b recall (owner auto items, vectors reused) and OWNER-DM history (reference,
|
|
4
|
+
* whatever its role) into agent_memory_item, in that order (L-1). Room and person-DM history and agent_memory_fact are never
|
|
5
|
+
* migrated. Idempotent on the item key. One marker per (source, conversation): a conversation never visited, or
|
|
6
|
+
* unclassifiable, holds back nobody else's (HIGH-1). Every write is pendingOnly: the migration never embeds (HIGH-2).
|
|
7
|
+
* At most MIGRATION_BATCH source rows per read, a yield between batches (OQ-P7). Never called on a turn's path.
|
|
8
|
+
*
|
|
9
|
+
* U11 (section 14.1 "a clear or purge during migration wins"): the capture is taken once per conversation, before its first
|
|
10
|
+
* read, and every item of that conversation is checked against it in the insert transaction. The first refusal for a
|
|
11
|
+
* clear, a room clear, a disable or a purge stops the conversation. A Clear all raises every conversation's history
|
|
12
|
+
* marker to just past the clear in the store's clear transaction (fix round 1), so no later run re-imports pre-clear
|
|
13
|
+
* history (a clear keeps history, section 14.4). After a disable or a purge the markers stay, so a later run resumes.
|
|
14
|
+
*/
|
|
15
|
+
import { type SqliteMlsStateStore } from "@agentvault/connect-mls-store";
|
|
16
|
+
import { type ServiceLog } from "../../service-log.js";
|
|
17
|
+
import type { ItemBook } from "./item-book.js";
|
|
18
|
+
/** Section 14.5: true while the 7a/7b tables are kept (one release). The release that drops them sets this false (the sweep stops). */
|
|
19
|
+
export declare const OLD_TABLES_PRESENT = true;
|
|
20
|
+
/** OQ-P7: at most this many source rows per read; a yield between reads. */
|
|
21
|
+
export declare const MIGRATION_BATCH = 100;
|
|
22
|
+
export type MigrationClass = {
|
|
23
|
+
kind: "owner";
|
|
24
|
+
ownerId: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "excluded";
|
|
27
|
+
} | {
|
|
28
|
+
kind: "unknown";
|
|
29
|
+
};
|
|
30
|
+
export interface MigrationDeps {
|
|
31
|
+
store: Pick<SqliteMlsStateStore, "readNotesForMigration" | "readRecallForMigration" | "readHistoryForMigration" | "noteConversationIds" | "recallConversationIds" | "historyConversationIds" | "recallTombstoned" | "migrationMarker" | "setMigrationMarker" | "itemCapture">;
|
|
32
|
+
items: Pick<ItemBook, "commit">;
|
|
33
|
+
/**
|
|
34
|
+
* OQ-P1: "owner" with the owner identity for an owner DM; "excluded" for a conversation persisted as non-DM (M-4:
|
|
35
|
+
* never migrated, never retried, holds nothing); "unknown" when it cannot be classified now (retried on a later run).
|
|
36
|
+
*/
|
|
37
|
+
classify: (conversationId: string) => MigrationClass;
|
|
38
|
+
/** Called after a conversation's notes committed pins (the bridge empties its pin cache under the memory mutex, as Task 28 does). */
|
|
39
|
+
pinsChanged?: () => Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Final review bridge I-3: shutdown. Checked before each conversation, each batch and each slice yield; a run that sees
|
|
42
|
+
* it stops (an abort, retried by a later run: markers cover only the batches that completed) so close() never waits
|
|
43
|
+
* on a migration ahead of the MLS drain.
|
|
44
|
+
*/
|
|
45
|
+
stopping?: () => boolean;
|
|
46
|
+
log?: ServiceLog;
|
|
47
|
+
}
|
|
48
|
+
export interface MigrationResult {
|
|
49
|
+
notes: number;
|
|
50
|
+
recall: number;
|
|
51
|
+
history: number;
|
|
52
|
+
/** Conversations: unknown now (retried later), excluded (M-4), stopped by a U11 refusal. */
|
|
53
|
+
skipped: number;
|
|
54
|
+
excluded: number;
|
|
55
|
+
aborted: number;
|
|
56
|
+
unreadable: number;
|
|
57
|
+
}
|
|
58
|
+
export declare function runMigration(d: MigrationDeps, o?: {
|
|
59
|
+
only?: string;
|
|
60
|
+
}): Promise<MigrationResult>;
|
|
61
|
+
/** Section 14.5, before anything is served, in ONE transaction (controller ruling C22): the per-source sweep, then the clear-generation check. */
|
|
62
|
+
export declare function reupgradeReconcile(store: Pick<SqliteMlsStateStore, "reupgradeReconcileUnified">): {
|
|
63
|
+
swept: {
|
|
64
|
+
notes: number;
|
|
65
|
+
recall: number;
|
|
66
|
+
};
|
|
67
|
+
cleared: number;
|
|
68
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type AbsenceRecord } from "../recall/membership-purge.js";
|
|
2
|
+
import { type ServiceLog } from "../../service-log.js";
|
|
3
|
+
export interface OwnerFacts {
|
|
4
|
+
isDm: boolean | undefined;
|
|
5
|
+
pin: {
|
|
6
|
+
identityId: string;
|
|
7
|
+
} | "anomalous" | undefined;
|
|
8
|
+
member: {
|
|
9
|
+
is_agent: boolean;
|
|
10
|
+
is_agent_owner: boolean;
|
|
11
|
+
} | undefined;
|
|
12
|
+
rosterKnown: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** A scope id when the channel is a DM whose single pinned human is the agent's owner; null when it is not; "unknown" without a fresh roster or a pin. */
|
|
15
|
+
export declare function ownerScopeFrom(channelId: string, f: OwnerFacts): string | null | "unknown";
|
|
16
|
+
export interface OwnerScopeSweepDeps {
|
|
17
|
+
/**
|
|
18
|
+
* The owner-DM scope ids holding items (NUL-safe, CARRY-NUL), and (final review store I-2, bridge half) the scope of each
|
|
19
|
+
* recorded DM owner, so an owner change is seen on a channel that holds no items too.
|
|
20
|
+
*/
|
|
21
|
+
scopes(): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Final review store I-2 (bridge half; ruling FINAL store I-2): the conversations whose 7a/7b rows a migration may still
|
|
24
|
+
* read. Each DM among them has its current owner recorded (first sight wins; a different recorded owner is left for
|
|
25
|
+
* the scope loop to settle with the same two-refresh evidence rule, and its purge raises that channel's markers).
|
|
26
|
+
*/
|
|
27
|
+
oldTableChannels?(): string[];
|
|
28
|
+
recordOwner?(channelId: string, ownerIdentityId: string): void;
|
|
29
|
+
/** Roster facts only: no kill switch, provider or memory-mode input (OQ-P3). `isDm` is the persisted flag (C8). */
|
|
30
|
+
factsOf(channelId: string): OwnerFacts;
|
|
31
|
+
absenceOf(scopeId: string): AbsenceRecord | undefined;
|
|
32
|
+
markAbsent(scopeId: string, now: number): AbsenceRecord;
|
|
33
|
+
clearAbsence(scopeId: string): void;
|
|
34
|
+
purge(scopeId: string): Promise<void>;
|
|
35
|
+
now(): number;
|
|
36
|
+
/** Shutdown: the sweep stops before the next scope (like MembershipPurgeSweeper). */
|
|
37
|
+
stopping?(): boolean;
|
|
38
|
+
log?: ServiceLog;
|
|
39
|
+
}
|
|
40
|
+
export declare class OwnerScopeSweeper {
|
|
41
|
+
private readonly d;
|
|
42
|
+
private running;
|
|
43
|
+
constructor(d: OwnerScopeSweepDeps);
|
|
44
|
+
sweep(): Promise<{
|
|
45
|
+
purged: number;
|
|
46
|
+
frozen: number;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** H-1: frozen; a pin token on this list needs no owner occurrence. Contents approved under OQ-P6. */
|
|
2
|
+
export declare const FRAMING_WORDS: ReadonlySet<string>;
|
|
3
|
+
/**
|
|
4
|
+
* C-1 with R2-L-1: NFC first; then keep letters (L), numbers (N), punctuation (P), space separators (Zs), the 14 symbols
|
|
5
|
+
* `$ % & + = < > ~ ^ | # @ * /`, and combining marks Mn and Mc except the variation selectors (including U+180B-180F). Everything else (Cf, Co,
|
|
6
|
+
* Cn, Cc, tag characters, bidi controls, zero-width characters, other symbols) is stripped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function allowlistPin(raw: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* H-1: every pin token not on FRAMING_WORDS must occur, as an exact token, in the owner's current message or in an
|
|
11
|
+
* owner-trust TYPED item delivered with this turn (the caller passes only COVERAGE_SOURCES texts). No stems, no allowance.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pinCoverage(pinStripped: string, ownerMessage: string, coverTexts: readonly string[]): {
|
|
14
|
+
ok: true;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
uncovered: number;
|
|
18
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ServiceLog } from "../../service-log.js";
|
|
2
|
+
import type { FromWorker, OpenedItem, RankQuery, ToWorker, WorkerInit } from "./worker-protocol.js";
|
|
3
|
+
export interface RankingTransport {
|
|
4
|
+
post(m: ToWorker): void;
|
|
5
|
+
onMessage(cb: (m: FromWorker) => void): void;
|
|
6
|
+
onExit(cb: () => void): void;
|
|
7
|
+
terminate(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function workerTransport(init: WorkerInit, distDir: string): RankingTransport;
|
|
10
|
+
export declare class RankingClient {
|
|
11
|
+
private readonly d;
|
|
12
|
+
private t;
|
|
13
|
+
/** Final review bridge I-2(b): set by close(); no call after it spawns a worker or posts. */
|
|
14
|
+
private closed;
|
|
15
|
+
private seq;
|
|
16
|
+
private readonly waiting;
|
|
17
|
+
private misses;
|
|
18
|
+
private hot;
|
|
19
|
+
constructor(d: {
|
|
20
|
+
spawn: () => RankingTransport;
|
|
21
|
+
log?: ServiceLog;
|
|
22
|
+
});
|
|
23
|
+
private transport;
|
|
24
|
+
private ask;
|
|
25
|
+
query(q: RankQuery, deadlineAt: number): Promise<{
|
|
26
|
+
items: OpenedItem[];
|
|
27
|
+
openFailed: number;
|
|
28
|
+
} | "late" | "failed">;
|
|
29
|
+
pins(scopeId: string, maxChars: number, deadlineAt: number): Promise<{
|
|
30
|
+
pins: OpenedItem[];
|
|
31
|
+
more: number;
|
|
32
|
+
} | "late" | "failed">;
|
|
33
|
+
pending(limit: number, deadlineAt: number): Promise<Extract<FromWorker, {
|
|
34
|
+
type: "pending";
|
|
35
|
+
}>["rows"] | "late" | "failed">;
|
|
36
|
+
/**
|
|
37
|
+
* Final review bridge I-2: hot-set notifications reach a RUNNING worker only. With none running they are no-ops (a worker
|
|
38
|
+
* spawned later warms from the database, so nothing is lost): a purge, clear or write while memory is off never starts
|
|
39
|
+
* a resident worker, nothing respawns after close(), and a post that throws never escapes into a committed write's path.
|
|
40
|
+
*/
|
|
41
|
+
private notify;
|
|
42
|
+
added(itemIds: number[]): void;
|
|
43
|
+
removed(itemIds: number[]): void;
|
|
44
|
+
reset(): void;
|
|
45
|
+
hotBytes(): number;
|
|
46
|
+
deadlineMisses(): number;
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified memory (spec section 8.1, section 8.5; plan Task 27): the read limits, split out of ranking-core.ts so the
|
|
3
|
+
* main-thread read path (read-book.ts) never imports the module that holds the SQL (T-33). No imports.
|
|
4
|
+
*/
|
|
5
|
+
export declare const TYPED_ITEMS_MAX = 10;
|
|
6
|
+
export declare const VOICE_ITEMS_MAX = 3;
|
|
7
|
+
export declare const READ_CHARS_MAX = 6000;
|
|
8
|
+
export declare const PINNED_CHARS_MAX = 4000;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified memory (spec section 8.1 steps 3 to 6, section 8.3, section 8.5; R-U16 rev2, R-U18, H-7): ranking over a READ-ONLY connection.
|
|
3
|
+
* Runs inside the ranking worker in production (Task 14) and in-process only in tests and the eval (Task 35).
|
|
4
|
+
* Every embedded item is hot (int8, one float scale per vector); there is no cold vector path. Keyword MACs boost items
|
|
5
|
+
* already in the meaning list and are the only candidate source for an item still pending its embed and for the
|
|
6
|
+
* keyword-only read (no query vector, or warm off: Windows, R-U11). U1-G (architect recall-gate memo, section 4).
|
|
7
|
+
*/
|
|
8
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
9
|
+
import type { FromWorker, ToWorker, WorkerInit } from "./worker-protocol.js";
|
|
10
|
+
export declare const DIMS = 384;
|
|
11
|
+
export declare const OWNER_SCOPE_PRIOR = 0.05;
|
|
12
|
+
export declare const RECENCY_FLOOR = 0.85;
|
|
13
|
+
/** U1-G (C): 0.15 -> 0.05, a holding value until a replaced-fact eval exists (Part 2). The 0.85 floor is unchanged. */
|
|
14
|
+
export declare const RECENCY_SLOPE = 0.05;
|
|
15
|
+
export declare const RRF_K = 60;
|
|
16
|
+
export declare const HOT_TOKEN_MAX_POSTINGS = 5000;
|
|
17
|
+
export declare const LIST_MAX = 50;
|
|
18
|
+
/** U1-G (A): section 8.3's freshness is a tie-break: it orders only fused scores within this of each other (float
|
|
19
|
+
* rounding; distinct RRF sums at k = 60 over ranks below 50 differ by far more). */
|
|
20
|
+
export declare const FRESHNESS_TIE_EPSILON = 1e-12;
|
|
21
|
+
/** U1-G (D): hot set slots reserved above the warm count, and the growth factor (was doubling). */
|
|
22
|
+
export declare const HOT_SET_HEADROOM = 1024;
|
|
23
|
+
export declare const HOT_SET_GROWTH = 1.25;
|
|
24
|
+
export { TYPED_ITEMS_MAX, VOICE_ITEMS_MAX, READ_CHARS_MAX, PINNED_CHARS_MAX } from "./ranking-core-constants.js";
|
|
25
|
+
/** section 8.5: max(0.85, 1 - age_days/365 x slope), slope 0.05 (U1-G). `slope` is the plan Task 35 eval override only. */
|
|
26
|
+
export declare function recencyWeight(ageDays: number, slope?: number): number;
|
|
27
|
+
/** U1-G (D): the warm reserve, min(count, N) plus the headroom (never N for every agent). */
|
|
28
|
+
export declare function hotSetReserve(embeddedCount: number): number;
|
|
29
|
+
/** U1-G (D): the capacity a grow to hold `need` slots picks: x1.25, capped at N, at least `need`. Past N (the store's
|
|
30
|
+
* eviction lags an insert batch) it steps by the headroom, so a long tail over N never grows one slot at a time. */
|
|
31
|
+
export declare function hotSetGrowth(cap: number, need: number): number;
|
|
32
|
+
/** Packed int8 vectors with a per-vector scale; swap-remove keeps it dense. */
|
|
33
|
+
export declare class HotSet {
|
|
34
|
+
private cap;
|
|
35
|
+
private n;
|
|
36
|
+
private q;
|
|
37
|
+
private scale;
|
|
38
|
+
private ids;
|
|
39
|
+
private at;
|
|
40
|
+
private scopeOf;
|
|
41
|
+
private pin;
|
|
42
|
+
private readonly slotOf;
|
|
43
|
+
private readonly scopes;
|
|
44
|
+
private readonly scopeIdx;
|
|
45
|
+
get size(): number;
|
|
46
|
+
get capacity(): number;
|
|
47
|
+
vectorBytes(): number;
|
|
48
|
+
bytes(): number;
|
|
49
|
+
/** U1-G (D): sets the capacity to at least `slots` in one allocation (the warm reserve). */
|
|
50
|
+
reserve(slots: number): void;
|
|
51
|
+
private grow;
|
|
52
|
+
/** The old buffers are dropped (unreferenced) as soon as they are copied. */
|
|
53
|
+
private resize;
|
|
54
|
+
add(e: {
|
|
55
|
+
itemId: number;
|
|
56
|
+
vec: Float32Array;
|
|
57
|
+
scopeId: string;
|
|
58
|
+
originAt: number;
|
|
59
|
+
pinned: boolean;
|
|
60
|
+
}): void;
|
|
61
|
+
remove(itemId: number): void;
|
|
62
|
+
clear(): void;
|
|
63
|
+
/** section 8.5: sim (int8 dot x scale), meaning threshold, then (sim + prior) x recency; the top `k` by that score. */
|
|
64
|
+
topK(query: Float32Array, allowed: ReadonlySet<string>, o: {
|
|
65
|
+
k: number;
|
|
66
|
+
nowMs: number;
|
|
67
|
+
ownerScope: string | null;
|
|
68
|
+
threshold: number;
|
|
69
|
+
prior?: number;
|
|
70
|
+
recencySlope?: number;
|
|
71
|
+
}): Array<{
|
|
72
|
+
itemId: number;
|
|
73
|
+
sim: number;
|
|
74
|
+
score: number;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
export declare class RankingCore {
|
|
78
|
+
private readonly init;
|
|
79
|
+
private readonly db;
|
|
80
|
+
private readonly hot;
|
|
81
|
+
private readonly stmts;
|
|
82
|
+
constructor(init: WorkerInit, db: DatabaseSync);
|
|
83
|
+
/** Prepared once per distinct statement text (the keyword statement varies only with the MAC and scope counts). */
|
|
84
|
+
private stmt;
|
|
85
|
+
hotCount(): number;
|
|
86
|
+
hotCapacity(): number;
|
|
87
|
+
/** The meaning list a query would fuse (section 8.5 scores, the init's eval overrides applied when set). */
|
|
88
|
+
meaningScores(vec: Float32Array, scopes: readonly string[], ownerScope: string | null, nowMs: number): Array<{
|
|
89
|
+
itemId: number;
|
|
90
|
+
sim: number;
|
|
91
|
+
score: number;
|
|
92
|
+
}>;
|
|
93
|
+
hotBytes(): number;
|
|
94
|
+
private open;
|
|
95
|
+
private fields;
|
|
96
|
+
private row;
|
|
97
|
+
private addRow;
|
|
98
|
+
/** section 8.5 warm: one row per read, a yield between rows; returns the hot count. A disabled warm (win32 keyword-only) loads nothing. */
|
|
99
|
+
warm(): Promise<number>;
|
|
100
|
+
/** Opens a record; null on any failure or a source_ref mismatch (section 11.3). */
|
|
101
|
+
private openRecord;
|
|
102
|
+
private opened;
|
|
103
|
+
/** H-7: one batched statement, LIMIT 5,001 per MAC inside the allowed scopes; a MAC that fills its limit is skipped. */
|
|
104
|
+
private keyword;
|
|
105
|
+
/** section 8.1 step 5: message ids of the newest 50 history turns of the conversation, plus the current message. */
|
|
106
|
+
private excluded;
|
|
107
|
+
private rank;
|
|
108
|
+
private pins;
|
|
109
|
+
private pending;
|
|
110
|
+
/**
|
|
111
|
+
* Final review bridge M-4: `handle` with a per-message catch. A request that throws (a prepare failure, a decode throw)
|
|
112
|
+
* is answered `failed` instead of becoming an unhandled rejection that kills the worker; a notification that throws is
|
|
113
|
+
* dropped (a later request still warms from the database). Content-free: the error is never sent or logged.
|
|
114
|
+
*/
|
|
115
|
+
handleSafe(m: ToWorker): Promise<FromWorker | null>;
|
|
116
|
+
handle(m: ToWorker): Promise<FromWorker | null>;
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified memory (spec section 8.1; H-4, U11, U15): the per-turn read. The capture is taken first (U11); pinned facts come
|
|
3
|
+
* from a cache keyed by the owner-DM scope id (never per agent, H-A) and filled from the worker under the memory mutex;
|
|
4
|
+
* the search runs on the worker; everything shares the turn kind's one deadline (600 ms typed, 250 ms voice). Past it:
|
|
5
|
+
* pinned facts only (from a current cache). Nothing attaches late. Delivery re-checks the capture and the owner scope.
|
|
6
|
+
* No SQL here (T-33): the constants come from the import-free ranking-core-constants.ts, never ranking-core.ts.
|
|
7
|
+
*/
|
|
8
|
+
import { type SqliteMlsStateStore } from "@agentvault/connect-mls-store";
|
|
9
|
+
import type { Embedder } from "../embedder/client.js";
|
|
10
|
+
import type { RankingClient } from "./ranking-client.js";
|
|
11
|
+
export interface UnifiedFrameItem {
|
|
12
|
+
block: "pinned" | "owner" | "reference";
|
|
13
|
+
id: number;
|
|
14
|
+
source: string;
|
|
15
|
+
at: number;
|
|
16
|
+
text: string;
|
|
17
|
+
}
|
|
18
|
+
/** The runtime's parseUnified limits (connect-agent-runtime memory-unified.ts): one item over them blanks the whole block there. */
|
|
19
|
+
export declare const FRAME_ITEMS_MAX = 210;
|
|
20
|
+
export declare const FRAME_TEXT_MAX = 1000;
|
|
21
|
+
export declare class UnifiedReadBook {
|
|
22
|
+
private readonly d;
|
|
23
|
+
private readonly entries;
|
|
24
|
+
private readonly pinCache;
|
|
25
|
+
/**
|
|
26
|
+
* Plan audit M-6: the texts DELIVERED with each message, kept until ack/nack/retire (`drop`), so a forget that follows
|
|
27
|
+
* a pin in the same message (the pin's dropChannelLocked empties `entries`) still finds its text for the Forgot: line.
|
|
28
|
+
* Cleared by a clear (dropAllLocked) and an owner-scope purge (dropScopeLocked), never by a channel cache refresh.
|
|
29
|
+
*/
|
|
30
|
+
private readonly shown;
|
|
31
|
+
private reads;
|
|
32
|
+
/** Fix round 1: turns whose read missed its deadline (a local late, or a pins/query answer late or failed); one per turn. */
|
|
33
|
+
private misses;
|
|
34
|
+
/**
|
|
35
|
+
* Fix round 1 (ruling): bumped by every drop*Locked and by bumpGeneration (Task 28: a pin insert). Taken before the
|
|
36
|
+
* pins request and compared at install and before the entry is kept, so a drop during an await is never undone.
|
|
37
|
+
*/
|
|
38
|
+
private gen;
|
|
39
|
+
/** Fix round 2: reads thrown away because the generation moved (a drop, a pin insert); not deadline misses. */
|
|
40
|
+
private discarded;
|
|
41
|
+
constructor(d: {
|
|
42
|
+
store: Pick<SqliteMlsStateStore, "itemCapture">;
|
|
43
|
+
ranking: Pick<RankingClient, "query" | "pins">;
|
|
44
|
+
embedder: Embedder;
|
|
45
|
+
embedOn: () => boolean;
|
|
46
|
+
indexKey: Uint8Array;
|
|
47
|
+
exclusive: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
48
|
+
now?: () => number;
|
|
49
|
+
});
|
|
50
|
+
/** The health key memory_reads: owner-DM reads started since boot. */
|
|
51
|
+
readsAttempted(): number;
|
|
52
|
+
/** The health key memory_deadline_misses: owner-DM turns that got no search result in time (U15). */
|
|
53
|
+
deadlineMisses(): number;
|
|
54
|
+
/** The health key memory_reads_discarded: reads not kept because a drop or pin insert moved the generation during them. */
|
|
55
|
+
readsDiscarded(): number;
|
|
56
|
+
/** Invalidates the pin cache and every read in flight (Task 28 calls it, under the memory mutex, when a pin is inserted). */
|
|
57
|
+
bumpGeneration(): void;
|
|
58
|
+
prepare(p: {
|
|
59
|
+
messageId: string;
|
|
60
|
+
channelId: string;
|
|
61
|
+
scopeId: string;
|
|
62
|
+
text: string;
|
|
63
|
+
kind: "text" | "voice";
|
|
64
|
+
}): Promise<void>;
|
|
65
|
+
/** ws flush (synchronous): the entry's own channel and owner scope, its capture still current (H-4, U11). */
|
|
66
|
+
deliverable(messageId: string, channelId: string, scopeIdNow: string): {
|
|
67
|
+
items: UnifiedFrameItem[];
|
|
68
|
+
pinnedMore: number;
|
|
69
|
+
} | undefined;
|
|
70
|
+
/** Section 7.4 step 3: texts of owner-trust TYPED items shown with this delivery (never owner_voice, never reference). */
|
|
71
|
+
coverageTexts(messageId: string, shown: ReadonlySet<number>): string[];
|
|
72
|
+
/** M-6: what was DELIVERED with this message (kept until ack), not the prepare-time entry a cache refresh can drop. */
|
|
73
|
+
shownText(messageId: string, itemId: number): string | undefined;
|
|
74
|
+
/** Task 29 fix round 1: a forgotten item's delivered text leaves the shown map at once (not only at ack). */
|
|
75
|
+
forgetShown(messageId: string, itemId: number): void;
|
|
76
|
+
drop(messageId: string): void;
|
|
77
|
+
dropAllLocked(): void;
|
|
78
|
+
dropChannelLocked(channelId: string): void;
|
|
79
|
+
dropScopeLocked(scopeId: string): void;
|
|
80
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified agent memory, part 1 (spec 2026-10-10 §18, R-U25): a compiled literal, default off, flipped only by the
|
|
3
|
+
* release task after the Mac mini live check. A backend flag is not used: it would let the server widen cross-scope
|
|
4
|
+
* reads. While false the bridge behaves as 0.28.2 on every memory path.
|
|
5
|
+
*/
|
|
6
|
+
export declare const UNIFIED_MEMORY_PART1 = true;
|
|
7
|
+
/** `injected` is the test seam (the bridge agent's deps field for this switch); production never passes it (switch.arch.test.ts). */
|
|
8
|
+
export declare function unifiedPart1On(injected?: boolean): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function foldText(s: string): string;
|
|
2
|
+
export declare function tokens(s: string): string[];
|
|
3
|
+
/** H-1, H-2: English cardinals and ordinals count as digit-bearing (65 words, frozen). */
|
|
4
|
+
export declare const NUMBER_WORDS: ReadonlySet<string>;
|
|
5
|
+
/** A canonical Roman numeral (lower case after foldText). Note: "i", "v", "x" and "mix" match; Task 35 measures the effect. */
|
|
6
|
+
export declare function isRomanNumeral(t: string): boolean;
|
|
7
|
+
export declare function isDigitBearing(t: string): boolean;
|
|
8
|
+
/** §11.5: a fixed English stop-word list (126 words: the NLTK English list without its contractions), never indexed. */
|
|
9
|
+
export declare const STOP_WORDS: ReadonlySet<string>;
|
|
10
|
+
/** L-3: the fixed English suffix set for query-side expansion (the plan enumerates it, spec §11.5). y↔ies is added below. */
|
|
11
|
+
export declare const QUERY_SUFFIXES: readonly string[];
|
|
12
|
+
/** §11.5: an item's exact, de-duplicated tokens, stop words dropped. No stems. */
|
|
13
|
+
export declare function indexTokens(text: string): string[];
|
|
14
|
+
/** L-3: the token, the token plus each suffix, the token minus each suffix it ends with (never below 3 characters), and y↔ies. */
|
|
15
|
+
export declare function queryExpansions(t: string): string[];
|