dsh-memsys 0.2.0 → 0.2.1
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 +11 -5
- package/client.js +43 -16
- package/index.mjs +77 -25
- package/package.json +1 -1
- package/src/context/compile.mjs +6 -5
- package/src/context/delivery.mjs +1 -1
- package/src/context/snapshots.mjs +10 -2
- package/src/read/get.mjs +57 -6
- package/src/runtime/inject-round.mjs +138 -0
- package/src/runtime/inject.mjs +12 -3
- package/src/runtime/modules.mjs +20 -0
- package/src/runtime/summary.mjs +68 -89
- package/src/runtime/tools.mjs +21 -4
- package/src/runtime/ui-config.mjs +176 -0
- package/test/p4-p4-delivery.test.mjs +7 -1
- package/test/p6-c1c4.test.mjs +69 -0
- package/test/p6-c3-append.test.mjs +85 -0
- package/test/p6-inject-round.test.mjs +76 -0
- package/test/p6-modules.test.mjs +77 -0
- package/test/p6-ui-summary.test.mjs +367 -110
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
|
|
5
|
+
import { createRoundInjector } from '../src/runtime/inject-round.mjs';
|
|
6
|
+
import { compileRound } from '../src/context/compile.mjs';
|
|
7
|
+
import { conservativeTokens } from '../src/context/budget.mjs';
|
|
8
|
+
import { ingestInput } from '../src/write/observe.mjs';
|
|
9
|
+
import { promoteCandidate, recordCandidates } from '../src/write/candidate.mjs';
|
|
10
|
+
import { createTestContext, mutableClock } from './helpers/fixtures.mjs';
|
|
11
|
+
import { fakeRuntime } from './helpers/runtime.mjs';
|
|
12
|
+
|
|
13
|
+
function seed(ctx, index) {
|
|
14
|
+
const runtime = fakeRuntime({ role: 'daily', identity: 'inj', callId: 'inj-' + index, messageSeq: 28000 + index });
|
|
15
|
+
const observation = ingestInput(runtime, { text: '注入目录记忆-' + index, messageSeq: 28000 + index }, { main: ctx.main, control: ctx.control, clock: ctx.clock });
|
|
16
|
+
const created = recordCandidates(runtime, {
|
|
17
|
+
items: [{
|
|
18
|
+
observationId: observation.observation.id,
|
|
19
|
+
text: '注入目录记忆-' + index,
|
|
20
|
+
why: 'w', type: 'fact', scene: 'work', scope: 'work-only',
|
|
21
|
+
evidenceRefs: [{ observationId: observation.observation.id, sourceSpan: 'bytes:0:5' }],
|
|
22
|
+
}],
|
|
23
|
+
}, { main: ctx.main, control: ctx.control, clock: ctx.clock });
|
|
24
|
+
promoteCandidate(runtime, created.items[0].id, null, { main: ctx.main, control: ctx.control, clock: ctx.clock });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('P6/inject-round: turn/start refreshes an empty directory into compiled memory ids', (t) => {
|
|
28
|
+
const ctx = createTestContext({ clock: mutableClock().clock });
|
|
29
|
+
t.after(() => ctx.cleanup());
|
|
30
|
+
seed(ctx, 1);
|
|
31
|
+
const bus = new EventEmitter();
|
|
32
|
+
const injector = createRoundInjector({
|
|
33
|
+
ctx: bus,
|
|
34
|
+
handles: ctx.handles,
|
|
35
|
+
modules: { context: true },
|
|
36
|
+
compileRound,
|
|
37
|
+
tokenizer: conservativeTokens,
|
|
38
|
+
H: 1000,
|
|
39
|
+
clock: ctx.clock,
|
|
40
|
+
});
|
|
41
|
+
t.after(() => injector.dispose());
|
|
42
|
+
assert.equal(injector.current().text, '');
|
|
43
|
+
bus.emit('session/event', { type: 'turn/start', session: { header: { id: 'session-inj' } }, id: 'turn-1' });
|
|
44
|
+
const current = injector.current();
|
|
45
|
+
assert.ok(current.text.length > 0, JSON.stringify(current));
|
|
46
|
+
assert.ok(current.manifest.eligible_ids.length > 0, JSON.stringify(current.manifest));
|
|
47
|
+
assert.equal(current.delivery, 'unknown');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('P6/inject-round: missing tokenizer/H falls back to tools-only and never pretends delivery', () => {
|
|
51
|
+
const ctx = createTestContext({ clock: mutableClock().clock });
|
|
52
|
+
try {
|
|
53
|
+
const injector = createRoundInjector({ ctx: { on() {} }, handles: ctx.handles, modules: {}, compileRound, tokenizer: null, H: null, clock: ctx.clock });
|
|
54
|
+
injector.refresh({ session: { header: { id: 's' } }, id: 't' });
|
|
55
|
+
assert.equal(injector.current().delivery_level, 'unavailable');
|
|
56
|
+
assert.equal(injector.current().delivery, 'unknown');
|
|
57
|
+
assert.match(injector.current().text, /tools-only/);
|
|
58
|
+
} finally {
|
|
59
|
+
ctx.cleanup();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('P6/inject-round: context=false yields empty text and does not subscribe', () => {
|
|
64
|
+
const ctx = createTestContext({ clock: mutableClock().clock });
|
|
65
|
+
try {
|
|
66
|
+
let subscribed = false;
|
|
67
|
+
const bus = { on() { subscribed = true; } };
|
|
68
|
+
const injector = createRoundInjector({ ctx: bus, handles: ctx.handles, modules: { context: false }, compileRound, tokenizer: conservativeTokens, H: 1000, clock: ctx.clock });
|
|
69
|
+
injector.refresh({ id: 't' });
|
|
70
|
+
assert.equal(injector.current().text, '');
|
|
71
|
+
assert.equal(injector.current().delivery_level, 'disabled');
|
|
72
|
+
assert.equal(subscribed, false);
|
|
73
|
+
} finally {
|
|
74
|
+
ctx.cleanup();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { moduleEnabled, resolveModules } from '../src/runtime/modules.mjs';
|
|
5
|
+
import { toolDefinitions } from '../src/runtime/tools.mjs';
|
|
6
|
+
import { compileRound } from '../src/context/compile.mjs';
|
|
7
|
+
import { conservativeTokens } from '../src/context/budget.mjs';
|
|
8
|
+
import {
|
|
9
|
+
insertCandidate, insertMemory, insertObservation, insertPointer, insertTimeline,
|
|
10
|
+
} from '../src/storage/repositories.mjs';
|
|
11
|
+
import { createTestContext, makeCandidate, makeMemory, makeObservation, mutableClock } from './helpers/fixtures.mjs';
|
|
12
|
+
|
|
13
|
+
let seq = 0;
|
|
14
|
+
function pendingMemory(ctx) {
|
|
15
|
+
seq += 1;
|
|
16
|
+
const text = '待确认惦记';
|
|
17
|
+
const observation = makeObservation(ctx, { messageSeq: 27000 + seq, text });
|
|
18
|
+
insertObservation(ctx.main, observation);
|
|
19
|
+
const candidate = makeCandidate(ctx, observation, { businessKey: `mod-${seq}`, text, why: '期限', type: 'promise', scene: 'work', scope: 'work-only' });
|
|
20
|
+
insertCandidate(ctx.main, candidate);
|
|
21
|
+
const memory = makeMemory(ctx, candidate);
|
|
22
|
+
Object.assign(memory, { due_state: 'pending-due', scene: 'work', scope: 'work-only' });
|
|
23
|
+
insertMemory(ctx.main, memory);
|
|
24
|
+
insertTimeline(ctx.main, { memory_id: memory.id, start_at: null, end_at: null, prec_start: 'unknown', prec_end: 'unknown', time_state: 'point', revision: 1 });
|
|
25
|
+
insertPointer(ctx.main, { memory_id: memory.id, status: 'unknown', location: null, search_hint: null, lossy: 0, revision: 1 });
|
|
26
|
+
ctx.main
|
|
27
|
+
.prepare("INSERT INTO due_queue (memory_id, due_state, enqueued_seq, enqueued_at, opened_at, deadline_at, revision, reason) VALUES (?, 'pending-due', 1, ?, NULL, NULL, 0, 'promote')")
|
|
28
|
+
.run(memory.id, ctx.clock.at().at);
|
|
29
|
+
return memory;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('P6/modules: defaults enable everything and register all 10 tools', () => {
|
|
33
|
+
const modules = resolveModules({});
|
|
34
|
+
assert.deepEqual(modules, {
|
|
35
|
+
context: true, read: true, write: true, dream: true, projects: true, due: true, feed: true,
|
|
36
|
+
});
|
|
37
|
+
assert.equal(toolDefinitions({}, modules).length, 10);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('P6/modules: read=false removes read tools but keeps write/dream/feed', () => {
|
|
41
|
+
const modules = resolveModules({ modules: { read: false } });
|
|
42
|
+
const names = toolDefinitions({}, modules).map((tool) => tool.name);
|
|
43
|
+
for (const name of ['memsys_get', 'memsys_search', 'memsys_review_get', 'memsys_context_get', 'memsys_project_get', 'memsys_self_get']) {
|
|
44
|
+
assert.ok(!names.includes(name), name);
|
|
45
|
+
}
|
|
46
|
+
assert.ok(names.includes('memsys_record'));
|
|
47
|
+
assert.ok(names.includes('memsys_dream_record'));
|
|
48
|
+
assert.ok(names.includes('memsys_feed'));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('P6/modules: context=false disables the injection section', () => {
|
|
52
|
+
const modules = resolveModules({ modules: { context: false } });
|
|
53
|
+
assert.equal(moduleEnabled(modules, 'context'), false);
|
|
54
|
+
assert.equal(moduleEnabled(modules, 'read'), true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('P6/modules: due=false removes review:* typed items from compile output', (t) => {
|
|
58
|
+
const ctx = createTestContext({ clock: mutableClock().clock });
|
|
59
|
+
t.after(() => ctx.cleanup());
|
|
60
|
+
const memory = pendingMemory(ctx);
|
|
61
|
+
const base = { main: ctx.main, control: ctx.control, clock: ctx.clock, sessionId: 's-mod', roundId: 'r-mod', mode: 'work', tokenizer: conservativeTokens, H: 200000 };
|
|
62
|
+
const enabled = compileRound(base);
|
|
63
|
+
assert.ok(enabled.eligible_ids.includes('review:due:' + memory.id), JSON.stringify(enabled.eligible_ids));
|
|
64
|
+
const disabled = compileRound({ ...base, roundId: 'r-mod2', modules: { due: false } });
|
|
65
|
+
assert.ok(!disabled.eligible_ids.some((id) => id.startsWith('review:')), JSON.stringify(disabled.eligible_ids));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('P6/modules: legacy auto_inject maps to context without disabling other modules', () => {
|
|
69
|
+
const modules = resolveModules({ auto_inject: true });
|
|
70
|
+
assert.equal(modules.context, true);
|
|
71
|
+
assert.equal(modules.read, true);
|
|
72
|
+
assert.equal(modules.write, true);
|
|
73
|
+
assert.equal(modules.dream, true);
|
|
74
|
+
assert.equal(modules.projects, true);
|
|
75
|
+
assert.equal(modules.due, true);
|
|
76
|
+
assert.equal(modules.feed, true);
|
|
77
|
+
});
|