@securecoms/agent-bridge 0.26.6 → 0.28.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 +119 -0
- package/dist/bin.d.ts +24 -1
- package/dist/bin.js +4490 -375
- package/dist/bridge-agent.d.ts +405 -0
- package/dist/config.d.ts +7 -0
- package/dist/consumer-grants.d.ts +2 -1
- package/dist/embedder-manifest.json +52 -0
- package/dist/embedder-worker.js +2898 -0
- package/dist/embedder.js +1574 -0
- package/dist/index.js +4282 -297
- package/dist/memory/consumer-notes.d.ts +34 -0
- package/dist/memory/consumer-recall.d.ts +21 -0
- package/dist/memory/embedder/client.d.ts +103 -0
- package/dist/memory/embedder/download.d.ts +28 -0
- package/dist/memory/embedder/embedder-main.d.ts +1 -0
- package/dist/memory/embedder/embedder-paths.d.ts +14 -0
- package/dist/memory/embedder/engine.d.ts +21 -0
- package/dist/memory/embedder/gc.d.ts +2 -0
- package/dist/memory/embedder/handshake.d.ts +7 -0
- package/dist/memory/embedder/heartbeats.d.ts +4 -0
- package/dist/memory/embedder/import-bundle.d.ts +6 -0
- package/dist/memory/embedder/inner-source.d.ts +2 -0
- package/dist/memory/embedder/layout.d.ts +36 -0
- package/dist/memory/embedder/locks.d.ts +17 -0
- package/dist/memory/embedder/manifest.d.ts +30 -0
- package/dist/memory/embedder/no-dial.d.ts +3 -0
- package/dist/memory/embedder/protocol.d.ts +55 -0
- package/dist/memory/embedder/run-dir.d.ts +11 -0
- package/dist/memory/embedder/scheduler.d.ts +24 -0
- package/dist/memory/embedder/server.d.ts +46 -0
- package/dist/memory/embedder/session.d.ts +17 -0
- package/dist/memory/embedder/spawn.d.ts +40 -0
- package/dist/memory/embedder/tokenizer.d.ts +41 -0
- package/dist/memory/embedder/types.d.ts +32 -0
- package/dist/memory/embedder/windows.d.ts +46 -0
- package/dist/memory/embedder/worker-body.d.ts +44 -0
- package/dist/memory/embedder/worker.d.ts +1 -0
- package/dist/memory/memory-config.d.ts +23 -0
- package/dist/memory/note-frames.d.ts +47 -0
- package/dist/memory/note-provenance.d.ts +40 -0
- package/dist/memory/notes-book.d.ts +187 -0
- package/dist/memory/provider.d.ts +2 -0
- package/dist/memory/recall/call-heartbeat.d.ts +35 -0
- package/dist/memory/recall/membership-purge.d.ts +82 -0
- package/dist/memory/recall/rank.d.ts +15 -0
- package/dist/memory/recall/recall-book.d.ts +140 -0
- package/dist/memory/recall/reembed.d.ts +35 -0
- package/dist/memory/secret-scan.d.ts +5 -0
- package/dist/memory-taps.d.ts +3 -2
- package/dist/ort-wasm-simd-threaded.wasm +0 -0
- package/dist/version.d.ts +1 -1
- package/dist/voice/fast-prompt.d.ts +5 -0
- package/dist/ws-server.d.ts +28 -1
- package/package.json +2 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Compiled; flipped only by Task 27b's follow-up PR. While false, a win32 bridge reports `unsupported_platform`. */
|
|
2
|
+
export declare const WINDOWS_SEMANTIC_ENABLED = false;
|
|
3
|
+
export declare const windowsSemanticOn: (platform: NodeJS.Platform, enabled?: boolean) => boolean;
|
|
4
|
+
/** %LOCALAPPDATA%\SecureComs\run\embedder; it lives in embedder-paths.ts so the embedder bundle never imports this module. */
|
|
5
|
+
export { windowsRunDir } from "./embedder-paths.js";
|
|
6
|
+
/** icacls /save writes UTF-16LE: line 1 is the path, line 2 the SDDL. */
|
|
7
|
+
export declare function parseSaveSddl(saveFile: Buffer): string;
|
|
8
|
+
/**
|
|
9
|
+
* P-7 delta M2 and 11d review L1/L2. The SDDL is parsed, not pattern-matched:
|
|
10
|
+
* - the owner (`O:`), when the save file carries one, must be the user SID, SYSTEM (SY) or Administrators (BA);
|
|
11
|
+
* - the DACL is anchored on the FIRST `D:`; its flags are letters only; then one or more `(…)` ACEs whose bodies may not
|
|
12
|
+
* contain `(` (a conditional ACE such as `(XA;;FA;;;S-1-…;(@User.x))` is rejected outright); after the ACEs only an
|
|
13
|
+
* `S:` section or the end may follow (a second `D:` or any trailing text is rejected);
|
|
14
|
+
* - "A" (allow) must name the user SID, SY or BA; "D" (deny) grants nothing and is accepted; every other type (OA, XA,
|
|
15
|
+
* ZA, AU, …), a missing DACL and an empty `D:` are rejected. Inherited entries (the ID flag) get no exemption.
|
|
16
|
+
*/
|
|
17
|
+
export declare function aclAllows(sddl: string, ownerSid: string): boolean;
|
|
18
|
+
/** Any component of the path is a reparse point (Node reports junctions and symlinks as symbolic links). */
|
|
19
|
+
export declare function hasReparsePoint(path: string, lstat?: (p: string) => {
|
|
20
|
+
isSymbolicLink(): boolean;
|
|
21
|
+
}): boolean;
|
|
22
|
+
type Runner = (cmd: string, args: string[]) => Promise<Buffer | string> | Buffer | string;
|
|
23
|
+
/** P-7 delta M1: the SID from `whoami /user /fo csv /nh`, never the environment; any S-1-… (local, domain, Microsoft account or Entra S-1-12-1-…). */
|
|
24
|
+
export declare function currentUserSid(run?: Runner): Promise<string>;
|
|
25
|
+
export interface AclDeps {
|
|
26
|
+
run?: Runner;
|
|
27
|
+
lstat?: (p: string) => {
|
|
28
|
+
isSymbolicLink(): boolean;
|
|
29
|
+
};
|
|
30
|
+
tmp?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* True only when `path` (the run directory, or the token file inside it) has no reparse point on the way and its DACL
|
|
34
|
+
* allows only this user, SYSTEM and Administrators. P-7 delta H1: icacls' stdout is localized text and is never parsed;
|
|
35
|
+
* the /save FILE is always read. Any failure is false (fail closed). `sid` is the cached per-process SID (WindowsAcl).
|
|
36
|
+
*/
|
|
37
|
+
export declare function verifyRunDirAcl(path: string, deps?: AclDeps, sid?: () => Promise<string>): Promise<boolean>;
|
|
38
|
+
/** One per process: the current user's SID is read once (a failed read is not cached, so the next check retries). */
|
|
39
|
+
export declare class WindowsAcl {
|
|
40
|
+
private readonly deps;
|
|
41
|
+
private sidP;
|
|
42
|
+
constructor(deps?: AclDeps);
|
|
43
|
+
sid(): Promise<string>;
|
|
44
|
+
check(path: string): Promise<boolean>;
|
|
45
|
+
}
|
|
46
|
+
export declare const PROCESS_WINDOWS_ACL: WindowsAcl;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { EmbedderWorkerData } from "./types.js";
|
|
2
|
+
/** The slice of onnxruntime-web this body uses; the real module satisfies it. */
|
|
3
|
+
export interface OrtLike {
|
|
4
|
+
env: {
|
|
5
|
+
wasm: {
|
|
6
|
+
wasmBinary?: ArrayBufferLike | Uint8Array;
|
|
7
|
+
numThreads?: number;
|
|
8
|
+
proxy?: boolean;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
Tensor: new (type: "int64", data: BigInt64Array, dims: number[]) => unknown;
|
|
12
|
+
InferenceSession: {
|
|
13
|
+
create(model: Uint8Array): Promise<{
|
|
14
|
+
run(feeds: Record<string, unknown>): Promise<Record<string, {
|
|
15
|
+
data: unknown;
|
|
16
|
+
}>>;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface PortLike {
|
|
21
|
+
postMessage(m: unknown, transfer?: ArrayBuffer[]): void;
|
|
22
|
+
on(ev: "message", f: (m: {
|
|
23
|
+
job_id: number;
|
|
24
|
+
text: string;
|
|
25
|
+
}) => void): void;
|
|
26
|
+
}
|
|
27
|
+
type Spec = {
|
|
28
|
+
path: string;
|
|
29
|
+
sha256: string;
|
|
30
|
+
size: number;
|
|
31
|
+
};
|
|
32
|
+
/** What the body needs: the worker data with a lookup key per file (erratum E1: `read` maps the key to the bytes it was handed). */
|
|
33
|
+
export type WorkerBodyData = Omit<EmbedderWorkerData, "bytes" | "model" | "vocab" | "wasm"> & {
|
|
34
|
+
model: Spec;
|
|
35
|
+
vocab: Spec;
|
|
36
|
+
wasm: Spec;
|
|
37
|
+
};
|
|
38
|
+
export declare function runWorker(o: {
|
|
39
|
+
read: (path: string) => Uint8Array;
|
|
40
|
+
ort: OrtLike;
|
|
41
|
+
port: PortLike;
|
|
42
|
+
data: WorkerBodyData;
|
|
43
|
+
}): Promise<"ready" | "fatal">;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec §11.2 (OQ-L), plan D-5: the bridge parse of `payload.memory`.
|
|
3
|
+
*
|
|
4
|
+
* The mode is not a security control. A value the bridge cannot read turns
|
|
5
|
+
* notes OFF (no reads, writes refused with `mode`) and deletes nothing; it
|
|
6
|
+
* never purges. Absent and `{}` mean `{ mode: "markdown", clear_generation: 0 }`
|
|
7
|
+
* (an old backend, or a row never given a memory value). `null` is neither,
|
|
8
|
+
* so it fails closed (D-5). `"semantic"` turns recall on as well (7b, spec §5.3);
|
|
9
|
+
* notes stay on in both modes.
|
|
10
|
+
*/
|
|
11
|
+
import { type RecallDays } from "@agentvault/connect-mls-store";
|
|
12
|
+
export type PayloadMemory = {
|
|
13
|
+
notesOn: true;
|
|
14
|
+
clearGeneration: number;
|
|
15
|
+
mode: "markdown" | "semantic";
|
|
16
|
+
recallDays?: RecallDays;
|
|
17
|
+
unrecognized?: undefined;
|
|
18
|
+
} | {
|
|
19
|
+
notesOn: false;
|
|
20
|
+
clearGeneration?: undefined;
|
|
21
|
+
unrecognized: "mode" | "generation";
|
|
22
|
+
};
|
|
23
|
+
export declare function parsePayloadMemory(raw: unknown): PayloadMemory;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec §8.1 (audit r1 M-2): the memory_note frames between the runtime and
|
|
3
|
+
* the bridge. The bridge trusts no field of the frame but these five, and
|
|
4
|
+
* none of them decides WHERE a note goes or WHO may write it: the channel,
|
|
5
|
+
* the sender, the authorization and the source text all come from the
|
|
6
|
+
* bridge-held in-flight record (W2 to W8 in BridgeAgent.applyMemoryNote).
|
|
7
|
+
* A `channel_id`, `audience`, `authorization` or `transcribed` in the frame
|
|
8
|
+
* is ignored.
|
|
9
|
+
*/
|
|
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"];
|
|
12
|
+
export type ReasonClass = (typeof REASON_CLASSES)[number];
|
|
13
|
+
export interface MemoryNoteFrame {
|
|
14
|
+
request_id: string;
|
|
15
|
+
message_id: string;
|
|
16
|
+
op: "add" | "replace" | "forget";
|
|
17
|
+
/** A note id, or (forget only, spec §9.1) a recall id written `r<n>` on the wire. */
|
|
18
|
+
id?: number | {
|
|
19
|
+
recall: number;
|
|
20
|
+
};
|
|
21
|
+
text?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface MemoryNoteResultFrame {
|
|
24
|
+
type: "memory_note_result";
|
|
25
|
+
request_id?: string;
|
|
26
|
+
message_id?: string;
|
|
27
|
+
ok: boolean;
|
|
28
|
+
id?: number;
|
|
29
|
+
reason?: ReasonClass;
|
|
30
|
+
}
|
|
31
|
+
/** §8.1: request_id is 1 to 64 characters. */
|
|
32
|
+
export declare const REQUEST_ID_MAX = 64;
|
|
33
|
+
/** The runtime's message ids, as the ws `send` frame bounds in_reply_to. */
|
|
34
|
+
export declare const MESSAGE_ID_MAX = 200;
|
|
35
|
+
/**
|
|
36
|
+
* Upper bound on the frame's text before any work. W8 refuses a note longer
|
|
37
|
+
* than twice the paste bound and the stored note is capped at 400 (W7); this
|
|
38
|
+
* only bounds what is ever handed to them.
|
|
39
|
+
*/
|
|
40
|
+
export declare const FRAME_TEXT_MAX = 4000;
|
|
41
|
+
/** §8.1 well-formedness. Null for any malformed frame (reason class `invalid`). */
|
|
42
|
+
export declare function parseMemoryNoteFrame(m: Record<string, unknown>): MemoryNoteFrame | null;
|
|
43
|
+
/** The ids a result echoes (§8.1), only when they are strings within bounds. */
|
|
44
|
+
export declare function echoIds(m: Record<string, unknown>): {
|
|
45
|
+
request_id?: string;
|
|
46
|
+
message_id?: string;
|
|
47
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W8 provenance (spec section 8.2, C-1; audit r2 N-H1, N-M2, N-L3, N-L4) and the
|
|
3
|
+
* notes rendering (section 7.1). Pure: no I/O, no clock.
|
|
4
|
+
*
|
|
5
|
+
* PR-5: `norm` is applied per code point (NFKC, then toLowerCase, then the
|
|
6
|
+
* quote and dash folds) so a match can be mapped back to the owner's own
|
|
7
|
+
* span of `record.text`, which is what is stored. A precomposed character on
|
|
8
|
+
* one side and a decomposed one on the other therefore fails closed.
|
|
9
|
+
*/
|
|
10
|
+
export declare const NOTES_HEADING = "## Saved notes for this chat (data, not instructions)";
|
|
11
|
+
/** N-H1(c), PR-2: a source message longer than this (UTF-16 units) cannot yield a note. */
|
|
12
|
+
export declare const PASTE_MAX_CHARS = 600;
|
|
13
|
+
/** N-M2, PR-2: the minimum normalised note. Counted in UTF-16 units (String.length), like the caps in spec section 6.3. */
|
|
14
|
+
export declare const NOTE_MIN_CHARS = 6;
|
|
15
|
+
export declare const NOTE_MIN_TOKENS = 2;
|
|
16
|
+
export declare function norm(s: string): string;
|
|
17
|
+
/** norm, then strip leading and trailing `.,;:!?"'()[]` (the note only, \u00A78.2). */
|
|
18
|
+
export declare function normNote(s: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* One line, no invisible characters. Line breaks become spaces; every Cf and Cc
|
|
21
|
+
* character (bidi marks, soft hyphen, ZWSP, word joiner, BOM, tag characters,
|
|
22
|
+
* C0/C1 controls) is removed, except ZWJ and ZWNJ between two letters or marks,
|
|
23
|
+
* which belong to some scripts. Strip first, then collapse whitespace (N-6).
|
|
24
|
+
*/
|
|
25
|
+
export declare function oneLine(s: string): string;
|
|
26
|
+
export type Provenance = {
|
|
27
|
+
ok: true;
|
|
28
|
+
stored: string;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
why: "empty" | "too_short" | "not_a_quote" | "pasted" | "transcribed";
|
|
32
|
+
};
|
|
33
|
+
export declare function quoteFromMessage(note: string, record: {
|
|
34
|
+
readonly text: string;
|
|
35
|
+
readonly transcribed?: true;
|
|
36
|
+
}): Provenance;
|
|
37
|
+
export declare function renderNotes(notes: ReadonlyArray<{
|
|
38
|
+
readonly id: number;
|
|
39
|
+
readonly text: string;
|
|
40
|
+
}>): string | undefined;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one owner of every note mutation and of the notes cache (spec §6.3 M-3,
|
|
3
|
+
* §6.5, §7.2, §10; audit r2 N-M1).
|
|
4
|
+
*
|
|
5
|
+
* One NotesBook per store, and a store is one identity: the mutex here is the
|
|
6
|
+
* per-identity note mutex of spec §6.3. Every add, replace, forget, the legacy
|
|
7
|
+
* import, the clear purge, the channel purge and every cache fill run inside it.
|
|
8
|
+
*
|
|
9
|
+
* LEAF LOCK: code under the mutex awaits only store seal/open/read calls.
|
|
10
|
+
* Never await heldPosition, the network, an MLS operation or an SDK lock here.
|
|
11
|
+
*
|
|
12
|
+
* TEXT CONTRACT: the `text` of an add or replace is a `StoredNoteText`. The
|
|
13
|
+
* only producer is `prepareStoredNote`, which runs W8 (`quoteFromMessage`, the
|
|
14
|
+
* owner's own span on one line) and then W5 (`scanNote` on that stored span),
|
|
15
|
+
* in the spec §8.2 table order. The model's text can therefore never reach
|
|
16
|
+
* write() without a cast. NotesBook then runs the duplicate check and the caps
|
|
17
|
+
* (W7) on that text. It also passes the text through `oneLine` (idempotent on
|
|
18
|
+
* `stored`) as a floor.
|
|
19
|
+
*
|
|
20
|
+
* Known limitation (Task 1 review m6): a forget or a clear deletes rows
|
|
21
|
+
* logically. The sealed ciphertext can remain in SQLite free pages and the WAL
|
|
22
|
+
* until overwritten, and still opens under the same device key. `secure_delete`
|
|
23
|
+
* is not enabled (database-wide cost). Recorded for the release notes.
|
|
24
|
+
*/
|
|
25
|
+
import { type NoteCommit, type NoteCommitResult, type NotesSnapshot, type RuntimeInboxRewrite } from "@agentvault/connect-mls-store";
|
|
26
|
+
import { type Provenance } from "./note-provenance.js";
|
|
27
|
+
import { type ServiceLog } from "../service-log.js";
|
|
28
|
+
export interface NotesStore {
|
|
29
|
+
memoryClearGeneration(): number;
|
|
30
|
+
readMemoryNotes(conversationId: string): Promise<NotesSnapshot>;
|
|
31
|
+
sealMemoryNote(conversationId: string, text: string): Promise<Uint8Array>;
|
|
32
|
+
peekNextMemoryNoteId(conversationId: string): number;
|
|
33
|
+
commitMemoryNote(c: NoteCommit): NoteCommitResult;
|
|
34
|
+
applyMemoryClear(generation: number): boolean;
|
|
35
|
+
readMemoryFactsForImport(conversationId: string): Promise<Array<{
|
|
36
|
+
factId: number;
|
|
37
|
+
text: string;
|
|
38
|
+
}>>;
|
|
39
|
+
purgeMemory(conversationId: string): void;
|
|
40
|
+
conversationPurgeEpoch(conversationId: string): number;
|
|
41
|
+
}
|
|
42
|
+
/** FIFO async mutex. A rejected holder releases the lock; it never wedges the queue. */
|
|
43
|
+
export declare class AsyncMutex {
|
|
44
|
+
private tail;
|
|
45
|
+
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
46
|
+
}
|
|
47
|
+
declare const storedNoteBrand: unique symbol;
|
|
48
|
+
/** A note text that passed W8 and then W5. Produced only by prepareStoredNote (Task 4 review m6b). */
|
|
49
|
+
export type StoredNoteText = string & {
|
|
50
|
+
readonly [storedNoteBrand]: true;
|
|
51
|
+
};
|
|
52
|
+
export type PreparedNote = {
|
|
53
|
+
ok: true;
|
|
54
|
+
text: StoredNoteText;
|
|
55
|
+
} | {
|
|
56
|
+
ok: false;
|
|
57
|
+
reason: "provenance";
|
|
58
|
+
why: Extract<Provenance, {
|
|
59
|
+
ok: false;
|
|
60
|
+
}>["why"];
|
|
61
|
+
} | {
|
|
62
|
+
ok: false;
|
|
63
|
+
reason: "secret";
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* W8 then W5 (spec §8.2 table order). W5 runs on the STORED span (the owner's
|
|
67
|
+
* own words, on one line), never on the model's text. A refusal yields no brand.
|
|
68
|
+
*/
|
|
69
|
+
export declare function prepareStoredNote(modelText: string, record: {
|
|
70
|
+
readonly text: string;
|
|
71
|
+
readonly transcribed?: true;
|
|
72
|
+
}): PreparedNote;
|
|
73
|
+
/** Proof that the caller classified this channel's turn as an owner-DM write (spec §8.3, §10 M-6). */
|
|
74
|
+
export interface ImportAuthorization {
|
|
75
|
+
readonly memory_write: true;
|
|
76
|
+
}
|
|
77
|
+
export type ClearOutcome = "applied" | "equal" | "lower" | "out_of_range";
|
|
78
|
+
/**
|
|
79
|
+
* 7b Task 18 (spec §9.1 W9, audit r2 M-B): a replace and a forget are destructive, so each carries the
|
|
80
|
+
* prepared runtime_inbox rewrite that spends this message's one destructive operation. It is REQUIRED by
|
|
81
|
+
* type: the count commits in the same transaction as the change, or neither does.
|
|
82
|
+
*/
|
|
83
|
+
export type NoteMutation = {
|
|
84
|
+
op: "add";
|
|
85
|
+
text: StoredNoteText;
|
|
86
|
+
} | {
|
|
87
|
+
op: "replace";
|
|
88
|
+
id: number;
|
|
89
|
+
text: StoredNoteText;
|
|
90
|
+
rewrite: RuntimeInboxRewrite;
|
|
91
|
+
} | {
|
|
92
|
+
op: "forget";
|
|
93
|
+
id: number;
|
|
94
|
+
rewrite: RuntimeInboxRewrite;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* `budget`: this message already spent its destructive operation (W9). `purged`: the
|
|
98
|
+
* conversation was purged after the write was queued (Task 14 M-A; Task 18 carry 5, distinct
|
|
99
|
+
* from the owner's clear).
|
|
100
|
+
*/
|
|
101
|
+
export type NoteWriteResult = {
|
|
102
|
+
ok: true;
|
|
103
|
+
id: number;
|
|
104
|
+
notesCount: number;
|
|
105
|
+
docChars: number;
|
|
106
|
+
deduped?: true;
|
|
107
|
+
} | {
|
|
108
|
+
ok: false;
|
|
109
|
+
reason: "cap" | "cleared" | "purged" | "budget" | "store" | "invalid" | PrecommitVeto;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* A caller's last check, run inside the mutex after the seal and immediately
|
|
113
|
+
* before the synchronous commit (Task 7 fix round 1: W1 and W2 there). A
|
|
114
|
+
* non-null result refuses with that reason and writes nothing.
|
|
115
|
+
*/
|
|
116
|
+
export type PrecommitVeto = "message" | "mode";
|
|
117
|
+
export declare class NotesBook {
|
|
118
|
+
private readonly store;
|
|
119
|
+
private readonly log;
|
|
120
|
+
private readonly onClearedLocked;
|
|
121
|
+
private readonly mutex;
|
|
122
|
+
private readonly cache;
|
|
123
|
+
/**
|
|
124
|
+
* Review m-4: channels whose legacy import ran to completion in this
|
|
125
|
+
* process. A per-turn re-run re-read and re-opened every remaining fact and
|
|
126
|
+
* logged memory.import_partial on every owner turn. Invalidation: an
|
|
127
|
+
* applied clear empties the set; invalidateLegacyImport(channel) re-arms
|
|
128
|
+
* one channel when a new legacy assignment lands (Task 8 wires it). An
|
|
129
|
+
* import that throws is not recorded. A restart re-arms all channels.
|
|
130
|
+
* Cost (accepted): a fact refused for a cap stays a fact after the owner
|
|
131
|
+
* forgets notes, until a clear, a new assignment or a restart.
|
|
132
|
+
*/
|
|
133
|
+
private readonly imported;
|
|
134
|
+
/** In-memory mirror of the stored applied generation, changed only inside clear() (an apply or a resync). */
|
|
135
|
+
private generation;
|
|
136
|
+
constructor(store: NotesStore, log?: ServiceLog, onClearedLocked?: () => void);
|
|
137
|
+
/** The memory mutex for recall work (spec §4 M2: the NotesBook mutex is the per-identity memory mutex). Leaf lock. */
|
|
138
|
+
exclusive<T>(fn: () => Promise<T>): Promise<T>;
|
|
139
|
+
appliedGeneration(): number;
|
|
140
|
+
/** Synchronous, for ws-server flush. A missing entry or a stale generation is a miss (N-M1). */
|
|
141
|
+
deliverable(channelId: string): {
|
|
142
|
+
notes?: string;
|
|
143
|
+
generation: number;
|
|
144
|
+
miss: boolean;
|
|
145
|
+
};
|
|
146
|
+
/** Warm one channel's entry under the mutex (N-M1). Never rejects: a read failure leaves a miss. */
|
|
147
|
+
fill(channelId: string): Promise<void>;
|
|
148
|
+
private fillQuiet;
|
|
149
|
+
/** Call only under the mutex. On a read failure or another generation the entry is dropped (a miss), never kept stale. */
|
|
150
|
+
private fillLocked;
|
|
151
|
+
write(channelId: string, mutation: NoteMutation, expectGeneration: number, precommit?: () => PrecommitVeto | null): Promise<NoteWriteResult>;
|
|
152
|
+
/**
|
|
153
|
+
* §6.5, N-M1: the commit, the mirror and the cache empty run with NO await between them.
|
|
154
|
+
* L-4: a generation outside 0..MAX_CLEAR_GENERATION is refused before the store.
|
|
155
|
+
* The outcome lets the caller log `memory.clear_generation_regressed` on "lower"
|
|
156
|
+
* (review m6c) without re-reading appliedGeneration().
|
|
157
|
+
*/
|
|
158
|
+
clear(generation: number): Promise<ClearOutcome>;
|
|
159
|
+
/** The clear is committed: a hook failure must not reject it (the caller would retry a done clear). Call only under the mutex. */
|
|
160
|
+
private callClearedHook;
|
|
161
|
+
/** §6.4 (OQ-K): the channel purge, with its cache entry emptied inside the mutex. */
|
|
162
|
+
purgeChannel(channelId: string): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* §10 (M-6): move this channel's legacy facts into notes, oldest first.
|
|
165
|
+
*
|
|
166
|
+
* `auth` is the proof (review m6a): the caller passes the turn's frozen
|
|
167
|
+
* authorization, which carries `memory_write: true` only for an owner-DM turn.
|
|
168
|
+
* Room and partner facts stay facts.
|
|
169
|
+
* §10 is the authority: this runs lazily per owner-DM channel, never as a
|
|
170
|
+
* sweep at bridge start (OQ-F as superseded by M-6, audit r2 N-M3).
|
|
171
|
+
*
|
|
172
|
+
* A fact moves only when it passes the secret scan, is not a duplicate of an
|
|
173
|
+
* existing note, and fits the 200-note and 8,000-character caps (it is exempt
|
|
174
|
+
* from the 400-character note cap and from W8). Anything else stays a fact
|
|
175
|
+
* under its 90-day clock, counted in `memory.import_partial { remaining }`.
|
|
176
|
+
*/
|
|
177
|
+
importLegacy(channelId: string, auth: ImportAuthorization): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Review m-4: re-arm one channel's import (a new legacy assignment landed).
|
|
180
|
+
* Task 8: under the mutex, so an import already in flight for the channel
|
|
181
|
+
* (which read the facts before the assignment landed) records itself first
|
|
182
|
+
* and this delete then wins; a bare delete would be undone by that record.
|
|
183
|
+
*/
|
|
184
|
+
invalidateLegacyImport(channelId: string): Promise<void>;
|
|
185
|
+
private importLocked;
|
|
186
|
+
}
|
|
187
|
+
export {};
|
|
@@ -4,6 +4,7 @@ export interface MemoryProvider {
|
|
|
4
4
|
record(input: MemoryRecordInput): Promise<{
|
|
5
5
|
seq: number;
|
|
6
6
|
deduped: boolean;
|
|
7
|
+
dropped?: true;
|
|
7
8
|
}>;
|
|
8
9
|
context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
|
|
9
10
|
/** Optional adapter counters surfaced on the daemon healthcheck. */
|
|
@@ -16,6 +17,7 @@ export declare class SecureComsLocalMemory implements MemoryProvider {
|
|
|
16
17
|
record(input: MemoryRecordInput): Promise<{
|
|
17
18
|
seq: number;
|
|
18
19
|
deduped: boolean;
|
|
20
|
+
dropped?: true;
|
|
19
21
|
}>;
|
|
20
22
|
context(conversationId: string, limit?: number): Promise<MemoryContextResult>;
|
|
21
23
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ServiceLog } from "../../service-log.js";
|
|
2
|
+
import { HEARTBEAT_STALE_MS } from "../embedder/heartbeats.js";
|
|
3
|
+
/** Ruling P-1 (plan, controller rulings): a `voiceMode` turn ingested within the last 120 s means a live call. */
|
|
4
|
+
export declare const VOICE_LIVE_WINDOW_MS = 120000;
|
|
5
|
+
/** Spec §8: the heartbeat's mtime is refreshed every 10 s while live. */
|
|
6
|
+
export declare const HEARTBEAT_REFRESH_MS = 10000;
|
|
7
|
+
/** Spec §8, §20 E1.4: background jobs are held while any heartbeat is newer than 30 s (fix M1: and not far in the future). */
|
|
8
|
+
export { HEARTBEAT_STALE_MS };
|
|
9
|
+
export declare class CallHeartbeat {
|
|
10
|
+
/** null: disabled (Task 19 fix M2, a construction without machine paths): nothing is written or read. */
|
|
11
|
+
private readonly dir;
|
|
12
|
+
private readonly identityId;
|
|
13
|
+
private readonly liveWindowMs;
|
|
14
|
+
private readonly now;
|
|
15
|
+
private readonly log;
|
|
16
|
+
private lastVoiceAt;
|
|
17
|
+
private wrote;
|
|
18
|
+
private refusedLogged;
|
|
19
|
+
constructor(
|
|
20
|
+
/** null: disabled (Task 19 fix M2, a construction without machine paths): nothing is written or read. */
|
|
21
|
+
dir: string | null, identityId: string, liveWindowMs: number, now?: () => number, log?: ServiceLog);
|
|
22
|
+
private live;
|
|
23
|
+
/** A `voiceMode` turn was ingested. Going live writes the heartbeat at once (no 10 s lag before other bridges pause). */
|
|
24
|
+
noteVoiceActivity(): void;
|
|
25
|
+
/** Every 10 s: touch the file while live (ruling P-1), remove it when not. Never throws. */
|
|
26
|
+
refresh(): void;
|
|
27
|
+
/** Bridge stop: the call (as far as this bridge knows) ends with it. */
|
|
28
|
+
stop(): void;
|
|
29
|
+
/** True when any identity's heartbeat (a regular file) is fresh (heartbeats.ts: age in [-60 s, staleMs]). Never throws. */
|
|
30
|
+
anyLive(staleMs?: number): boolean;
|
|
31
|
+
/** The checked directory, or null when disabled or refused. */
|
|
32
|
+
private dirOk;
|
|
33
|
+
private touch;
|
|
34
|
+
private remove;
|
|
35
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec §5.4 (H4, D-6) with audit r2 H-A and audit r1 (plan) C2, H6, M6, plus
|
|
3
|
+
* the Task 13 dr-evil fix round. The only production caller of purgeMemory.
|
|
4
|
+
* Purges turns, facts, notes and recall of a conversation the agent has left,
|
|
5
|
+
* on evidence only:
|
|
6
|
+
*
|
|
7
|
+
* - The conversation's OWN recorded enclave was listed successfully and
|
|
8
|
+
* NON-EMPTY in this sweep, and that listing does not contain it. Nothing
|
|
9
|
+
* else is evidence: no recorded enclave, a failed listing, an empty listing
|
|
10
|
+
* (a membership or RLS false-zero answers 200 []) and an enclave not
|
|
11
|
+
* enumerated this sweep all leave the record as it is.
|
|
12
|
+
* - The conversation has been listed before (the store's persisted is_dm is
|
|
13
|
+
* set only from a listing). A channel no listing has ever shown (a hidden
|
|
14
|
+
* FileSafe vault channel) is never evidence, across restarts too.
|
|
15
|
+
* - A DM (store is_dm === true) additionally needs a per-channel 404 in the
|
|
16
|
+
* same sweep. A DM is never purged on the listing alone, and a 404 alone is
|
|
17
|
+
* never enough either (fix round CRITICAL: an empty listing plus a 404 on
|
|
18
|
+
* every probe must not purge the owner DM).
|
|
19
|
+
* - No inbound message arrived for it since the previous absence (LOW 3 veto).
|
|
20
|
+
* - The absences are consecutive: a record whose newest absence is older than
|
|
21
|
+
* two sweep intervals restarts (MEDIUM 1).
|
|
22
|
+
* - Two absences at least an hour apart; more than one NEW absence in one
|
|
23
|
+
* sweep is held.
|
|
24
|
+
*
|
|
25
|
+
* A malicious backend can still cause a purge by hiding one channel twice:
|
|
26
|
+
* availability only (§2).
|
|
27
|
+
*
|
|
28
|
+
* Limitation (stated, not fixed): removal from a whole enclave makes its
|
|
29
|
+
* listing 404, which is a failed listing, so no conversation in it is ever
|
|
30
|
+
* purged.
|
|
31
|
+
*/
|
|
32
|
+
import { type ServiceLog } from "../../service-log.js";
|
|
33
|
+
export declare const PURGE_MIN_GAP_MS: number;
|
|
34
|
+
export declare const PURGE_SWEEP_INTERVAL_MS: number;
|
|
35
|
+
/** Fix round MEDIUM 1: a record whose newest absence is older than this restarts. */
|
|
36
|
+
export declare const PURGE_ABSENCE_EXPIRY_MS: number;
|
|
37
|
+
export type ProbeResult = "member" | "gone" | "error";
|
|
38
|
+
export type AbsenceRecord = {
|
|
39
|
+
firstAbsentAt: number;
|
|
40
|
+
refreshes: number;
|
|
41
|
+
lastAbsentAt: number;
|
|
42
|
+
};
|
|
43
|
+
/** Audit r1 C2: only a 404 is "gone"; 2xx is "member"; 401, 403, any other status, and a throw are "error". */
|
|
44
|
+
export declare function probeResultFor(status: number | "threw"): ProbeResult;
|
|
45
|
+
export interface MembershipPurgeDeps {
|
|
46
|
+
conversations(): string[];
|
|
47
|
+
enclaveOf(conversationId: string): string | undefined;
|
|
48
|
+
/** The store's persisted is_dm: true, false, or undefined when no listing has ever shown the channel. */
|
|
49
|
+
isDm(conversationId: string): boolean | undefined;
|
|
50
|
+
enclaves(): Promise<Iterable<string>>;
|
|
51
|
+
listChannels(enclaveId: string): Promise<ReadonlyArray<{
|
|
52
|
+
channel_id: string;
|
|
53
|
+
}>>;
|
|
54
|
+
probeChannel(conversationId: string): Promise<ProbeResult>;
|
|
55
|
+
absenceOf(conversationId: string): AbsenceRecord | undefined;
|
|
56
|
+
markAbsent(conversationId: string, now: number): AbsenceRecord;
|
|
57
|
+
clearAbsence(conversationId: string): void;
|
|
58
|
+
purge(conversationId: string): Promise<void>;
|
|
59
|
+
now(): number;
|
|
60
|
+
/** LOW 3: when an inbound message for the conversation was last received, if known. */
|
|
61
|
+
lastInboundAt?(conversationId: string): number | undefined;
|
|
62
|
+
/** MEDIUM 3: true once the host is closing; the sweep stops before its next step. */
|
|
63
|
+
stopping?(): boolean;
|
|
64
|
+
log?: ServiceLog;
|
|
65
|
+
}
|
|
66
|
+
export type SweepOutcome = {
|
|
67
|
+
purged: string[];
|
|
68
|
+
held: number;
|
|
69
|
+
absent: number;
|
|
70
|
+
};
|
|
71
|
+
export declare class MembershipPurgeSweeper {
|
|
72
|
+
private readonly d;
|
|
73
|
+
private running;
|
|
74
|
+
private held;
|
|
75
|
+
private prevSweepAt;
|
|
76
|
+
constructor(d: MembershipPurgeDeps);
|
|
77
|
+
/** Audit r2 LOW: the held-departure count of the newest sweep, for machine_report.memory_purge_held. */
|
|
78
|
+
lastHeld(): number;
|
|
79
|
+
/** Task 13 carry LOW 1: the start time of the newest COMPLETED sweep (a sweep stopped while closing is not one). */
|
|
80
|
+
lastSweepAt(): number | undefined;
|
|
81
|
+
sweep(): Promise<SweepOutcome>;
|
|
82
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Spec §7.3: brute-force cosine (dot product of normalised vectors), on the main thread (≤ 5,000 × 384 multiply-adds). */
|
|
2
|
+
export declare function rankRecall(query: Float32Array, cache: {
|
|
3
|
+
vecs: Float32Array;
|
|
4
|
+
dims: number;
|
|
5
|
+
messageIds: readonly string[];
|
|
6
|
+
recallIds: readonly number[];
|
|
7
|
+
}, o: {
|
|
8
|
+
exclude: ReadonlySet<string>;
|
|
9
|
+
threshold: number;
|
|
10
|
+
k: number;
|
|
11
|
+
}): Array<{
|
|
12
|
+
recallId: number;
|
|
13
|
+
messageId: string;
|
|
14
|
+
score: number;
|
|
15
|
+
}>;
|