@oharness/engine 0.4.0 → 0.5.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.
Files changed (117) hide show
  1. package/dist/agent/agent.d.ts +63 -0
  2. package/dist/agent/agent.js +257 -7
  3. package/dist/agent/agent.js.map +1 -1
  4. package/dist/agent/memory-backup.d.ts +38 -0
  5. package/dist/agent/memory-backup.js +126 -0
  6. package/dist/agent/memory-backup.js.map +1 -0
  7. package/dist/agent/memory-consolidate.d.ts +47 -0
  8. package/dist/agent/memory-consolidate.js +118 -0
  9. package/dist/agent/memory-consolidate.js.map +1 -0
  10. package/dist/agent/memory-errors.d.ts +73 -0
  11. package/dist/agent/memory-errors.js +74 -0
  12. package/dist/agent/memory-errors.js.map +1 -0
  13. package/dist/agent/memory-extract.d.ts +36 -0
  14. package/dist/agent/memory-extract.js +169 -0
  15. package/dist/agent/memory-extract.js.map +1 -0
  16. package/dist/agent/memory-limits.d.ts +115 -0
  17. package/dist/agent/memory-limits.js +109 -0
  18. package/dist/agent/memory-limits.js.map +1 -0
  19. package/dist/agent/memory-links.d.ts +21 -0
  20. package/dist/agent/memory-links.js +45 -0
  21. package/dist/agent/memory-links.js.map +1 -0
  22. package/dist/agent/memory-merge.d.ts +34 -0
  23. package/dist/agent/memory-merge.js +152 -0
  24. package/dist/agent/memory-merge.js.map +1 -0
  25. package/dist/agent/memory-provider.d.ts +73 -0
  26. package/dist/agent/memory-provider.js +2 -0
  27. package/dist/agent/memory-provider.js.map +1 -0
  28. package/dist/agent/memory-recall.d.ts +72 -0
  29. package/dist/agent/memory-recall.js +192 -0
  30. package/dist/agent/memory-recall.js.map +1 -0
  31. package/dist/agent/memory-redact.d.ts +15 -0
  32. package/dist/agent/memory-redact.js +48 -0
  33. package/dist/agent/memory-redact.js.map +1 -0
  34. package/dist/agent/memory-review.d.ts +47 -0
  35. package/dist/agent/memory-review.js +127 -0
  36. package/dist/agent/memory-review.js.map +1 -0
  37. package/dist/agent/memory-usage.d.ts +24 -0
  38. package/dist/agent/memory-usage.js +68 -0
  39. package/dist/agent/memory-usage.js.map +1 -0
  40. package/dist/agent/memory.d.ts +137 -14
  41. package/dist/agent/memory.js +431 -130
  42. package/dist/agent/memory.js.map +1 -1
  43. package/dist/agent/permissions.d.ts +10 -2
  44. package/dist/agent/permissions.js +22 -3
  45. package/dist/agent/permissions.js.map +1 -1
  46. package/dist/agent/session.d.ts +8 -1
  47. package/dist/agent/session.js +9 -0
  48. package/dist/agent/session.js.map +1 -1
  49. package/dist/agent/store.d.ts +3 -0
  50. package/dist/agent/store.js.map +1 -1
  51. package/dist/agent/titles.js +2 -2
  52. package/dist/auth/providers/official.d.ts +13 -0
  53. package/dist/auth/providers/official.js +38 -3
  54. package/dist/auth/providers/official.js.map +1 -1
  55. package/dist/config.d.ts +20 -1
  56. package/dist/config.js +31 -1
  57. package/dist/config.js.map +1 -1
  58. package/dist/core/audit.d.ts +19 -0
  59. package/dist/core/types.d.ts +6 -0
  60. package/dist/core/types.js +12 -0
  61. package/dist/core/types.js.map +1 -1
  62. package/dist/harness.d.ts +7 -1
  63. package/dist/harness.js +33 -2
  64. package/dist/harness.js.map +1 -1
  65. package/dist/providers/builtin.d.ts +20 -0
  66. package/dist/providers/builtin.js +82 -0
  67. package/dist/providers/builtin.js.map +1 -1
  68. package/dist/providers/catalog.js +30 -3
  69. package/dist/providers/catalog.js.map +1 -1
  70. package/dist/providers/index.d.ts +2 -2
  71. package/dist/providers/index.js +1 -1
  72. package/dist/providers/index.js.map +1 -1
  73. package/dist/providers/piai.js +17 -8
  74. package/dist/providers/piai.js.map +1 -1
  75. package/dist/providers/registry.d.ts +5 -0
  76. package/dist/providers/registry.js +10 -2
  77. package/dist/providers/registry.js.map +1 -1
  78. package/dist/providers/types.d.ts +31 -0
  79. package/dist/providers/types.js +36 -0
  80. package/dist/providers/types.js.map +1 -1
  81. package/dist/runtime-plugins/builtin/memory-coordinator.d.ts +79 -0
  82. package/dist/runtime-plugins/builtin/memory-coordinator.js +169 -0
  83. package/dist/runtime-plugins/builtin/memory-coordinator.js.map +1 -0
  84. package/dist/runtime-plugins/builtin/memory-migrate.d.ts +35 -0
  85. package/dist/runtime-plugins/builtin/memory-migrate.js +141 -0
  86. package/dist/runtime-plugins/builtin/memory-migrate.js.map +1 -0
  87. package/dist/runtime-plugins/builtin/memory.d.ts +144 -5
  88. package/dist/runtime-plugins/builtin/memory.js +1016 -112
  89. package/dist/runtime-plugins/builtin/memory.js.map +1 -1
  90. package/dist/runtime-plugins/builtin/studio.js +21 -4
  91. package/dist/runtime-plugins/builtin/studio.js.map +1 -1
  92. package/dist/runtime-plugins/cloud-connector.js +4 -1
  93. package/dist/runtime-plugins/cloud-connector.js.map +1 -1
  94. package/dist/runtime-plugins/extensions.d.ts +9 -0
  95. package/dist/runtime-plugins/extensions.js +15 -0
  96. package/dist/runtime-plugins/extensions.js.map +1 -1
  97. package/dist/runtime-plugins/host.js +25 -2
  98. package/dist/runtime-plugins/host.js.map +1 -1
  99. package/dist/runtime-plugins/types.d.ts +15 -0
  100. package/dist/tools/builtin/ask.d.ts +9 -0
  101. package/dist/tools/builtin/ask.js +71 -0
  102. package/dist/tools/builtin/ask.js.map +1 -0
  103. package/dist/tools/builtin/index.d.ts +1 -0
  104. package/dist/tools/builtin/index.js +3 -0
  105. package/dist/tools/builtin/index.js.map +1 -1
  106. package/dist/tools/builtin/memory.d.ts +5 -5
  107. package/dist/tools/builtin/memory.js +151 -40
  108. package/dist/tools/builtin/memory.js.map +1 -1
  109. package/dist/tools/index.d.ts +2 -0
  110. package/dist/tools/index.js +1 -0
  111. package/dist/tools/index.js.map +1 -1
  112. package/dist/tools/questions.d.ts +89 -0
  113. package/dist/tools/questions.js +97 -0
  114. package/dist/tools/questions.js.map +1 -0
  115. package/dist/tools/types.d.ts +7 -0
  116. package/dist/tools/types.js.map +1 -1
  117. package/package.json +2 -2
@@ -0,0 +1,169 @@
1
+ import { userMessage, visibleText } from "../core/types.js";
2
+ import { MEMORY_SAFETY, defaultScopeFor, isMemoryScope, isMemoryType } from "./memory-limits.js";
3
+ import { redactSecrets } from "./memory-redact.js";
4
+ /**
5
+ * Notes proposed by a model reading a finished stretch of conversation.
6
+ *
7
+ * The complement to `remember`, which depends on the agent noticing in the
8
+ * moment. Off by default, because every pass is a model call on the user's
9
+ * own credit. What comes back is treated as untrusted: ids it was not shown
10
+ * are dropped, links it invents are unlinked, credentials are redacted, and
11
+ * everything lands through the same store paths a human edit takes.
12
+ */
13
+ export const EXTRACTION_PROMPT = `You maintain a set of durable notes an assistant reads in future sessions. Read the conversation and propose notes worth keeping.
14
+
15
+ Worth keeping:
16
+ - a correction the user gave, or an approach they confirmed (type "feedback");
17
+ - who the user is or how they like to work (type "user");
18
+ - a decision and its reason, a constraint, or ongoing work that the code and git history do not record (type "project");
19
+ - where external information lives: a dashboard, a tracker, a document (type "reference").
20
+
21
+ Not worth keeping: anything the code or git history already records, anything only relevant to the task in progress, small talk, credentials or secrets of any kind.
22
+
23
+ Existing notes come in two lists. Notes shown with their full body may be updated: if a proposal covers the same subject as one of them, use "action": "update" with its "id" and write the complete new body, keeping every detail of the existing body that is still true. Notes listed by summary only may be linked but never updated; propose a new note instead if one of them needs changing.
24
+
25
+ Bodies state the fact, then a line starting "Why:" and a line starting "How to apply:". Tags carry the words someone might search with instead of the summary's own: synonyms, and the term in the other language the conversation uses. Link related notes as [[id]] using only the ids listed. A new note in this batch can be linked from another new note as [[new:N]], where N is that note's "ref".
26
+
27
+ Respond with a JSON array and nothing else. Each element:
28
+ {"action":"create"|"update","id":"<existing id, for update>","ref":<number, for create>,"summary":"<one concrete line>","body":"<markdown>","type":"user"|"feedback"|"project"|"reference","scope":"user"|"project","tags":["..."]}
29
+
30
+ Scope "project" is the default, including for feedback about how this codebase is worked on. Use scope "user" for facts about the user and for preferences that plainly hold in every project. Return [] when nothing qualifies — that is the common case.`;
31
+ /** The conversation as plain text, newest material kept when it has to be cut. */
32
+ export function transcriptText(messages, maxChars = 60_000) {
33
+ const parts = [];
34
+ for (const message of messages) {
35
+ if (message.meta?.compacted)
36
+ continue;
37
+ const text = visibleText(message).trim();
38
+ const calls = message.content.filter((block) => block.type === "tool_call").map((block) => block.name);
39
+ if (!text && !calls.length)
40
+ continue;
41
+ const who = message.role === "user" ? (message.meta?.synthetic ? "Tool results" : "User") : "Assistant";
42
+ parts.push(`${who}: ${text}${calls.length ? `\n[called ${calls.join(", ")}]` : ""}`);
43
+ }
44
+ const joined = parts.join("\n\n");
45
+ return joined.length > maxChars ? `[earlier conversation omitted]\n${joined.slice(joined.length - maxChars)}` : joined;
46
+ }
47
+ /** Real messages from the user: typed, not tool results or harness summaries. */
48
+ export function countUserTurns(messages) {
49
+ return messages.filter((message) => message.role === "user" && !message.meta?.synthetic && !message.meta?.compacted &&
50
+ message.content.some((block) => block.type === "text")).length;
51
+ }
52
+ const LINKISH = /\[\[([^\]\n]{1,140})\]\]/g;
53
+ /**
54
+ * Validate an extractor's reply. Returns only well-formed notes; a reply that
55
+ * is not JSON at all yields none rather than an error, since a model that
56
+ * rambles instead of answering has proposed nothing.
57
+ */
58
+ export function parseExtraction(reply, candidates) {
59
+ const start = reply.indexOf("[");
60
+ const end = reply.lastIndexOf("]");
61
+ if (start === -1 || end <= start)
62
+ return [];
63
+ let raw;
64
+ try {
65
+ raw = JSON.parse(reply.slice(start, end + 1));
66
+ }
67
+ catch {
68
+ return [];
69
+ }
70
+ if (!Array.isArray(raw))
71
+ return [];
72
+ const byId = new Map(candidates.map((candidate) => [candidate.id, candidate]));
73
+ const notes = [];
74
+ const refs = new Set();
75
+ const updated = new Set();
76
+ for (const item of raw.slice(0, 50)) {
77
+ if (!item || typeof item !== "object")
78
+ continue;
79
+ const value = item;
80
+ const summary = typeof value.summary === "string" ? redactSecrets(value.summary.replace(/[\r\n]+/g, " ").trim()).text : "";
81
+ const body = typeof value.body === "string" ? redactSecrets(value.body.trim()).text : "";
82
+ if (summary.length < 4 || summary.length > MEMORY_SAFETY.maxSummaryChars || body.length < 4)
83
+ continue;
84
+ const type = isMemoryType(value.type) ? value.type : undefined;
85
+ const tags = Array.isArray(value.tags)
86
+ ? value.tags.filter((tag) => typeof tag === "string" && MEMORY_SAFETY.tagPattern.test(tag)).slice(0, MEMORY_SAFETY.maxTags)
87
+ : [];
88
+ if (value.action === "update") {
89
+ const target = typeof value.id === "string" ? byId.get(value.id) : undefined;
90
+ // An id the extractor was not shown is a guess, and updating a guess
91
+ // would overwrite whatever note happens to have that id.
92
+ // Nor may it update a note it was only shown the summary of: the reply
93
+ // replaces the body, and a body written without reading the old one
94
+ // silently drops whatever the old one said.
95
+ if (!target || typeof target.body !== "string" || updated.has(target.id))
96
+ continue;
97
+ updated.add(target.id);
98
+ notes.push({ action: "update", id: target.id, summary, body, tags, ...(type ?? target.type ? { type: type ?? target.type } : {}), scope: target.scope });
99
+ continue;
100
+ }
101
+ if (value.action !== "create")
102
+ continue;
103
+ const ref = typeof value.ref === "number" && Number.isSafeInteger(value.ref) && value.ref > 0 && !refs.has(value.ref) ? value.ref : undefined;
104
+ if (ref !== undefined)
105
+ refs.add(ref);
106
+ const scope = isMemoryScope(value.scope) ? value.scope : defaultScopeFor(type);
107
+ notes.push({ action: "create", ...(ref !== undefined ? { ref } : {}), summary, body, tags, ...(type ? { type } : {}), scope });
108
+ }
109
+ for (const note of notes) {
110
+ note.body = note.body.replace(LINKISH, (whole, inner) => {
111
+ const target = inner.trim();
112
+ const placeholder = /^new:(\d+)$/.exec(target);
113
+ if (placeholder && refs.has(Number(placeholder[1])))
114
+ return `[[new:${placeholder[1]}]]`;
115
+ if (byId.has(target))
116
+ return `[[${target}]]`;
117
+ // Invented: keep the words, drop the link.
118
+ return target;
119
+ });
120
+ }
121
+ return notes;
122
+ }
123
+ /**
124
+ * Replace `[[new:N]]` with the id note N was given. A placeholder whose note
125
+ * was not written becomes plain text.
126
+ */
127
+ export function resolvePlaceholders(body, ids) {
128
+ return body.replace(/\[\[new:(\d+)\]\]/g, (_, ref) => {
129
+ const id = ids.get(Number(ref));
130
+ return id ? `[[${id}]]` : `note ${ref}`;
131
+ });
132
+ }
133
+ export async function extractMemories(run) {
134
+ const transcript = transcriptText(run.messages);
135
+ if (!transcript.trim())
136
+ return { created: [], updated: [] };
137
+ const policy = run.target.policy();
138
+ const candidates = policy.maxCandidates > 0 ? await run.target.candidates(transcript, policy.maxCandidates) : [];
139
+ const label = (candidate) => `${candidate.id} (${[candidate.scope, candidate.type].filter(Boolean).join(", ")})`;
140
+ const editable = candidates.filter((candidate) => typeof candidate.body === "string");
141
+ const linkOnly = candidates.filter((candidate) => typeof candidate.body !== "string");
142
+ const listing = [
143
+ "Notes you may update (full body shown):",
144
+ editable.length
145
+ ? editable.map((candidate) => `<note id="${candidate.id}" label="${label(candidate)}">\n${candidate.summary}\n\n${candidate.body}\n</note>`).join("\n\n")
146
+ : "(none)",
147
+ "",
148
+ "Other existing notes (link only, never update):",
149
+ linkOnly.length ? linkOnly.map((candidate) => `- ${label(candidate)}: ${candidate.summary}`).join("\n") : "(none)",
150
+ ].join("\n");
151
+ const request = {
152
+ model: run.model,
153
+ system: EXTRACTION_PROMPT,
154
+ messages: [userMessage(`Existing notes:\n${listing}\n\nConversation:\n<conversation>\n${transcript}\n</conversation>\n\nPropose notes now, as a JSON array.`)],
155
+ maxOutputTokens: Math.min(4_000, run.model.maxOutputTokens),
156
+ thinking: { enabled: false, effort: "low" },
157
+ ...(run.signal ? { signal: run.signal } : {}),
158
+ };
159
+ const parts = [];
160
+ for await (const event of run.provider.stream(request)) {
161
+ if (event.type === "text_delta")
162
+ parts.push(event.text);
163
+ }
164
+ const notes = parseExtraction(parts.join(""), candidates);
165
+ if (!notes.length)
166
+ return { created: [], updated: [] };
167
+ return run.target.commit(notes);
168
+ }
169
+ //# sourceMappingURL=memory-extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-extract.js","sourceRoot":"","sources":["../../src/agent/memory-extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Every number the memory subsystem runs on, in one place.
3
+ *
4
+ * Three kinds live here and they are kept apart on purpose. Policy is the
5
+ * user's to change and arrives through `memory` in config. Safety bounds are
6
+ * not configurable, because each one is a defence rather than a preference.
7
+ * Sync quotas are neither: they belong to whoever runs the sync server, which
8
+ * announces them in its responses, so no client-side copy exists at all.
9
+ */
10
+ export declare const MEMORY_TYPES: readonly ["user", "feedback", "project", "reference"];
11
+ export type MemoryType = (typeof MEMORY_TYPES)[number];
12
+ export declare const MEMORY_SCOPES: readonly ["project", "user"];
13
+ export type MemoryScope = (typeof MEMORY_SCOPES)[number];
14
+ export interface MemoryRecallPolicy {
15
+ /** Attach relevant notes to each user message. */
16
+ enabled: boolean;
17
+ /** Most notes attached to one message. */
18
+ perTurn: number;
19
+ /** Characters the attached notes may take up, bodies and related lines together. */
20
+ budgetChars: number;
21
+ /** Below this score a note is not attached. */
22
+ minScore: number;
23
+ }
24
+ export interface MemoryExtractionPolicy {
25
+ /** Extract candidate notes from the part of a conversation compaction drops. */
26
+ onCompaction: boolean;
27
+ /** Extract candidate notes when a run ends. */
28
+ onStop: boolean;
29
+ /** Conversations with fewer real user messages than this are skipped. */
30
+ minUserTurns: number;
31
+ /** Existing notes shown to the extractor so it can update or link them. */
32
+ maxCandidates: number;
33
+ }
34
+ export interface MemoryConsolidationPolicy {
35
+ /**
36
+ * Merge notes about the same subject in the background, with the small
37
+ * model. Only duplicates, never the other review findings. `"subscribers"`
38
+ * means on for an account the sync service reports as paid, off otherwise.
39
+ */
40
+ auto: boolean | "subscribers";
41
+ /** Least time between background passes. */
42
+ cooldownHours: number;
43
+ /** Most merges one background pass makes. */
44
+ maxMerges: number;
45
+ /** A note neither recalled nor edited for this long is listed for review. */
46
+ unusedDays: number;
47
+ }
48
+ export interface MemoryPolicy {
49
+ maxNotes: number;
50
+ maxNoteBytes: number;
51
+ /** Characters of note summaries the system prompt index may list. */
52
+ indexChars: number;
53
+ recall: MemoryRecallPolicy;
54
+ autoExtract: MemoryExtractionPolicy;
55
+ consolidate: MemoryConsolidationPolicy;
56
+ }
57
+ /** The `memory` section of a config file. Every field is optional. */
58
+ export interface MemoryConfig {
59
+ /** Project-level notes directory, relative to the project root. */
60
+ dir?: string;
61
+ maxNotes?: number;
62
+ maxNoteBytes?: number;
63
+ indexChars?: number;
64
+ recall?: Partial<MemoryRecallPolicy>;
65
+ autoExtract?: Partial<MemoryExtractionPolicy>;
66
+ consolidate?: Partial<MemoryConsolidationPolicy>;
67
+ }
68
+ export declare const DEFAULT_MEMORY_POLICY: MemoryPolicy;
69
+ /** Defences. Not configurable. */
70
+ export declare const MEMORY_SAFETY: {
71
+ readonly idPattern: RegExp;
72
+ readonly reservedIds: readonly string[];
73
+ readonly maxSummaryChars: 500;
74
+ readonly maxTags: 30;
75
+ readonly tagPattern: RegExp;
76
+ /** A configured note size is clamped to this. */
77
+ readonly maxNoteBytesCeiling: number;
78
+ /** Room for frontmatter on top of the body when reading a file. */
79
+ readonly fileOverheadBytes: number;
80
+ /** A configured note count is clamped to this. */
81
+ readonly maxNotesCeiling: 50000;
82
+ /** Largest sync response a client will buffer. */
83
+ readonly syncResponseBytes: number;
84
+ /** Serialized size of one commit request, below the server's body limit. */
85
+ readonly syncCommitBatchBytes: number;
86
+ /** Changes per commit when the server has not announced a quota yet. */
87
+ readonly syncCommitBatchChanges: 500;
88
+ /** Parallel stats when scanning a notes directory. */
89
+ readonly statConcurrency: 32;
90
+ /** Notes shown to an extractor in full, and so the only ones it may update. */
91
+ readonly extractEditableNotes: 8;
92
+ /** A note longer than this is shown by summary only, and cannot be updated by extraction. */
93
+ readonly extractEditableBodyChars: 4000;
94
+ /** Characters of note bodies one extraction request may carry. */
95
+ readonly extractBodyBudgetChars: 20000;
96
+ };
97
+ /** Defaults filled in and every number clamped to something that cannot break a run. */
98
+ export declare function resolveMemoryPolicy(config: MemoryConfig | undefined): MemoryPolicy;
99
+ /**
100
+ * Whether background merging runs. An explicit true or false is the user's
101
+ * choice; `"subscribers"` follows the plan the sync service last announced,
102
+ * so an account whose plan is unknown (sync off, not signed in) stays off.
103
+ */
104
+ export declare function consolidationEnabled(setting: MemoryConsolidationPolicy["auto"], tiers: ReadonlyArray<string | undefined>): boolean;
105
+ export declare function isMemoryType(value: unknown): value is MemoryType;
106
+ export declare function isMemoryScope(value: unknown): value is MemoryScope;
107
+ /**
108
+ * Where a note of this type goes when nobody said.
109
+ *
110
+ * Only facts about the user themselves default to every project. Feedback
111
+ * usually names this codebase's conventions, and a correction recalled in an
112
+ * unrelated repository is noise at best and wrong at worst; one that really
113
+ * holds everywhere is put in the user scope on purpose.
114
+ */
115
+ export declare function defaultScopeFor(type: MemoryType | undefined): MemoryScope;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Every number the memory subsystem runs on, in one place.
3
+ *
4
+ * Three kinds live here and they are kept apart on purpose. Policy is the
5
+ * user's to change and arrives through `memory` in config. Safety bounds are
6
+ * not configurable, because each one is a defence rather than a preference.
7
+ * Sync quotas are neither: they belong to whoever runs the sync server, which
8
+ * announces them in its responses, so no client-side copy exists at all.
9
+ */
10
+ export const MEMORY_TYPES = ["user", "feedback", "project", "reference"];
11
+ export const MEMORY_SCOPES = ["project", "user"];
12
+ export const DEFAULT_MEMORY_POLICY = {
13
+ maxNotes: 5_000,
14
+ maxNoteBytes: 128 * 1024,
15
+ indexChars: 4_000,
16
+ recall: { enabled: true, perTurn: 3, budgetChars: 2_000, minScore: 1.2 },
17
+ // Off: extraction spends the user's own model credit on every long session.
18
+ autoExtract: { onCompaction: false, onStop: false, minUserTurns: 3, maxCandidates: 40 },
19
+ // On only for paid accounts: a merge spends model credit nobody asked to spend
20
+ // otherwise, and every merge it makes is backed up.
21
+ consolidate: { auto: "subscribers", cooldownHours: 24, maxMerges: 3, unusedDays: 90 },
22
+ };
23
+ /** Defences. Not configurable. */
24
+ export const MEMORY_SAFETY = {
25
+ idPattern: /^[a-zA-Z0-9_-]{1,128}$/,
26
+ reservedIds: ["__proto__", "constructor", "prototype"],
27
+ maxSummaryChars: 500,
28
+ maxTags: 30,
29
+ tagPattern: /^[^\s,\r\n]{1,64}$/,
30
+ /** A configured note size is clamped to this. */
31
+ maxNoteBytesCeiling: 1024 * 1024,
32
+ /** Room for frontmatter on top of the body when reading a file. */
33
+ fileOverheadBytes: 64 * 1024,
34
+ /** A configured note count is clamped to this. */
35
+ maxNotesCeiling: 50_000,
36
+ /** Largest sync response a client will buffer. */
37
+ syncResponseBytes: 64 * 1024 * 1024,
38
+ /** Serialized size of one commit request, below the server's body limit. */
39
+ syncCommitBatchBytes: 2 * 1024 * 1024,
40
+ /** Changes per commit when the server has not announced a quota yet. */
41
+ syncCommitBatchChanges: 500,
42
+ /** Parallel stats when scanning a notes directory. */
43
+ statConcurrency: 32,
44
+ /** Notes shown to an extractor in full, and so the only ones it may update. */
45
+ extractEditableNotes: 8,
46
+ /** A note longer than this is shown by summary only, and cannot be updated by extraction. */
47
+ extractEditableBodyChars: 4_000,
48
+ /** Characters of note bodies one extraction request may carry. */
49
+ extractBodyBudgetChars: 20_000,
50
+ };
51
+ const clampInt = (value, fallback, min, max) => typeof value === "number" && Number.isFinite(value) ? Math.min(max, Math.max(min, Math.floor(value))) : fallback;
52
+ const clampNumber = (value, fallback, min, max) => typeof value === "number" && Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;
53
+ const flag = (value, fallback) => (typeof value === "boolean" ? value : fallback);
54
+ /** Defaults filled in and every number clamped to something that cannot break a run. */
55
+ export function resolveMemoryPolicy(config) {
56
+ const d = DEFAULT_MEMORY_POLICY;
57
+ const recall = config?.recall ?? {};
58
+ const extract = config?.autoExtract ?? {};
59
+ const consolidate = config?.consolidate ?? {};
60
+ return {
61
+ maxNotes: clampInt(config?.maxNotes, d.maxNotes, 1, MEMORY_SAFETY.maxNotesCeiling),
62
+ maxNoteBytes: clampInt(config?.maxNoteBytes, d.maxNoteBytes, 1024, MEMORY_SAFETY.maxNoteBytesCeiling),
63
+ indexChars: clampInt(config?.indexChars, d.indexChars, 0, 100_000),
64
+ recall: {
65
+ enabled: flag(recall.enabled, d.recall.enabled),
66
+ perTurn: clampInt(recall.perTurn, d.recall.perTurn, 0, 20),
67
+ budgetChars: clampInt(recall.budgetChars, d.recall.budgetChars, 200, 50_000),
68
+ minScore: clampNumber(recall.minScore, d.recall.minScore, 0, 1_000),
69
+ },
70
+ autoExtract: {
71
+ onCompaction: flag(extract.onCompaction, d.autoExtract.onCompaction),
72
+ onStop: flag(extract.onStop, d.autoExtract.onStop),
73
+ minUserTurns: clampInt(extract.minUserTurns, d.autoExtract.minUserTurns, 1, 1_000),
74
+ maxCandidates: clampInt(extract.maxCandidates, d.autoExtract.maxCandidates, 0, 500),
75
+ },
76
+ consolidate: {
77
+ auto: consolidate.auto === "subscribers" ? "subscribers" : flag(consolidate.auto, d.consolidate.auto),
78
+ cooldownHours: clampNumber(consolidate.cooldownHours, d.consolidate.cooldownHours, 1, 24 * 365),
79
+ maxMerges: clampInt(consolidate.maxMerges, d.consolidate.maxMerges, 0, 50),
80
+ unusedDays: clampInt(consolidate.unusedDays, d.consolidate.unusedDays, 1, 3650),
81
+ },
82
+ };
83
+ }
84
+ /**
85
+ * Whether background merging runs. An explicit true or false is the user's
86
+ * choice; `"subscribers"` follows the plan the sync service last announced,
87
+ * so an account whose plan is unknown (sync off, not signed in) stays off.
88
+ */
89
+ export function consolidationEnabled(setting, tiers) {
90
+ return setting === "subscribers" ? tiers.includes("paid") : setting;
91
+ }
92
+ export function isMemoryType(value) {
93
+ return typeof value === "string" && MEMORY_TYPES.includes(value);
94
+ }
95
+ export function isMemoryScope(value) {
96
+ return typeof value === "string" && MEMORY_SCOPES.includes(value);
97
+ }
98
+ /**
99
+ * Where a note of this type goes when nobody said.
100
+ *
101
+ * Only facts about the user themselves default to every project. Feedback
102
+ * usually names this codebase's conventions, and a correction recalled in an
103
+ * unrelated repository is noise at best and wrong at worst; one that really
104
+ * holds everywhere is put in the user scope on purpose.
105
+ */
106
+ export function defaultScopeFor(type) {
107
+ return type === "user" ? "user" : "project";
108
+ }
109
+ //# sourceMappingURL=memory-limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-limits.js","sourceRoot":"","sources":["../../src/agent/memory-limits.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `[[note-id]]` links between memory notes.
3
+ *
4
+ * Links live only in note bodies. Nothing about them is stored in frontmatter
5
+ * or sent to the sync server separately, so the graph is always derived from
6
+ * what the notes say and can never disagree with them.
7
+ */
8
+ /** Link targets in order of first appearance, ignoring code. */
9
+ export declare function extractLinks(body: string): string[];
10
+ export interface LinkGraph {
11
+ /** Resolved targets each note links to. */
12
+ outgoing: Map<string, string[]>;
13
+ /** Notes that link to each note. */
14
+ incoming: Map<string, string[]>;
15
+ /** Targets each note links to that no note has. */
16
+ dangling: Map<string, string[]>;
17
+ }
18
+ export declare function buildLinkGraph(notes: ReadonlyArray<{
19
+ id: string;
20
+ body: string;
21
+ }>): LinkGraph;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `[[note-id]]` links between memory notes.
3
+ *
4
+ * Links live only in note bodies. Nothing about them is stored in frontmatter
5
+ * or sent to the sync server separately, so the graph is always derived from
6
+ * what the notes say and can never disagree with them.
7
+ */
8
+ const LINK = /\[\[([a-zA-Z0-9_-]{1,128})\]\]/g;
9
+ const FENCE = /^(```|~~~)[^\n]*\n[\s\S]*?^\1[ \t]*$/gm;
10
+ const INLINE_CODE = /`[^`\n]*`/g;
11
+ /** Link targets in order of first appearance, ignoring code. */
12
+ export function extractLinks(body) {
13
+ const prose = body.replace(FENCE, "").replace(INLINE_CODE, "");
14
+ const seen = new Set();
15
+ for (const match of prose.matchAll(LINK))
16
+ seen.add(match[1]);
17
+ return [...seen];
18
+ }
19
+ export function buildLinkGraph(notes) {
20
+ const ids = new Set(notes.map((note) => note.id));
21
+ const graph = { outgoing: new Map(), incoming: new Map(), dangling: new Map() };
22
+ for (const note of notes) {
23
+ const resolved = [];
24
+ const missing = [];
25
+ for (const target of extractLinks(note.body)) {
26
+ if (target === note.id)
27
+ continue;
28
+ if (ids.has(target))
29
+ resolved.push(target);
30
+ else
31
+ missing.push(target);
32
+ }
33
+ if (resolved.length)
34
+ graph.outgoing.set(note.id, resolved);
35
+ if (missing.length)
36
+ graph.dangling.set(note.id, missing);
37
+ for (const target of resolved) {
38
+ const list = graph.incoming.get(target) ?? [];
39
+ list.push(note.id);
40
+ graph.incoming.set(target, list);
41
+ }
42
+ }
43
+ return graph;
44
+ }
45
+ //# sourceMappingURL=memory-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-links.js","sourceRoot":"","sources":["../../src/agent/memory-links.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,IAAI,GAAG,iCAAiC,CAAC;AAC/C,MAAM,KAAK,GAAG,wCAAwC,CAAC;AACvD,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,gEAAgE;AAChE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IAC9D,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC;AAWD,MAAM,UAAU,cAAc,CAAC,KAAkD;IAC/E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAc,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;IAC3F,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,IAAI,MAAM,KAAK,IAAI,CAAC,EAAE;gBAAE,SAAS;YACjC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;;gBACtC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM;YAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC3D,IAAI,OAAO,CAAC,MAAM;YAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACzD,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC9C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnB,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { MemoryNote } from "./memory.ts";
2
+ /**
3
+ * Combining two edits of one note, against the version both started from.
4
+ *
5
+ * Sync calls this when a note changed on this machine and in the cloud since
6
+ * the last sync. Changes that do not touch are combined; changes that do are
7
+ * reported by field and left to a person. Nothing here guesses: two different
8
+ * summaries, or two edits to neighbouring lines, are a conflict rather than a
9
+ * choice made on someone's behalf. Pure functions, so every rule is a test.
10
+ */
11
+ export type MergeField = "summary" | "type" | "body" | "content";
12
+ export type MergeResult = {
13
+ note: MemoryNote;
14
+ } | {
15
+ conflict: MergeField[];
16
+ };
17
+ /**
18
+ * Tags as sets: what either side added is kept, what either side removed is
19
+ * gone. Order follows this machine's list, then the cloud's additions.
20
+ */
21
+ export declare function mergeTags(base: readonly string[], local: readonly string[], cloud: readonly string[]): string[];
22
+ /**
23
+ * A three-way merge of note bodies by line.
24
+ *
25
+ * Changes on the two sides combine when they are apart. Overlapping changes,
26
+ * changes to adjacent lines and insertions at the same place conflict, unless
27
+ * both sides made exactly the same change. Returns undefined on a conflict.
28
+ */
29
+ export declare function mergeLines(base: string, local: string, cloud: string): string | undefined;
30
+ /**
31
+ * Merge two edits of a note. Without the last synced version there is nothing
32
+ * to tell an edit from an original, so that is a conflict too.
33
+ */
34
+ export declare function mergeNotes(base: MemoryNote | undefined, local: MemoryNote, cloud: MemoryNote): MergeResult;