@4fu/arbor-notes-sqlite 2026.1106.0 → 2026.1106.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 +8 -5
- package/notes.d.ts +10 -0
- package/notes.js +70 -38
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -27,9 +27,11 @@ everything that takes judgment, such as git, merges and conflicts, to the agent.
|
|
|
27
27
|
guarantees what an agent cannot guarantee for itself - the record, isolation,
|
|
28
28
|
exactly-once effects, the checks, dispatch. Everything that takes judgment,
|
|
29
29
|
such as git, merges and conflicts, is the agent's.
|
|
30
|
-
- **A
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
- **A shared workspace with per-window checkouts.** The default workspace is
|
|
31
|
+
ovnfs: one shared folder every window reads, and a window opens its own
|
|
32
|
+
copy-on-write checkout of the project it needs, works there and merges back
|
|
33
|
+
with `apply`. It falls back to a plain directory per window when ovnfs is not
|
|
34
|
+
installed. It is not a sandbox: ovnfs isolates files, not processes.
|
|
33
35
|
- **A self-changing environment.** The model adds and changes skills, MCP
|
|
34
36
|
servers and resident services itself; a `reload` validates the change and
|
|
35
37
|
broadcasts it to every live window as a delta of names and descriptions.
|
|
@@ -118,7 +120,8 @@ window gets, and how a continuation keeps working in it, are in
|
|
|
118
120
|
| [`@4fu/arbor-journal-sqlite`](packages/journal-sqlite) | the default `Journal` and `BlobStore` on SQLite |
|
|
119
121
|
| [`@4fu/arbor-notes-sqlite`](packages/notes-sqlite) | the default `NoteStore` on SQLite, beside the journal |
|
|
120
122
|
| [`@4fu/arbor-segmenter-jieba`](packages/segmenter-jieba) | the default `Segmenter`: jieba's dictionary segmentation for the word index, optional |
|
|
121
|
-
| [`@4fu/arbor-workspace-dir`](packages/workspace-dir) | the
|
|
123
|
+
| [`@4fu/arbor-workspace-dir`](packages/workspace-dir) | the fallback `Workspace`: one directory per window, with a continuation working in its predecessor's directory in place |
|
|
124
|
+
| [`@4fu/arbor-workspace-ovnfs`](packages/workspace-ovnfs) | the default `Workspace` when ovnfs is installed: one shared folder, per-window copy-on-write checkouts the model opens and applies back |
|
|
122
125
|
| [`@4fu/arbor-environment-dir`](packages/environment-dir) | the default `Environment`: one `/env` directory, a revision per item, validation and deltas |
|
|
123
126
|
| [`@4fu/arbor-supervisor-process`](packages/supervisor-process) | the default `Supervisor`: one host process per service, restart with backoff, logs |
|
|
124
127
|
| [`@4fu/arbor-judge-jev`](packages/judge-jev) | the default `Judge`: Jev, TypeSafe AI's System One classifier, answering the dispatch decisions |
|
|
@@ -151,7 +154,7 @@ deployment can see what it would be taking on before it adopts one.
|
|
|
151
154
|
| [docs/coordination.md](docs/coordination.md) | the board, expectations, deliveries and broadcasts, staying current, effects |
|
|
152
155
|
| [docs/environment.md](docs/environment.md) | skills, MCP servers and services: using, changing, reload, built-in skills, deltas |
|
|
153
156
|
| [docs/memory.md](docs/memory.md) | notes, the active set, anchors, links |
|
|
154
|
-
| [docs/workspaces.md](docs/workspaces.md) | per-window
|
|
157
|
+
| [docs/workspaces.md](docs/workspaces.md) | the shared folder and per-window checkouts: isolation, continuity, what the agent does |
|
|
155
158
|
| [docs/tools.md](docs/tools.md) | direct tools and code mode modules |
|
|
156
159
|
| [docs/prompting.md](docs/prompting.md) | how every model-facing text is written: layers, notes, deliveries, errors, judge questions, terms |
|
|
157
160
|
| [docs/release.md](docs/release.md) | calendar versioning, the manual release steps, how a package is published |
|
package/notes.d.ts
CHANGED
|
@@ -46,6 +46,12 @@ export interface SqliteNoteStoreOptions {
|
|
|
46
46
|
now?: () => number;
|
|
47
47
|
/** Usage loses half of its weight this often. 24 hours. */
|
|
48
48
|
usageHalfLifeMs?: number;
|
|
49
|
+
/**
|
|
50
|
+
* One fixed string from the deployment's text table, so this implementation
|
|
51
|
+
* writes none of its own (docs/prompting.md#language-and-the-text-table).
|
|
52
|
+
* Defaults to the framework's own Chinese table.
|
|
53
|
+
*/
|
|
54
|
+
text?: (key: string, args?: Record<string, string | number>) => string;
|
|
49
55
|
}
|
|
50
56
|
/** The opening of a body, when a write carries no summary of its own. */
|
|
51
57
|
export declare function deriveSummary(body: string): string;
|
|
@@ -55,6 +61,8 @@ export declare class SqliteNoteStore implements NoteStore {
|
|
|
55
61
|
private readonly journal;
|
|
56
62
|
private readonly clock;
|
|
57
63
|
private readonly halfLifeMs;
|
|
64
|
+
/** The table every refusal this store raises is written from. */
|
|
65
|
+
private readonly text;
|
|
58
66
|
/** One write at a time: a write spans the notes database and the journal. */
|
|
59
67
|
private writeQueue;
|
|
60
68
|
private closed;
|
|
@@ -121,6 +129,8 @@ export declare class SqliteNoteStore implements NoteStore {
|
|
|
121
129
|
* the changes since reach it as a delta.
|
|
122
130
|
*/
|
|
123
131
|
private epochAt;
|
|
132
|
+
/** What the active notes of one epoch cost, as the epoch froze them. */
|
|
133
|
+
private epochTokens;
|
|
124
134
|
/** The active notes one epoch froze, by summary, sorted by id. */
|
|
125
135
|
private epochSummaries;
|
|
126
136
|
}
|
package/notes.js
CHANGED
|
@@ -31,6 +31,7 @@ import { noteIdOf, noteRefOf, noteRefForms, parseNoteId, parseNoteRef, storedEve
|
|
|
31
31
|
import { FTS_TABLE, createSchema } from "./schema.js";
|
|
32
32
|
import { USE_WEIGHTS, applyJudgeShare, pickByBudget, scoreNotes } from "./score.js";
|
|
33
33
|
import { holdingIds, substringClause } from "./search.js";
|
|
34
|
+
import { textOf, zhTextTable } from "@4fu/arbor-prompt";
|
|
34
35
|
/** A summary is at most this many characters (docs/memory.md). */
|
|
35
36
|
export const SUMMARY_LIMIT = 200;
|
|
36
37
|
/** How many characters of a body one read returns. */
|
|
@@ -87,19 +88,19 @@ export function deriveSummary(body) {
|
|
|
87
88
|
const characters = Array.from(flat);
|
|
88
89
|
return characters.length <= SUMMARY_LIMIT ? flat : `${characters.slice(0, SUMMARY_LIMIT).join("")}…`;
|
|
89
90
|
}
|
|
90
|
-
function invalidNoteRef(call, ref) {
|
|
91
|
+
function invalidNoteRef(text, call, ref) {
|
|
91
92
|
return arborError({
|
|
92
93
|
code: "invalid_input",
|
|
93
94
|
rule: `note.${call}.ref`,
|
|
94
|
-
message:
|
|
95
|
-
nextAction: "
|
|
95
|
+
message: text("refused.note.ref", { ref }),
|
|
96
|
+
nextAction: text("next.note.ref"),
|
|
96
97
|
details: { ref },
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
|
-
function parseOrThrow(ref, call) {
|
|
100
|
+
function parseOrThrow(text, ref, call) {
|
|
100
101
|
const parsed = parseNoteRef(ref);
|
|
101
102
|
if (parsed === null)
|
|
102
|
-
throw invalidNoteRef(call, ref);
|
|
103
|
+
throw invalidNoteRef(text, call, ref);
|
|
103
104
|
return parsed;
|
|
104
105
|
}
|
|
105
106
|
function toSummary(row) {
|
|
@@ -164,6 +165,8 @@ export class SqliteNoteStore {
|
|
|
164
165
|
journal;
|
|
165
166
|
clock;
|
|
166
167
|
halfLifeMs;
|
|
168
|
+
/** The table every refusal this store raises is written from. */
|
|
169
|
+
text;
|
|
167
170
|
/** One write at a time: a write spans the notes database and the journal. */
|
|
168
171
|
writeQueue = Promise.resolve();
|
|
169
172
|
closed = false;
|
|
@@ -174,6 +177,7 @@ export class SqliteNoteStore {
|
|
|
174
177
|
this.journal = options.journal;
|
|
175
178
|
this.clock = options.now ?? (() => Date.now());
|
|
176
179
|
this.halfLifeMs = options.usageHalfLifeMs ?? DEFAULT_HALF_LIFE_MS;
|
|
180
|
+
this.text = options.text ?? ((key, args) => textOf(zhTextTable, key, args));
|
|
177
181
|
}
|
|
178
182
|
// --- writes -------------------------------------------------------------
|
|
179
183
|
async write(input) {
|
|
@@ -217,8 +221,8 @@ export class SqliteNoteStore {
|
|
|
217
221
|
throw arborError({
|
|
218
222
|
code: isArborError(error) ? error.code : "internal",
|
|
219
223
|
rule: "note.write.journal",
|
|
220
|
-
message:
|
|
221
|
-
nextAction: "
|
|
224
|
+
message: this.text("refused.note.write.journal", { ref: noteRef, detail }),
|
|
225
|
+
nextAction: this.text("next.note.write.journal"),
|
|
222
226
|
details: { upstream: detail },
|
|
223
227
|
cause: error,
|
|
224
228
|
});
|
|
@@ -233,36 +237,44 @@ export class SqliteNoteStore {
|
|
|
233
237
|
}
|
|
234
238
|
/** The rules a write has to meet before it touches the one copy. */
|
|
235
239
|
checkWrite(input) {
|
|
240
|
+
if (input.id !== undefined && input.id.trim() === "") {
|
|
241
|
+
throw arborError({
|
|
242
|
+
code: "invalid_input",
|
|
243
|
+
rule: "note.write.id",
|
|
244
|
+
message: this.text("refused.note.write.id"),
|
|
245
|
+
nextAction: this.text("next.note.write.id"),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
236
248
|
if (input.title.trim() === "") {
|
|
237
249
|
throw arborError({
|
|
238
250
|
code: "invalid_input",
|
|
239
251
|
rule: "note.write.title",
|
|
240
|
-
message: "note.write
|
|
241
|
-
nextAction: "
|
|
252
|
+
message: this.text("refused.note.write.title"),
|
|
253
|
+
nextAction: this.text("next.note.write.title"),
|
|
242
254
|
});
|
|
243
255
|
}
|
|
244
256
|
if (input.delete !== true && input.body.trim() === "") {
|
|
245
257
|
throw arborError({
|
|
246
258
|
code: "invalid_input",
|
|
247
259
|
rule: "note.write.body",
|
|
248
|
-
message: "note.write
|
|
249
|
-
nextAction: "write
|
|
260
|
+
message: this.text("refused.note.write.body"),
|
|
261
|
+
nextAction: this.text("next.note.write.body"),
|
|
250
262
|
});
|
|
251
263
|
}
|
|
252
264
|
if (input.summary !== undefined && Array.from(input.summary).length > SUMMARY_LIMIT) {
|
|
253
265
|
throw arborError({
|
|
254
266
|
code: "invalid_input",
|
|
255
267
|
rule: "note.write.summary",
|
|
256
|
-
message:
|
|
257
|
-
nextAction:
|
|
268
|
+
message: this.text("refused.note.write.summary", { limit: SUMMARY_LIMIT }),
|
|
269
|
+
nextAction: this.text("next.note.write.summary", { limit: SUMMARY_LIMIT }),
|
|
258
270
|
});
|
|
259
271
|
}
|
|
260
272
|
if (input.id === undefined && input.expectedRevision !== undefined) {
|
|
261
273
|
throw arborError({
|
|
262
274
|
code: "invalid_input",
|
|
263
275
|
rule: "note.write.expected_revision",
|
|
264
|
-
message: "note.write
|
|
265
|
-
nextAction: "
|
|
276
|
+
message: this.text("refused.note.write.expected_revision"),
|
|
277
|
+
nextAction: this.text("next.note.write.expected_revision"),
|
|
266
278
|
});
|
|
267
279
|
}
|
|
268
280
|
}
|
|
@@ -280,22 +292,22 @@ export class SqliteNoteStore {
|
|
|
280
292
|
else {
|
|
281
293
|
const parsed = parseNoteId(input.id);
|
|
282
294
|
if (parsed === null)
|
|
283
|
-
throw invalidNoteRef("write", input.id);
|
|
295
|
+
throw invalidNoteRef(this.text, "write", input.id);
|
|
284
296
|
previous = this.noteRow(parsed);
|
|
285
297
|
if (previous === null) {
|
|
286
298
|
throw arborError({
|
|
287
299
|
code: "not_found",
|
|
288
300
|
rule: "note.write.note",
|
|
289
|
-
message:
|
|
290
|
-
nextAction: "
|
|
301
|
+
message: this.text("refused.note.write.note", { id: input.id }),
|
|
302
|
+
nextAction: this.text("next.note.write.note"),
|
|
291
303
|
});
|
|
292
304
|
}
|
|
293
305
|
if (previous.status === "deleted") {
|
|
294
306
|
throw arborError({
|
|
295
307
|
code: "not_found",
|
|
296
308
|
rule: "note.write.deleted",
|
|
297
|
-
message:
|
|
298
|
-
nextAction: "
|
|
309
|
+
message: this.text("refused.note.write.deleted", { id: input.id }),
|
|
310
|
+
nextAction: this.text("next.note.write.deleted"),
|
|
299
311
|
});
|
|
300
312
|
}
|
|
301
313
|
const expected = input.expectedRevision;
|
|
@@ -303,16 +315,20 @@ export class SqliteNoteStore {
|
|
|
303
315
|
throw arborError({
|
|
304
316
|
code: "invalid_input",
|
|
305
317
|
rule: "note.write.revision",
|
|
306
|
-
message:
|
|
307
|
-
nextAction:
|
|
318
|
+
message: this.text("refused.note.write.revision", { id: input.id, revision: previous.revision }),
|
|
319
|
+
nextAction: this.text("next.note.write.revision", { revision: previous.revision }),
|
|
308
320
|
});
|
|
309
321
|
}
|
|
310
322
|
if (expected > previous.revision) {
|
|
311
323
|
throw arborError({
|
|
312
324
|
code: "invalid_input",
|
|
313
|
-
rule: "note.write.revision",
|
|
314
|
-
message:
|
|
315
|
-
|
|
325
|
+
rule: "note.write.revision.absent",
|
|
326
|
+
message: this.text("refused.note.write.revision.absent", {
|
|
327
|
+
id: input.id,
|
|
328
|
+
expected,
|
|
329
|
+
revision: previous.revision,
|
|
330
|
+
}),
|
|
331
|
+
nextAction: this.text("next.note.write.revision.absent", { revision: previous.revision }),
|
|
316
332
|
});
|
|
317
333
|
}
|
|
318
334
|
if (expected < previous.revision)
|
|
@@ -326,9 +342,9 @@ export class SqliteNoteStore {
|
|
|
326
342
|
input.body !== previous.body) {
|
|
327
343
|
throw arborError({
|
|
328
344
|
code: "invalid_input",
|
|
329
|
-
rule: "note.write.
|
|
330
|
-
message:
|
|
331
|
-
nextAction:
|
|
345
|
+
rule: "note.write.replace_summary",
|
|
346
|
+
message: this.text("refused.note.write.replace_summary", { id: input.id }),
|
|
347
|
+
nextAction: this.text("next.note.write.replace_summary", { limit: SUMMARY_LIMIT }),
|
|
332
348
|
});
|
|
333
349
|
}
|
|
334
350
|
}
|
|
@@ -435,8 +451,16 @@ export class SqliteNoteStore {
|
|
|
435
451
|
return arborError({
|
|
436
452
|
code: "conflict",
|
|
437
453
|
rule: "note.write.stale_revision",
|
|
438
|
-
message:
|
|
439
|
-
|
|
454
|
+
message: this.text("refused.note.write.stale_revision", {
|
|
455
|
+
ref: noteRefOf(note.id, current),
|
|
456
|
+
current,
|
|
457
|
+
read,
|
|
458
|
+
}),
|
|
459
|
+
nextAction: this.text("next.note.write.stale_revision", {
|
|
460
|
+
ref: noteRefOf(note.id, current),
|
|
461
|
+
current,
|
|
462
|
+
read,
|
|
463
|
+
}),
|
|
440
464
|
refs: [noteRefOf(note.id, current), ...revisions.map((revision) => revision.ref)],
|
|
441
465
|
details: {
|
|
442
466
|
conflict: { read, current, revisions },
|
|
@@ -447,7 +471,7 @@ export class SqliteNoteStore {
|
|
|
447
471
|
// --- reads --------------------------------------------------------------
|
|
448
472
|
async read(ref, options) {
|
|
449
473
|
this.ensureOpen();
|
|
450
|
-
const parsed = parseOrThrow(ref, "read");
|
|
474
|
+
const parsed = parseOrThrow(this.text, ref, "read");
|
|
451
475
|
const row = parsed.revision === null ? this.latestRevisionRow(parsed.id) : this.revisionRow(parsed.id, parsed.revision);
|
|
452
476
|
if (row === null)
|
|
453
477
|
return null;
|
|
@@ -469,7 +493,7 @@ export class SqliteNoteStore {
|
|
|
469
493
|
}
|
|
470
494
|
async revisions(ref) {
|
|
471
495
|
this.ensureOpen();
|
|
472
|
-
const parsed = parseOrThrow(ref, "revisions");
|
|
496
|
+
const parsed = parseOrThrow(this.text, ref, "revisions");
|
|
473
497
|
const limit = DEFAULT_PAGE_LIMIT;
|
|
474
498
|
const nextCall = `notes.revisions(${noteIdOf(parsed.id)}, offset=${limit})`;
|
|
475
499
|
const note = this.noteRow(parsed.id);
|
|
@@ -510,7 +534,7 @@ export class SqliteNoteStore {
|
|
|
510
534
|
}
|
|
511
535
|
async context(ref) {
|
|
512
536
|
this.ensureOpen();
|
|
513
|
-
const parsed = parseOrThrow(ref, "context");
|
|
537
|
+
const parsed = parseOrThrow(this.text, ref, "context");
|
|
514
538
|
const note = this.noteRow(parsed.id);
|
|
515
539
|
if (note === null)
|
|
516
540
|
return null;
|
|
@@ -552,6 +576,7 @@ export class SqliteNoteStore {
|
|
|
552
576
|
limit: items.length,
|
|
553
577
|
nextCall: `notes.delta(${epoch.epoch})`,
|
|
554
578
|
budget,
|
|
579
|
+
tokens: this.epochTokens(epoch.epoch),
|
|
555
580
|
};
|
|
556
581
|
});
|
|
557
582
|
}
|
|
@@ -564,8 +589,8 @@ export class SqliteNoteStore {
|
|
|
564
589
|
throw arborError({
|
|
565
590
|
code: "invalid_input",
|
|
566
591
|
rule: "note.delta.epoch",
|
|
567
|
-
message:
|
|
568
|
-
nextAction: "
|
|
592
|
+
message: this.text("refused.note.delta.epoch", { since }),
|
|
593
|
+
nextAction: this.text("next.note.delta.epoch"),
|
|
569
594
|
details: { since },
|
|
570
595
|
});
|
|
571
596
|
}
|
|
@@ -631,7 +656,7 @@ export class SqliteNoteStore {
|
|
|
631
656
|
}
|
|
632
657
|
async recordUse(input) {
|
|
633
658
|
this.ensureOpen();
|
|
634
|
-
const parsed = parseOrThrow(input.ref, "recordUse");
|
|
659
|
+
const parsed = parseOrThrow(this.text, input.ref, "recordUse");
|
|
635
660
|
// A use of a note that is not there names a ref that does not resolve; the
|
|
636
661
|
// host's own append must not fail for it.
|
|
637
662
|
if (this.noteRow(parsed.id) === null)
|
|
@@ -668,8 +693,8 @@ export class SqliteNoteStore {
|
|
|
668
693
|
throw arborError({
|
|
669
694
|
code: "unavailable",
|
|
670
695
|
rule: "note.closed",
|
|
671
|
-
message: "
|
|
672
|
-
nextAction: "
|
|
696
|
+
message: this.text("refused.note.closed"),
|
|
697
|
+
nextAction: this.text("next.note.closed"),
|
|
673
698
|
});
|
|
674
699
|
}
|
|
675
700
|
}
|
|
@@ -841,6 +866,13 @@ export class SqliteNoteStore {
|
|
|
841
866
|
activate.run(item.note);
|
|
842
867
|
return this.epochRow(epoch);
|
|
843
868
|
}
|
|
869
|
+
/** What the active notes of one epoch cost, as the epoch froze them. */
|
|
870
|
+
epochTokens(epoch) {
|
|
871
|
+
const row = this.db
|
|
872
|
+
.prepare("SELECT COALESCE(SUM(tokens), 0) AS total FROM note_epoch_items WHERE epoch = ?")
|
|
873
|
+
.get(epoch);
|
|
874
|
+
return row === undefined ? 0 : toNumber(row.total);
|
|
875
|
+
}
|
|
844
876
|
/** The active notes one epoch froze, by summary, sorted by id. */
|
|
845
877
|
epochSummaries(epoch) {
|
|
846
878
|
const rows = this.db
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4fu/arbor-notes-sqlite",
|
|
3
|
-
"version": "2026.1106.
|
|
3
|
+
"version": "2026.1106.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The default NoteStore: one copy of every note, checked writes, revisions, anchors and the active set on SQLite beside the journal.",
|
|
6
6
|
"exports": {
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@4fu/arbor-errors": "2026.1106.
|
|
44
|
-
"@4fu/arbor-interfaces": "2026.1106.
|
|
43
|
+
"@4fu/arbor-errors": "2026.1106.1",
|
|
44
|
+
"@4fu/arbor-interfaces": "2026.1106.1",
|
|
45
|
+
"@4fu/arbor-prompt": "2026.1106.1"
|
|
45
46
|
}
|
|
46
47
|
}
|