@oortmem/core 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/address.d.ts +161 -0
- package/dist/address.js +225 -0
- package/dist/agents.d.ts +38 -0
- package/dist/agents.js +67 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +7 -0
- package/dist/consolidate.d.ts +4 -0
- package/dist/consolidate.js +15 -3
- package/dist/date-cue.d.ts +26 -0
- package/dist/date-cue.js +70 -0
- package/dist/export-format.d.ts +77 -0
- package/dist/export-format.js +168 -0
- package/dist/grant.d.ts +122 -15
- package/dist/grant.js +233 -26
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/isolation.d.ts +48 -2
- package/dist/isolation.js +362 -71
- package/dist/levels.d.ts +79 -0
- package/dist/levels.js +140 -0
- package/dist/memory-store.js +27 -23
- package/dist/model.d.ts +17 -0
- package/dist/observe.d.ts +9 -0
- package/dist/observe.js +27 -3
- package/dist/recall.d.ts +27 -1
- package/dist/recall.js +163 -17
- package/dist/store-conformance.d.ts +12 -0
- package/dist/store-conformance.js +366 -2
- package/dist/temporal.js +12 -7
- package/package.json +1 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { Memory, Scope } from './model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Levels: whose memory it is (CON-61). The code keeps plain names; the space
|
|
4
|
+
* names are the product's:
|
|
5
|
+
*
|
|
6
|
+
* | level | space | what it is |
|
|
7
|
+
* | ------------ | ------------- | -------------------------------------------------- |
|
|
8
|
+
* | `user` | Planet | one person's own memory |
|
|
9
|
+
* | `agent` | Star | one agent's own private memory |
|
|
10
|
+
* | `agent-user` | Orbit | what one agent knows about one person |
|
|
11
|
+
* | `run` | Flyby | one run or session, short-lived |
|
|
12
|
+
* | `hive` | Constellation | a group sharing memory (`hiveId` names it) |
|
|
13
|
+
* | `hive` | Galaxy | an organisation's own node (`hiveId` is `root`) |
|
|
14
|
+
*
|
|
15
|
+
* Levels say whose a memory is; tiers (hot, warm, cold) say how warm it is.
|
|
16
|
+
* The two are independent.
|
|
17
|
+
*/
|
|
18
|
+
export type Level = 'user' | 'agent' | 'agent-user' | 'run' | 'hive';
|
|
19
|
+
export declare const LEVELS: readonly Level[];
|
|
20
|
+
/** The `hiveId` of a Galaxy's own node (the hive tree is two deep: a Galaxy holds Constellations). */
|
|
21
|
+
export declare const GALAXY_ROOT = "root";
|
|
22
|
+
/**
|
|
23
|
+
* In every address, `orgId` is the **owner**: the storage, key and deletion
|
|
24
|
+
* boundary (`local` on a laptop, an account, or an organisation). `projectId`
|
|
25
|
+
* binds a memory to a project inside its node; absent means unbound (global).
|
|
26
|
+
*/
|
|
27
|
+
export interface PlanetAddress {
|
|
28
|
+
level: 'user';
|
|
29
|
+
orgId: string;
|
|
30
|
+
customerId: string;
|
|
31
|
+
projectId?: string;
|
|
32
|
+
agentId?: never;
|
|
33
|
+
hiveId?: never;
|
|
34
|
+
runId?: never;
|
|
35
|
+
}
|
|
36
|
+
export interface StarAddress {
|
|
37
|
+
level: 'agent';
|
|
38
|
+
orgId: string;
|
|
39
|
+
agentId: string;
|
|
40
|
+
projectId?: string;
|
|
41
|
+
customerId?: never;
|
|
42
|
+
hiveId?: never;
|
|
43
|
+
runId?: never;
|
|
44
|
+
}
|
|
45
|
+
export interface OrbitAddress {
|
|
46
|
+
level: 'agent-user';
|
|
47
|
+
orgId: string;
|
|
48
|
+
agentId: string;
|
|
49
|
+
customerId: string;
|
|
50
|
+
projectId?: string;
|
|
51
|
+
hiveId?: never;
|
|
52
|
+
runId?: never;
|
|
53
|
+
}
|
|
54
|
+
export interface FlybyAddress {
|
|
55
|
+
level: 'run';
|
|
56
|
+
orgId: string;
|
|
57
|
+
agentId: string;
|
|
58
|
+
runId: string;
|
|
59
|
+
customerId?: string;
|
|
60
|
+
projectId?: string;
|
|
61
|
+
hiveId?: never;
|
|
62
|
+
}
|
|
63
|
+
/** A Constellation, or the Galaxy's own node when `hiveId` is `GALAXY_ROOT`. Subject-free: no `customerId`. */
|
|
64
|
+
export interface HiveAddress {
|
|
65
|
+
level: 'hive';
|
|
66
|
+
orgId: string;
|
|
67
|
+
hiveId: string;
|
|
68
|
+
projectId?: string;
|
|
69
|
+
customerId?: never;
|
|
70
|
+
agentId?: never;
|
|
71
|
+
runId?: never;
|
|
72
|
+
}
|
|
73
|
+
/** Where a memory lives. Every memory has exactly one; the level decides which fields exist. */
|
|
74
|
+
export type Address = PlanetAddress | StarAddress | OrbitAddress | FlybyAddress | HiveAddress;
|
|
75
|
+
type WithoutProject<T> = T extends unknown ? Omit<T, 'projectId'> : never;
|
|
76
|
+
/** A node: an address without its project binding. Grants name nodes. */
|
|
77
|
+
export type NodeRef = WithoutProject<Address>;
|
|
78
|
+
/** Who wrote a memory. Not part of the address: a person's agents share their Planet. */
|
|
79
|
+
export interface Author {
|
|
80
|
+
agentId: string;
|
|
81
|
+
/** The account that ran the agent, where the store knows it. */
|
|
82
|
+
accountRef?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Where a shared copy came from (filled by sharing, a later feature). Absent on
|
|
86
|
+
* ordinary memories. Stores keep it as given; nothing in the engine reads it yet.
|
|
87
|
+
*/
|
|
88
|
+
export interface Provenance {
|
|
89
|
+
derivedFrom: {
|
|
90
|
+
vault: string;
|
|
91
|
+
memoryId: string;
|
|
92
|
+
subjectRef?: string;
|
|
93
|
+
}[];
|
|
94
|
+
contributors: {
|
|
95
|
+
memberRef: string;
|
|
96
|
+
agentId: string;
|
|
97
|
+
at: string;
|
|
98
|
+
action: 'asserted' | 'confirmed' | 'contradicted';
|
|
99
|
+
}[];
|
|
100
|
+
support: {
|
|
101
|
+
members: number;
|
|
102
|
+
agents: number;
|
|
103
|
+
};
|
|
104
|
+
gate: {
|
|
105
|
+
policyVersion: string;
|
|
106
|
+
mode: 'confirmed' | 'open' | 'approval';
|
|
107
|
+
approvedBy?: string;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Throws a `RangeError` naming the problem unless `a` is a legal `Address`. */
|
|
111
|
+
export declare function assertAddress(a: unknown): asserts a is Address;
|
|
112
|
+
/** Throws a `RangeError` unless `n` is a legal `NodeRef`. */
|
|
113
|
+
export declare function assertNode(n: unknown): asserts n is NodeRef;
|
|
114
|
+
/** Throws a `RangeError` unless `a` is a legal `Author`. */
|
|
115
|
+
export declare function assertAuthor(a: unknown): asserts a is Author;
|
|
116
|
+
/** The Planet a scope names: today's (org, customer, project), without the agent. */
|
|
117
|
+
export declare function planetOf(scope: Scope): PlanetAddress;
|
|
118
|
+
/** Where `m` lives: its `address`, or the Planet its `scope` names (every memory written before levels). */
|
|
119
|
+
export declare function addressOf(m: Pick<Memory, 'scope' | 'address'>): Address;
|
|
120
|
+
/** Who wrote `m`: its `author`, or the agent its `scope` names. */
|
|
121
|
+
export declare function authorOf(m: Pick<Memory, 'scope' | 'author'>): Author;
|
|
122
|
+
/**
|
|
123
|
+
* The `scope` a memory at `address` written by `author` carries: the
|
|
124
|
+
* pre-level view, kept so existing readers work. The agent is the address's
|
|
125
|
+
* own agent where it has one (Star, Orbit, Flyby), else the author. A level
|
|
126
|
+
* without a customer (Star, Constellation, Galaxy) has `customerId: ''`.
|
|
127
|
+
* Authorisation never reads it.
|
|
128
|
+
*/
|
|
129
|
+
export declare function scopeOf(address: Address, author: Author): Scope;
|
|
130
|
+
/** `address`, `author` and the matching `scope`, to spread into a new memory at a level. */
|
|
131
|
+
export declare function addressed(address: Address, author: Author): Pick<Memory, 'scope' | 'address' | 'author'>;
|
|
132
|
+
/** The node `address` sits in (the address without its project binding). */
|
|
133
|
+
export declare function nodeOf(address: Address): NodeRef;
|
|
134
|
+
/**
|
|
135
|
+
* A node as a JSON tuple: `["user", orgId, customerId]`, `["hive", orgId, hiveId]`
|
|
136
|
+
* and so on, with `null` for an absent optional field. Structured and compared
|
|
137
|
+
* whole, never as a prefix.
|
|
138
|
+
*/
|
|
139
|
+
export declare function nodeKey(n: NodeRef): string;
|
|
140
|
+
/** Two addresses name the same node and the same project binding. */
|
|
141
|
+
export declare function sameAddress(a: Address, b: Address): boolean;
|
|
142
|
+
export declare function sameAuthor(a: Author, b: Author): boolean;
|
|
143
|
+
/** The space name of `address`'s level, for people: Planet, Star, Orbit, Flyby, Constellation or Galaxy. */
|
|
144
|
+
export declare function spaceName(address: Address | NodeRef): string;
|
|
145
|
+
/**
|
|
146
|
+
* `m`'s address, author and scope checked against each other: the address and
|
|
147
|
+
* author are legal, and `scope` is exactly the view `scopeOf` gives (extra
|
|
148
|
+
* scope fields aside). Throws a `RangeError` otherwise. Every store runs this
|
|
149
|
+
* on each memory it writes.
|
|
150
|
+
*/
|
|
151
|
+
export declare function resolveAddressing(m: Pick<Memory, 'scope' | 'address' | 'author'>): {
|
|
152
|
+
address: Address;
|
|
153
|
+
author: Author;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* `m` in the shape stores return: `address` only when it is not the Planet
|
|
157
|
+
* `scope` names, `author` only when it is not `scope.agentId`, `provenance`
|
|
158
|
+
* only when set. A memory written before levels comes back exactly as it was.
|
|
159
|
+
*/
|
|
160
|
+
export declare function canonicalMemory<M extends Memory>(m: M): M;
|
|
161
|
+
export {};
|
package/dist/address.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
export const LEVELS = Object.freeze([
|
|
2
|
+
'user',
|
|
3
|
+
'agent',
|
|
4
|
+
'agent-user',
|
|
5
|
+
'run',
|
|
6
|
+
'hive',
|
|
7
|
+
]);
|
|
8
|
+
/** The `hiveId` of a Galaxy's own node (the hive tree is two deep: a Galaxy holds Constellations). */
|
|
9
|
+
export const GALAXY_ROOT = 'root';
|
|
10
|
+
/** The fields each level requires (besides `level` and `orgId`) and allows. */
|
|
11
|
+
const SHAPE = {
|
|
12
|
+
user: { required: ['customerId'], optional: [] },
|
|
13
|
+
agent: { required: ['agentId'], optional: [] },
|
|
14
|
+
'agent-user': { required: ['agentId', 'customerId'], optional: [] },
|
|
15
|
+
run: { required: ['agentId', 'runId'], optional: ['customerId'] },
|
|
16
|
+
hive: { required: ['hiveId'], optional: [] },
|
|
17
|
+
};
|
|
18
|
+
const NODE_FIELDS = ['customerId', 'agentId', 'hiveId', 'runId'];
|
|
19
|
+
const isText = (x) => typeof x === 'string' && x !== '';
|
|
20
|
+
function checkNode(a, what, allowProject) {
|
|
21
|
+
const level = a['level'];
|
|
22
|
+
if (typeof level !== 'string' || !LEVELS.includes(level)) {
|
|
23
|
+
throw new RangeError(`${what}: unknown level ${JSON.stringify(level)}`);
|
|
24
|
+
}
|
|
25
|
+
const shape = SHAPE[level];
|
|
26
|
+
if (!isText(a['orgId']))
|
|
27
|
+
throw new RangeError(`${what}: orgId must be a non-empty string`);
|
|
28
|
+
for (const f of shape.required) {
|
|
29
|
+
if (!isText(a[f]))
|
|
30
|
+
throw new RangeError(`${what}: a ${level} address needs ${f}`);
|
|
31
|
+
}
|
|
32
|
+
for (const f of NODE_FIELDS) {
|
|
33
|
+
if (a[f] === undefined || shape.required.includes(f))
|
|
34
|
+
continue;
|
|
35
|
+
if (!shape.optional.includes(f))
|
|
36
|
+
throw new RangeError(`${what}: a ${level} address has no ${f}`);
|
|
37
|
+
if (!isText(a[f]))
|
|
38
|
+
throw new RangeError(`${what}: ${f} must be a non-empty string`);
|
|
39
|
+
}
|
|
40
|
+
if (a['projectId'] !== undefined) {
|
|
41
|
+
if (!allowProject)
|
|
42
|
+
throw new RangeError(`${what}: a node has no projectId`);
|
|
43
|
+
if (typeof a['projectId'] !== 'string') {
|
|
44
|
+
throw new RangeError(`${what}: projectId must be a string`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const known = new Set(['level', 'orgId', 'projectId', ...NODE_FIELDS]);
|
|
48
|
+
for (const k of Object.keys(a)) {
|
|
49
|
+
if (!known.has(k))
|
|
50
|
+
throw new RangeError(`${what}: unknown field ${k}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Throws a `RangeError` naming the problem unless `a` is a legal `Address`. */
|
|
54
|
+
export function assertAddress(a) {
|
|
55
|
+
if (typeof a !== 'object' || a === null)
|
|
56
|
+
throw new RangeError('address must be an object');
|
|
57
|
+
checkNode(a, 'address', true);
|
|
58
|
+
}
|
|
59
|
+
/** Throws a `RangeError` unless `n` is a legal `NodeRef`. */
|
|
60
|
+
export function assertNode(n) {
|
|
61
|
+
if (typeof n !== 'object' || n === null)
|
|
62
|
+
throw new RangeError('node must be an object');
|
|
63
|
+
checkNode(n, 'node', false);
|
|
64
|
+
}
|
|
65
|
+
/** Throws a `RangeError` unless `a` is a legal `Author`. */
|
|
66
|
+
export function assertAuthor(a) {
|
|
67
|
+
if (typeof a !== 'object' || a === null)
|
|
68
|
+
throw new RangeError('author must be an object');
|
|
69
|
+
const o = a;
|
|
70
|
+
if (!isText(o['agentId']))
|
|
71
|
+
throw new RangeError('author: agentId must be a non-empty string');
|
|
72
|
+
if (o['accountRef'] !== undefined && !isText(o['accountRef'])) {
|
|
73
|
+
throw new RangeError('author: accountRef must be a non-empty string');
|
|
74
|
+
}
|
|
75
|
+
for (const k of Object.keys(o)) {
|
|
76
|
+
if (k !== 'agentId' && k !== 'accountRef')
|
|
77
|
+
throw new RangeError(`author: unknown field ${k}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** The Planet a scope names: today's (org, customer, project), without the agent. */
|
|
81
|
+
export function planetOf(scope) {
|
|
82
|
+
return {
|
|
83
|
+
level: 'user',
|
|
84
|
+
orgId: scope.orgId,
|
|
85
|
+
customerId: scope.customerId,
|
|
86
|
+
...(scope.projectId !== undefined && { projectId: scope.projectId }),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** A copy of `a` with only its own level's fields, in a fixed order. */
|
|
90
|
+
function clean(a) {
|
|
91
|
+
const out = { level: a.level, orgId: a.orgId };
|
|
92
|
+
for (const f of NODE_FIELDS) {
|
|
93
|
+
const v = a[f];
|
|
94
|
+
if (typeof v === 'string')
|
|
95
|
+
out[f] = v;
|
|
96
|
+
}
|
|
97
|
+
if (a.projectId !== undefined)
|
|
98
|
+
out['projectId'] = a.projectId;
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/** Where `m` lives: its `address`, or the Planet its `scope` names (every memory written before levels). */
|
|
102
|
+
export function addressOf(m) {
|
|
103
|
+
return m.address === undefined ? planetOf(m.scope) : clean(m.address);
|
|
104
|
+
}
|
|
105
|
+
/** Who wrote `m`: its `author`, or the agent its `scope` names. */
|
|
106
|
+
export function authorOf(m) {
|
|
107
|
+
if (m.author === undefined)
|
|
108
|
+
return { agentId: m.scope.agentId };
|
|
109
|
+
const { agentId, accountRef } = m.author;
|
|
110
|
+
return accountRef === undefined ? { agentId } : { agentId, accountRef };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The `scope` a memory at `address` written by `author` carries: the
|
|
114
|
+
* pre-level view, kept so existing readers work. The agent is the address's
|
|
115
|
+
* own agent where it has one (Star, Orbit, Flyby), else the author. A level
|
|
116
|
+
* without a customer (Star, Constellation, Galaxy) has `customerId: ''`.
|
|
117
|
+
* Authorisation never reads it.
|
|
118
|
+
*/
|
|
119
|
+
export function scopeOf(address, author) {
|
|
120
|
+
const a = address;
|
|
121
|
+
return {
|
|
122
|
+
orgId: address.orgId,
|
|
123
|
+
customerId: a.customerId ?? '',
|
|
124
|
+
agentId: a.agentId ?? author.agentId,
|
|
125
|
+
...(address.projectId !== undefined && { projectId: address.projectId }),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** `address`, `author` and the matching `scope`, to spread into a new memory at a level. */
|
|
129
|
+
export function addressed(address, author) {
|
|
130
|
+
assertAddress(address);
|
|
131
|
+
assertAuthor(author);
|
|
132
|
+
const a = clean(address);
|
|
133
|
+
const w = author.accountRef === undefined
|
|
134
|
+
? { agentId: author.agentId }
|
|
135
|
+
: { agentId: author.agentId, accountRef: author.accountRef };
|
|
136
|
+
return { scope: scopeOf(a, w), address: a, author: w };
|
|
137
|
+
}
|
|
138
|
+
/** The node `address` sits in (the address without its project binding). */
|
|
139
|
+
export function nodeOf(address) {
|
|
140
|
+
const { projectId: _p, ...node } = clean(address);
|
|
141
|
+
return node;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A node as a JSON tuple: `["user", orgId, customerId]`, `["hive", orgId, hiveId]`
|
|
145
|
+
* and so on, with `null` for an absent optional field. Structured and compared
|
|
146
|
+
* whole, never as a prefix.
|
|
147
|
+
*/
|
|
148
|
+
export function nodeKey(n) {
|
|
149
|
+
const x = n;
|
|
150
|
+
switch (n.level) {
|
|
151
|
+
case 'user':
|
|
152
|
+
return JSON.stringify([n.level, n.orgId, x.customerId]);
|
|
153
|
+
case 'agent':
|
|
154
|
+
return JSON.stringify([n.level, n.orgId, x.agentId]);
|
|
155
|
+
case 'agent-user':
|
|
156
|
+
return JSON.stringify([n.level, n.orgId, x.agentId, x.customerId]);
|
|
157
|
+
case 'run':
|
|
158
|
+
return JSON.stringify([n.level, n.orgId, x.agentId, x.runId, x.customerId ?? null]);
|
|
159
|
+
case 'hive':
|
|
160
|
+
return JSON.stringify([n.level, n.orgId, x.hiveId]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** Two addresses name the same node and the same project binding. */
|
|
164
|
+
export function sameAddress(a, b) {
|
|
165
|
+
return (nodeKey(nodeOf(a)) === nodeKey(nodeOf(b)) && (a.projectId ?? null) === (b.projectId ?? null));
|
|
166
|
+
}
|
|
167
|
+
export function sameAuthor(a, b) {
|
|
168
|
+
return a.agentId === b.agentId && (a.accountRef ?? null) === (b.accountRef ?? null);
|
|
169
|
+
}
|
|
170
|
+
/** The space name of `address`'s level, for people: Planet, Star, Orbit, Flyby, Constellation or Galaxy. */
|
|
171
|
+
export function spaceName(address) {
|
|
172
|
+
switch (address.level) {
|
|
173
|
+
case 'user':
|
|
174
|
+
return 'Planet';
|
|
175
|
+
case 'agent':
|
|
176
|
+
return 'Star';
|
|
177
|
+
case 'agent-user':
|
|
178
|
+
return 'Orbit';
|
|
179
|
+
case 'run':
|
|
180
|
+
return 'Flyby';
|
|
181
|
+
case 'hive':
|
|
182
|
+
return address.hiveId === GALAXY_ROOT ? 'Galaxy' : 'Constellation';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* `m`'s address, author and scope checked against each other: the address and
|
|
187
|
+
* author are legal, and `scope` is exactly the view `scopeOf` gives (extra
|
|
188
|
+
* scope fields aside). Throws a `RangeError` otherwise. Every store runs this
|
|
189
|
+
* on each memory it writes.
|
|
190
|
+
*/
|
|
191
|
+
export function resolveAddressing(m) {
|
|
192
|
+
if (m.address !== undefined)
|
|
193
|
+
assertAddress(m.address);
|
|
194
|
+
if (m.author !== undefined)
|
|
195
|
+
assertAuthor(m.author);
|
|
196
|
+
const address = addressOf(m);
|
|
197
|
+
const author = authorOf(m);
|
|
198
|
+
const want = scopeOf(address, author);
|
|
199
|
+
const s = m.scope;
|
|
200
|
+
if (s.orgId !== want.orgId ||
|
|
201
|
+
s.customerId !== want.customerId ||
|
|
202
|
+
s.agentId !== want.agentId ||
|
|
203
|
+
(s.projectId ?? null) !== (want.projectId ?? null)) {
|
|
204
|
+
throw new RangeError('memory scope does not match its address and author (use addressed())');
|
|
205
|
+
}
|
|
206
|
+
return { address, author };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* `m` in the shape stores return: `address` only when it is not the Planet
|
|
210
|
+
* `scope` names, `author` only when it is not `scope.agentId`, `provenance`
|
|
211
|
+
* only when set. A memory written before levels comes back exactly as it was.
|
|
212
|
+
*/
|
|
213
|
+
export function canonicalMemory(m) {
|
|
214
|
+
const { address, author } = resolveAddressing(m);
|
|
215
|
+
const { address: _a, author: _w, provenance, ...rest } = m;
|
|
216
|
+
const out = { ...rest };
|
|
217
|
+
if (address.level !== 'user')
|
|
218
|
+
out.address = address;
|
|
219
|
+
if (author.accountRef !== undefined || author.agentId !== m.scope.agentId)
|
|
220
|
+
out.author = author;
|
|
221
|
+
if (provenance !== undefined && provenance !== null)
|
|
222
|
+
out.provenance = provenance;
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=address.js.map
|
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ANONYMOUS_AGENT, type Home } from './grant.js';
|
|
2
|
+
/**
|
|
3
|
+
* Agent identities on one machine (CON-62). Every host Oort is connected to
|
|
4
|
+
* acts as its own agent (`claude-code`, `codex`, `cursor`, …): they share the
|
|
5
|
+
* person's Planet, and each has a Star of its own that no other agent reads.
|
|
6
|
+
* An agent configured as a Star (`oort connect cursor --star`) writes new
|
|
7
|
+
* memories to its Star instead of the Planet. The configuration lives in
|
|
8
|
+
* `$OORT_HOME/agents.json` (0600).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Claude Code's agent id: what every local memory before agents was written as, and what the
|
|
12
|
+
* Claude Code hooks and transcript import always act as. A caller that names no agent is
|
|
13
|
+
* `ANONYMOUS_AGENT` (Planet only), never this.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_AGENT = "claude-code";
|
|
16
|
+
export { ANONYMOUS_AGENT };
|
|
17
|
+
/** Agents configured as Stars, under `OORT_HOME`. */
|
|
18
|
+
export declare const AGENTS_FILE = "agents.json";
|
|
19
|
+
/** A lowercase id: letters, digits, `.`, `_` and `-`, at most 64 characters, starting alphanumeric. */
|
|
20
|
+
export declare const AGENT_ID: RegExp;
|
|
21
|
+
/** A valid agent id that is not reserved (`anonymous` names a caller that named none). */
|
|
22
|
+
export declare const isAgentId: (s: string) => boolean;
|
|
23
|
+
export interface AgentSettings {
|
|
24
|
+
/** New memories land in this agent's Star (private to it) instead of the person's Planet. */
|
|
25
|
+
star: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The agents configured in `home`. A missing or unreadable file means none,
|
|
29
|
+
* so every agent writes to the Planet (as before agents existed).
|
|
30
|
+
*/
|
|
31
|
+
export declare function readAgents(home: string): Record<string, AgentSettings>;
|
|
32
|
+
/**
|
|
33
|
+
* Records whether agent `id` keeps a Star (0600). Returns true when that changed anything. An agent
|
|
34
|
+
* that stops keeping one stays listed (`star: false`), so its Star is still found (export).
|
|
35
|
+
*/
|
|
36
|
+
export declare function setAgentStar(home: string, id: string, star: boolean): boolean;
|
|
37
|
+
/** Where agent `id`'s new memories land. */
|
|
38
|
+
export declare function agentHome(home: string, id: string): Home;
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { ANONYMOUS_AGENT } from './grant.js';
|
|
4
|
+
import { writePrivateFile } from './private-fs.js';
|
|
5
|
+
/**
|
|
6
|
+
* Agent identities on one machine (CON-62). Every host Oort is connected to
|
|
7
|
+
* acts as its own agent (`claude-code`, `codex`, `cursor`, …): they share the
|
|
8
|
+
* person's Planet, and each has a Star of its own that no other agent reads.
|
|
9
|
+
* An agent configured as a Star (`oort connect cursor --star`) writes new
|
|
10
|
+
* memories to its Star instead of the Planet. The configuration lives in
|
|
11
|
+
* `$OORT_HOME/agents.json` (0600).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Claude Code's agent id: what every local memory before agents was written as, and what the
|
|
15
|
+
* Claude Code hooks and transcript import always act as. A caller that names no agent is
|
|
16
|
+
* `ANONYMOUS_AGENT` (Planet only), never this.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_AGENT = 'claude-code';
|
|
19
|
+
export { ANONYMOUS_AGENT };
|
|
20
|
+
/** Agents configured as Stars, under `OORT_HOME`. */
|
|
21
|
+
export const AGENTS_FILE = 'agents.json';
|
|
22
|
+
/** A lowercase id: letters, digits, `.`, `_` and `-`, at most 64 characters, starting alphanumeric. */
|
|
23
|
+
export const AGENT_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
24
|
+
/** A valid agent id that is not reserved (`anonymous` names a caller that named none). */
|
|
25
|
+
export const isAgentId = (s) => AGENT_ID.test(s) && s !== ANONYMOUS_AGENT;
|
|
26
|
+
/**
|
|
27
|
+
* The agents configured in `home`. A missing or unreadable file means none,
|
|
28
|
+
* so every agent writes to the Planet (as before agents existed).
|
|
29
|
+
*/
|
|
30
|
+
export function readAgents(home) {
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(fs.readFileSync(path.join(home, AGENTS_FILE), 'utf8'));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const agents = parsed?.agents;
|
|
39
|
+
if (typeof agents !== 'object' || agents === null || Array.isArray(agents))
|
|
40
|
+
return {};
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [id, v] of Object.entries(agents)) {
|
|
43
|
+
if (!isAgentId(id) || typeof v !== 'object' || v === null)
|
|
44
|
+
continue;
|
|
45
|
+
out[id] = { star: v.star === true };
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Records whether agent `id` keeps a Star (0600). Returns true when that changed anything. An agent
|
|
51
|
+
* that stops keeping one stays listed (`star: false`), so its Star is still found (export).
|
|
52
|
+
*/
|
|
53
|
+
export function setAgentStar(home, id, star) {
|
|
54
|
+
if (!isAgentId(id))
|
|
55
|
+
throw new RangeError(`not an agent id: ${JSON.stringify(id)}`);
|
|
56
|
+
const agents = readAgents(home);
|
|
57
|
+
if ((agents[id]?.star ?? false) === star)
|
|
58
|
+
return false;
|
|
59
|
+
agents[id] = { star };
|
|
60
|
+
writePrivateFile(path.join(home, AGENTS_FILE), `${JSON.stringify({ version: 1, agents }, null, 2)}\n`);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
/** Where agent `id`'s new memories land. */
|
|
64
|
+
export function agentHome(home, id) {
|
|
65
|
+
return readAgents(home)[id]?.star === true ? 'star' : 'planet';
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=agents.js.map
|
package/dist/config.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export declare const OortConfig: z.ZodObject<{
|
|
|
42
42
|
rerankBudgetMs: z.ZodDefault<z.ZodNumber>;
|
|
43
43
|
queryCacheTtlSec: z.ZodDefault<z.ZodNumber>;
|
|
44
44
|
staleAfterDays: z.ZodDefault<z.ZodNumber>;
|
|
45
|
+
specificityPrior: z.ZodDefault<z.ZodNumber>;
|
|
45
46
|
}, z.core.$strict>>;
|
|
46
47
|
project: z.ZodDefault<z.ZodObject<{
|
|
47
48
|
id: z.ZodOptional<z.ZodString>;
|
package/dist/config.js
CHANGED
|
@@ -69,6 +69,12 @@ const recall = z
|
|
|
69
69
|
* marked "may be outdated" when injected, and listed by `oort revalidate`. 0 turns it off.
|
|
70
70
|
*/
|
|
71
71
|
staleAfterDays: z.number().int().min(0).default(30),
|
|
72
|
+
/**
|
|
73
|
+
* The specificity prior ε (spec 7.2): when a recall reads several nodes, each fused score is
|
|
74
|
+
* scaled by 1 + ε × specificity / 3 (an agent's Star 3, the Planet 2, a Constellation 1, a
|
|
75
|
+
* Galaxy 0). 0, the default, turns it off: it is turned on only if the eval shows a gain.
|
|
76
|
+
*/
|
|
77
|
+
specificityPrior: z.number().min(0).max(1).default(0),
|
|
72
78
|
})
|
|
73
79
|
.default({
|
|
74
80
|
lexicalK: 50,
|
|
@@ -84,6 +90,7 @@ const recall = z
|
|
|
84
90
|
rerankBudgetMs: 400,
|
|
85
91
|
queryCacheTtlSec: 600,
|
|
86
92
|
staleAfterDays: 30,
|
|
93
|
+
specificityPrior: 0,
|
|
87
94
|
});
|
|
88
95
|
const project = z
|
|
89
96
|
.strictObject({
|
package/dist/consolidate.d.ts
CHANGED
|
@@ -37,6 +37,10 @@ export declare const COLD_EVICT_BATCH = 500;
|
|
|
37
37
|
* left in its current tier (hot or warm); consolidate never promotes cold to warm/hot, since
|
|
38
38
|
* hydration is recall's job.
|
|
39
39
|
* Idempotent for a fixed `now`.
|
|
40
|
+
*
|
|
41
|
+
* Each node the scope's grant covers (the person's Planet, this agent's Star)
|
|
42
|
+
* is consolidated on its own, so the Planet's tiers do not depend on which of
|
|
43
|
+
* the person's agents ran consolidation.
|
|
40
44
|
*/
|
|
41
45
|
export declare function consolidate(scope: ScopeFilter, deps: {
|
|
42
46
|
store: MemoryStore;
|
package/dist/consolidate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { activation, tierFor } from './activation.js';
|
|
2
|
-
import {
|
|
2
|
+
import { nodeKey } from './address.js';
|
|
3
|
+
import { localGrant, narrowGrant, readableNodes } from './grant.js';
|
|
3
4
|
import { isCurrent } from './model.js';
|
|
4
5
|
/** Evictions per `coldPutMany` call: one store transaction and one directory sync each. */
|
|
5
6
|
export const COLD_EVICT_BATCH = 500;
|
|
@@ -23,10 +24,14 @@ const RANK = { cold: 0, warm: 1, hot: 2 };
|
|
|
23
24
|
* left in its current tier (hot or warm); consolidate never promotes cold to warm/hot, since
|
|
24
25
|
* hydration is recall's job.
|
|
25
26
|
* Idempotent for a fixed `now`.
|
|
27
|
+
*
|
|
28
|
+
* Each node the scope's grant covers (the person's Planet, this agent's Star)
|
|
29
|
+
* is consolidated on its own, so the Planet's tiers do not depend on which of
|
|
30
|
+
* the person's agents ran consolidation.
|
|
26
31
|
*/
|
|
27
32
|
export async function consolidate(scope, deps) {
|
|
28
33
|
const { store, config } = deps;
|
|
29
|
-
const
|
|
34
|
+
const full = localGrant(scope);
|
|
30
35
|
const now = deps.now ?? new Date();
|
|
31
36
|
const result = {
|
|
32
37
|
promoted: [],
|
|
@@ -35,6 +40,14 @@ export async function consolidate(scope, deps) {
|
|
|
35
40
|
expired: [],
|
|
36
41
|
skipped: 0,
|
|
37
42
|
};
|
|
43
|
+
// Only nodes this grant may change: a Star agent's grant reads the Planet but does not write it.
|
|
44
|
+
const writable = new Set(full.writeNodes.map((p) => nodeKey(p.node)));
|
|
45
|
+
for (const node of readableNodes(full).filter((n) => writable.has(nodeKey(n)))) {
|
|
46
|
+
await consolidateNode(store, narrowGrant(full, [node]), config, now, result);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
async function consolidateNode(store, grant, config, now, result) {
|
|
38
51
|
// 1. Expire.
|
|
39
52
|
const due = (await store.list(grant, { validOnly: false })).filter((m) => m.validTo !== null && m.expiredAt === null && Date.parse(m.validTo) <= now.getTime());
|
|
40
53
|
if (due.length > 0) {
|
|
@@ -76,7 +89,6 @@ export async function consolidate(scope, deps) {
|
|
|
76
89
|
evictions.push(m);
|
|
77
90
|
}
|
|
78
91
|
await evictAll(store, grant, evictions, result);
|
|
79
|
-
return result;
|
|
80
92
|
}
|
|
81
93
|
async function evictAll(store, grant, evictions, result) {
|
|
82
94
|
for (let i = 0; i < evictions.length; i += COLD_EVICT_BATCH) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type * as Chrono from 'chrono-node';
|
|
2
|
+
/**
|
|
3
|
+
* chrono-node, loaded only when a text may hold a date (CON-82). Importing it costs about
|
|
4
|
+
* 95 ms, and a hook process pays that on every prompt; most prompts name no date, so the
|
|
5
|
+
* temporal functions ask `mayHaveDateCue` first and treat a text without a cue as one chrono
|
|
6
|
+
* finds nothing in.
|
|
7
|
+
*/
|
|
8
|
+
type ChronoModule = typeof Chrono;
|
|
9
|
+
/**
|
|
10
|
+
* Whether `text` may hold a date or time expression chrono-node would read. False means
|
|
11
|
+
* chrono reads none that the temporal functions act on (a bare "now" is the present, and
|
|
12
|
+
* only counts after a bound word such as "from" or "until"). True may be a false alarm: that
|
|
13
|
+
* only costs loading chrono.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mayHaveDateCue(text: string): boolean;
|
|
16
|
+
/** Whether chrono-node has been loaded in this process. */
|
|
17
|
+
export declare function chronoLoaded(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Loads chrono-node ahead of need. Node never has to: the temporal functions load it
|
|
20
|
+
* synchronously on first use. A bundled runtime without `require` (a Worker) awaits this once
|
|
21
|
+
* at startup.
|
|
22
|
+
*/
|
|
23
|
+
export declare function preloadChrono(): Promise<void>;
|
|
24
|
+
/** chrono-node, loaded synchronously on first call. */
|
|
25
|
+
export declare function loadChrono(): ChronoModule;
|
|
26
|
+
export {};
|