@mastra/server 1.73.1-alpha.1 → 1.75.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{dist-DzTVFzGu.js → dist-DH39C5Bz.js} +1096 -552
- package/dist/dist-DH39C5Bz.js.map +1 -0
- package/dist/{dist-CxDKuckB.cjs → dist-IDt748Ya.cjs} +1096 -552
- package/dist/dist-IDt748Ya.cjs.map +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/server/handlers/agent-builder.cjs +1 -1
- package/dist/server/handlers/agent-builder.js +1 -1
- package/dist/server/handlers/responses.adapter.cjs +3 -2
- package/dist/server/handlers/responses.adapter.cjs.map +1 -1
- package/dist/server/handlers/responses.adapter.d.ts.map +1 -1
- package/dist/server/handlers/responses.adapter.js +3 -2
- package/dist/server/handlers/responses.adapter.js.map +1 -1
- package/package.json +8 -8
- package/dist/dist-CxDKuckB.cjs.map +0 -1
- package/dist/dist-DzTVFzGu.js.map +0 -1
|
@@ -4637,7 +4637,7 @@ function splitLines(text) {
|
|
|
4637
4637
|
return result;
|
|
4638
4638
|
}
|
|
4639
4639
|
//#endregion
|
|
4640
|
-
//#region ../memory/dist/src-
|
|
4640
|
+
//#region ../memory/dist/src-BV9eXuMM.js
|
|
4641
4641
|
var __defProp$3 = Object.defineProperty;
|
|
4642
4642
|
var __exportAll = (all, no_symbols) => {
|
|
4643
4643
|
let target = {};
|
|
@@ -22961,6 +22961,8 @@ const OBSERVATION_CONTEXT_INSTRUCTIONS = `IMPORTANT: When responding, reference
|
|
|
22961
22961
|
|
|
22962
22962
|
KNOWLEDGE UPDATES: When asked about current state (e.g., "where do I currently...", "what is my current..."), always prefer the MOST RECENT information. Observations include dates - if you see conflicting information, the newer observation supersedes the older one. Look for phrases like "will start", "is switching", "changed to", "moved to" as indicators that previous information has been updated.
|
|
22963
22963
|
|
|
22964
|
+
USER STATEMENTS VS ASSISTANT SUGGESTIONS: Treat what the user said about their own life, plans, decisions, and dates as authoritative, unless data or their own later messages say otherwise. Treat what the assistant said, such as proposed schedules, example dates, and recommendations, as suggestions rather than facts about what happened, unless the user adopted or confirmed them. What the assistant did, such as editing a file, running a command, or calling a tool, is a record of what happened.
|
|
22965
|
+
|
|
22964
22966
|
PLANNED ACTIONS: If the user stated they planned to do something (e.g., "I'm going to...", "I'm looking forward to...", "I will...") and the date they planned to do it is now in the past (check the relative time like "3 weeks ago"), assume they completed the action unless there's evidence they didn't. For example, if someone said "I'll start my new diet on Monday" and that was 2 weeks ago, assume they started the diet.
|
|
22965
22967
|
|
|
22966
22968
|
MOST RECENT USER INPUT: Treat the most recent user message as the highest-priority signal for what to do next. Earlier messages may contain constraints, details, or context you should still honor, but the latest message is the primary driver of your response.
|
|
@@ -22981,30 +22983,19 @@ SYSTEM REMINDERS: Messages wrapped in <system-reminder>...</system-reminder> con
|
|
|
22981
22983
|
* @param searchEnabled - Whether semantic search (`retrieval: { vector: true }`) is
|
|
22982
22984
|
* available. When false, the guidance only covers \`threads\`/\`messages\` browsing so
|
|
22983
22985
|
* the agent is not steered toward a mode that cannot work.
|
|
22986
|
+
* @param observationPagingEnabled - Whether the storage adapter supports original
|
|
22987
|
+
* observation-group paging. Only used when search is enabled.
|
|
22984
22988
|
*/
|
|
22985
|
-
function getRetrievalInstructions(scope = "resource", customInstructions, searchEnabled = true) {
|
|
22989
|
+
function getRetrievalInstructions(scope = "resource", customInstructions, searchEnabled = true, observationPagingEnabled = searchEnabled) {
|
|
22986
22990
|
const isResource = scope === "resource";
|
|
22987
|
-
const
|
|
22988
|
-
|
|
22989
|
-
Your memory is comprised of observations which are sometimes wrapped in <observation-group> xml tags containing ranges like <observation-group range="startId:endId">. These ranges point back to the raw messages that each observation group was derived from. The original messages are still available — use the **recall** tool to retrieve them.
|
|
22990
|
-
|
|
22991
|
-
### When to use recall
|
|
22992
|
-
- The user asks you to **repeat, show, or reproduce** something from a past conversation
|
|
22993
|
-
- The user asks for **exact content** — code, text, quotes, error messages, URLs, file paths, specific numbers
|
|
22994
|
-
- Your observations mention something but your memory lacks the detail needed to fully answer (e.g. you know a blog post was shared but only have a summary of it)
|
|
22995
|
-
- You want to **verify or expand on** an observation before responding${isResource ? `
|
|
22996
|
-
- The user references another conversation that your observations don't cover — even if you have no observations yet, their other threads may contain it` : ""}
|
|
22997
|
-
|
|
22998
|
-
**Default to using recall when the user references specific past content.** Your observations capture the gist, not the details. If there's any doubt whether your memory is complete enough, use recall.
|
|
22999
|
-
|
|
23000
|
-
${isResource ? searchEnabled ? `### Choosing a mode
|
|
22991
|
+
const modeSection = isResource ? searchEnabled ? `### Choosing a mode
|
|
23001
22992
|
The recall tool works across ALL of this user's conversation threads, not just the current one.
|
|
23002
22993
|
|
|
23003
|
-
- Use \`mode: "search"\` with a \`query\` when you
|
|
23004
|
-
- Use \`mode: "messages"\`
|
|
22994
|
+
- Use \`mode: "search"\` with a \`query\` to find relevant history, even when you already know the thread. Each result includes its thread ID, observation group ID, and source message range.
|
|
22995
|
+
- Use \`mode: "messages"\` for original wording or details behind an observation — pass \`threadId\` to read another thread and a \`cursor\` from the source message range.
|
|
23005
22996
|
- Use \`mode: "threads"\` to list the user's threads (IDs, titles, dates) when you need to discover where something was discussed. Use \`before\`/\`after\` to narrow by date.
|
|
23006
22997
|
|
|
23007
|
-
**If search results look irrelevant, do not give up.** Search only covers content that has been indexed — a short or recent conversation may exist in raw message history before any observation of it was created. When search returns nothing suitable but the user is clearly referring to a past conversation, call \`mode: "threads"\` to find candidate threads (titles and dates are strong clues), then read them with \`mode: "messages"\`.
|
|
22998
|
+
**If search results look irrelevant, do not give up.** Search only covers content that has been indexed — a short or recent conversation may exist in raw message history before any observation of it was created. When search returns nothing suitable but the user is clearly referring to a past conversation, call \`mode: "threads"\` to find candidate threads (titles and dates are strong clues), then read them with \`mode: "messages"\`. Use a useful hit as an entry point to the surrounding history rather than treating it as the whole answer.` : `### Choosing a mode
|
|
23008
22999
|
The recall tool works across ALL of this user's conversation threads, not just the current one.
|
|
23009
23000
|
|
|
23010
23001
|
- Use \`mode: "threads"\` to list the user's threads (IDs, titles, dates) when you need to discover where something was discussed. Use \`before\`/\`after\` to narrow by date.
|
|
@@ -23015,7 +23006,62 @@ The recall tool is limited to the current conversation thread.
|
|
|
23015
23006
|
|
|
23016
23007
|
- Use \`mode: "messages"\` (default) to page through this thread's message history near a cursor.${searchEnabled ? `
|
|
23017
23008
|
- Use \`mode: "search"\` with a \`query\` to find messages by content within this thread.` : ""}
|
|
23018
|
-
- Use \`mode: "threads"\` to get the current thread's ID, title, and dates
|
|
23009
|
+
- Use \`mode: "threads"\` to get the current thread's ID, title, and dates.`;
|
|
23010
|
+
const pagingEnabled = searchEnabled && observationPagingEnabled;
|
|
23011
|
+
const lookupSteps = [
|
|
23012
|
+
`**Search to locate.** Run a few differently worded queries rather than relying on one. When you roughly know when something happened, or need events from different periods, repeat the search with \`after\`/\`before\` date windows. Date filters apply to when an observation was recorded, not necessarily to dates mentioned inside it, so widen or remove them when nothing fits. If nothing useful comes up, try the user's message verbatim as the search query.`,
|
|
23013
|
+
...pagingEnabled ? [`**Page observations for context.** Open a relevant hit with \`mode: "observations"\` and its \`groupId\` to read the full group and the dated conclusions and decisions around it, without loading every tool call or diff. Do this for truncated excerpts, for dates you need to pin down, and for events that unfolded over several turns. To understand what led to an event and what followed, page both before and after the anchor.`] : [],
|
|
23014
|
+
`**Read source messages to confirm.** When exact wording, numbers, code, who said what, or conflicting accounts matter, read the raw messages from the hit's source range. The range connects the summary view to the raw-message view.`
|
|
23015
|
+
];
|
|
23016
|
+
const searchSection = searchEnabled ? `### Finding evidence
|
|
23017
|
+
Search matches are entry points, not a complete timeline. Similarity selects the matches; they are displayed by observation date. A missing search hit is not evidence that an event did not happen.
|
|
23018
|
+
|
|
23019
|
+
${pagingEnabled ? "Observations and messages are two views of the same conversation history, not separate archives. Use search to find an entry point, observations for breadth, and messages for depth:" : "Use search to find an entry point and messages for depth:"}
|
|
23020
|
+
${lookupSteps.map((step, i) => `${i + 1}. ${step}`).join("\n")}
|
|
23021
|
+
|
|
23022
|
+
When searches keep returning the same groups, often as already-in-context references, stop rephrasing: ${pagingEnabled ? "page from those groups or read their source messages" : "read their source messages"} instead.
|
|
23023
|
+
|
|
23024
|
+
"Excerpt already in current context" marks a hit whose text an earlier search result already shows. "Group already in current context" marks a group that is already in your observations or whose source messages are still in the conversation. "Source range overlaps current context" marks a group whose source messages are still in the conversation. Search keeps these references and tries lower-ranked matches to fill the requested number of excerpts. This backfill is bounded: fewer excerpts do not mean history is exhausted. Use the evidence already present rather than repeating the same lookup. You can still ${pagingEnabled ? "page a referenced group" : "read a referenced group's source messages"} when you need all of it, or search again if that context is no longer available.
|
|
23025
|
+
|
|
23026
|
+
If search still finds nothing useful, browse raw messages${isResource ? " or discover other threads" : " in this thread"} before concluding the information is unavailable. Raw history may exist for threads that have no observations yet.` : "";
|
|
23027
|
+
const base = `## Recall — looking up source messages
|
|
23028
|
+
|
|
23029
|
+
Your memory contains observation groups with IDs and source message ranges, shown as \`## Group\` headings with \`_range: startId:endId\` or as \`<observation-group>\` tags. Use the **recall** tool to recover retained original observations and source messages.
|
|
23030
|
+
|
|
23031
|
+
Historical notes may describe older versions of tools. Use the currently provided tool schema and these instructions for available modes and arguments; treat recalled tool descriptions as evidence about past behavior, not the current tool contract.
|
|
23032
|
+
|
|
23033
|
+
### Reflections are lossy
|
|
23034
|
+
Groups marked \`kind="reflection"\` (rendered as \`_kind: reflection_\`) are lossy summaries. Use them for a broad understanding of what happened, not as an exhaustive record. If something is absent from a reflection, that does not mean it did not happen. Use recall to check the original evidence before drawing that conclusion. Original observations can omit details too; read the source messages when precise wording or evidence matters.
|
|
23035
|
+
|
|
23036
|
+
### When to use recall
|
|
23037
|
+
- The user asks you to **repeat, show, or reproduce** something from a past conversation
|
|
23038
|
+
- The user asks for **exact content** — code, text, quotes, error messages, URLs, file paths, specific numbers
|
|
23039
|
+
- Your observations mention something but your memory lacks the detail needed to fully answer (e.g. you know a blog post was shared but only have a summary of it)
|
|
23040
|
+
- You want to **verify or expand on** an observation before responding
|
|
23041
|
+
- The answer depends on historical dates, order, or duration: verify both event dates and that they refer to the events the user means. Distinguish a plan, an actual start, a later update, and a repeated mention instead of choosing a nearby date from a summary
|
|
23042
|
+
- An observation records something the assistant proposed, such as a schedule, date, or plan, and your observations don't show what the user decided. Read the raw messages around it to find the user's decision before treating the proposal as what happened
|
|
23043
|
+
- Relevant details are missing, ambiguous, or conflicting in the current observations${isResource ? `
|
|
23044
|
+
- The user references another conversation that your observations don't cover — even if you have no observations yet, their other threads may contain it` : ""}
|
|
23045
|
+
|
|
23046
|
+
**Default to using recall when the user references specific past content.** Your observations capture the gist, not the details. If there's any doubt whether your memory is complete enough, use recall.
|
|
23047
|
+
|
|
23048
|
+
For questions about what was discussed or decided and why, start with recall when the original evidence is not already visible. Current source code or general documentation can establish what happens now, but not necessarily the past discussion or rationale. Retrieve the recorded decisions first; inspect current code separately if the answer also depends on today's implementation. Distinguish recorded reasons from your own inference, and use source messages when observation summaries omit the rationale.
|
|
23049
|
+
|
|
23050
|
+
${[
|
|
23051
|
+
modeSection,
|
|
23052
|
+
searchSection,
|
|
23053
|
+
pagingEnabled ? `### Paging original observations
|
|
23054
|
+
Use \`mode: "observations"\` around a relevant search hit to fill in missing details, check earlier or later events, and recover context omitted from a reflection or search result.
|
|
23055
|
+
|
|
23056
|
+
- Copy the hit's \`groupId\`${isResource ? " and `threadId`" : ""} exactly as shown. Omit \`direction\` to read the full anchor group and following groups, including text truncated in search.
|
|
23057
|
+
- Use \`direction: "before"\` or \`direction: "after"\` to read groups strictly before or after that anchor. Pages contain 5 groups by default; \`limit\` allows up to 20. Dense groups can be large; use \`limit: 1\` or \`limit: 2\` for a quick skim.
|
|
23058
|
+
- Follow the returned continuation calls, using the first or last group ID on each page as the next anchor. \`groupId\` is an observation cursor; \`cursor\` is a raw message ID. Do not interchange them.
|
|
23059
|
+
- \`hasMore\` reports whether more groups exist in the requested direction (after when direction is omitted). A full page can still have \`hasMore: false\`; stop in that direction without making an empty follow-up call. Explicit start/end markers describe retained observation history, not whether older raw messages exist. When newer messages have not been observed yet, the end marker says how many and gives the \`mode: "messages"\` call that reads them.
|
|
23060
|
+
- Search results mark where other observation groups may sit between two hits from the same thread. Page from those hits instead of repeatedly searching for the same isolated hit.
|
|
23061
|
+
- These pages contain original observations, not reflections. For exact wording or details absent from the observations, use \`mode: "messages"\` with a message ID from the group's \`_range\`.
|
|
23062
|
+
|
|
23063
|
+
Stop once the relevant evidence is sufficient. If retained history is incomplete, say what is unknown rather than guessing.` : ""
|
|
23064
|
+
].filter(Boolean).join("\n\n")}
|
|
23019
23065
|
|
|
23020
23066
|
### How to use recall with a cursor
|
|
23021
23067
|
Each range has the format \`startId:endId\` where both are message IDs separated by a colon.
|
|
@@ -23026,8 +23072,8 @@ Each range has the format \`startId:endId\` where both are message IDs separated
|
|
|
23026
23072
|
4. If the first page doesn't have what you need, increment the page number to keep paginating.
|
|
23027
23073
|
5. Check \`hasNextPage\`/\`hasPrevPage\` in the result to know if more pages exist in each direction.
|
|
23028
23074
|
|
|
23029
|
-
###
|
|
23030
|
-
|
|
23075
|
+
### Raw-message detail levels
|
|
23076
|
+
In \`mode: "messages"\`, recall returns **low** detail by default: truncated text and tool names only. Each message shows its ID and each part has a positional index like \`[p0]\`, \`[p1]\`, etc.
|
|
23031
23077
|
|
|
23032
23078
|
- Use \`detail: "high"\` to get full message content including tool arguments and results. This will only return the high detail version of a single message part at a time.
|
|
23033
23079
|
- Use \`partIndex\` with a cursor to fetch a single part at full detail — for example, to read one specific tool result or code block without loading every part.
|
|
@@ -23044,8 +23090,8 @@ If a single part is larger than the token budget, the \`partIndex\` result is \`
|
|
|
23044
23090
|
|
|
23045
23091
|
### When recall is NOT needed
|
|
23046
23092
|
- The user is asking for a high-level summary and your observations already cover it
|
|
23047
|
-
- The question is
|
|
23048
|
-
|
|
23093
|
+
- The question is general knowledge that doesn't depend on this user's history
|
|
23094
|
+
- The necessary original evidence is already visible, unambiguous, and not contradicted by other observations; do not repeat a lookup just to call the tool
|
|
23049
23095
|
|
|
23050
23096
|
Observation groups with range IDs and your recall tool allows you to think back and remember details you're fuzzy on.`;
|
|
23051
23097
|
const custom = customInstructions?.trim();
|
|
@@ -23688,6 +23734,10 @@ function calendarDayIn(instant, timeZone) {
|
|
|
23688
23734
|
const part = (type) => Number(parts.find((p) => p.type === type)?.value);
|
|
23689
23735
|
return Date.UTC(part("year"), part("month") - 1, part("day")) / DAY_MS;
|
|
23690
23736
|
}
|
|
23737
|
+
/** Format an instant relative to today, using the record's calendar time zone. */
|
|
23738
|
+
function formatRelativeTime(date, currentDate, timeZone) {
|
|
23739
|
+
return formatRelativeDays(calendarDayIn(currentDate, timeZone) - calendarDayIn(date, timeZone));
|
|
23740
|
+
}
|
|
23691
23741
|
function formatRelativeDays(diffDays) {
|
|
23692
23742
|
if (diffDays < 0) {
|
|
23693
23743
|
const futureDays = Math.abs(diffDays);
|
|
@@ -28263,6 +28313,676 @@ var WorkingMemoryExtractor = class extends Extractor {
|
|
|
28263
28313
|
});
|
|
28264
28314
|
}
|
|
28265
28315
|
};
|
|
28316
|
+
/**
|
|
28317
|
+
* Find the index of the last completed observation boundary (end marker) in a message's parts.
|
|
28318
|
+
* Returns -1 if no completed observation is found.
|
|
28319
|
+
*/
|
|
28320
|
+
function findLastCompletedObservationBoundary(message) {
|
|
28321
|
+
const parts = message.content?.parts;
|
|
28322
|
+
if (!parts || !Array.isArray(parts)) return -1;
|
|
28323
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
28324
|
+
const part = parts[i];
|
|
28325
|
+
if (part?.type === "data-om-observation-end" && part.data?.operationType !== "reflection") return i;
|
|
28326
|
+
}
|
|
28327
|
+
return -1;
|
|
28328
|
+
}
|
|
28329
|
+
/**
|
|
28330
|
+
* Get unobserved parts from a message.
|
|
28331
|
+
* If the message has a completed observation (start + end), only return parts after the end.
|
|
28332
|
+
* If observation is in progress (start without end), include parts before the start.
|
|
28333
|
+
* Otherwise, return all parts.
|
|
28334
|
+
*/
|
|
28335
|
+
function getUnobservedParts(message) {
|
|
28336
|
+
const parts = message.content?.parts;
|
|
28337
|
+
if (!parts || !Array.isArray(parts)) return [];
|
|
28338
|
+
const endMarkerIndex = findLastCompletedObservationBoundary(message);
|
|
28339
|
+
if (endMarkerIndex === -1) return parts.filter((p) => {
|
|
28340
|
+
return p?.type !== "data-om-observation-start";
|
|
28341
|
+
});
|
|
28342
|
+
return parts.slice(endMarkerIndex + 1).filter((p) => {
|
|
28343
|
+
return !p?.type?.startsWith("data-om-observation-");
|
|
28344
|
+
});
|
|
28345
|
+
}
|
|
28346
|
+
/**
|
|
28347
|
+
* Compute a cursor pointing at the latest message by createdAt.
|
|
28348
|
+
* Used to derive a stable observation boundary for replay pruning.
|
|
28349
|
+
*/
|
|
28350
|
+
function getLastObservedMessageCursor(messages) {
|
|
28351
|
+
let latest;
|
|
28352
|
+
for (const msg of messages) {
|
|
28353
|
+
if (!msg?.id || !msg.createdAt) continue;
|
|
28354
|
+
if (!latest || new Date(msg.createdAt).getTime() > new Date(latest.createdAt).getTime()) latest = msg;
|
|
28355
|
+
}
|
|
28356
|
+
return latest ? {
|
|
28357
|
+
createdAt: new Date(latest.createdAt).toISOString(),
|
|
28358
|
+
id: latest.id
|
|
28359
|
+
} : void 0;
|
|
28360
|
+
}
|
|
28361
|
+
/**
|
|
28362
|
+
* Check if a message is at or before a cursor (by createdAt then id).
|
|
28363
|
+
*/
|
|
28364
|
+
function isMessageAtOrBeforeCursor(msg, cursor) {
|
|
28365
|
+
if (!msg.createdAt) return false;
|
|
28366
|
+
const msgIso = new Date(msg.createdAt).toISOString();
|
|
28367
|
+
if (msgIso < cursor.createdAt) return true;
|
|
28368
|
+
if (msgIso === cursor.createdAt && msg.id === cursor.id) return true;
|
|
28369
|
+
return false;
|
|
28370
|
+
}
|
|
28371
|
+
/**
|
|
28372
|
+
* Return the toolCallId of a tool-invocation part in the given state, or undefined.
|
|
28373
|
+
*/
|
|
28374
|
+
function getToolInvocationId(part, state) {
|
|
28375
|
+
if (part?.type !== "tool-invocation") return void 0;
|
|
28376
|
+
if (part.toolInvocation.state !== state) return void 0;
|
|
28377
|
+
return part.toolInvocation.toolCallId || void 0;
|
|
28378
|
+
}
|
|
28379
|
+
function collectPendingToolResultIds(allMessages, observedIds) {
|
|
28380
|
+
const pendingToolResultIds = /* @__PURE__ */ new Set();
|
|
28381
|
+
for (const msg of allMessages) {
|
|
28382
|
+
if (!msg?.id) continue;
|
|
28383
|
+
if (observedIds?.has(msg.id)) continue;
|
|
28384
|
+
const parts = msg.content?.parts;
|
|
28385
|
+
if (!parts || !Array.isArray(parts)) continue;
|
|
28386
|
+
for (const part of parts) {
|
|
28387
|
+
const id = getToolInvocationId(part, "result");
|
|
28388
|
+
if (id) pendingToolResultIds.add(id);
|
|
28389
|
+
}
|
|
28390
|
+
}
|
|
28391
|
+
return pendingToolResultIds;
|
|
28392
|
+
}
|
|
28393
|
+
function hasPendingToolResultPair(message, pendingToolResultIds) {
|
|
28394
|
+
if (pendingToolResultIds.size === 0) return false;
|
|
28395
|
+
const parts = message.content?.parts;
|
|
28396
|
+
if (!parts || !Array.isArray(parts)) return false;
|
|
28397
|
+
return parts.some((part) => {
|
|
28398
|
+
const id = getToolInvocationId(part, "call");
|
|
28399
|
+
return id !== void 0 && pendingToolResultIds.has(id);
|
|
28400
|
+
});
|
|
28401
|
+
}
|
|
28402
|
+
function collectPendingToolResultIdsAfterMarker(allMessages, markerMessageIndex, markerMessage) {
|
|
28403
|
+
const pendingToolResultIds = /* @__PURE__ */ new Set();
|
|
28404
|
+
const unobservedMarkerParts = getUnobservedParts(markerMessage);
|
|
28405
|
+
if (unobservedMarkerParts.length > 0) collectPendingToolResultIds([{
|
|
28406
|
+
...markerMessage,
|
|
28407
|
+
content: {
|
|
28408
|
+
...markerMessage.content,
|
|
28409
|
+
parts: unobservedMarkerParts
|
|
28410
|
+
}
|
|
28411
|
+
}], void 0).forEach((id) => pendingToolResultIds.add(id));
|
|
28412
|
+
collectPendingToolResultIds(allMessages.slice(markerMessageIndex + 1), void 0).forEach((id) => pendingToolResultIds.add(id));
|
|
28413
|
+
return pendingToolResultIds;
|
|
28414
|
+
}
|
|
28415
|
+
function getUnobservedPartsPreservingToolCallPairs(message) {
|
|
28416
|
+
const parts = message.content?.parts;
|
|
28417
|
+
if (!parts || !Array.isArray(parts)) return [];
|
|
28418
|
+
const endMarkerIndex = findLastCompletedObservationBoundary(message);
|
|
28419
|
+
if (endMarkerIndex === -1) return getUnobservedParts(message);
|
|
28420
|
+
const unobservedParts = getUnobservedParts(message);
|
|
28421
|
+
const unobservedResultIds = /* @__PURE__ */ new Set();
|
|
28422
|
+
for (const part of unobservedParts) {
|
|
28423
|
+
const id = getToolInvocationId(part, "result");
|
|
28424
|
+
if (id) unobservedResultIds.add(id);
|
|
28425
|
+
}
|
|
28426
|
+
if (unobservedResultIds.size === 0) return unobservedParts;
|
|
28427
|
+
const preservedCalls = parts.slice(0, endMarkerIndex).filter((part) => {
|
|
28428
|
+
const id = getToolInvocationId(part, "call");
|
|
28429
|
+
return id !== void 0 && unobservedResultIds.has(id);
|
|
28430
|
+
});
|
|
28431
|
+
return preservedCalls.length > 0 ? [...preservedCalls, ...unobservedParts] : unobservedParts;
|
|
28432
|
+
}
|
|
28433
|
+
/**
|
|
28434
|
+
* Get the messages Observational Memory is allowed to work with.
|
|
28435
|
+
*
|
|
28436
|
+
* Messages supplied through the `context` option and the synthetic `om-continuation`
|
|
28437
|
+
* message are prompt-only input. Core's persistence contract already treats context as
|
|
28438
|
+
* never-persist, while the continuation remains memory-sourced so it can stay in the live
|
|
28439
|
+
* actor prompt.
|
|
28440
|
+
*
|
|
28441
|
+
* OM builds its windows from `get.all.db()`, which includes both categories, and then seals
|
|
28442
|
+
* and persists candidates directly. Excluding them here keeps OM's observation, buffering,
|
|
28443
|
+
* persistence, and token accounting consistent with their ephemeral contract.
|
|
28444
|
+
*/
|
|
28445
|
+
function getObservableMessages(messageList) {
|
|
28446
|
+
const contextMessageIds = messageList.makeMessageSourceChecker().context;
|
|
28447
|
+
return messageList.get.all.db().filter((message) => {
|
|
28448
|
+
return message.id !== "om-continuation" && !contextMessageIds.has(message.id);
|
|
28449
|
+
});
|
|
28450
|
+
}
|
|
28451
|
+
/**
|
|
28452
|
+
* Safely extract buffered observation chunks from a record.
|
|
28453
|
+
* Handles both array and JSON-string formats, returning empty array if malformed.
|
|
28454
|
+
*/
|
|
28455
|
+
/**
|
|
28456
|
+
* Filter out already-observed messages from the in-memory context.
|
|
28457
|
+
* Uses marker-boundary pruning (preferred) or record-based fallback.
|
|
28458
|
+
*
|
|
28459
|
+
* The `fallbackCursor` is optional — callers that need it should resolve it
|
|
28460
|
+
* from thread metadata before calling this function.
|
|
28461
|
+
*/
|
|
28462
|
+
function filterObservedMessages(opts) {
|
|
28463
|
+
const { messageList, record } = opts;
|
|
28464
|
+
const allMessages = getObservableMessages(messageList);
|
|
28465
|
+
const useMarkerBoundaryPruning = opts.useMarkerBoundaryPruning ?? true;
|
|
28466
|
+
const preserveMessageIds = opts.preserveMessageIds ?? /* @__PURE__ */ new Set();
|
|
28467
|
+
const observedIds = new Set(Array.isArray(record?.observedMessageIds) ? record.observedMessageIds : []);
|
|
28468
|
+
let markerMessageIndex = -1;
|
|
28469
|
+
let markerMessage = null;
|
|
28470
|
+
for (let i = allMessages.length - 1; i >= 0; i--) {
|
|
28471
|
+
const msg = allMessages[i];
|
|
28472
|
+
if (!msg) continue;
|
|
28473
|
+
if (findLastCompletedObservationBoundary(msg) !== -1) {
|
|
28474
|
+
markerMessageIndex = i;
|
|
28475
|
+
markerMessage = msg;
|
|
28476
|
+
break;
|
|
28477
|
+
}
|
|
28478
|
+
}
|
|
28479
|
+
if (useMarkerBoundaryPruning && markerMessage && markerMessageIndex !== -1) {
|
|
28480
|
+
const pendingToolResultIds = collectPendingToolResultIdsAfterMarker(allMessages, markerMessageIndex, markerMessage);
|
|
28481
|
+
const messagesToRemove = [];
|
|
28482
|
+
for (let i = 0; i < markerMessageIndex; i++) {
|
|
28483
|
+
const msg = allMessages[i];
|
|
28484
|
+
if (msg?.id && msg.id !== "om-continuation" && !preserveMessageIds.has(msg.id) && !hasPendingToolResultPair(msg, pendingToolResultIds)) messagesToRemove.push(msg.id);
|
|
28485
|
+
}
|
|
28486
|
+
if (messagesToRemove.length > 0) messageList.removeByIds(messagesToRemove);
|
|
28487
|
+
const unobserved = getUnobservedPartsPreservingToolCallPairs(markerMessage);
|
|
28488
|
+
if (unobserved.length === 0) {
|
|
28489
|
+
if (markerMessage.id) messageList.removeByIds([markerMessage.id]);
|
|
28490
|
+
} else if (unobserved.length < (markerMessage.content?.parts?.length ?? 0)) markerMessage.content.parts = unobserved;
|
|
28491
|
+
} else if (record) {
|
|
28492
|
+
const pendingToolResultIds = collectPendingToolResultIds(allMessages, observedIds);
|
|
28493
|
+
const derivedCursor = opts.fallbackCursor ?? getLastObservedMessageCursor(allMessages.filter((msg) => !!msg?.id && observedIds.has(msg.id) && !!msg.createdAt));
|
|
28494
|
+
const lastObservedAt = record.lastObservedAt;
|
|
28495
|
+
const messagesToRemove = [];
|
|
28496
|
+
for (const msg of allMessages) {
|
|
28497
|
+
if (!msg?.id || msg.id === "om-continuation" || preserveMessageIds.has(msg.id)) continue;
|
|
28498
|
+
const hasPendingPair = hasPendingToolResultPair(msg, pendingToolResultIds);
|
|
28499
|
+
if (observedIds.has(msg.id)) {
|
|
28500
|
+
if (hasPendingPair) continue;
|
|
28501
|
+
messagesToRemove.push(msg.id);
|
|
28502
|
+
continue;
|
|
28503
|
+
}
|
|
28504
|
+
if (derivedCursor && isMessageAtOrBeforeCursor(msg, derivedCursor)) {
|
|
28505
|
+
if (hasPendingPair) continue;
|
|
28506
|
+
messagesToRemove.push(msg.id);
|
|
28507
|
+
continue;
|
|
28508
|
+
}
|
|
28509
|
+
if (lastObservedAt && msg.createdAt) {
|
|
28510
|
+
if (new Date(msg.createdAt) <= lastObservedAt) {
|
|
28511
|
+
if (hasPendingPair) continue;
|
|
28512
|
+
messagesToRemove.push(msg.id);
|
|
28513
|
+
}
|
|
28514
|
+
}
|
|
28515
|
+
}
|
|
28516
|
+
if (messagesToRemove.length > 0) messageList.removeByIds(messagesToRemove);
|
|
28517
|
+
}
|
|
28518
|
+
}
|
|
28519
|
+
function getBufferedChunks(record) {
|
|
28520
|
+
if (!record?.bufferedObservationChunks) return [];
|
|
28521
|
+
if (Array.isArray(record.bufferedObservationChunks)) return record.bufferedObservationChunks;
|
|
28522
|
+
if (typeof record.bufferedObservationChunks === "string") try {
|
|
28523
|
+
const parsed = JSON.parse(record.bufferedObservationChunks);
|
|
28524
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
28525
|
+
} catch {
|
|
28526
|
+
return [];
|
|
28527
|
+
}
|
|
28528
|
+
return [];
|
|
28529
|
+
}
|
|
28530
|
+
/**
|
|
28531
|
+
* Sort threads by their oldest unobserved message.
|
|
28532
|
+
* Returns thread IDs in order from oldest to most recent.
|
|
28533
|
+
*/
|
|
28534
|
+
/**
|
|
28535
|
+
* Combine active and buffered observations for the buffering observer context.
|
|
28536
|
+
*/
|
|
28537
|
+
function combineObservationsForBuffering(activeObservations, bufferedObservations) {
|
|
28538
|
+
if (!activeObservations && !bufferedObservations) return void 0;
|
|
28539
|
+
if (!activeObservations) return bufferedObservations;
|
|
28540
|
+
if (!bufferedObservations) return activeObservations;
|
|
28541
|
+
return `${activeObservations}\n\n--- BUFFERED (pending activation) ---\n\n${bufferedObservations}`;
|
|
28542
|
+
}
|
|
28543
|
+
function sortThreadsByOldestMessage(messagesByThread) {
|
|
28544
|
+
return Array.from(messagesByThread.entries()).map(([threadId, messages]) => ({
|
|
28545
|
+
threadId,
|
|
28546
|
+
oldestTimestamp: Math.min(...messages.map((m) => m.createdAt ? new Date(m.createdAt).getTime() : Date.now()))
|
|
28547
|
+
})).sort((a, b) => a.oldestTimestamp - b.oldestTimestamp).map((t) => t.threadId);
|
|
28548
|
+
}
|
|
28549
|
+
/**
|
|
28550
|
+
* Strip any thread tags that the Observer might have added.
|
|
28551
|
+
* Thread attribution is handled externally by the system, not by the Observer.
|
|
28552
|
+
*/
|
|
28553
|
+
function stripThreadTags(observations) {
|
|
28554
|
+
return observations.replace(/<\/?thread\b[^>]{0,1024}>/gi, "").trim();
|
|
28555
|
+
}
|
|
28556
|
+
const OBSERVATION_GROUP_OPEN = "<observation-group";
|
|
28557
|
+
const OBSERVATION_GROUP_CLOSE = "</observation-group>";
|
|
28558
|
+
const ATTRIBUTE_PATTERN = /([\w][\w-]*)="([^"]*)"/g;
|
|
28559
|
+
const REFLECTION_GROUP_SPLIT_PATTERN = /^##\s+Group\s+/m;
|
|
28560
|
+
function parseObservationGroupAttributes(attributeString) {
|
|
28561
|
+
const attributes = {};
|
|
28562
|
+
for (const match of attributeString.matchAll(ATTRIBUTE_PATTERN)) {
|
|
28563
|
+
const [, key, value] = match;
|
|
28564
|
+
if (key && value !== void 0) attributes[key] = value;
|
|
28565
|
+
}
|
|
28566
|
+
return attributes;
|
|
28567
|
+
}
|
|
28568
|
+
function isLineStart(observations, index) {
|
|
28569
|
+
return index === 0 || observations[index - 1] === "\n";
|
|
28570
|
+
}
|
|
28571
|
+
function scanObservationGroupTags(observations) {
|
|
28572
|
+
const tags = [];
|
|
28573
|
+
const incompleteOpenings = [];
|
|
28574
|
+
let cursor = 0;
|
|
28575
|
+
let closeFloor = 0;
|
|
28576
|
+
let nestedFloor = -1;
|
|
28577
|
+
while (cursor < observations.length) {
|
|
28578
|
+
const start = observations.indexOf(OBSERVATION_GROUP_OPEN, cursor);
|
|
28579
|
+
if (start === -1) break;
|
|
28580
|
+
const attributesStart = start + 18;
|
|
28581
|
+
if (!/\s/.test(observations[attributesStart] ?? "")) {
|
|
28582
|
+
cursor = attributesStart;
|
|
28583
|
+
continue;
|
|
28584
|
+
}
|
|
28585
|
+
const openEnd = observations.indexOf(">", attributesStart);
|
|
28586
|
+
if (openEnd === -1) break;
|
|
28587
|
+
const closeStart = observations.indexOf(OBSERVATION_GROUP_CLOSE, Math.max(openEnd + 1, closeFloor));
|
|
28588
|
+
if (closeStart === -1) {
|
|
28589
|
+
if (isLineStart(observations, start)) incompleteOpenings.push({
|
|
28590
|
+
start,
|
|
28591
|
+
end: openEnd + 1
|
|
28592
|
+
});
|
|
28593
|
+
closeFloor = observations.length;
|
|
28594
|
+
cursor = openEnd + 1;
|
|
28595
|
+
continue;
|
|
28596
|
+
}
|
|
28597
|
+
if (nestedFloor <= openEnd) {
|
|
28598
|
+
const lineOpen = observations.indexOf(`\n${OBSERVATION_GROUP_OPEN}`, openEnd);
|
|
28599
|
+
nestedFloor = lineOpen === -1 ? observations.length : lineOpen + 1;
|
|
28600
|
+
}
|
|
28601
|
+
const nestedStart = nestedFloor;
|
|
28602
|
+
if (nestedStart < closeStart) {
|
|
28603
|
+
incompleteOpenings.push({
|
|
28604
|
+
start,
|
|
28605
|
+
end: openEnd + 1
|
|
28606
|
+
});
|
|
28607
|
+
closeFloor = closeStart;
|
|
28608
|
+
cursor = nestedStart;
|
|
28609
|
+
continue;
|
|
28610
|
+
}
|
|
28611
|
+
tags.push({
|
|
28612
|
+
start,
|
|
28613
|
+
end: closeStart + 20,
|
|
28614
|
+
attributeString: observations.slice(attributesStart, openEnd),
|
|
28615
|
+
content: observations.slice(openEnd + 1, closeStart)
|
|
28616
|
+
});
|
|
28617
|
+
closeFloor = closeStart;
|
|
28618
|
+
cursor = closeStart + 20;
|
|
28619
|
+
}
|
|
28620
|
+
return {
|
|
28621
|
+
tags,
|
|
28622
|
+
incompleteOpenings
|
|
28623
|
+
};
|
|
28624
|
+
}
|
|
28625
|
+
function replaceObservationGroupTags(observations, replace) {
|
|
28626
|
+
const { tags, incompleteOpenings } = scanObservationGroupTags(observations);
|
|
28627
|
+
const parts = [];
|
|
28628
|
+
let cursor = 0;
|
|
28629
|
+
let openingIndex = 0;
|
|
28630
|
+
const skipIncompleteOpeningsBefore = (limit) => {
|
|
28631
|
+
while (openingIndex < incompleteOpenings.length && incompleteOpenings[openingIndex].start < limit) {
|
|
28632
|
+
const opening = incompleteOpenings[openingIndex];
|
|
28633
|
+
openingIndex++;
|
|
28634
|
+
if (opening.start < cursor) continue;
|
|
28635
|
+
parts.push(observations.slice(cursor, opening.start));
|
|
28636
|
+
cursor = opening.end;
|
|
28637
|
+
}
|
|
28638
|
+
};
|
|
28639
|
+
for (const tag of tags) {
|
|
28640
|
+
skipIncompleteOpeningsBefore(tag.start);
|
|
28641
|
+
parts.push(observations.slice(cursor, tag.start), replace(tag));
|
|
28642
|
+
cursor = tag.end;
|
|
28643
|
+
}
|
|
28644
|
+
skipIncompleteOpeningsBefore(Number.POSITIVE_INFINITY);
|
|
28645
|
+
parts.push(observations.slice(cursor));
|
|
28646
|
+
return parts.join("");
|
|
28647
|
+
}
|
|
28648
|
+
function parseReflectionObservationGroupSections(content) {
|
|
28649
|
+
const normalizedContent = content.trim();
|
|
28650
|
+
if (!normalizedContent || !REFLECTION_GROUP_SPLIT_PATTERN.test(normalizedContent)) return [];
|
|
28651
|
+
return normalizedContent.split(REFLECTION_GROUP_SPLIT_PATTERN).map((section) => section.trim()).filter(Boolean).map((section) => {
|
|
28652
|
+
const newlineIndex = section.indexOf("\n");
|
|
28653
|
+
return {
|
|
28654
|
+
heading: (newlineIndex >= 0 ? section.slice(0, newlineIndex) : section).trim(),
|
|
28655
|
+
body: stripReflectionGroupMetadata((newlineIndex >= 0 ? section.slice(newlineIndex + 1) : "").trim())
|
|
28656
|
+
};
|
|
28657
|
+
});
|
|
28658
|
+
}
|
|
28659
|
+
function stripReflectionGroupMetadata(body) {
|
|
28660
|
+
return body.replace(/^_range:\s*`[^`]*`_\s*\n?/m, "").trim();
|
|
28661
|
+
}
|
|
28662
|
+
function generateAnchorId() {
|
|
28663
|
+
return Buffer.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8))).toString("hex");
|
|
28664
|
+
}
|
|
28665
|
+
function wrapInObservationGroup(observations, range, id = generateAnchorId(), _sourceGroupIds, kind) {
|
|
28666
|
+
const content = observations.trim();
|
|
28667
|
+
return `<observation-group id="${id}" range="${range}"${kind ? ` kind="${kind}"` : ""}>\n${content}\n</observation-group>`;
|
|
28668
|
+
}
|
|
28669
|
+
function parseObservationGroups(observations) {
|
|
28670
|
+
if (!observations) return [];
|
|
28671
|
+
const groups = [];
|
|
28672
|
+
for (const tag of scanObservationGroupTags(observations).tags) {
|
|
28673
|
+
const attributes = parseObservationGroupAttributes(tag.attributeString);
|
|
28674
|
+
const id = attributes.id;
|
|
28675
|
+
const range = attributes.range;
|
|
28676
|
+
if (!id || !range) continue;
|
|
28677
|
+
groups.push({
|
|
28678
|
+
id,
|
|
28679
|
+
range,
|
|
28680
|
+
kind: attributes.kind,
|
|
28681
|
+
content: tag.content.trim()
|
|
28682
|
+
});
|
|
28683
|
+
}
|
|
28684
|
+
return groups;
|
|
28685
|
+
}
|
|
28686
|
+
function stripObservationGroups(observations) {
|
|
28687
|
+
if (!observations) return observations;
|
|
28688
|
+
return replaceObservationGroupTags(observations, (tag) => tag.content.trim()).replace(/\n{3,}/g, "\n\n").trim();
|
|
28689
|
+
}
|
|
28690
|
+
function getRangeSegments(range) {
|
|
28691
|
+
return range.split(",").map((segment) => segment.trim()).filter(Boolean);
|
|
28692
|
+
}
|
|
28693
|
+
function getRangeEndpoints(segment) {
|
|
28694
|
+
const parts = segment.split(":").map((part) => part.trim());
|
|
28695
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
28696
|
+
return {
|
|
28697
|
+
start: parts[0],
|
|
28698
|
+
end: parts[1]
|
|
28699
|
+
};
|
|
28700
|
+
}
|
|
28701
|
+
/**
|
|
28702
|
+
* `buildMessageRange` produces opaque UUID endpoints, so those segments cannot be ordered by value.
|
|
28703
|
+
* Spanning from the first start to the last end keeps reflected metadata bounded — the full segment
|
|
28704
|
+
* list is deliberately not preserved (#14791). Segments that are not `start:end` pairs are left as
|
|
28705
|
+
* they are rather than being joined into a fabricated span.
|
|
28706
|
+
*/
|
|
28707
|
+
function spanRangeSegments(segments) {
|
|
28708
|
+
const endpoints = segments.map(getRangeEndpoints);
|
|
28709
|
+
if (endpoints.some((endpoint) => endpoint === null)) return segments.join(",");
|
|
28710
|
+
const first = endpoints[0];
|
|
28711
|
+
const last = endpoints.at(-1);
|
|
28712
|
+
return `${first.start}:${last.end}`;
|
|
28713
|
+
}
|
|
28714
|
+
function combineObservationGroupRanges(groups) {
|
|
28715
|
+
const segments = Array.from(new Set(groups.flatMap((group) => getRangeSegments(group.range))));
|
|
28716
|
+
if (segments.length === 0) return "";
|
|
28717
|
+
const numericEndpoints = [];
|
|
28718
|
+
if (!segments.every((segment) => {
|
|
28719
|
+
const endpoints = getRangeEndpoints(segment);
|
|
28720
|
+
if (!endpoints || !/^\d+$/.test(endpoints.start) || !/^\d+$/.test(endpoints.end)) return false;
|
|
28721
|
+
const start = {
|
|
28722
|
+
label: endpoints.start,
|
|
28723
|
+
value: Number(endpoints.start)
|
|
28724
|
+
};
|
|
28725
|
+
const end = {
|
|
28726
|
+
label: endpoints.end,
|
|
28727
|
+
value: Number(endpoints.end)
|
|
28728
|
+
};
|
|
28729
|
+
if (!Number.isSafeInteger(start.value) || !Number.isSafeInteger(end.value)) return false;
|
|
28730
|
+
numericEndpoints.push(start.value <= end.value ? {
|
|
28731
|
+
start,
|
|
28732
|
+
end
|
|
28733
|
+
} : {
|
|
28734
|
+
start: end,
|
|
28735
|
+
end: start
|
|
28736
|
+
});
|
|
28737
|
+
return true;
|
|
28738
|
+
})) return spanRangeSegments(segments);
|
|
28739
|
+
const first = numericEndpoints.reduce((lowest, pair) => pair.start.value < lowest.start.value ? pair : lowest);
|
|
28740
|
+
const last = numericEndpoints.reduce((highest, pair) => pair.end.value > highest.end.value ? pair : highest);
|
|
28741
|
+
return `${first.start.label}:${last.end.label}`;
|
|
28742
|
+
}
|
|
28743
|
+
function renderObservationGroupsForReflection(observations, options) {
|
|
28744
|
+
if (parseObservationGroups(observations).length === 0) return null;
|
|
28745
|
+
return replaceObservationGroupTags(observations, (tag) => {
|
|
28746
|
+
const attributes = parseObservationGroupAttributes(tag.attributeString);
|
|
28747
|
+
if (!attributes.id || !attributes.range) return tag.content.trim();
|
|
28748
|
+
const kind = options?.includeReflectionKind && attributes.kind === "reflection" ? "\n_kind: reflection_" : "";
|
|
28749
|
+
return `## Group \`${attributes.id}\`${kind}\n_range: \`${attributes.range}\`_\n\n${tag.content.trim()}`;
|
|
28750
|
+
}).replace(/\n{3,}/g, "\n\n").trim();
|
|
28751
|
+
}
|
|
28752
|
+
function getCanonicalGroupId(sectionHeading, fallbackIndex) {
|
|
28753
|
+
return sectionHeading.match(/`([^`]+)`/)?.[1]?.trim() || `derived-group-${fallbackIndex + 1}`;
|
|
28754
|
+
}
|
|
28755
|
+
function getContentLines(content) {
|
|
28756
|
+
return content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
28757
|
+
}
|
|
28758
|
+
function deriveObservationGroupProvenance(content, groups) {
|
|
28759
|
+
const sections = parseReflectionObservationGroupSections(content);
|
|
28760
|
+
if (sections.length === 0 || groups.length === 0) return [];
|
|
28761
|
+
return sections.map((section, index) => {
|
|
28762
|
+
const canonicalGroupId = getCanonicalGroupId(section.heading, index);
|
|
28763
|
+
const identifiedGroup = groups.find((group) => group.id === canonicalGroupId);
|
|
28764
|
+
const bodyLines = new Set(getContentLines(section.body));
|
|
28765
|
+
const matchingGroups = groups.filter((group) => getContentLines(group.content).some((line) => bodyLines.has(line)));
|
|
28766
|
+
const fallbackGroup = groups[Math.min(index, groups.length - 1)];
|
|
28767
|
+
const identifiedLines = new Set(getContentLines(identifiedGroup?.content ?? ""));
|
|
28768
|
+
const contributingGroups = identifiedGroup ? matchingGroups.filter((group) => group.id !== identifiedGroup.id && getContentLines(group.content).some((line) => bodyLines.has(line) && !identifiedLines.has(line))) : [];
|
|
28769
|
+
return {
|
|
28770
|
+
id: canonicalGroupId,
|
|
28771
|
+
range: combineObservationGroupRanges(identifiedGroup ? groups.filter((group) => group === identifiedGroup || contributingGroups.includes(group)) : matchingGroups.length > 0 ? matchingGroups : fallbackGroup ? [fallbackGroup] : []),
|
|
28772
|
+
kind: "reflection",
|
|
28773
|
+
content: section.body
|
|
28774
|
+
};
|
|
28775
|
+
});
|
|
28776
|
+
}
|
|
28777
|
+
function reconcileObservationGroupsFromReflection(content, sourceObservations) {
|
|
28778
|
+
const sourceGroups = parseObservationGroups(sourceObservations);
|
|
28779
|
+
if (sourceGroups.length === 0) return null;
|
|
28780
|
+
const normalizedContent = content.trim();
|
|
28781
|
+
if (!normalizedContent) return "";
|
|
28782
|
+
const derivedGroups = deriveObservationGroupProvenance(normalizedContent, sourceGroups);
|
|
28783
|
+
if (derivedGroups.length > 0) return derivedGroups.map((group) => wrapInObservationGroup(group.content, group.range, group.id, void 0, group.kind)).join("\n\n");
|
|
28784
|
+
return wrapInObservationGroup(normalizedContent, combineObservationGroupRanges(sourceGroups), generateAnchorId(), void 0, "reflection");
|
|
28785
|
+
}
|
|
28786
|
+
const hasher = e();
|
|
28787
|
+
/** Thrown when storage returns history records without applying the requested filters. */
|
|
28788
|
+
var HistoryFiltersIgnoredError = class extends Error {};
|
|
28789
|
+
const HISTORY_FILTERS_IGNORED = "Observation paging is unavailable: the storage adapter returned observation history without applying the history filters (group, record, and generation). If you use Convex, redeploy the Mastra server functions; otherwise upgrade the storage adapter together with @mastra/memory. Use mode=\"messages\" with a message ID from the search hit's source range instead.";
|
|
28790
|
+
function rawText(record) {
|
|
28791
|
+
return [record.activeObservations, ...getBufferedChunks(record).map((chunk) => chunk.observations)].join("\n");
|
|
28792
|
+
}
|
|
28793
|
+
async function buildTimeline(record, threadId) {
|
|
28794
|
+
let text = rawText(record);
|
|
28795
|
+
if (record.threadId === null) {
|
|
28796
|
+
const obscuredId = (await hasher).h32ToString(threadId);
|
|
28797
|
+
text = [...text.matchAll(/<thread id="([^"]+)">([\s\S]*?)<\/thread>/g)].filter((match) => match[1] === threadId || match[1] === obscuredId).map((match) => match[2]).join("\n");
|
|
28798
|
+
}
|
|
28799
|
+
const groups = parseObservationGroups(text).filter((group) => group.kind !== "reflection");
|
|
28800
|
+
const indexById = /* @__PURE__ */ new Map();
|
|
28801
|
+
return {
|
|
28802
|
+
record,
|
|
28803
|
+
groups: groups.filter((group) => {
|
|
28804
|
+
if (indexById.has(group.id)) return false;
|
|
28805
|
+
indexById.set(group.id, indexById.size);
|
|
28806
|
+
return true;
|
|
28807
|
+
}),
|
|
28808
|
+
indexById
|
|
28809
|
+
};
|
|
28810
|
+
}
|
|
28811
|
+
async function findGroupTimeline(om, threadId, resourceId, groupId, recordId) {
|
|
28812
|
+
const resolve = async (options) => {
|
|
28813
|
+
const [record] = await om.getHistory(threadId, resourceId, 1, {
|
|
28814
|
+
...options,
|
|
28815
|
+
groupId
|
|
28816
|
+
});
|
|
28817
|
+
if (!record || record.threadId !== null && record.threadId !== threadId) return null;
|
|
28818
|
+
if (!rawText(record).includes(`<observation-group id="${groupId}"`)) throw new HistoryFiltersIgnoredError();
|
|
28819
|
+
const timeline = await buildTimeline(record, threadId);
|
|
28820
|
+
return timeline.indexById.has(groupId) ? timeline : null;
|
|
28821
|
+
};
|
|
28822
|
+
return recordId !== void 0 && await resolve({ recordId }) || await resolve({ sortDirection: "ASC" });
|
|
28823
|
+
}
|
|
28824
|
+
/** Group IDs shown to the agent carry the record holding the group, so paging can read it by ID. */
|
|
28825
|
+
function groupCursor(groupId, recordId) {
|
|
28826
|
+
return recordId ? `${groupId}@${recordId}` : groupId;
|
|
28827
|
+
}
|
|
28828
|
+
function parseGroupCursor(cursor) {
|
|
28829
|
+
const at = cursor.indexOf("@");
|
|
28830
|
+
return at === -1 ? { groupId: cursor } : {
|
|
28831
|
+
groupId: cursor.slice(0, at),
|
|
28832
|
+
recordId: cursor.slice(at + 1) || void 0
|
|
28833
|
+
};
|
|
28834
|
+
}
|
|
28835
|
+
function pagingCall(groupId, direction, threadId, recordId) {
|
|
28836
|
+
return `recall(${JSON.stringify({
|
|
28837
|
+
mode: "observations",
|
|
28838
|
+
threadId,
|
|
28839
|
+
groupId: groupCursor(groupId, recordId),
|
|
28840
|
+
direction
|
|
28841
|
+
})})`;
|
|
28842
|
+
}
|
|
28843
|
+
/** Messages after the last observed range are raw history only; say so instead of implying the thread ends. */
|
|
28844
|
+
async function newerMessagesNote(group, countNewerMessages, threadId) {
|
|
28845
|
+
const endpoints = group?.range.split(",").at(-1)?.split(":");
|
|
28846
|
+
const cursor = endpoints?.length === 2 ? endpoints[1] : void 0;
|
|
28847
|
+
if (!cursor || !countNewerMessages) return null;
|
|
28848
|
+
const count = await countNewerMessages(cursor);
|
|
28849
|
+
if (count <= 0) return null;
|
|
28850
|
+
const call = `recall(${JSON.stringify({
|
|
28851
|
+
mode: "messages",
|
|
28852
|
+
threadId,
|
|
28853
|
+
cursor
|
|
28854
|
+
})})`;
|
|
28855
|
+
return count === 1 ? `1 newer message has not been observed yet; read it with ${call}` : `${count} newer messages have not been observed yet; read them with ${call}`;
|
|
28856
|
+
}
|
|
28857
|
+
async function pageObservationGroups(options) {
|
|
28858
|
+
try {
|
|
28859
|
+
return await pageGroups(options);
|
|
28860
|
+
} catch (error) {
|
|
28861
|
+
if (error instanceof HistoryFiltersIgnoredError) return {
|
|
28862
|
+
results: HISTORY_FILTERS_IGNORED,
|
|
28863
|
+
count: 0
|
|
28864
|
+
};
|
|
28865
|
+
throw error;
|
|
28866
|
+
}
|
|
28867
|
+
}
|
|
28868
|
+
async function pageGroups({ om, threadId, resourceId, groupId, recordId, direction: requestedDirection, limit, threadTitle, includeThreadId = true, countNewerMessages }) {
|
|
28869
|
+
const home = await findGroupTimeline(om, threadId, resourceId, groupId, recordId);
|
|
28870
|
+
if (!home) return {
|
|
28871
|
+
results: `No original observation group ${JSON.stringify(groupId)} was found in the active or buffered observations of thread ${JSON.stringify(threadId)} across its retained history. Check the threadId and groupId from the search hit, or use mode="messages" with a message ID from its source range.`,
|
|
28872
|
+
count: 0
|
|
28873
|
+
};
|
|
28874
|
+
const direction = requestedDirection ?? "after";
|
|
28875
|
+
let current = home;
|
|
28876
|
+
let position = home.indexById.get(groupId) - (requestedDirection === void 0 ? 1 : 0);
|
|
28877
|
+
const seen = new Set((direction === "after" ? home.groups.slice(0, position + 1) : home.groups.slice(position)).map((group) => group.id));
|
|
28878
|
+
const entries = [];
|
|
28879
|
+
while (entries.length <= limit) {
|
|
28880
|
+
position += direction === "after" ? 1 : -1;
|
|
28881
|
+
const group = current.groups[position];
|
|
28882
|
+
if (group) {
|
|
28883
|
+
if (!seen.has(group.id)) {
|
|
28884
|
+
seen.add(group.id);
|
|
28885
|
+
entries.push({
|
|
28886
|
+
group,
|
|
28887
|
+
record: current.record
|
|
28888
|
+
});
|
|
28889
|
+
}
|
|
28890
|
+
continue;
|
|
28891
|
+
}
|
|
28892
|
+
const generation = current.record.generationCount;
|
|
28893
|
+
const [record] = await om.getHistory(threadId, resourceId, 1, direction === "after" ? {
|
|
28894
|
+
afterGeneration: generation,
|
|
28895
|
+
sortDirection: "ASC"
|
|
28896
|
+
} : {
|
|
28897
|
+
beforeGeneration: generation,
|
|
28898
|
+
sortDirection: "DESC"
|
|
28899
|
+
});
|
|
28900
|
+
if (!record) break;
|
|
28901
|
+
if ((direction === "after" ? record.generationCount <= generation : record.generationCount >= generation) || record.threadId !== null && record.threadId !== threadId) throw new HistoryFiltersIgnoredError();
|
|
28902
|
+
current = await buildTimeline(record, threadId);
|
|
28903
|
+
position = direction === "after" ? -1 : current.groups.length;
|
|
28904
|
+
}
|
|
28905
|
+
const hasMore = entries.length > limit;
|
|
28906
|
+
const page = entries.slice(0, limit);
|
|
28907
|
+
if (direction === "before") page.reverse();
|
|
28908
|
+
const pagingThreadId = includeThreadId ? threadId : void 0;
|
|
28909
|
+
if (!page.length) {
|
|
28910
|
+
const note = direction === "after" ? await newerMessagesNote(home.groups[home.indexById.get(groupId)], countNewerMessages, pagingThreadId) : null;
|
|
28911
|
+
return {
|
|
28912
|
+
results: `No ${direction === "before" ? "earlier" : "later"} original observation groups in this thread's retained history.${note ? ` ${note}.` : ""}`,
|
|
28913
|
+
count: 0,
|
|
28914
|
+
hasMore: false
|
|
28915
|
+
};
|
|
28916
|
+
}
|
|
28917
|
+
const now = /* @__PURE__ */ new Date();
|
|
28918
|
+
const text = page.map(({ group, record }) => [
|
|
28919
|
+
`## Group \`${group.id}\``,
|
|
28920
|
+
`_range: \`${group.range}\`_`,
|
|
28921
|
+
addRelativeTimeToObservations(group.content, now, record.observedTimezone ?? void 0)
|
|
28922
|
+
].join("\n"));
|
|
28923
|
+
const hasEarlier = direction === "before" ? hasMore : requestedDirection !== void 0 || home.indexById.get(groupId) > 0 || home.record.generationCount > 0 && (await pageGroups({
|
|
28924
|
+
om,
|
|
28925
|
+
threadId,
|
|
28926
|
+
resourceId,
|
|
28927
|
+
groupId,
|
|
28928
|
+
recordId: home.record.id,
|
|
28929
|
+
direction: "before",
|
|
28930
|
+
limit: 1
|
|
28931
|
+
})).count > 0;
|
|
28932
|
+
const hasLater = direction === "before" || hasMore;
|
|
28933
|
+
const newer = hasLater ? null : await newerMessagesNote(page.at(-1).group, countNewerMessages, pagingThreadId);
|
|
28934
|
+
text.unshift(hasEarlier ? `— Browse earlier: ${pagingCall(page[0].group.id, "before", pagingThreadId, page[0].record.id)} —` : "— Start of retained observation history for this thread. —");
|
|
28935
|
+
text.push(hasLater ? `— Browse later: ${pagingCall(page.at(-1).group.id, "after", pagingThreadId, page.at(-1).record.id)} —` : newer ? `— End of observation history for this thread. ${newer} —` : "— End of retained observation history for this thread. —");
|
|
28936
|
+
text.unshift(`### Observation page\nThread: ${threadTitle || "(untitled)"}\nShowing ${page.length} groups ${requestedDirection === void 0 ? "starting at" : `strictly ${direction}`} \`${groupId}\` (oldest first).`);
|
|
28937
|
+
return {
|
|
28938
|
+
results: text.join("\n\n"),
|
|
28939
|
+
count: page.length,
|
|
28940
|
+
hasMore
|
|
28941
|
+
};
|
|
28942
|
+
}
|
|
28943
|
+
function searchContextKey(match) {
|
|
28944
|
+
return JSON.stringify([match.threadId, match.groupId]);
|
|
28945
|
+
}
|
|
28946
|
+
/** Read only real recall results, not quoted tool output in user or assistant text. */
|
|
28947
|
+
function getVisibleSearchExcerpts(messages) {
|
|
28948
|
+
const excerpts = /* @__PURE__ */ new Map();
|
|
28949
|
+
for (const message of messages) {
|
|
28950
|
+
if (message.role !== "assistant") continue;
|
|
28951
|
+
for (const part of message.content.parts) {
|
|
28952
|
+
if (part.type !== "tool-invocation") continue;
|
|
28953
|
+
const invocation = part.toolInvocation;
|
|
28954
|
+
if (invocation.toolName !== "recall" || invocation.state !== "result" || invocation.args?.mode !== "search") continue;
|
|
28955
|
+
const resolved = resolveToolResultValue(part, invocation.result);
|
|
28956
|
+
let value = resolved.value;
|
|
28957
|
+
if (resolved.usingStoredModelOutput) {
|
|
28958
|
+
if (!value || typeof value !== "object" || !("type" in value) || value.type !== "json" || !("value" in value)) continue;
|
|
28959
|
+
value = value.value;
|
|
28960
|
+
}
|
|
28961
|
+
if (!value || typeof value !== "object" || !("results" in value) || typeof value.results !== "string") continue;
|
|
28962
|
+
for (const section of value.results.split(/^### (?:Current thread memory|Memory from another thread)\s*$/m)) {
|
|
28963
|
+
const threadId = /^- thread: (\S+)/m.exec(section)?.[1];
|
|
28964
|
+
const groupId = (/^- observation group: ([^\n]+)$/m.exec(section)?.[1])?.split("@")[0];
|
|
28965
|
+
const excerpt = /\n```text\n([\s\S]*?)\n```/.exec(section)?.[1];
|
|
28966
|
+
if (!threadId || !groupId || !excerpt) continue;
|
|
28967
|
+
const key = searchContextKey({
|
|
28968
|
+
threadId,
|
|
28969
|
+
groupId
|
|
28970
|
+
});
|
|
28971
|
+
const previous = excerpts.get(key) ?? [];
|
|
28972
|
+
previous.push(excerpt);
|
|
28973
|
+
excerpts.set(key, previous);
|
|
28974
|
+
}
|
|
28975
|
+
}
|
|
28976
|
+
}
|
|
28977
|
+
return excerpts;
|
|
28978
|
+
}
|
|
28979
|
+
function sourceRangeOverlapsContext({ match, messages }) {
|
|
28980
|
+
if (!match.groupId || !match.range) return false;
|
|
28981
|
+
const endpoints = /^([^:,]+):([^:,]+)$/.exec(match.range);
|
|
28982
|
+
if (!endpoints) return false;
|
|
28983
|
+
const visible = new Set(messages.filter((message) => message.threadId === match.threadId).map((message) => message.id));
|
|
28984
|
+
return visible.has(endpoints[1]) && visible.has(endpoints[2]);
|
|
28985
|
+
}
|
|
28266
28986
|
function getMessageParts(msg) {
|
|
28267
28987
|
if (typeof msg.content === "string") return [];
|
|
28268
28988
|
if (Array.isArray(msg.content)) return msg.content;
|
|
@@ -28389,7 +29109,7 @@ async function listThreadsForResource({ memory, resourceId, currentThreadId, pag
|
|
|
28389
29109
|
};
|
|
28390
29110
|
}
|
|
28391
29111
|
const SEARCH_NOT_CONFIGURED_MESSAGE = "Search is not configured. Enable it with `retrieval: { vector: true }` and configure a vector store and embedder on your Memory instance.";
|
|
28392
|
-
async function searchMessagesForResource({ memory, resourceId, currentThreadId, query, topK = 10, maxTokens = DEFAULT_MAX_RESULT_TOKENS, before, after, threadScope }) {
|
|
29112
|
+
async function searchMessagesForResource({ memory, om, resourceId, currentThreadId, query, topK = 10, maxTokens = DEFAULT_MAX_RESULT_TOKENS, before, after, threadScope, includeThreadId = true, currentMessages = [] }) {
|
|
28393
29113
|
if (!memory.searchMessages) return {
|
|
28394
29114
|
results: SEARCH_NOT_CONFIGURED_MESSAGE,
|
|
28395
29115
|
count: 0
|
|
@@ -28400,15 +29120,16 @@ async function searchMessagesForResource({ memory, resourceId, currentThreadId,
|
|
|
28400
29120
|
const searchTopK = Math.min(MAX_TOPK, effectiveTopK);
|
|
28401
29121
|
const beforeDate = before ? new Date(before) : void 0;
|
|
28402
29122
|
const afterDate = after ? new Date(after) : void 0;
|
|
29123
|
+
const filter = {
|
|
29124
|
+
...threadScope ? { threadId: threadScope } : {},
|
|
29125
|
+
...afterDate ? { observedAfter: afterDate } : {},
|
|
29126
|
+
...beforeDate ? { observedBefore: beforeDate } : {}
|
|
29127
|
+
};
|
|
28403
29128
|
const { results } = await memory.searchMessages({
|
|
28404
29129
|
query,
|
|
28405
29130
|
resourceId,
|
|
28406
29131
|
topK: searchTopK,
|
|
28407
|
-
filter
|
|
28408
|
-
...threadScope ? { threadId: threadScope } : {},
|
|
28409
|
-
...afterDate ? { observedAfter: afterDate } : {},
|
|
28410
|
-
...beforeDate ? { observedBefore: beforeDate } : {}
|
|
28411
|
-
}
|
|
29132
|
+
filter
|
|
28412
29133
|
});
|
|
28413
29134
|
if (results.length === 0) return {
|
|
28414
29135
|
results: "No matching messages found.",
|
|
@@ -28420,47 +29141,169 @@ async function searchMessagesForResource({ memory, resourceId, currentThreadId,
|
|
|
28420
29141
|
const thread = await memory.getThreadById({ threadId: id });
|
|
28421
29142
|
if (thread) threadMap.set(id, thread);
|
|
28422
29143
|
}));
|
|
28423
|
-
const
|
|
29144
|
+
const matchesFilter = (match) => {
|
|
28424
29145
|
if (threadScope && match.threadId !== threadScope) return false;
|
|
28425
29146
|
if (beforeDate && match.observedAt && match.observedAt >= beforeDate) return false;
|
|
28426
29147
|
if (afterDate && match.observedAt && match.observedAt <= afterDate) return false;
|
|
28427
29148
|
return true;
|
|
28428
|
-
}
|
|
29149
|
+
};
|
|
29150
|
+
const filteredMatches = results.filter(matchesFilter);
|
|
28429
29151
|
if (filteredMatches.length === 0) return {
|
|
28430
29152
|
results: "No matching messages found.",
|
|
28431
29153
|
count: 0
|
|
28432
29154
|
};
|
|
28433
29155
|
const limitedMatches = filteredMatches.slice(0, clampedTopK);
|
|
28434
|
-
const
|
|
29156
|
+
const contentBudget = Math.max(0, Math.floor(maxTokens));
|
|
29157
|
+
const perGroupBudget = Math.floor(contentBudget / limitedMatches.length);
|
|
29158
|
+
const now = /* @__PURE__ */ new Date();
|
|
29159
|
+
const terms = excerptQueryTerms(query);
|
|
29160
|
+
const setExcerpt = (entry, budget) => {
|
|
29161
|
+
const selected = selectSearchExcerpt(entry.body, terms, budget);
|
|
29162
|
+
entry.excerpt = selected.text;
|
|
29163
|
+
entry.excerptContent = selected.content;
|
|
29164
|
+
};
|
|
29165
|
+
const [currentRecord] = om && currentThreadId ? await om.getHistory(currentThreadId, resourceId, 1) : [];
|
|
29166
|
+
const timeZone = resolveTimeZone(currentRecord?.observedTimezone ?? void 0);
|
|
29167
|
+
const prepareEntry = (match, index, budget) => {
|
|
29168
|
+
const rawBody = (match.text || "").trim() || "_Observation text unavailable._";
|
|
29169
|
+
const entry = {
|
|
29170
|
+
match,
|
|
29171
|
+
index,
|
|
29172
|
+
body: match.groupId ? addRelativeTimeToObservations(rawBody, now, timeZone) : rawBody,
|
|
29173
|
+
excerpt: "",
|
|
29174
|
+
excerptContent: "",
|
|
29175
|
+
suppression: ""
|
|
29176
|
+
};
|
|
29177
|
+
setExcerpt(entry, budget);
|
|
29178
|
+
return entry;
|
|
29179
|
+
};
|
|
29180
|
+
const ordered = limitedMatches.map((match, index) => prepareEntry(match, index, perGroupBudget));
|
|
29181
|
+
let remaining = contentBudget - ordered.reduce((sum, entry) => sum + estimateTokenCount(entry.excerpt), 0);
|
|
29182
|
+
for (const entry of ordered) {
|
|
29183
|
+
if (remaining <= 0) break;
|
|
29184
|
+
if (entry.excerpt === entry.body) continue;
|
|
29185
|
+
const previousTokens = estimateTokenCount(entry.excerpt);
|
|
29186
|
+
setExcerpt(entry, previousTokens + remaining);
|
|
29187
|
+
remaining -= estimateTokenCount(entry.excerpt) - previousTokens;
|
|
29188
|
+
}
|
|
29189
|
+
const visibleExcerpts = getVisibleSearchExcerpts(currentMessages);
|
|
29190
|
+
const contextGroupIds = /* @__PURE__ */ new Set();
|
|
29191
|
+
for (const observations of [currentRecord?.activeObservations ?? "", ...getBufferedChunks(currentRecord).map((chunk) => chunk.observations)]) for (const group of parseObservationGroups(observations)) contextGroupIds.add(group.id);
|
|
29192
|
+
const suppressCoveredEntry = (entry) => {
|
|
29193
|
+
if (!entry.match.groupId) return;
|
|
29194
|
+
if (entry.excerptContent && visibleExcerpts.get(searchContextKey(entry.match))?.some((text) => text.includes(entry.excerptContent))) entry.suppression = "Excerpt already in current context.";
|
|
29195
|
+
else if (contextGroupIds.has(entry.match.groupId)) entry.suppression = "Group already in current context.";
|
|
29196
|
+
else if (sourceRangeOverlapsContext({
|
|
29197
|
+
match: entry.match,
|
|
29198
|
+
messages: currentMessages
|
|
29199
|
+
})) entry.suppression = "Source range overlaps current context.";
|
|
29200
|
+
if (entry.suppression) {
|
|
29201
|
+
entry.excerpt = "";
|
|
29202
|
+
entry.excerptContent = "";
|
|
29203
|
+
}
|
|
29204
|
+
};
|
|
29205
|
+
ordered.forEach(suppressCoveredEntry);
|
|
29206
|
+
let freshCount = ordered.filter((entry) => !entry.suppression).length;
|
|
29207
|
+
if (freshCount < ordered.length && contentBudget > 0) {
|
|
29208
|
+
const backfillBudget = Math.floor(contentBudget / clampedTopK);
|
|
29209
|
+
for (const entry of ordered) {
|
|
29210
|
+
if (entry.suppression) continue;
|
|
29211
|
+
setExcerpt(entry, backfillBudget);
|
|
29212
|
+
suppressCoveredEntry(entry);
|
|
29213
|
+
}
|
|
29214
|
+
freshCount = ordered.filter((entry) => !entry.suppression).length;
|
|
29215
|
+
const candidateLimit = Math.max(searchTopK, Math.min(100, clampedTopK * 5));
|
|
29216
|
+
const candidateKey = (match) => match.groupId ? searchContextKey(match) : JSON.stringify([
|
|
29217
|
+
match.threadId,
|
|
29218
|
+
match.range,
|
|
29219
|
+
match.text
|
|
29220
|
+
]);
|
|
29221
|
+
const seen = new Set(ordered.map((entry) => candidateKey(entry.match)));
|
|
29222
|
+
const addCandidates = async (candidates) => {
|
|
29223
|
+
for (const match of candidates.slice(0, candidateLimit)) {
|
|
29224
|
+
if (freshCount >= clampedTopK || seen.size >= candidateLimit) break;
|
|
29225
|
+
if (!matchesFilter(match)) continue;
|
|
29226
|
+
const key = candidateKey(match);
|
|
29227
|
+
if (seen.has(key)) continue;
|
|
29228
|
+
seen.add(key);
|
|
29229
|
+
if (!threadMap.has(match.threadId) && memory.getThreadById) {
|
|
29230
|
+
const thread = await memory.getThreadById({ threadId: match.threadId });
|
|
29231
|
+
if (thread) threadMap.set(match.threadId, thread);
|
|
29232
|
+
}
|
|
29233
|
+
const entry = prepareEntry(match, ordered.length, backfillBudget);
|
|
29234
|
+
suppressCoveredEntry(entry);
|
|
29235
|
+
ordered.push(entry);
|
|
29236
|
+
if (!entry.suppression) freshCount++;
|
|
29237
|
+
}
|
|
29238
|
+
};
|
|
29239
|
+
await addCandidates(filteredMatches.slice(limitedMatches.length));
|
|
29240
|
+
if (freshCount < clampedTopK && seen.size < candidateLimit && searchTopK < candidateLimit) await addCandidates((await memory.searchMessages({
|
|
29241
|
+
query,
|
|
29242
|
+
resourceId,
|
|
29243
|
+
topK: candidateLimit,
|
|
29244
|
+
filter
|
|
29245
|
+
})).results);
|
|
29246
|
+
}
|
|
29247
|
+
remaining = contentBudget - ordered.reduce((sum, entry) => sum + estimateTokenCount(entry.excerpt), 0);
|
|
29248
|
+
for (const entry of ordered) {
|
|
29249
|
+
if (remaining <= 0) break;
|
|
29250
|
+
if (entry.suppression || entry.excerpt === entry.body) continue;
|
|
29251
|
+
const previousTokens = estimateTokenCount(entry.excerpt);
|
|
29252
|
+
setExcerpt(entry, previousTokens + remaining);
|
|
29253
|
+
remaining -= estimateTokenCount(entry.excerpt) - previousTokens;
|
|
29254
|
+
}
|
|
29255
|
+
ordered.sort((a, b) => {
|
|
29256
|
+
const at = a.match.observedAt?.getTime() ?? Number.POSITIVE_INFINITY;
|
|
29257
|
+
const bt = b.match.observedAt?.getTime() ?? Number.POSITIVE_INFINITY;
|
|
29258
|
+
if (at !== bt) return at < bt ? -1 : 1;
|
|
29259
|
+
if (a.match.threadId !== b.match.threadId) return a.match.threadId.localeCompare(b.match.threadId);
|
|
29260
|
+
return a.index - b.index;
|
|
29261
|
+
});
|
|
29262
|
+
const sections = [];
|
|
29263
|
+
const compactCount = ordered.length - freshCount;
|
|
29264
|
+
if (compactCount > 0) sections.push(`Showing ${freshCount} excerpts and ${compactCount} already-in-context references.` + (freshCount < clampedTopK ? " Backfill is bounded; fewer excerpts do not mean history is exhausted." : ""));
|
|
29265
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
29266
|
+
const { match, body, excerpt, suppression } = ordered[i];
|
|
28435
29267
|
const thread = threadMap.get(match.threadId);
|
|
28436
29268
|
const title = thread?.title || "(untitled)";
|
|
28437
29269
|
const isCurrentThread = match.threadId === currentThreadId;
|
|
28438
|
-
const generationLabel = isCurrentThread ? "Current thread memory" : "
|
|
28439
|
-
const generationDetail = isCurrentThread ? "This result came from the current thread." : "This result came from
|
|
29270
|
+
const generationLabel = isCurrentThread ? "Current thread memory" : "Memory from another thread";
|
|
29271
|
+
const generationDetail = isCurrentThread ? "This result came from the current thread." : "This result came from another thread.";
|
|
28440
29272
|
const threadLine = `- thread: ${match.threadId}${thread ? ` (${title})` : ""}`;
|
|
29273
|
+
const observedLine = match.observedAt ? `- observed: ${formatTimestamp(match.observedAt)} (${formatRelativeTime(match.observedAt, now, timeZone)})` : void 0;
|
|
28441
29274
|
const sourceLine = match.range ? `- source: raw messages from ID ${match.range.split(":")[0] ?? "(unknown)"} through ID ${match.range.split(":")[1] ?? "(unknown)"}` : "- source: raw message range unavailable";
|
|
28442
|
-
const
|
|
28443
|
-
const groupLine = match.groupId ? `- observation group: ${match.groupId}` : void 0;
|
|
29275
|
+
const groupLine = match.groupId ? `- observation group: ${groupCursor(match.groupId, match.recordId)}` : void 0;
|
|
28444
29276
|
const scoreLine = `- score: ${match.score.toFixed(2)}`;
|
|
28445
|
-
const
|
|
28446
|
-
|
|
29277
|
+
const prev = i > 0 ? ordered[i - 1].match : void 0;
|
|
29278
|
+
if (om && prev?.groupId && match.groupId && prev.threadId === match.threadId) sections.push(`— Observation groups may be hidden between these results; continue with ${pagingCall(prev.groupId, "after", includeThreadId ? prev.threadId : void 0, prev.recordId)} —`);
|
|
29279
|
+
if (suppression) {
|
|
29280
|
+
sections.push(`${groupLine}\n thread: ${match.threadId}; ${suppression}`);
|
|
29281
|
+
continue;
|
|
29282
|
+
}
|
|
29283
|
+
sections.push([
|
|
28447
29284
|
`### ${generationLabel}`,
|
|
28448
29285
|
"",
|
|
28449
29286
|
generationDetail,
|
|
28450
29287
|
threadLine,
|
|
29288
|
+
observedLine,
|
|
28451
29289
|
sourceLine,
|
|
28452
|
-
updatedLine,
|
|
28453
29290
|
groupLine,
|
|
28454
29291
|
scoreLine,
|
|
28455
29292
|
"",
|
|
28456
29293
|
"```text",
|
|
28457
|
-
|
|
28458
|
-
"```"
|
|
28459
|
-
|
|
28460
|
-
|
|
29294
|
+
excerpt,
|
|
29295
|
+
"```",
|
|
29296
|
+
excerpt !== body ? "[Excerpt truncated]" : void 0
|
|
29297
|
+
].filter(Boolean).join("\n"));
|
|
29298
|
+
}
|
|
29299
|
+
if (ordered.some((entry) => !entry.suppression && entry.excerpt !== entry.body)) sections.push([
|
|
29300
|
+
`All ${ordered.length} selected matching groups are shown; some excerpts are truncated.`,
|
|
29301
|
+
om ? "To read a full group, use mode=\"observations\" with its threadId and groupId, omitting direction." : void 0,
|
|
29302
|
+
"Use mode=\"messages\" with a source-range cursor for original messages."
|
|
29303
|
+
].filter(Boolean).join(" "));
|
|
28461
29304
|
return {
|
|
28462
|
-
results:
|
|
28463
|
-
count:
|
|
29305
|
+
results: sections.join("\n\n"),
|
|
29306
|
+
count: ordered.length
|
|
28464
29307
|
};
|
|
28465
29308
|
}
|
|
28466
29309
|
const LOW_DETAIL_PART_TOKENS = 30;
|
|
@@ -28511,6 +29354,54 @@ function chunkTextByTokens(text, maxTokens, charOffset = 0) {
|
|
|
28511
29354
|
truncated: nextCharOffset < text.length
|
|
28512
29355
|
};
|
|
28513
29356
|
}
|
|
29357
|
+
const EXCERPT_STOP_WORDS = new Set("the and for with that this from have has had what when where which while who whom why how many much did does was were are you your our their them they there been being some also just than then those these its into about any all can could would should not but user agent asked said mention mentioned total".split(" "));
|
|
29358
|
+
function excerptQueryTerms(query) {
|
|
29359
|
+
const words = query.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
29360
|
+
return [...new Set(words.filter((word) => word.length >= 3 && !EXCERPT_STOP_WORDS.has(word)))];
|
|
29361
|
+
}
|
|
29362
|
+
const EXCERPT_OMISSION_MARKER = "[earlier lines omitted]";
|
|
29363
|
+
/**
|
|
29364
|
+
* Fit an observation to a token budget. When it does not fit, start at the observation
|
|
29365
|
+
* line matching the most query terms (keeping that line's date header) instead of the head,
|
|
29366
|
+
* so repeated searches can reach text a head-only excerpt would never show.
|
|
29367
|
+
* `content` is the slice of the body that is shown, used to recognize excerpts already in context.
|
|
29368
|
+
*/
|
|
29369
|
+
function selectSearchExcerpt(body, terms, maxTokens) {
|
|
29370
|
+
const head = chunkTextByTokens(body, maxTokens);
|
|
29371
|
+
if (!head.truncated || terms.length === 0) return {
|
|
29372
|
+
text: head.text,
|
|
29373
|
+
content: head.text
|
|
29374
|
+
};
|
|
29375
|
+
const starts = [0, ...[...body.matchAll(/(?<=\s)(?:Date: |\* )/g)].map((match) => match.index)];
|
|
29376
|
+
let best;
|
|
29377
|
+
for (let i = 0; i < starts.length; i++) {
|
|
29378
|
+
const start = starts[i];
|
|
29379
|
+
const end = starts[i + 1] ?? body.length;
|
|
29380
|
+
const line = body.slice(start, end).toLowerCase();
|
|
29381
|
+
const score = terms.filter((term) => line.includes(term)).length;
|
|
29382
|
+
if (score > (best?.score ?? 0)) best = {
|
|
29383
|
+
start,
|
|
29384
|
+
end,
|
|
29385
|
+
score
|
|
29386
|
+
};
|
|
29387
|
+
}
|
|
29388
|
+
if (!best || best.end <= head.text.length) return {
|
|
29389
|
+
text: head.text,
|
|
29390
|
+
content: head.text
|
|
29391
|
+
};
|
|
29392
|
+
const dateStart = body.lastIndexOf("Date: ", best.start);
|
|
29393
|
+
const dateHeader = dateStart >= 0 && dateStart < best.start ? body.slice(dateStart, starts.find((start) => start > dateStart) ?? best.start).trim() : "";
|
|
29394
|
+
const prefix = `${dateHeader ? `${dateHeader} ` : ""}${EXCERPT_OMISSION_MARKER} `;
|
|
29395
|
+
const window = chunkTextByTokens(body, maxTokens - estimateTokenCount(prefix), best.start);
|
|
29396
|
+
if (!window.text) return {
|
|
29397
|
+
text: head.text,
|
|
29398
|
+
content: head.text
|
|
29399
|
+
};
|
|
29400
|
+
return {
|
|
29401
|
+
text: prefix + window.text,
|
|
29402
|
+
content: window.text
|
|
29403
|
+
};
|
|
29404
|
+
}
|
|
28514
29405
|
function lowDetailPartLimit(type) {
|
|
28515
29406
|
if (type === "text") return AUTO_EXPAND_TEXT_TOKENS;
|
|
28516
29407
|
if (type === "tool-result" || type === "tool-call") return AUTO_EXPAND_TOOL_TOKENS;
|
|
@@ -29125,11 +30016,13 @@ const recallTool = (_memoryConfig, options) => {
|
|
|
29125
30016
|
const retrievalScope = options?.retrievalScope ?? "thread";
|
|
29126
30017
|
const isResourceScope = retrievalScope === "resource";
|
|
29127
30018
|
const searchEnabled = options?.searchEnabled ?? true;
|
|
29128
|
-
const
|
|
30019
|
+
const getOMEngine = options?.getOMEngine;
|
|
30020
|
+
const description = isResourceScope ? `Browse conversation history. Use mode="threads" to list all threads for the current user. Use mode="messages" (default) to browse messages in the current thread or pass threadId to browse another thread in the active resource. When mode="messages" has no cursor or threadId, it defaults to the current thread and says so at the top of the result. If you pass only a cursor, it must belong to the current thread.${searchEnabled ? " Use mode=\"search\" to find messages by content across all threads; results come back oldest-first with markers where observation groups were skipped. Use mode=\"observations\" with a groupId from a search result to page chronologically through the observation groups around it." : ""}` : `Browse conversation history in the current thread. Use mode="messages" (default) to page through messages near a cursor.${searchEnabled ? " Use mode=\"search\" to find messages by content in this thread; results come back oldest-first with markers where observation groups were skipped. Use mode=\"observations\" with a groupId from a search result to page chronologically through the observation groups around it." : ""} Use mode="threads" to get the current thread's ID and title.`;
|
|
29129
30021
|
const modeEnum = searchEnabled ? [
|
|
29130
30022
|
"messages",
|
|
29131
30023
|
"threads",
|
|
29132
|
-
"search"
|
|
30024
|
+
"search",
|
|
30025
|
+
"observations"
|
|
29133
30026
|
] : ["messages", "threads"];
|
|
29134
30027
|
return createTool({
|
|
29135
30028
|
id: "recall",
|
|
@@ -29142,7 +30035,7 @@ const recallTool = (_memoryConfig, options) => {
|
|
|
29142
30035
|
mode: {
|
|
29143
30036
|
type: "string",
|
|
29144
30037
|
enum: modeEnum,
|
|
29145
|
-
description: `What to retrieve. "messages" (default) pages through message history. "threads" lists all threads for the current user.${searchEnabled ? " \"search\" finds messages by semantic similarity across all threads." : ""}`
|
|
30038
|
+
description: `What to retrieve. "messages" (default) pages through message history. "threads" lists all threads for the current user.${searchEnabled ? " \"search\" finds messages by semantic similarity across all threads. \"observations\" pages observation groups chronologically around a groupId from a search result." : ""}`
|
|
29146
30039
|
},
|
|
29147
30040
|
threadId: {
|
|
29148
30041
|
type: "string",
|
|
@@ -29151,22 +30044,44 @@ const recallTool = (_memoryConfig, options) => {
|
|
|
29151
30044
|
},
|
|
29152
30045
|
before: {
|
|
29153
30046
|
type: "string",
|
|
29154
|
-
description: "
|
|
30047
|
+
description: "Only show results before this date. Applies to mode=\"threads\" (thread creation date) and mode=\"search\" (observation date). ISO 8601 or natural date string (e.g. \"2026-03-15\", \"2026-03-10T00:00:00Z\")."
|
|
29155
30048
|
},
|
|
29156
30049
|
after: {
|
|
29157
30050
|
type: "string",
|
|
29158
|
-
description: "
|
|
30051
|
+
description: "Only show results after this date. Applies to mode=\"threads\" (thread creation date) and mode=\"search\" (observation date). ISO 8601 or natural date string (e.g. \"2026-03-01\", \"2026-03-10T00:00:00Z\")."
|
|
29159
30052
|
}
|
|
29160
30053
|
} : { mode: {
|
|
29161
30054
|
type: "string",
|
|
29162
30055
|
enum: modeEnum,
|
|
29163
|
-
description: `What to retrieve. "messages" (default) pages through message history. "threads" returns info about the current thread.${searchEnabled ? " \"search\" finds messages by semantic similarity in this thread." : ""}`
|
|
30056
|
+
description: `What to retrieve. "messages" (default) pages through message history. "threads" returns info about the current thread.${searchEnabled ? " \"search\" finds messages by semantic similarity in this thread. \"observations\" pages observation groups chronologically around a groupId from a search result." : ""}`
|
|
29164
30057
|
} },
|
|
29165
|
-
...searchEnabled ? {
|
|
29166
|
-
|
|
29167
|
-
|
|
29168
|
-
|
|
29169
|
-
|
|
30058
|
+
...searchEnabled ? {
|
|
30059
|
+
...!isResourceScope ? {
|
|
30060
|
+
before: {
|
|
30061
|
+
type: "string",
|
|
30062
|
+
description: "For mode=\"search\": only show observations dated before this ISO 8601 date."
|
|
30063
|
+
},
|
|
30064
|
+
after: {
|
|
30065
|
+
type: "string",
|
|
30066
|
+
description: "For mode=\"search\": only show observations dated after this ISO 8601 date."
|
|
30067
|
+
}
|
|
30068
|
+
} : {},
|
|
30069
|
+
query: {
|
|
30070
|
+
type: "string",
|
|
30071
|
+
minLength: 1,
|
|
30072
|
+
description: "Search query for mode=\"search\". Finds messages semantically similar to this text."
|
|
30073
|
+
},
|
|
30074
|
+
groupId: {
|
|
30075
|
+
type: "string",
|
|
30076
|
+
minLength: 1,
|
|
30077
|
+
description: "Observation group ID to page around, for mode=\"observations\". Copy it exactly from the \"observation group:\" line of a search result or a continuation call."
|
|
30078
|
+
},
|
|
30079
|
+
direction: {
|
|
30080
|
+
type: "string",
|
|
30081
|
+
enum: ["before", "after"],
|
|
30082
|
+
description: "For mode=\"observations\": page strictly before or after the groupId. Omit to read the full anchor group and following groups."
|
|
30083
|
+
}
|
|
30084
|
+
} : {},
|
|
29170
30085
|
cursor: {
|
|
29171
30086
|
type: "string",
|
|
29172
30087
|
minLength: 1,
|
|
@@ -29187,7 +30102,7 @@ const recallTool = (_memoryConfig, options) => {
|
|
|
29187
30102
|
type: "integer",
|
|
29188
30103
|
minimum: 1,
|
|
29189
30104
|
maximum: 20,
|
|
29190
|
-
description: "Maximum
|
|
30105
|
+
description: "Maximum items per page: defaults to 20 for messages, 5 for observations. For search, target number of excerpts (default 10), plus compact references for evidence already in context; bounded backfill may return fewer excerpts."
|
|
29191
30106
|
},
|
|
29192
30107
|
detail: {
|
|
29193
30108
|
type: "string",
|
|
@@ -29228,13 +30143,59 @@ const recallTool = (_memoryConfig, options) => {
|
|
|
29228
30143
|
}
|
|
29229
30144
|
},
|
|
29230
30145
|
execute: async (inputData, context) => {
|
|
29231
|
-
const { mode, query, cursor, threadId: explicitThreadId, anchor, page, limit, detail, partType, toolName, partIndex, charOffset, before, after, viewAttachment } = inputData;
|
|
30146
|
+
const { mode, query, groupId, direction, cursor, threadId: explicitThreadId, anchor, page, limit, detail, partType, toolName, partIndex, charOffset, before, after, viewAttachment } = inputData;
|
|
29232
30147
|
const memory = context?.memory;
|
|
29233
30148
|
const currentThreadId = context?.agent?.threadId;
|
|
29234
30149
|
const resourceId = context?.agent?.resourceId;
|
|
29235
30150
|
const resolvedExplicitThreadId = explicitThreadId === "current" ? currentThreadId : explicitThreadId;
|
|
29236
30151
|
if (!memory) throw new Error("Memory instance is required for recall");
|
|
29237
30152
|
if (explicitThreadId === "current" && !currentThreadId) throw new Error("Could not resolve current thread.");
|
|
30153
|
+
if (mode === "observations") {
|
|
30154
|
+
if (!searchEnabled) return {
|
|
30155
|
+
results: SEARCH_NOT_CONFIGURED_MESSAGE,
|
|
30156
|
+
count: 0
|
|
30157
|
+
};
|
|
30158
|
+
if (!groupId) throw new Error("groupId is required for mode=\"observations\"");
|
|
30159
|
+
if (!resourceId) throw new Error("Resource ID is required for recall");
|
|
30160
|
+
const om = await getOMEngine?.() ?? null;
|
|
30161
|
+
if (!om) return {
|
|
30162
|
+
results: "Observation group paging requires observational memory and a storage adapter supporting observation history search.",
|
|
30163
|
+
count: 0
|
|
30164
|
+
};
|
|
30165
|
+
const pagingThreadId = !isResourceScope ? currentThreadId : resolvedExplicitThreadId || currentThreadId;
|
|
30166
|
+
if (!pagingThreadId) throw new Error("Could not resolve current thread.");
|
|
30167
|
+
const thread = await memory.getThreadById?.({ threadId: pagingThreadId });
|
|
30168
|
+
if (!thread || thread.resourceId !== resourceId) throw new Error("Thread not found");
|
|
30169
|
+
return pageObservationGroups({
|
|
30170
|
+
om,
|
|
30171
|
+
threadId: pagingThreadId,
|
|
30172
|
+
resourceId,
|
|
30173
|
+
...parseGroupCursor(groupId),
|
|
30174
|
+
direction,
|
|
30175
|
+
limit: Math.min(Math.max(limit ?? 5, 1), 20),
|
|
30176
|
+
threadTitle: thread.title,
|
|
30177
|
+
includeThreadId: isResourceScope,
|
|
30178
|
+
countNewerMessages: async (cursor) => {
|
|
30179
|
+
const end = (await (await memory.getMemoryStore()).listMessagesById({ messageIds: [cursor] })).messages.find((m) => m.id === cursor);
|
|
30180
|
+
if (!end || end.threadId !== pagingThreadId) return 0;
|
|
30181
|
+
const { total } = await memory.recall({
|
|
30182
|
+
threadId: pagingThreadId,
|
|
30183
|
+
resourceId,
|
|
30184
|
+
page: 0,
|
|
30185
|
+
perPage: 1,
|
|
30186
|
+
orderBy: {
|
|
30187
|
+
field: "createdAt",
|
|
30188
|
+
direction: "ASC"
|
|
30189
|
+
},
|
|
30190
|
+
filter: { dateRange: {
|
|
30191
|
+
start: end.createdAt,
|
|
30192
|
+
startExclusive: true
|
|
30193
|
+
} }
|
|
30194
|
+
});
|
|
30195
|
+
return total;
|
|
30196
|
+
}
|
|
30197
|
+
});
|
|
30198
|
+
}
|
|
29238
30199
|
if (mode === "search") {
|
|
29239
30200
|
if (!searchEnabled) return {
|
|
29240
30201
|
results: SEARCH_NOT_CONFIGURED_MESSAGE,
|
|
@@ -29244,13 +30205,16 @@ const recallTool = (_memoryConfig, options) => {
|
|
|
29244
30205
|
if (!resourceId) throw new Error("Resource ID is required for recall");
|
|
29245
30206
|
return searchMessagesForResource({
|
|
29246
30207
|
memory,
|
|
30208
|
+
om: await getOMEngine?.() ?? null,
|
|
29247
30209
|
resourceId,
|
|
29248
30210
|
currentThreadId: currentThreadId || void 0,
|
|
30211
|
+
currentMessages: context?.agent?.getMessages?.(),
|
|
29249
30212
|
query,
|
|
29250
30213
|
topK: limit ?? 10,
|
|
29251
30214
|
before,
|
|
29252
30215
|
after,
|
|
29253
|
-
threadScope: !isResourceScope ? currentThreadId || void 0 : resolvedExplicitThreadId || void 0
|
|
30216
|
+
threadScope: !isResourceScope ? currentThreadId || void 0 : resolvedExplicitThreadId || void 0,
|
|
30217
|
+
includeThreadId: isResourceScope
|
|
29254
30218
|
});
|
|
29255
30219
|
}
|
|
29256
30220
|
if (mode === "threads") {
|
|
@@ -29534,8 +30498,23 @@ function getOpenAIPromptCacheRetention(providerOptions) {
|
|
|
29534
30498
|
const openaiOptions = providerOptions?.openai;
|
|
29535
30499
|
return typeof openaiOptions?.promptCacheRetention === "string" ? openaiOptions.promptCacheRetention.toLowerCase() : void 0;
|
|
29536
30500
|
}
|
|
30501
|
+
function isActivationTTLMap(activateAfterIdle) {
|
|
30502
|
+
return typeof activateAfterIdle === "object" && activateAfterIdle !== null;
|
|
30503
|
+
}
|
|
30504
|
+
/** The configured TTL for marker payloads. Per-provider maps are omitted; markers only carry scalar values. */
|
|
30505
|
+
function getMarkerActivationTTL(activateAfterIdle) {
|
|
30506
|
+
return isActivationTTLMap(activateAfterIdle) ? void 0 : activateAfterIdle;
|
|
30507
|
+
}
|
|
30508
|
+
/** A map entry applies when its key equals the model's provider before the first `.`, lowercased. */
|
|
30509
|
+
function resolveActivationTTLMapEntry(activateAfterIdle, modelContext) {
|
|
30510
|
+
const providerKey = normalize(modelContext?.provider).split(".")[0];
|
|
30511
|
+
if (providerKey && Object.hasOwn(activateAfterIdle.providers, providerKey)) return activateAfterIdle.providers[providerKey];
|
|
30512
|
+
return activateAfterIdle.default;
|
|
30513
|
+
}
|
|
29537
30514
|
function resolveActivationTTL(activateAfterIdle, modelContext) {
|
|
29538
|
-
|
|
30515
|
+
const ttl = isActivationTTLMap(activateAfterIdle) ? resolveActivationTTLMapEntry(activateAfterIdle, modelContext) : activateAfterIdle;
|
|
30516
|
+
if (ttl === void 0 || ttl === false) return;
|
|
30517
|
+
if (ttl !== "auto") return ttl;
|
|
29539
30518
|
return resolveAutoActivationTTL(modelContext);
|
|
29540
30519
|
}
|
|
29541
30520
|
function resolveAutoActivationTTL(modelContext) {
|
|
@@ -29967,475 +30946,6 @@ function createThreadUpdateMarker(params) {
|
|
|
29967
30946
|
}
|
|
29968
30947
|
};
|
|
29969
30948
|
}
|
|
29970
|
-
/**
|
|
29971
|
-
* Find the index of the last completed observation boundary (end marker) in a message's parts.
|
|
29972
|
-
* Returns -1 if no completed observation is found.
|
|
29973
|
-
*/
|
|
29974
|
-
function findLastCompletedObservationBoundary(message) {
|
|
29975
|
-
const parts = message.content?.parts;
|
|
29976
|
-
if (!parts || !Array.isArray(parts)) return -1;
|
|
29977
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
29978
|
-
const part = parts[i];
|
|
29979
|
-
if (part?.type === "data-om-observation-end" && part.data?.operationType !== "reflection") return i;
|
|
29980
|
-
}
|
|
29981
|
-
return -1;
|
|
29982
|
-
}
|
|
29983
|
-
/**
|
|
29984
|
-
* Get unobserved parts from a message.
|
|
29985
|
-
* If the message has a completed observation (start + end), only return parts after the end.
|
|
29986
|
-
* If observation is in progress (start without end), include parts before the start.
|
|
29987
|
-
* Otherwise, return all parts.
|
|
29988
|
-
*/
|
|
29989
|
-
function getUnobservedParts(message) {
|
|
29990
|
-
const parts = message.content?.parts;
|
|
29991
|
-
if (!parts || !Array.isArray(parts)) return [];
|
|
29992
|
-
const endMarkerIndex = findLastCompletedObservationBoundary(message);
|
|
29993
|
-
if (endMarkerIndex === -1) return parts.filter((p) => {
|
|
29994
|
-
return p?.type !== "data-om-observation-start";
|
|
29995
|
-
});
|
|
29996
|
-
return parts.slice(endMarkerIndex + 1).filter((p) => {
|
|
29997
|
-
return !p?.type?.startsWith("data-om-observation-");
|
|
29998
|
-
});
|
|
29999
|
-
}
|
|
30000
|
-
/**
|
|
30001
|
-
* Compute a cursor pointing at the latest message by createdAt.
|
|
30002
|
-
* Used to derive a stable observation boundary for replay pruning.
|
|
30003
|
-
*/
|
|
30004
|
-
function getLastObservedMessageCursor(messages) {
|
|
30005
|
-
let latest;
|
|
30006
|
-
for (const msg of messages) {
|
|
30007
|
-
if (!msg?.id || !msg.createdAt) continue;
|
|
30008
|
-
if (!latest || new Date(msg.createdAt).getTime() > new Date(latest.createdAt).getTime()) latest = msg;
|
|
30009
|
-
}
|
|
30010
|
-
return latest ? {
|
|
30011
|
-
createdAt: new Date(latest.createdAt).toISOString(),
|
|
30012
|
-
id: latest.id
|
|
30013
|
-
} : void 0;
|
|
30014
|
-
}
|
|
30015
|
-
/**
|
|
30016
|
-
* Check if a message is at or before a cursor (by createdAt then id).
|
|
30017
|
-
*/
|
|
30018
|
-
function isMessageAtOrBeforeCursor(msg, cursor) {
|
|
30019
|
-
if (!msg.createdAt) return false;
|
|
30020
|
-
const msgIso = new Date(msg.createdAt).toISOString();
|
|
30021
|
-
if (msgIso < cursor.createdAt) return true;
|
|
30022
|
-
if (msgIso === cursor.createdAt && msg.id === cursor.id) return true;
|
|
30023
|
-
return false;
|
|
30024
|
-
}
|
|
30025
|
-
/**
|
|
30026
|
-
* Return the toolCallId of a tool-invocation part in the given state, or undefined.
|
|
30027
|
-
*/
|
|
30028
|
-
function getToolInvocationId(part, state) {
|
|
30029
|
-
if (part?.type !== "tool-invocation") return void 0;
|
|
30030
|
-
if (part.toolInvocation.state !== state) return void 0;
|
|
30031
|
-
return part.toolInvocation.toolCallId || void 0;
|
|
30032
|
-
}
|
|
30033
|
-
function collectPendingToolResultIds(allMessages, observedIds) {
|
|
30034
|
-
const pendingToolResultIds = /* @__PURE__ */ new Set();
|
|
30035
|
-
for (const msg of allMessages) {
|
|
30036
|
-
if (!msg?.id) continue;
|
|
30037
|
-
if (observedIds?.has(msg.id)) continue;
|
|
30038
|
-
const parts = msg.content?.parts;
|
|
30039
|
-
if (!parts || !Array.isArray(parts)) continue;
|
|
30040
|
-
for (const part of parts) {
|
|
30041
|
-
const id = getToolInvocationId(part, "result");
|
|
30042
|
-
if (id) pendingToolResultIds.add(id);
|
|
30043
|
-
}
|
|
30044
|
-
}
|
|
30045
|
-
return pendingToolResultIds;
|
|
30046
|
-
}
|
|
30047
|
-
function hasPendingToolResultPair(message, pendingToolResultIds) {
|
|
30048
|
-
if (pendingToolResultIds.size === 0) return false;
|
|
30049
|
-
const parts = message.content?.parts;
|
|
30050
|
-
if (!parts || !Array.isArray(parts)) return false;
|
|
30051
|
-
return parts.some((part) => {
|
|
30052
|
-
const id = getToolInvocationId(part, "call");
|
|
30053
|
-
return id !== void 0 && pendingToolResultIds.has(id);
|
|
30054
|
-
});
|
|
30055
|
-
}
|
|
30056
|
-
function collectPendingToolResultIdsAfterMarker(allMessages, markerMessageIndex, markerMessage) {
|
|
30057
|
-
const pendingToolResultIds = /* @__PURE__ */ new Set();
|
|
30058
|
-
const unobservedMarkerParts = getUnobservedParts(markerMessage);
|
|
30059
|
-
if (unobservedMarkerParts.length > 0) collectPendingToolResultIds([{
|
|
30060
|
-
...markerMessage,
|
|
30061
|
-
content: {
|
|
30062
|
-
...markerMessage.content,
|
|
30063
|
-
parts: unobservedMarkerParts
|
|
30064
|
-
}
|
|
30065
|
-
}], void 0).forEach((id) => pendingToolResultIds.add(id));
|
|
30066
|
-
collectPendingToolResultIds(allMessages.slice(markerMessageIndex + 1), void 0).forEach((id) => pendingToolResultIds.add(id));
|
|
30067
|
-
return pendingToolResultIds;
|
|
30068
|
-
}
|
|
30069
|
-
function getUnobservedPartsPreservingToolCallPairs(message) {
|
|
30070
|
-
const parts = message.content?.parts;
|
|
30071
|
-
if (!parts || !Array.isArray(parts)) return [];
|
|
30072
|
-
const endMarkerIndex = findLastCompletedObservationBoundary(message);
|
|
30073
|
-
if (endMarkerIndex === -1) return getUnobservedParts(message);
|
|
30074
|
-
const unobservedParts = getUnobservedParts(message);
|
|
30075
|
-
const unobservedResultIds = /* @__PURE__ */ new Set();
|
|
30076
|
-
for (const part of unobservedParts) {
|
|
30077
|
-
const id = getToolInvocationId(part, "result");
|
|
30078
|
-
if (id) unobservedResultIds.add(id);
|
|
30079
|
-
}
|
|
30080
|
-
if (unobservedResultIds.size === 0) return unobservedParts;
|
|
30081
|
-
const preservedCalls = parts.slice(0, endMarkerIndex).filter((part) => {
|
|
30082
|
-
const id = getToolInvocationId(part, "call");
|
|
30083
|
-
return id !== void 0 && unobservedResultIds.has(id);
|
|
30084
|
-
});
|
|
30085
|
-
return preservedCalls.length > 0 ? [...preservedCalls, ...unobservedParts] : unobservedParts;
|
|
30086
|
-
}
|
|
30087
|
-
/**
|
|
30088
|
-
* Get the messages Observational Memory is allowed to work with.
|
|
30089
|
-
*
|
|
30090
|
-
* Messages supplied through the `context` option and the synthetic `om-continuation`
|
|
30091
|
-
* message are prompt-only input. Core's persistence contract already treats context as
|
|
30092
|
-
* never-persist, while the continuation remains memory-sourced so it can stay in the live
|
|
30093
|
-
* actor prompt.
|
|
30094
|
-
*
|
|
30095
|
-
* OM builds its windows from `get.all.db()`, which includes both categories, and then seals
|
|
30096
|
-
* and persists candidates directly. Excluding them here keeps OM's observation, buffering,
|
|
30097
|
-
* persistence, and token accounting consistent with their ephemeral contract.
|
|
30098
|
-
*/
|
|
30099
|
-
function getObservableMessages(messageList) {
|
|
30100
|
-
const contextMessageIds = messageList.makeMessageSourceChecker().context;
|
|
30101
|
-
return messageList.get.all.db().filter((message) => {
|
|
30102
|
-
return message.id !== "om-continuation" && !contextMessageIds.has(message.id);
|
|
30103
|
-
});
|
|
30104
|
-
}
|
|
30105
|
-
/**
|
|
30106
|
-
* Safely extract buffered observation chunks from a record.
|
|
30107
|
-
* Handles both array and JSON-string formats, returning empty array if malformed.
|
|
30108
|
-
*/
|
|
30109
|
-
/**
|
|
30110
|
-
* Filter out already-observed messages from the in-memory context.
|
|
30111
|
-
* Uses marker-boundary pruning (preferred) or record-based fallback.
|
|
30112
|
-
*
|
|
30113
|
-
* The `fallbackCursor` is optional — callers that need it should resolve it
|
|
30114
|
-
* from thread metadata before calling this function.
|
|
30115
|
-
*/
|
|
30116
|
-
function filterObservedMessages(opts) {
|
|
30117
|
-
const { messageList, record } = opts;
|
|
30118
|
-
const allMessages = getObservableMessages(messageList);
|
|
30119
|
-
const useMarkerBoundaryPruning = opts.useMarkerBoundaryPruning ?? true;
|
|
30120
|
-
const preserveMessageIds = opts.preserveMessageIds ?? /* @__PURE__ */ new Set();
|
|
30121
|
-
const observedIds = new Set(Array.isArray(record?.observedMessageIds) ? record.observedMessageIds : []);
|
|
30122
|
-
let markerMessageIndex = -1;
|
|
30123
|
-
let markerMessage = null;
|
|
30124
|
-
for (let i = allMessages.length - 1; i >= 0; i--) {
|
|
30125
|
-
const msg = allMessages[i];
|
|
30126
|
-
if (!msg) continue;
|
|
30127
|
-
if (findLastCompletedObservationBoundary(msg) !== -1) {
|
|
30128
|
-
markerMessageIndex = i;
|
|
30129
|
-
markerMessage = msg;
|
|
30130
|
-
break;
|
|
30131
|
-
}
|
|
30132
|
-
}
|
|
30133
|
-
if (useMarkerBoundaryPruning && markerMessage && markerMessageIndex !== -1) {
|
|
30134
|
-
const pendingToolResultIds = collectPendingToolResultIdsAfterMarker(allMessages, markerMessageIndex, markerMessage);
|
|
30135
|
-
const messagesToRemove = [];
|
|
30136
|
-
for (let i = 0; i < markerMessageIndex; i++) {
|
|
30137
|
-
const msg = allMessages[i];
|
|
30138
|
-
if (msg?.id && msg.id !== "om-continuation" && !preserveMessageIds.has(msg.id) && !hasPendingToolResultPair(msg, pendingToolResultIds)) messagesToRemove.push(msg.id);
|
|
30139
|
-
}
|
|
30140
|
-
if (messagesToRemove.length > 0) messageList.removeByIds(messagesToRemove);
|
|
30141
|
-
const unobserved = getUnobservedPartsPreservingToolCallPairs(markerMessage);
|
|
30142
|
-
if (unobserved.length === 0) {
|
|
30143
|
-
if (markerMessage.id) messageList.removeByIds([markerMessage.id]);
|
|
30144
|
-
} else if (unobserved.length < (markerMessage.content?.parts?.length ?? 0)) markerMessage.content.parts = unobserved;
|
|
30145
|
-
} else if (record) {
|
|
30146
|
-
const pendingToolResultIds = collectPendingToolResultIds(allMessages, observedIds);
|
|
30147
|
-
const derivedCursor = opts.fallbackCursor ?? getLastObservedMessageCursor(allMessages.filter((msg) => !!msg?.id && observedIds.has(msg.id) && !!msg.createdAt));
|
|
30148
|
-
const lastObservedAt = record.lastObservedAt;
|
|
30149
|
-
const messagesToRemove = [];
|
|
30150
|
-
for (const msg of allMessages) {
|
|
30151
|
-
if (!msg?.id || msg.id === "om-continuation" || preserveMessageIds.has(msg.id)) continue;
|
|
30152
|
-
const hasPendingPair = hasPendingToolResultPair(msg, pendingToolResultIds);
|
|
30153
|
-
if (observedIds.has(msg.id)) {
|
|
30154
|
-
if (hasPendingPair) continue;
|
|
30155
|
-
messagesToRemove.push(msg.id);
|
|
30156
|
-
continue;
|
|
30157
|
-
}
|
|
30158
|
-
if (derivedCursor && isMessageAtOrBeforeCursor(msg, derivedCursor)) {
|
|
30159
|
-
if (hasPendingPair) continue;
|
|
30160
|
-
messagesToRemove.push(msg.id);
|
|
30161
|
-
continue;
|
|
30162
|
-
}
|
|
30163
|
-
if (lastObservedAt && msg.createdAt) {
|
|
30164
|
-
if (new Date(msg.createdAt) <= lastObservedAt) {
|
|
30165
|
-
if (hasPendingPair) continue;
|
|
30166
|
-
messagesToRemove.push(msg.id);
|
|
30167
|
-
}
|
|
30168
|
-
}
|
|
30169
|
-
}
|
|
30170
|
-
if (messagesToRemove.length > 0) messageList.removeByIds(messagesToRemove);
|
|
30171
|
-
}
|
|
30172
|
-
}
|
|
30173
|
-
function getBufferedChunks(record) {
|
|
30174
|
-
if (!record?.bufferedObservationChunks) return [];
|
|
30175
|
-
if (Array.isArray(record.bufferedObservationChunks)) return record.bufferedObservationChunks;
|
|
30176
|
-
if (typeof record.bufferedObservationChunks === "string") try {
|
|
30177
|
-
const parsed = JSON.parse(record.bufferedObservationChunks);
|
|
30178
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
30179
|
-
} catch {
|
|
30180
|
-
return [];
|
|
30181
|
-
}
|
|
30182
|
-
return [];
|
|
30183
|
-
}
|
|
30184
|
-
/**
|
|
30185
|
-
* Sort threads by their oldest unobserved message.
|
|
30186
|
-
* Returns thread IDs in order from oldest to most recent.
|
|
30187
|
-
*/
|
|
30188
|
-
/**
|
|
30189
|
-
* Combine active and buffered observations for the buffering observer context.
|
|
30190
|
-
*/
|
|
30191
|
-
function combineObservationsForBuffering(activeObservations, bufferedObservations) {
|
|
30192
|
-
if (!activeObservations && !bufferedObservations) return void 0;
|
|
30193
|
-
if (!activeObservations) return bufferedObservations;
|
|
30194
|
-
if (!bufferedObservations) return activeObservations;
|
|
30195
|
-
return `${activeObservations}\n\n--- BUFFERED (pending activation) ---\n\n${bufferedObservations}`;
|
|
30196
|
-
}
|
|
30197
|
-
function sortThreadsByOldestMessage(messagesByThread) {
|
|
30198
|
-
return Array.from(messagesByThread.entries()).map(([threadId, messages]) => ({
|
|
30199
|
-
threadId,
|
|
30200
|
-
oldestTimestamp: Math.min(...messages.map((m) => m.createdAt ? new Date(m.createdAt).getTime() : Date.now()))
|
|
30201
|
-
})).sort((a, b) => a.oldestTimestamp - b.oldestTimestamp).map((t) => t.threadId);
|
|
30202
|
-
}
|
|
30203
|
-
/**
|
|
30204
|
-
* Strip any thread tags that the Observer might have added.
|
|
30205
|
-
* Thread attribution is handled externally by the system, not by the Observer.
|
|
30206
|
-
*/
|
|
30207
|
-
function stripThreadTags(observations) {
|
|
30208
|
-
return observations.replace(/<\/?thread\b[^>]{0,1024}>/gi, "").trim();
|
|
30209
|
-
}
|
|
30210
|
-
const OBSERVATION_GROUP_OPEN = "<observation-group";
|
|
30211
|
-
const OBSERVATION_GROUP_CLOSE = "</observation-group>";
|
|
30212
|
-
const ATTRIBUTE_PATTERN = /([\w][\w-]*)="([^"]*)"/g;
|
|
30213
|
-
const REFLECTION_GROUP_SPLIT_PATTERN = /^##\s+Group\s+/m;
|
|
30214
|
-
function parseObservationGroupAttributes(attributeString) {
|
|
30215
|
-
const attributes = {};
|
|
30216
|
-
for (const match of attributeString.matchAll(ATTRIBUTE_PATTERN)) {
|
|
30217
|
-
const [, key, value] = match;
|
|
30218
|
-
if (key && value !== void 0) attributes[key] = value;
|
|
30219
|
-
}
|
|
30220
|
-
return attributes;
|
|
30221
|
-
}
|
|
30222
|
-
function isLineStart(observations, index) {
|
|
30223
|
-
return index === 0 || observations[index - 1] === "\n";
|
|
30224
|
-
}
|
|
30225
|
-
function scanObservationGroupTags(observations) {
|
|
30226
|
-
const tags = [];
|
|
30227
|
-
const incompleteOpenings = [];
|
|
30228
|
-
let cursor = 0;
|
|
30229
|
-
let closeFloor = 0;
|
|
30230
|
-
let nestedFloor = -1;
|
|
30231
|
-
while (cursor < observations.length) {
|
|
30232
|
-
const start = observations.indexOf(OBSERVATION_GROUP_OPEN, cursor);
|
|
30233
|
-
if (start === -1) break;
|
|
30234
|
-
const attributesStart = start + 18;
|
|
30235
|
-
if (!/\s/.test(observations[attributesStart] ?? "")) {
|
|
30236
|
-
cursor = attributesStart;
|
|
30237
|
-
continue;
|
|
30238
|
-
}
|
|
30239
|
-
const openEnd = observations.indexOf(">", attributesStart);
|
|
30240
|
-
if (openEnd === -1) break;
|
|
30241
|
-
const closeStart = observations.indexOf(OBSERVATION_GROUP_CLOSE, Math.max(openEnd + 1, closeFloor));
|
|
30242
|
-
if (closeStart === -1) {
|
|
30243
|
-
if (isLineStart(observations, start)) incompleteOpenings.push({
|
|
30244
|
-
start,
|
|
30245
|
-
end: openEnd + 1
|
|
30246
|
-
});
|
|
30247
|
-
closeFloor = observations.length;
|
|
30248
|
-
cursor = openEnd + 1;
|
|
30249
|
-
continue;
|
|
30250
|
-
}
|
|
30251
|
-
if (nestedFloor <= openEnd) {
|
|
30252
|
-
const lineOpen = observations.indexOf(`\n${OBSERVATION_GROUP_OPEN}`, openEnd);
|
|
30253
|
-
nestedFloor = lineOpen === -1 ? observations.length : lineOpen + 1;
|
|
30254
|
-
}
|
|
30255
|
-
const nestedStart = nestedFloor;
|
|
30256
|
-
if (nestedStart < closeStart) {
|
|
30257
|
-
incompleteOpenings.push({
|
|
30258
|
-
start,
|
|
30259
|
-
end: openEnd + 1
|
|
30260
|
-
});
|
|
30261
|
-
closeFloor = closeStart;
|
|
30262
|
-
cursor = nestedStart;
|
|
30263
|
-
continue;
|
|
30264
|
-
}
|
|
30265
|
-
tags.push({
|
|
30266
|
-
start,
|
|
30267
|
-
end: closeStart + 20,
|
|
30268
|
-
attributeString: observations.slice(attributesStart, openEnd),
|
|
30269
|
-
content: observations.slice(openEnd + 1, closeStart)
|
|
30270
|
-
});
|
|
30271
|
-
closeFloor = closeStart;
|
|
30272
|
-
cursor = closeStart + 20;
|
|
30273
|
-
}
|
|
30274
|
-
return {
|
|
30275
|
-
tags,
|
|
30276
|
-
incompleteOpenings
|
|
30277
|
-
};
|
|
30278
|
-
}
|
|
30279
|
-
function replaceObservationGroupTags(observations, replace) {
|
|
30280
|
-
const { tags, incompleteOpenings } = scanObservationGroupTags(observations);
|
|
30281
|
-
const parts = [];
|
|
30282
|
-
let cursor = 0;
|
|
30283
|
-
let openingIndex = 0;
|
|
30284
|
-
const skipIncompleteOpeningsBefore = (limit) => {
|
|
30285
|
-
while (openingIndex < incompleteOpenings.length && incompleteOpenings[openingIndex].start < limit) {
|
|
30286
|
-
const opening = incompleteOpenings[openingIndex];
|
|
30287
|
-
openingIndex++;
|
|
30288
|
-
if (opening.start < cursor) continue;
|
|
30289
|
-
parts.push(observations.slice(cursor, opening.start));
|
|
30290
|
-
cursor = opening.end;
|
|
30291
|
-
}
|
|
30292
|
-
};
|
|
30293
|
-
for (const tag of tags) {
|
|
30294
|
-
skipIncompleteOpeningsBefore(tag.start);
|
|
30295
|
-
parts.push(observations.slice(cursor, tag.start), replace(tag));
|
|
30296
|
-
cursor = tag.end;
|
|
30297
|
-
}
|
|
30298
|
-
skipIncompleteOpeningsBefore(Number.POSITIVE_INFINITY);
|
|
30299
|
-
parts.push(observations.slice(cursor));
|
|
30300
|
-
return parts.join("");
|
|
30301
|
-
}
|
|
30302
|
-
function parseReflectionObservationGroupSections(content) {
|
|
30303
|
-
const normalizedContent = content.trim();
|
|
30304
|
-
if (!normalizedContent || !REFLECTION_GROUP_SPLIT_PATTERN.test(normalizedContent)) return [];
|
|
30305
|
-
return normalizedContent.split(REFLECTION_GROUP_SPLIT_PATTERN).map((section) => section.trim()).filter(Boolean).map((section) => {
|
|
30306
|
-
const newlineIndex = section.indexOf("\n");
|
|
30307
|
-
return {
|
|
30308
|
-
heading: (newlineIndex >= 0 ? section.slice(0, newlineIndex) : section).trim(),
|
|
30309
|
-
body: stripReflectionGroupMetadata((newlineIndex >= 0 ? section.slice(newlineIndex + 1) : "").trim())
|
|
30310
|
-
};
|
|
30311
|
-
});
|
|
30312
|
-
}
|
|
30313
|
-
function stripReflectionGroupMetadata(body) {
|
|
30314
|
-
return body.replace(/^_range:\s*`[^`]*`_\s*\n?/m, "").trim();
|
|
30315
|
-
}
|
|
30316
|
-
function generateAnchorId() {
|
|
30317
|
-
return Buffer.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8))).toString("hex");
|
|
30318
|
-
}
|
|
30319
|
-
function wrapInObservationGroup(observations, range, id = generateAnchorId(), _sourceGroupIds, kind) {
|
|
30320
|
-
const content = observations.trim();
|
|
30321
|
-
return `<observation-group id="${id}" range="${range}"${kind ? ` kind="${kind}"` : ""}>\n${content}\n</observation-group>`;
|
|
30322
|
-
}
|
|
30323
|
-
function parseObservationGroups(observations) {
|
|
30324
|
-
if (!observations) return [];
|
|
30325
|
-
const groups = [];
|
|
30326
|
-
for (const tag of scanObservationGroupTags(observations).tags) {
|
|
30327
|
-
const attributes = parseObservationGroupAttributes(tag.attributeString);
|
|
30328
|
-
const id = attributes.id;
|
|
30329
|
-
const range = attributes.range;
|
|
30330
|
-
if (!id || !range) continue;
|
|
30331
|
-
groups.push({
|
|
30332
|
-
id,
|
|
30333
|
-
range,
|
|
30334
|
-
kind: attributes.kind,
|
|
30335
|
-
content: tag.content.trim()
|
|
30336
|
-
});
|
|
30337
|
-
}
|
|
30338
|
-
return groups;
|
|
30339
|
-
}
|
|
30340
|
-
function stripObservationGroups(observations) {
|
|
30341
|
-
if (!observations) return observations;
|
|
30342
|
-
return replaceObservationGroupTags(observations, (tag) => tag.content.trim()).replace(/\n{3,}/g, "\n\n").trim();
|
|
30343
|
-
}
|
|
30344
|
-
function getRangeSegments(range) {
|
|
30345
|
-
return range.split(",").map((segment) => segment.trim()).filter(Boolean);
|
|
30346
|
-
}
|
|
30347
|
-
function getRangeEndpoints(segment) {
|
|
30348
|
-
const parts = segment.split(":").map((part) => part.trim());
|
|
30349
|
-
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
30350
|
-
return {
|
|
30351
|
-
start: parts[0],
|
|
30352
|
-
end: parts[1]
|
|
30353
|
-
};
|
|
30354
|
-
}
|
|
30355
|
-
/**
|
|
30356
|
-
* `buildMessageRange` produces opaque UUID endpoints, so those segments cannot be ordered by value.
|
|
30357
|
-
* Spanning from the first start to the last end keeps reflected metadata bounded — the full segment
|
|
30358
|
-
* list is deliberately not preserved (#14791). Segments that are not `start:end` pairs are left as
|
|
30359
|
-
* they are rather than being joined into a fabricated span.
|
|
30360
|
-
*/
|
|
30361
|
-
function spanRangeSegments(segments) {
|
|
30362
|
-
const endpoints = segments.map(getRangeEndpoints);
|
|
30363
|
-
if (endpoints.some((endpoint) => endpoint === null)) return segments.join(",");
|
|
30364
|
-
const first = endpoints[0];
|
|
30365
|
-
const last = endpoints.at(-1);
|
|
30366
|
-
return `${first.start}:${last.end}`;
|
|
30367
|
-
}
|
|
30368
|
-
function combineObservationGroupRanges(groups) {
|
|
30369
|
-
const segments = Array.from(new Set(groups.flatMap((group) => getRangeSegments(group.range))));
|
|
30370
|
-
if (segments.length === 0) return "";
|
|
30371
|
-
const numericEndpoints = [];
|
|
30372
|
-
if (!segments.every((segment) => {
|
|
30373
|
-
const endpoints = getRangeEndpoints(segment);
|
|
30374
|
-
if (!endpoints || !/^\d+$/.test(endpoints.start) || !/^\d+$/.test(endpoints.end)) return false;
|
|
30375
|
-
const start = {
|
|
30376
|
-
label: endpoints.start,
|
|
30377
|
-
value: Number(endpoints.start)
|
|
30378
|
-
};
|
|
30379
|
-
const end = {
|
|
30380
|
-
label: endpoints.end,
|
|
30381
|
-
value: Number(endpoints.end)
|
|
30382
|
-
};
|
|
30383
|
-
if (!Number.isSafeInteger(start.value) || !Number.isSafeInteger(end.value)) return false;
|
|
30384
|
-
numericEndpoints.push(start.value <= end.value ? {
|
|
30385
|
-
start,
|
|
30386
|
-
end
|
|
30387
|
-
} : {
|
|
30388
|
-
start: end,
|
|
30389
|
-
end: start
|
|
30390
|
-
});
|
|
30391
|
-
return true;
|
|
30392
|
-
})) return spanRangeSegments(segments);
|
|
30393
|
-
const first = numericEndpoints.reduce((lowest, pair) => pair.start.value < lowest.start.value ? pair : lowest);
|
|
30394
|
-
const last = numericEndpoints.reduce((highest, pair) => pair.end.value > highest.end.value ? pair : highest);
|
|
30395
|
-
return `${first.start.label}:${last.end.label}`;
|
|
30396
|
-
}
|
|
30397
|
-
function renderObservationGroupsForReflection(observations) {
|
|
30398
|
-
if (parseObservationGroups(observations).length === 0) return null;
|
|
30399
|
-
return replaceObservationGroupTags(observations, (tag) => {
|
|
30400
|
-
const attributes = parseObservationGroupAttributes(tag.attributeString);
|
|
30401
|
-
if (!attributes.id || !attributes.range) return tag.content.trim();
|
|
30402
|
-
return `## Group \`${attributes.id}\`\n_range: \`${attributes.range}\`_\n\n${tag.content.trim()}`;
|
|
30403
|
-
}).replace(/\n{3,}/g, "\n\n").trim();
|
|
30404
|
-
}
|
|
30405
|
-
function getCanonicalGroupId(sectionHeading, fallbackIndex) {
|
|
30406
|
-
return sectionHeading.match(/`([^`]+)`/)?.[1]?.trim() || `derived-group-${fallbackIndex + 1}`;
|
|
30407
|
-
}
|
|
30408
|
-
function getContentLines(content) {
|
|
30409
|
-
return content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
30410
|
-
}
|
|
30411
|
-
function deriveObservationGroupProvenance(content, groups) {
|
|
30412
|
-
const sections = parseReflectionObservationGroupSections(content);
|
|
30413
|
-
if (sections.length === 0 || groups.length === 0) return [];
|
|
30414
|
-
return sections.map((section, index) => {
|
|
30415
|
-
const canonicalGroupId = getCanonicalGroupId(section.heading, index);
|
|
30416
|
-
const identifiedGroup = groups.find((group) => group.id === canonicalGroupId);
|
|
30417
|
-
const bodyLines = new Set(getContentLines(section.body));
|
|
30418
|
-
const matchingGroups = groups.filter((group) => getContentLines(group.content).some((line) => bodyLines.has(line)));
|
|
30419
|
-
const fallbackGroup = groups[Math.min(index, groups.length - 1)];
|
|
30420
|
-
const identifiedLines = new Set(getContentLines(identifiedGroup?.content ?? ""));
|
|
30421
|
-
const contributingGroups = identifiedGroup ? matchingGroups.filter((group) => group.id !== identifiedGroup.id && getContentLines(group.content).some((line) => bodyLines.has(line) && !identifiedLines.has(line))) : [];
|
|
30422
|
-
return {
|
|
30423
|
-
id: canonicalGroupId,
|
|
30424
|
-
range: combineObservationGroupRanges(identifiedGroup ? groups.filter((group) => group === identifiedGroup || contributingGroups.includes(group)) : matchingGroups.length > 0 ? matchingGroups : fallbackGroup ? [fallbackGroup] : []),
|
|
30425
|
-
kind: "reflection",
|
|
30426
|
-
content: section.body
|
|
30427
|
-
};
|
|
30428
|
-
});
|
|
30429
|
-
}
|
|
30430
|
-
function reconcileObservationGroupsFromReflection(content, sourceObservations) {
|
|
30431
|
-
const sourceGroups = parseObservationGroups(sourceObservations);
|
|
30432
|
-
if (sourceGroups.length === 0) return null;
|
|
30433
|
-
const normalizedContent = content.trim();
|
|
30434
|
-
if (!normalizedContent) return "";
|
|
30435
|
-
const derivedGroups = deriveObservationGroupProvenance(normalizedContent, sourceGroups);
|
|
30436
|
-
if (derivedGroups.length > 0) return derivedGroups.map((group) => wrapInObservationGroup(group.content, group.range, group.id, void 0, group.kind)).join("\n\n");
|
|
30437
|
-
return wrapInObservationGroup(normalizedContent, combineObservationGroupRanges(sourceGroups), generateAnchorId(), void 0, "reflection");
|
|
30438
|
-
}
|
|
30439
30949
|
function isRecord$1(value) {
|
|
30440
30950
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
30441
30951
|
}
|
|
@@ -30774,7 +31284,7 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
30774
31284
|
}
|
|
30775
31285
|
return `${existingObservations}${lastObservedAt ? ObservationStrategy.createMessageBoundary(lastObservedAt) : "\n\n"}${newThreadSection}`;
|
|
30776
31286
|
}
|
|
30777
|
-
async indexObservationGroups(observations, threadId, resourceId, observedAt) {
|
|
31287
|
+
async indexObservationGroups(observations, threadId, resourceId, observedAt, recordId) {
|
|
30778
31288
|
if (!resourceId || !this.deps.onIndexObservations) return;
|
|
30779
31289
|
const groups = parseObservationGroups(observations);
|
|
30780
31290
|
if (groups.length === 0) return;
|
|
@@ -30784,7 +31294,8 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
30784
31294
|
range: group.range,
|
|
30785
31295
|
threadId,
|
|
30786
31296
|
resourceId,
|
|
30787
|
-
observedAt
|
|
31297
|
+
observedAt,
|
|
31298
|
+
recordId
|
|
30788
31299
|
}), {
|
|
30789
31300
|
label: "index-observations",
|
|
30790
31301
|
abortSignal: this.opts.abortSignal
|
|
@@ -31058,7 +31569,7 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
31058
31569
|
lastObservedAt: processed.lastObservedAt,
|
|
31059
31570
|
observedMessageIds: processed.observedMessageIds
|
|
31060
31571
|
});
|
|
31061
|
-
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt);
|
|
31572
|
+
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, record.id);
|
|
31062
31573
|
}
|
|
31063
31574
|
async emitEndMarkers(cycleId, processed) {
|
|
31064
31575
|
const actualTokensObserved = await this.tokenCounter.countMessagesAsync(this.opts.messages);
|
|
@@ -31230,7 +31741,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
31230
31741
|
label: "persist-buffered-observations",
|
|
31231
31742
|
abortSignal: this.opts.abortSignal
|
|
31232
31743
|
});
|
|
31233
|
-
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt);
|
|
31744
|
+
await this.indexObservationGroups(processed.observations, threadId, resourceId, processed.lastObservedAt, record.id);
|
|
31234
31745
|
const candidateTitle = processed.threadTitle?.trim();
|
|
31235
31746
|
const hasValidThreadTitle = !!candidateTitle && candidateTitle.length >= 3;
|
|
31236
31747
|
if (hasValidThreadTitle || processed.extractedValues) {
|
|
@@ -31633,7 +32144,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
31633
32144
|
lastObservedAt: processed.lastObservedAt,
|
|
31634
32145
|
observedMessageIds: processed.observedMessageIds
|
|
31635
32146
|
});
|
|
31636
|
-
if (resourceId) await Promise.all(this.observationResults.map(({ threadId, threadMessages, result }) => this.indexObservationGroups(result.observations, threadId, resourceId, this.getMaxMessageTimestamp(threadMessages))));
|
|
32147
|
+
if (resourceId) await Promise.all(this.observationResults.map(({ threadId, threadMessages, result }) => this.indexObservationGroups(result.observations, threadId, resourceId, this.getMaxMessageTimestamp(threadMessages), record.id)));
|
|
31637
32148
|
}
|
|
31638
32149
|
async emitEndMarkers(cycleId, processed) {
|
|
31639
32150
|
for (const obsResult of this.observationResults) {
|
|
@@ -32777,7 +33288,7 @@ var ReflectorRunner = class {
|
|
|
32777
33288
|
messageTokens: getMaxThreshold(this.observationConfig.messageTokens),
|
|
32778
33289
|
observationTokens: getMaxThreshold(record ? this.getEffectiveReflectionTokens(record) : this.reflectionConfig.observationTokens),
|
|
32779
33290
|
scope: this.scope,
|
|
32780
|
-
activateAfterIdle: this.reflectionConfig.activateAfterIdle
|
|
33291
|
+
activateAfterIdle: getMarkerActivationTTL(this.reflectionConfig.activateAfterIdle)
|
|
32781
33292
|
};
|
|
32782
33293
|
}
|
|
32783
33294
|
/**
|
|
@@ -33208,7 +33719,7 @@ var ReflectorRunner = class {
|
|
|
33208
33719
|
currentModel: activationMetadata?.currentModel,
|
|
33209
33720
|
config: {
|
|
33210
33721
|
...this.getObservationMarkerConfig(freshRecord),
|
|
33211
|
-
activateAfterIdle: activationMetadata?.activateAfterIdle ?? this.reflectionConfig.activateAfterIdle
|
|
33722
|
+
activateAfterIdle: activationMetadata?.activateAfterIdle ?? getMarkerActivationTTL(this.reflectionConfig.activateAfterIdle)
|
|
33212
33723
|
}
|
|
33213
33724
|
});
|
|
33214
33725
|
writer.custom({
|
|
@@ -33893,6 +34404,32 @@ function parseActivationTTL(value, fieldPath) {
|
|
|
33893
34404
|
if (!Number.isFinite(amount) || amount < 0) throw new Error(`${fieldPath} must be a non-negative number of milliseconds or a duration string like "5m".`);
|
|
33894
34405
|
return amount * (unit === "ms" || unit === "msec" || unit === "msecs" || unit === "millisecond" || unit === "milliseconds" ? 1 : unit === "s" || unit === "sec" || unit === "secs" || unit === "second" || unit === "seconds" ? 1e3 : unit === "m" || unit === "min" || unit === "mins" || unit === "minute" || unit === "minutes" ? 6e4 : 36e5);
|
|
33895
34406
|
}
|
|
34407
|
+
/**
|
|
34408
|
+
* Parse an `activateAfterIdle` config value: a scalar TTL, or a per-provider map
|
|
34409
|
+
* like `{ default: 'auto', anthropic: '1h' }`.
|
|
34410
|
+
*/
|
|
34411
|
+
function parseActivationTTLConfig(value, fieldPath) {
|
|
34412
|
+
if (typeof value !== "object") return parseActivationTTL(value, fieldPath);
|
|
34413
|
+
if (value === null || Array.isArray(value)) throw new Error(`${fieldPath} must be a TTL value or an object of per-provider TTLs, e.g. { default: 'auto', anthropic: '1h' }.`);
|
|
34414
|
+
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0);
|
|
34415
|
+
if (entries.length === 0) throw new Error(`${fieldPath} must set at least one provider or "default" when using per-provider TTLs, e.g. { default: 'auto', anthropic: '1h' }.`);
|
|
34416
|
+
const parsed = { providers: Object.create(null) };
|
|
34417
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
34418
|
+
for (const [rawKey, entryValue] of entries) {
|
|
34419
|
+
const key = rawKey.trim().toLowerCase();
|
|
34420
|
+
const entryPath = `${fieldPath}.${rawKey}`;
|
|
34421
|
+
if (!key) throw new Error(`${fieldPath} contains an empty provider key.`);
|
|
34422
|
+
if (key.includes(".") || key.includes("/")) throw new Error(`${entryPath} is not a valid provider key. Use the provider name before the first ".", e.g. "anthropic" instead of "anthropic.messages".`);
|
|
34423
|
+
if (seenKeys.has(key)) throw new Error(`${entryPath} duplicates another key in ${fieldPath}. Provider keys are case-insensitive.`);
|
|
34424
|
+
seenKeys.add(key);
|
|
34425
|
+
if (entryValue !== false && typeof entryValue !== "number" && typeof entryValue !== "string") throw new Error(`${entryPath} must be a non-negative number of milliseconds, a duration string like "5m" or "1hr", "auto", or false.`);
|
|
34426
|
+
const ttl = entryValue === false ? false : parseActivationTTL(entryValue, entryPath);
|
|
34427
|
+
if (key === "default") {
|
|
34428
|
+
if (ttl !== false) parsed.default = ttl;
|
|
34429
|
+
} else parsed.providers[key] = ttl;
|
|
34430
|
+
}
|
|
34431
|
+
return parsed;
|
|
34432
|
+
}
|
|
33896
34433
|
let hasWarnedResourceScopeDeprecation = false;
|
|
33897
34434
|
/**
|
|
33898
34435
|
* ObservationalMemory - A three-agent memory system for long conversations.
|
|
@@ -34107,7 +34644,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
34107
34644
|
bufferTokens: asyncBufferingDisabled ? void 0 : resolveBufferTokens(config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens, config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens),
|
|
34108
34645
|
bufferOnIdle: config.observation?.bufferOnIdle ?? false,
|
|
34109
34646
|
bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
|
|
34110
|
-
activateAfterIdle:
|
|
34647
|
+
activateAfterIdle: parseActivationTTLConfig(observationActivateAfterIdle, observationActivateAfterIdlePath),
|
|
34111
34648
|
activateOnProviderChange: config.observation?.activateOnProviderChange ?? config.activateOnProviderChange ?? false,
|
|
34112
34649
|
blockAfter: asyncBufferingDisabled ? void 0 : resolveBlockAfter(config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0), config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens),
|
|
34113
34650
|
previousObserverTokens: config.observation?.previousObserverTokens ?? 2e3,
|
|
@@ -34132,7 +34669,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
34132
34669
|
},
|
|
34133
34670
|
providerOptions: config.reflection?.providerOptions ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.providerOptions,
|
|
34134
34671
|
bufferActivation: asyncBufferingDisabled ? void 0 : config?.reflection?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation,
|
|
34135
|
-
activateAfterIdle:
|
|
34672
|
+
activateAfterIdle: parseActivationTTLConfig(config.reflection?.activateAfterIdle, "reflection.activateAfterIdle"),
|
|
34136
34673
|
activateOnProviderChange: config.reflection?.activateOnProviderChange ?? false,
|
|
34137
34674
|
blockAfter: asyncBufferingDisabled ? void 0 : resolveBlockAfter(config.reflection?.blockAfter ?? (config.reflection?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation ? 1.2 : void 0), config.reflection?.observationTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.observationTokens),
|
|
34138
34675
|
instruction: config.reflection?.instruction,
|
|
@@ -34458,7 +34995,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
34458
34995
|
messageTokens: getMaxThreshold(this.observationConfig.messageTokens),
|
|
34459
34996
|
observationTokens: getMaxThreshold(this.reflectionConfig.observationTokens),
|
|
34460
34997
|
scope: this.scope,
|
|
34461
|
-
activateAfterIdle: this.observationConfig.activateAfterIdle
|
|
34998
|
+
activateAfterIdle: getMarkerActivationTTL(this.observationConfig.activateAfterIdle)
|
|
34462
34999
|
};
|
|
34463
35000
|
}
|
|
34464
35001
|
/**
|
|
@@ -34753,9 +35290,9 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
34753
35290
|
* the message for the current thread.
|
|
34754
35291
|
*/
|
|
34755
35292
|
formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false, timeZone) {
|
|
34756
|
-
let optimized = retrieval ? renderObservationGroupsForReflection(observations) ?? optimizeObservationsForContext(observations) : optimizeObservationsForContext(observations);
|
|
35293
|
+
let optimized = retrieval ? renderObservationGroupsForReflection(observations, { includeReflectionKind: true }) ?? optimizeObservationsForContext(observations) : optimizeObservationsForContext(observations);
|
|
34757
35294
|
if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate, timeZone);
|
|
34758
|
-
const messages = [`${getObservationContextPrompt(this.scope)}\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}${retrieval ? `\n\n${getRetrievalInstructions(this.retrievalScope, this.retrievalInstructions, this.retrievalSearch)}` : ""}`];
|
|
35295
|
+
const messages = [`${getObservationContextPrompt(this.scope)}\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}${retrieval ? `\n\n${getRetrievalInstructions(this.retrievalScope, this.retrievalInstructions, this.retrievalSearch, this.storage.supportsObservationalMemoryHistorySearch === true)}` : ""}`];
|
|
34759
35296
|
if (unobservedContextBlocks) messages.push(`The following content is from OTHER conversations different from the current conversation, they're here for reference, but they're not necessarily your focus:\nSTART_OTHER_CONVERSATIONS_BLOCK\n${unobservedContextBlocks}\nEND_OTHER_CONVERSATIONS_BLOCK`);
|
|
34760
35297
|
const observationChunks = this.splitObservationContextChunks(optimized);
|
|
34761
35298
|
if (observationChunks.length > 0) messages.push("<observations>", ...observationChunks);
|
|
@@ -35252,7 +35789,8 @@ ${formattedMessages}
|
|
|
35252
35789
|
*
|
|
35253
35790
|
* Loads thread metadata (currentTask, suggestedResponse), formats observations
|
|
35254
35791
|
* with context prompts and instructions, and returns the fully-formed string.
|
|
35255
|
-
* Returns
|
|
35792
|
+
* Returns recall guidance even without observations when retrieval is enabled;
|
|
35793
|
+
* otherwise returns undefined when no observations exist.
|
|
35256
35794
|
*
|
|
35257
35795
|
* This is the public entry point for context formatting — used by both
|
|
35258
35796
|
* Memory.getContext() (standalone) and the processor (via injectObservationsIntoMessages).
|
|
@@ -35276,9 +35814,9 @@ ${formattedMessages}
|
|
|
35276
35814
|
*/
|
|
35277
35815
|
async buildContextSystemMessages(opts) {
|
|
35278
35816
|
const { threadId, resourceId, unobservedContextBlocks } = opts;
|
|
35279
|
-
const record = opts.record
|
|
35280
|
-
if (!record
|
|
35281
|
-
if (this.retrieval
|
|
35817
|
+
const record = opts.record === void 0 ? await this.getOrCreateRecord(threadId, resourceId) : opts.record;
|
|
35818
|
+
if (!record?.activeObservations) {
|
|
35819
|
+
if (this.retrieval) return [getRetrievalInstructions(this.retrievalScope, this.retrievalInstructions, this.retrievalSearch, this.storage.supportsObservationalMemoryHistorySearch === true)];
|
|
35282
35820
|
return;
|
|
35283
35821
|
}
|
|
35284
35822
|
const omMetadata = getThreadOMMetadata((await this.storage.getThreadById({ threadId }))?.metadata);
|
|
@@ -35858,7 +36396,7 @@ ${formattedMessages}
|
|
|
35858
36396
|
currentModel,
|
|
35859
36397
|
config: {
|
|
35860
36398
|
...this.getObservationMarkerConfig(),
|
|
35861
|
-
activateAfterIdle: activationActivateAfterIdle ?? this.observationConfig.activateAfterIdle
|
|
36399
|
+
activateAfterIdle: activationActivateAfterIdle ?? getMarkerActivationTTL(this.observationConfig.activateAfterIdle)
|
|
35862
36400
|
}
|
|
35863
36401
|
});
|
|
35864
36402
|
opts.writer.custom({
|
|
@@ -36514,12 +37052,12 @@ var ObservationalMemoryProcessor = class {
|
|
|
36514
37052
|
resourceId,
|
|
36515
37053
|
runState
|
|
36516
37054
|
});
|
|
36517
|
-
const systemMessages =
|
|
37055
|
+
const systemMessages = await this.engine.buildContextSystemMessages({
|
|
36518
37056
|
threadId,
|
|
36519
37057
|
resourceId,
|
|
36520
37058
|
record: ctx.omRecord,
|
|
36521
37059
|
unobservedContextBlocks: ctx.otherThreadsContext
|
|
36522
|
-
})
|
|
37060
|
+
});
|
|
36523
37061
|
injectObservationContextMessages({
|
|
36524
37062
|
messageList,
|
|
36525
37063
|
systemMessages,
|
|
@@ -38141,6 +38679,7 @@ Notes:
|
|
|
38141
38679
|
threadId: r.metadata.thread_id,
|
|
38142
38680
|
score: r.score,
|
|
38143
38681
|
groupId,
|
|
38682
|
+
recordId: typeof r.metadata.record_id === "string" ? r.metadata.record_id : void 0,
|
|
38144
38683
|
range: typeof r.metadata.range === "string" ? r.metadata.range : void 0,
|
|
38145
38684
|
text: typeof r.metadata.text === "string" ? r.metadata.text : void 0,
|
|
38146
38685
|
observedAt: typeof r.metadata.observed_at === "string" || r.metadata.observed_at instanceof Date ? new Date(r.metadata.observed_at) : void 0
|
|
@@ -38158,7 +38697,7 @@ Notes:
|
|
|
38158
38697
|
/**
|
|
38159
38698
|
* Index a single observation group into the observation vector store.
|
|
38160
38699
|
*/
|
|
38161
|
-
async indexObservation({ text, groupId, range, threadId, resourceId, observedAt }) {
|
|
38700
|
+
async indexObservation({ text, groupId, range, threadId, resourceId, observedAt, recordId }) {
|
|
38162
38701
|
if (!this.vector || !this.embedder) return;
|
|
38163
38702
|
const embedResult = await this.embedMessageContent(text);
|
|
38164
38703
|
if (embedResult.embeddings.length === 0 || embedResult.dimension === void 0) return;
|
|
@@ -38186,6 +38725,7 @@ Notes:
|
|
|
38186
38725
|
thread_id: threadId,
|
|
38187
38726
|
resource_id: resourceId,
|
|
38188
38727
|
observed_at: observedAt?.toISOString(),
|
|
38728
|
+
...recordId ? { record_id: recordId } : {},
|
|
38189
38729
|
text: chunk
|
|
38190
38730
|
}))
|
|
38191
38731
|
});
|
|
@@ -38362,7 +38902,11 @@ Notes:
|
|
|
38362
38902
|
const omConfig = normalizeObservationalMemoryConfig(mergedConfig.observationalMemory);
|
|
38363
38903
|
if (omConfig?.retrieval) tools.recall = recallTool(mergedConfig, {
|
|
38364
38904
|
retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource",
|
|
38365
|
-
searchEnabled: this.hasRetrievalSearch(omConfig.retrieval)
|
|
38905
|
+
searchEnabled: this.hasRetrievalSearch(omConfig.retrieval),
|
|
38906
|
+
getOMEngine: async () => {
|
|
38907
|
+
const om = await this.omEngine;
|
|
38908
|
+
return om?.getStorage().supportsObservationalMemoryHistorySearch ? om : null;
|
|
38909
|
+
}
|
|
38366
38910
|
});
|
|
38367
38911
|
if (omConfig?.experimental_subconscious instanceof Subconscious && omConfig.experimental_subconscious.resolved.tools) {
|
|
38368
38912
|
Object.assign(tools, createKnowledgeTools(this));
|
|
@@ -49827,4 +50371,4 @@ const agentBuilderWorkflows = {
|
|
|
49827
50371
|
//#endregion
|
|
49828
50372
|
export { agentBuilderWorkflows };
|
|
49829
50373
|
|
|
49830
|
-
//# sourceMappingURL=dist-
|
|
50374
|
+
//# sourceMappingURL=dist-DH39C5Bz.js.map
|