@openagentsy/memory 0.0.0-stage → 0.2.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/LICENSE +674 -0
- package/README.md +33 -2
- package/dist/acceptance.test.d.ts +2 -0
- package/dist/acceptance.test.d.ts.map +1 -0
- package/dist/acceptance.test.js +138 -0
- package/dist/acceptance.test.js.map +1 -0
- package/dist/fixtures.d.ts +30 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +49 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/inbox.d.ts +131 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +161 -0
- package/dist/inbox.js.map +1 -0
- package/dist/inbox.test.d.ts +2 -0
- package/dist/inbox.test.d.ts.map +1 -0
- package/dist/inbox.test.js +144 -0
- package/dist/inbox.test.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +85 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +71 -0
- package/dist/model.js.map +1 -0
- package/dist/permissions.test.d.ts +2 -0
- package/dist/permissions.test.d.ts.map +1 -0
- package/dist/permissions.test.js +53 -0
- package/dist/permissions.test.js.map +1 -0
- package/dist/store.d.ts +142 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +263 -0
- package/dist/store.js.map +1 -0
- package/dist/store.test.d.ts +2 -0
- package/dist/store.test.d.ts.map +1 -0
- package/dist/store.test.js +223 -0
- package/dist/store.test.js.map +1 -0
- package/dist/tools.d.ts +86 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +108 -0
- package/dist/tools.js.map +1 -0
- package/dist/tools.test.d.ts +2 -0
- package/dist/tools.test.d.ts.map +1 -0
- package/dist/tools.test.js +142 -0
- package/dist/tools.test.js.map +1 -0
- package/package.json +47 -4
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { candidateRecord, fakeReader, temporaryRoot } from './fixtures.js';
|
|
3
|
+
import { MemoryInbox, parseCandidateState } from './inbox.js';
|
|
4
|
+
import { MemoryStore } from './store.js';
|
|
5
|
+
async function inboxIn(root, records) {
|
|
6
|
+
const audited = [];
|
|
7
|
+
const store = new MemoryStore({ root });
|
|
8
|
+
const inbox = new MemoryInbox({
|
|
9
|
+
root,
|
|
10
|
+
read: fakeReader(records),
|
|
11
|
+
store,
|
|
12
|
+
onAudit: (policyId, detail) => {
|
|
13
|
+
audited.push({ policyId, detail });
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
return { inbox, store, audited };
|
|
17
|
+
}
|
|
18
|
+
describe('the memory inbox', () => {
|
|
19
|
+
it('lists the candidates no person has decided on', async () => {
|
|
20
|
+
const { root, remove } = await temporaryRoot();
|
|
21
|
+
try {
|
|
22
|
+
const records = [
|
|
23
|
+
candidateRecord({ id: 1 }),
|
|
24
|
+
candidateRecord({ id: 2, state: 'promoted' }),
|
|
25
|
+
candidateRecord({ id: 3, bean: 'other' }),
|
|
26
|
+
];
|
|
27
|
+
const { inbox } = await inboxIn(root, records);
|
|
28
|
+
const pending = await inbox.pending();
|
|
29
|
+
expect(pending.map((record) => record.id)).toEqual([1]);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
await remove();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
it('reads the category and the source out of the record state', () => {
|
|
36
|
+
expect(parseCandidateState('build · 2a-1')).toEqual({
|
|
37
|
+
category: 'build',
|
|
38
|
+
source: '2a-1',
|
|
39
|
+
});
|
|
40
|
+
expect(parseCandidateState('nothing here')).toEqual({
|
|
41
|
+
category: 'uncategorized',
|
|
42
|
+
source: 'nothing here',
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it('approves a candidate into the store and marks the record promoted', async () => {
|
|
46
|
+
const { root, remove } = await temporaryRoot();
|
|
47
|
+
try {
|
|
48
|
+
const records = [candidateRecord({ id: 7 })];
|
|
49
|
+
const { inbox, store, audited } = await inboxIn(root, records);
|
|
50
|
+
const outcome = await inbox.decide(7, 'approve', 'daniel');
|
|
51
|
+
expect(outcome.ok).toBe(true);
|
|
52
|
+
const entries = await store.entries();
|
|
53
|
+
expect(entries).toHaveLength(1);
|
|
54
|
+
expect(entries[0]?.source).toBe('7 · 2a-1');
|
|
55
|
+
expect(entries[0]?.category).toBe('build');
|
|
56
|
+
// An approved candidate becomes a trusted memory (the human's
|
|
57
|
+
// decision of 2026-10-28); the approval is the person's act.
|
|
58
|
+
expect(entries[0]?.verified).toBe(true);
|
|
59
|
+
expect(records[0]?.state).toBe('promoted');
|
|
60
|
+
expect(audited).toEqual([
|
|
61
|
+
{
|
|
62
|
+
policyId: 'oa.memory.decide',
|
|
63
|
+
detail: { recordId: 7, decidedBy: 'daniel', decision: 'approve' },
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await remove();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
it('rejects a candidate, and the store never sees the text', async () => {
|
|
72
|
+
const { root, remove } = await temporaryRoot();
|
|
73
|
+
try {
|
|
74
|
+
const records = [candidateRecord({ id: 7 })];
|
|
75
|
+
const { inbox, store, audited } = await inboxIn(root, records);
|
|
76
|
+
const outcome = await inbox.decide(7, 'reject', 'daniel');
|
|
77
|
+
expect(outcome.ok).toBe(true);
|
|
78
|
+
expect(await store.entries()).toEqual([]);
|
|
79
|
+
expect(records[0]?.state).toBe('rejected');
|
|
80
|
+
expect(audited).toEqual([
|
|
81
|
+
{
|
|
82
|
+
policyId: 'oa.memory.decide',
|
|
83
|
+
detail: { recordId: 7, decidedBy: 'daniel', decision: 'reject' },
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await remove();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
it('refuses a record that is not a candidate, and one that was decided', async () => {
|
|
92
|
+
const { root, remove } = await temporaryRoot();
|
|
93
|
+
try {
|
|
94
|
+
const records = [candidateRecord({ id: 7, bean: 'other' }), candidateRecord({ id: 8 })];
|
|
95
|
+
const { inbox } = await inboxIn(root, records);
|
|
96
|
+
const notACandidate = await inbox.decide(7, 'approve', 'daniel');
|
|
97
|
+
expect(notACandidate.ok).toBe(false);
|
|
98
|
+
if (!notACandidate.ok)
|
|
99
|
+
expect(notACandidate.reason).toBe('not-a-candidate');
|
|
100
|
+
await inbox.decide(8, 'reject', 'daniel');
|
|
101
|
+
const twice = await inbox.decide(8, 'approve', 'daniel');
|
|
102
|
+
expect(twice.ok).toBe(false);
|
|
103
|
+
if (!twice.ok)
|
|
104
|
+
expect(twice.reason).toBe('already-decided');
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await remove();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
it('refuses a record that does not exist', async () => {
|
|
111
|
+
const { root, remove } = await temporaryRoot();
|
|
112
|
+
try {
|
|
113
|
+
const { inbox } = await inboxIn(root, []);
|
|
114
|
+
const outcome = await inbox.decide(99, 'approve', 'daniel');
|
|
115
|
+
expect(outcome.ok).toBe(false);
|
|
116
|
+
if (!outcome.ok)
|
|
117
|
+
expect(outcome.reason).toBe('unknown-record');
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await remove();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
it('writes the state back through the injected writer', async () => {
|
|
124
|
+
const { root, remove } = await temporaryRoot();
|
|
125
|
+
try {
|
|
126
|
+
const marked = [];
|
|
127
|
+
const store = new MemoryStore({ root });
|
|
128
|
+
const inbox = new MemoryInbox({
|
|
129
|
+
root,
|
|
130
|
+
read: fakeReader([candidateRecord({ id: 7 })]),
|
|
131
|
+
store,
|
|
132
|
+
markState: (recordId, state) => {
|
|
133
|
+
marked.push({ recordId, state });
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
await inbox.decide(7, 'approve', 'daniel');
|
|
137
|
+
expect(marked).toEqual([{ recordId: 7, state: 'promoted' }]);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await remove();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=inbox.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.test.js","sourceRoot":"","sources":["../src/inbox.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openagentsy/memory
|
|
3
|
+
*
|
|
4
|
+
* The consumer side of the memory pipeline: the decision, the store, and the
|
|
5
|
+
* way back in. The producer side is the archivist, which posts candidate
|
|
6
|
+
* memories as board records.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here touches a prompt. A memory is found by asking for it, and a
|
|
9
|
+
* person decides what is kept.
|
|
10
|
+
*/
|
|
11
|
+
export * from './inbox.js';
|
|
12
|
+
export * from './model.js';
|
|
13
|
+
export * from './store.js';
|
|
14
|
+
export * from './tools.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openagentsy/memory
|
|
3
|
+
*
|
|
4
|
+
* The consumer side of the memory pipeline: the decision, the store, and the
|
|
5
|
+
* way back in. The producer side is the archivist, which posts candidate
|
|
6
|
+
* memories as board records.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here touches a prompt. A memory is found by asking for it, and a
|
|
9
|
+
* person decides what is kept.
|
|
10
|
+
*/
|
|
11
|
+
export * from './inbox.js';
|
|
12
|
+
export * from './model.js';
|
|
13
|
+
export * from './store.js';
|
|
14
|
+
export * from './tools.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory slice's own vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* A memory is what a person decided to keep. It is small on purpose: a
|
|
5
|
+
* sentence, a tag, a source and a label. Everything else about the pipeline
|
|
6
|
+
* - the candidates, the decisions, the audit - lives on the board, and this
|
|
7
|
+
* package is only what survives a decision.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* How sure the archivist was.
|
|
11
|
+
*
|
|
12
|
+
* The label travels with the memory, so a reader can tell a claim that was
|
|
13
|
+
* checked from one that was inferred. It is not a verification: nobody has
|
|
14
|
+
* looked at the memory, and `verified` says whether they have.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MEMORY_CONFIDENCES: readonly ["verified", "inferred", "guess"];
|
|
17
|
+
export type MemoryConfidence = (typeof MEMORY_CONFIDENCES)[number];
|
|
18
|
+
/**
|
|
19
|
+
* One memory.
|
|
20
|
+
*
|
|
21
|
+
* The shape the plan gives it (§4.2), plus the verified flag the revision-3
|
|
22
|
+
* fold adds (§4.8).
|
|
23
|
+
*/
|
|
24
|
+
export interface MemoryEntry {
|
|
25
|
+
/** `oa-mem-<nanoid6>`. */
|
|
26
|
+
id: string;
|
|
27
|
+
/** At most 280 characters. Enforced on write. */
|
|
28
|
+
text: string;
|
|
29
|
+
/** A free-form tag: at most 24 characters, in the tasks TAG grammar. */
|
|
30
|
+
category: string;
|
|
31
|
+
/** `<recordId> · <bean|commit|file:line>` - where it came from. */
|
|
32
|
+
source: string;
|
|
33
|
+
/** The label the archivist gave the candidate. */
|
|
34
|
+
confidence: MemoryConfidence;
|
|
35
|
+
/** ISO 8601. */
|
|
36
|
+
created_at: string;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a person has verified this memory.
|
|
39
|
+
*
|
|
40
|
+
* It is true on append (the human's decision of 2026-10-28: the
|
|
41
|
+
* archivist's judgement is the source, and a person marks a memory
|
|
42
|
+
* unverified when they disagree) and a person can mark it either way
|
|
43
|
+
* afterwards. The search redacts an unverified memory by default, so this
|
|
44
|
+
* flag decides what an agent sees before anyone has said anything about
|
|
45
|
+
* it.
|
|
46
|
+
*/
|
|
47
|
+
verified: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A marker line.
|
|
51
|
+
*
|
|
52
|
+
* The store's file is append-only, so a person who forgets or verifies a
|
|
53
|
+
* memory does not change the entry: the store appends a line that says what
|
|
54
|
+
* happened, and the view the store returns folds the markers into the
|
|
55
|
+
* entries. The entry itself is what the archivist said, and it stays that
|
|
56
|
+
* way.
|
|
57
|
+
*/
|
|
58
|
+
export interface MemoryMarker {
|
|
59
|
+
kind: 'forget' | 'verify';
|
|
60
|
+
/** The id of the entry the marker is about. */
|
|
61
|
+
id: string;
|
|
62
|
+
/** Who did it. */
|
|
63
|
+
by: string;
|
|
64
|
+
/** ISO 8601. */
|
|
65
|
+
at: string;
|
|
66
|
+
}
|
|
67
|
+
/** The longest text a memory may hold. */
|
|
68
|
+
export declare const MAX_TEXT_LENGTH = 280;
|
|
69
|
+
/** The longest category a memory may hold. */
|
|
70
|
+
export declare const MAX_CATEGORY_LENGTH = 24;
|
|
71
|
+
/** The category a candidate gets when its record carries none. */
|
|
72
|
+
export declare const DEFAULT_CATEGORY = "uncategorized";
|
|
73
|
+
/** The id of a memory: `oa-mem-<nanoid6>`. */
|
|
74
|
+
export declare function createMemoryId(): string;
|
|
75
|
+
/** The text as the dedup sees it: lowercased, whitespace collapsed. */
|
|
76
|
+
export declare function normalizeText(text: string): string;
|
|
77
|
+
/** Whether a category is a tag. */
|
|
78
|
+
export declare function isValidCategory(category: string): boolean;
|
|
79
|
+
/** Whether a value is one of the three confidence labels. */
|
|
80
|
+
export declare function isMemoryConfidence(value: unknown): value is MemoryConfidence;
|
|
81
|
+
/** Whether a value is a memory entry. */
|
|
82
|
+
export declare function isMemoryEntry(value: unknown): value is MemoryEntry;
|
|
83
|
+
/** Whether a value is a marker line. */
|
|
84
|
+
export declare function isMemoryMarker(value: unknown): value is MemoryMarker;
|
|
85
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,4CAA6C,CAAC;AAE7E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gBAAgB;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,0CAA0C;AAC1C,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC,8CAA8C;AAC9C,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAWtC,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAEhD,8CAA8C;AAC9C,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,mCAAmC;AACnC,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAED,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAYlE;AAED,wCAAwC;AACxC,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CASpE"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
/**
|
|
3
|
+
* The memory slice's own vocabulary.
|
|
4
|
+
*
|
|
5
|
+
* A memory is what a person decided to keep. It is small on purpose: a
|
|
6
|
+
* sentence, a tag, a source and a label. Everything else about the pipeline
|
|
7
|
+
* - the candidates, the decisions, the audit - lives on the board, and this
|
|
8
|
+
* package is only what survives a decision.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* How sure the archivist was.
|
|
12
|
+
*
|
|
13
|
+
* The label travels with the memory, so a reader can tell a claim that was
|
|
14
|
+
* checked from one that was inferred. It is not a verification: nobody has
|
|
15
|
+
* looked at the memory, and `verified` says whether they have.
|
|
16
|
+
*/
|
|
17
|
+
export const MEMORY_CONFIDENCES = ['verified', 'inferred', 'guess'];
|
|
18
|
+
/** The longest text a memory may hold. */
|
|
19
|
+
export const MAX_TEXT_LENGTH = 280;
|
|
20
|
+
/** The longest category a memory may hold. */
|
|
21
|
+
export const MAX_CATEGORY_LENGTH = 24;
|
|
22
|
+
/**
|
|
23
|
+
* The category grammar.
|
|
24
|
+
*
|
|
25
|
+
* The same grammar the task store uses for a tag: a lowercase word, and
|
|
26
|
+
* words joined by single hyphens. A category that is not a tag is a
|
|
27
|
+
* sentence, and a sentence is not a category.
|
|
28
|
+
*/
|
|
29
|
+
const CATEGORY_GRAMMAR = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
30
|
+
/** The category a candidate gets when its record carries none. */
|
|
31
|
+
export const DEFAULT_CATEGORY = 'uncategorized';
|
|
32
|
+
/** The id of a memory: `oa-mem-<nanoid6>`. */
|
|
33
|
+
export function createMemoryId() {
|
|
34
|
+
return `oa-mem-${nanoid(6)}`;
|
|
35
|
+
}
|
|
36
|
+
/** The text as the dedup sees it: lowercased, whitespace collapsed. */
|
|
37
|
+
export function normalizeText(text) {
|
|
38
|
+
return text.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
39
|
+
}
|
|
40
|
+
/** Whether a category is a tag. */
|
|
41
|
+
export function isValidCategory(category) {
|
|
42
|
+
return category.length <= MAX_CATEGORY_LENGTH && CATEGORY_GRAMMAR.test(category);
|
|
43
|
+
}
|
|
44
|
+
/** Whether a value is one of the three confidence labels. */
|
|
45
|
+
export function isMemoryConfidence(value) {
|
|
46
|
+
return typeof value === 'string' && MEMORY_CONFIDENCES.includes(value);
|
|
47
|
+
}
|
|
48
|
+
/** Whether a value is a memory entry. */
|
|
49
|
+
export function isMemoryEntry(value) {
|
|
50
|
+
if (typeof value !== 'object' || value === null)
|
|
51
|
+
return false;
|
|
52
|
+
const entry = value;
|
|
53
|
+
return (typeof entry.id === 'string' &&
|
|
54
|
+
typeof entry.text === 'string' &&
|
|
55
|
+
typeof entry.category === 'string' &&
|
|
56
|
+
typeof entry.source === 'string' &&
|
|
57
|
+
typeof entry.created_at === 'string' &&
|
|
58
|
+
typeof entry.verified === 'boolean' &&
|
|
59
|
+
isMemoryConfidence(entry.confidence));
|
|
60
|
+
}
|
|
61
|
+
/** Whether a value is a marker line. */
|
|
62
|
+
export function isMemoryMarker(value) {
|
|
63
|
+
if (typeof value !== 'object' || value === null)
|
|
64
|
+
return false;
|
|
65
|
+
const marker = value;
|
|
66
|
+
return ((marker.kind === 'forget' || marker.kind === 'verify') &&
|
|
67
|
+
typeof marker.id === 'string' &&
|
|
68
|
+
typeof marker.by === 'string' &&
|
|
69
|
+
typeof marker.at === 'string');
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;;;;;GAOG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAU,CAAC;AAuD7E,0CAA0C;AAC1C,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAEnC,8CAA8C;AAC9C,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG,iCAAiC,CAAC;AAE3D,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG,eAAe,CAAC;AAEhD,8CAA8C;AAC9C,MAAM,UAAU,cAAc;IAC5B,OAAO,UAAU,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,QAAQ,CAAC,MAAM,IAAI,mBAAmB,IAAI,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACnF,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,kBAAwC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAChG,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,KAAK,GAAG,KAAgC,CAAC;IAC/C,OAAO,CACL,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ;QAC5B,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAC9B,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAClC,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;QAChC,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ;QACpC,OAAO,KAAK,CAAC,QAAQ,KAAK,SAAS;QACnC,kBAAkB,CAAC,KAAK,CAAC,UAAU,CAAC,CACrC,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,OAAO,CACL,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC;QACtD,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ;QAC7B,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ;QAC7B,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,CAC9B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.test.d.ts","sourceRoot":"","sources":["../src/permissions.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { toV2Permissions, withProtectedPaths } from '@openagentsy/core';
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { loadRealRoles, repositoryRoot } from './fixtures.js';
|
|
7
|
+
/**
|
|
8
|
+
* The permission map is the enforcement.
|
|
9
|
+
*
|
|
10
|
+
* The hook cannot see an MCP call on the pinned binary, so the map is what
|
|
11
|
+
* stops an agent from deciding what is kept. The map has to carry the deny
|
|
12
|
+
* under both of the names the tool is called by: the bare name, and the
|
|
13
|
+
* name the MCP server publishes it under.
|
|
14
|
+
*/
|
|
15
|
+
const DECIDE_NAMES = ['oa_memory_decide', 'openagentsy_memory_decide'];
|
|
16
|
+
/** Whether a generated prompt denies a tool. */
|
|
17
|
+
function deniesTool(content, tool) {
|
|
18
|
+
if (new RegExp(`^\\s*${tool}: deny$`, 'm').test(content))
|
|
19
|
+
return true;
|
|
20
|
+
return new RegExp(`action: ${tool}\\n\\s+resource: [^\\n]*\\n\\s+effect: deny`).test(content);
|
|
21
|
+
}
|
|
22
|
+
describe('the permission map and the decide tool', () => {
|
|
23
|
+
it('denies the decide tool to every role, under both of its names', async () => {
|
|
24
|
+
const roles = await loadRealRoles();
|
|
25
|
+
const names = Object.keys(roles.roles);
|
|
26
|
+
expect(names.length).toBeGreaterThan(0);
|
|
27
|
+
for (const name of names) {
|
|
28
|
+
const role = roles.roles[name];
|
|
29
|
+
if (role === undefined)
|
|
30
|
+
continue;
|
|
31
|
+
const rules = toV2Permissions(withProtectedPaths(role, roles.protectedPaths));
|
|
32
|
+
for (const tool of DECIDE_NAMES) {
|
|
33
|
+
expect(rules).toContainEqual({ action: tool, resource: '*', effect: 'deny' });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
it('carries the deny into every generated agent', async () => {
|
|
38
|
+
const dir = join(repositoryRoot, '.opencode', 'agents');
|
|
39
|
+
// The generated files are not in the repository: they are written by
|
|
40
|
+
// `openagentsy build`, and a checkout that never ran it has none.
|
|
41
|
+
if (!existsSync(dir))
|
|
42
|
+
return;
|
|
43
|
+
const files = (await readdir(dir)).filter((file) => file.endsWith('.md'));
|
|
44
|
+
expect(files.length).toBeGreaterThan(0);
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const content = await readFile(join(dir, file), 'utf8');
|
|
47
|
+
for (const tool of DECIDE_NAMES) {
|
|
48
|
+
expect(deniesTool(content, tool)).toBe(true);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=permissions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.test.js","sourceRoot":"","sources":["../src/permissions.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE9D;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG,CAAC,kBAAkB,EAAE,2BAA2B,CAAU,CAAC;AAEhF,gDAAgD;AAChD,SAAS,UAAU,CAAC,OAAe,EAAE,IAAY;IAC/C,IAAI,IAAI,MAAM,CAAC,QAAQ,IAAI,SAAS,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,OAAO,IAAI,MAAM,CAAC,WAAW,IAAI,6CAA6C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAChG,CAAC;AAED,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAExC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAS;YACjC,MAAM,KAAK,GAAG,eAAe,CAAC,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;YAC9E,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;gBAChC,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YAChF,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;QACxD,qEAAqE;QACrE,kEAAkE;QAClE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO;QAE7B,MAAM,KAAK,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1E,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;YACxD,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;gBAChC,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type MemoryConfidence, type MemoryEntry } from './model.js';
|
|
2
|
+
/**
|
|
3
|
+
* The memory store.
|
|
4
|
+
*
|
|
5
|
+
* One file, one JSON object per line, so `grep` and `wc -l` work on it. The
|
|
6
|
+
* file lives at the state root (§3.9), which is the root of the project the
|
|
7
|
+
* store is given: `.openagentsy/memory.jsonl`. The resolution of that root
|
|
8
|
+
* from a worktree is a later phase, so the root is injected here and a test
|
|
9
|
+
* passes a temporary directory.
|
|
10
|
+
*/
|
|
11
|
+
/** What a decision is audited with. */
|
|
12
|
+
export type MemoryAuditDetail = {
|
|
13
|
+
/** The board record the decision was about. */
|
|
14
|
+
recordId?: number;
|
|
15
|
+
/** The memory the decision was about. */
|
|
16
|
+
id?: string;
|
|
17
|
+
/** Who decided. */
|
|
18
|
+
decidedBy: string;
|
|
19
|
+
/** The decision itself, for the inbox's decisions. */
|
|
20
|
+
decision?: 'approve' | 'reject';
|
|
21
|
+
};
|
|
22
|
+
/** The audit sink a decision is written to. */
|
|
23
|
+
export type MemoryAuditSink = (policyId: string, detail: MemoryAuditDetail) => void | Promise<void>;
|
|
24
|
+
export type MemoryStoreOptions = {
|
|
25
|
+
/** The state root. The file is `<root>/.openagentsy/memory.jsonl`. */
|
|
26
|
+
root: string;
|
|
27
|
+
/** The clock, so a test can pin the time. */
|
|
28
|
+
now?: () => Date;
|
|
29
|
+
/** Where a person-only change is audited. */
|
|
30
|
+
onAudit?: MemoryAuditSink;
|
|
31
|
+
};
|
|
32
|
+
/** What a caller hands the store. */
|
|
33
|
+
export type AppendInput = {
|
|
34
|
+
text: string;
|
|
35
|
+
category: string;
|
|
36
|
+
source: string;
|
|
37
|
+
confidence: MemoryConfidence;
|
|
38
|
+
/** Whether a person has already verified it. False unless said so. */
|
|
39
|
+
verified?: boolean;
|
|
40
|
+
};
|
|
41
|
+
/** What the store did with an append. */
|
|
42
|
+
export type AppendOutcome = {
|
|
43
|
+
ok: true;
|
|
44
|
+
entry: MemoryEntry;
|
|
45
|
+
redactions: number;
|
|
46
|
+
} | {
|
|
47
|
+
ok: false;
|
|
48
|
+
reason: 'duplicate';
|
|
49
|
+
id: string;
|
|
50
|
+
redactions: number;
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
reason: 'invalid';
|
|
54
|
+
detail: string;
|
|
55
|
+
redactions: number;
|
|
56
|
+
};
|
|
57
|
+
/** How the results are ordered. */
|
|
58
|
+
export type MemoryOrdering = 'relevance' | 'recency' | 'lost-in-the-middle';
|
|
59
|
+
/** A search. */
|
|
60
|
+
export type MemoryQuery = {
|
|
61
|
+
q: string;
|
|
62
|
+
category?: string;
|
|
63
|
+
limit?: number;
|
|
64
|
+
ordering?: MemoryOrdering;
|
|
65
|
+
};
|
|
66
|
+
/** A result and the score that found it. */
|
|
67
|
+
export type ScoredMemory = {
|
|
68
|
+
entry: MemoryEntry;
|
|
69
|
+
score: number;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The store of what a person decided to keep.
|
|
73
|
+
*
|
|
74
|
+
* Append-only: an entry is written once and never changed. A person who
|
|
75
|
+
* forgets or verifies a memory appends a marker instead, and the view this
|
|
76
|
+
* class returns folds the markers into the entries. The file therefore only
|
|
77
|
+
* ever grows, and a line in it is never rewritten.
|
|
78
|
+
*/
|
|
79
|
+
export declare class MemoryStore {
|
|
80
|
+
private readonly root;
|
|
81
|
+
private readonly now;
|
|
82
|
+
private readonly onAudit;
|
|
83
|
+
private cached;
|
|
84
|
+
constructor(options: MemoryStoreOptions);
|
|
85
|
+
/** The file the store lives in. */
|
|
86
|
+
get path(): string;
|
|
87
|
+
/**
|
|
88
|
+
* Add a memory.
|
|
89
|
+
*
|
|
90
|
+
* The redaction runs first: nothing reaches the file that carries a
|
|
91
|
+
* secret, and the count comes back with the outcome so the caller can
|
|
92
|
+
* report it. A text that is already a duplicate is refused with the id of
|
|
93
|
+
* the entry that holds it, because a memory that is already known is not
|
|
94
|
+
* a new thing.
|
|
95
|
+
*/
|
|
96
|
+
append(input: AppendInput): Promise<AppendOutcome>;
|
|
97
|
+
/** Every memory that is neither forgotten nor superseded. */
|
|
98
|
+
entries(): Promise<MemoryEntry[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Find the memories that mention the terms of a query.
|
|
101
|
+
*
|
|
102
|
+
* The store is small - a target of under ten thousand entries - so the
|
|
103
|
+
* scan is over the parsed lines, and the parsed lines are cached against
|
|
104
|
+
* the file's stat, so a search that follows a search reads the file once.
|
|
105
|
+
*/
|
|
106
|
+
search(query: MemoryQuery): Promise<ScoredMemory[]>;
|
|
107
|
+
/**
|
|
108
|
+
* Forget a memory.
|
|
109
|
+
*
|
|
110
|
+
* The entry stays in the file and the store stops returning it. The
|
|
111
|
+
* removal command is a later phase; this is the path it will take, and
|
|
112
|
+
* the person who did it is on the marker.
|
|
113
|
+
*/
|
|
114
|
+
forget(id: string, by?: string): Promise<boolean>;
|
|
115
|
+
/**
|
|
116
|
+
* Mark a memory verified.
|
|
117
|
+
*
|
|
118
|
+
* A memory is trusted on write, and this is the other direction: a
|
|
119
|
+
* person says it is true. The archivist's label is not a verification:
|
|
120
|
+
* the archivist never looked at the memory.
|
|
121
|
+
*/
|
|
122
|
+
verify(id: string, decidedBy: string): Promise<boolean>;
|
|
123
|
+
/** Append one line to the file. */
|
|
124
|
+
private appendLine;
|
|
125
|
+
/**
|
|
126
|
+
* The parsed file, from the cache when the file has not changed.
|
|
127
|
+
*
|
|
128
|
+
* The key is the file's size and modification time, so a file that was
|
|
129
|
+
* appended to misses the cache and a file that was not is read once.
|
|
130
|
+
*/
|
|
131
|
+
private read;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The order that keeps the best results where a model looks first.
|
|
135
|
+
*
|
|
136
|
+
* A model attends to the start and the end of a context more than to the
|
|
137
|
+
* middle, so the strongest result goes first and the second strongest goes
|
|
138
|
+
* last, and the rest fill in between. The ranking is unchanged: only the
|
|
139
|
+
* positions move.
|
|
140
|
+
*/
|
|
141
|
+
export declare function lostInTheMiddle<T>(ranked: readonly T[]): T[];
|
|
142
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|