@rubytech/create-maxy 1.0.420 → 1.0.422
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/package.json
CHANGED
package/payload/maxy/server.js
CHANGED
|
@@ -3822,7 +3822,7 @@ async function loadSessionContext(accountId) {
|
|
|
3822
3822
|
try {
|
|
3823
3823
|
const summaryResult = await session.run(
|
|
3824
3824
|
`MATCH (n:CreativeWork {accountId: $accountId})
|
|
3825
|
-
WHERE n.title STARTS WITH 'Session Summary'
|
|
3825
|
+
WHERE n.title STARTS WITH 'Session Summary' OR n.title = 'Session Snapshot'
|
|
3826
3826
|
RETURN n.title AS title, n.createdAt AS createdAt, n.abstract AS abstract
|
|
3827
3827
|
ORDER BY n.createdAt DESC LIMIT 1`,
|
|
3828
3828
|
{ accountId }
|
|
@@ -3852,14 +3852,24 @@ async function loadSessionContext(accountId) {
|
|
|
3852
3852
|
{ accountId, limit: neo4j.int(MAX_SESSION_TASKS) }
|
|
3853
3853
|
);
|
|
3854
3854
|
const sections = [];
|
|
3855
|
+
const STALENESS_LIMIT_MS = 48 * 60 * 60 * 1e3;
|
|
3855
3856
|
if (summaryResult.records.length > 0) {
|
|
3856
3857
|
const rec = summaryResult.records[0];
|
|
3857
3858
|
const title = rec.get("title");
|
|
3858
3859
|
const createdAt = rec.get("createdAt");
|
|
3859
3860
|
const abstract = rec.get("abstract");
|
|
3860
|
-
|
|
3861
|
-
|
|
3861
|
+
let isStale = false;
|
|
3862
|
+
if (createdAt) {
|
|
3863
|
+
const ageMs = Date.now() - new Date(createdAt).getTime();
|
|
3864
|
+
isStale = !isNaN(ageMs) && ageMs > STALENESS_LIMIT_MS;
|
|
3865
|
+
}
|
|
3866
|
+
if (isStale) {
|
|
3867
|
+
console.error(`[session-context] Skipped stale summary for ${accountId.slice(0, 8)}\u2026: title="${title}" createdAt=${createdAt}`);
|
|
3868
|
+
} else {
|
|
3869
|
+
const dateSuffix = createdAt ? ` (${createdAt.slice(0, 10)})` : "";
|
|
3870
|
+
sections.push(`## Last Session${dateSuffix}
|
|
3862
3871
|
${abstract}`);
|
|
3872
|
+
}
|
|
3863
3873
|
}
|
|
3864
3874
|
if (digestResult.records.length > 0) {
|
|
3865
3875
|
const rec = digestResult.records[0];
|
|
@@ -3885,8 +3895,9 @@ ${abstract}`);
|
|
|
3885
3895
|
${taskLines.join("\n")}`);
|
|
3886
3896
|
}
|
|
3887
3897
|
if (sections.length === 0) return null;
|
|
3898
|
+
const summaryCreatedAt = summaryResult.records.length > 0 ? summaryResult.records[0].get("createdAt") ?? "unknown" : "n/a";
|
|
3888
3899
|
console.error(
|
|
3889
|
-
`[session-context] Loaded for ${accountId.slice(0, 8)}\u2026: summary=${summaryResult.records.length > 0 ? "yes" : "no"}, digest=${digestResult.records.length > 0 ? "yes" : "no"}, tasks=${tasksResult.records.length}`
|
|
3900
|
+
`[session-context] Loaded for ${accountId.slice(0, 8)}\u2026: summary=${summaryResult.records.length > 0 ? "yes" : "no"}, summaryDate=${summaryCreatedAt}, digest=${digestResult.records.length > 0 ? "yes" : "no"}, tasks=${tasksResult.records.length}`
|
|
3890
3901
|
);
|
|
3891
3902
|
return `<previous-context>
|
|
3892
3903
|
${sections.join("\n\n")}
|
|
@@ -3898,6 +3909,22 @@ ${sections.join("\n\n")}
|
|
|
3898
3909
|
await session.close();
|
|
3899
3910
|
}
|
|
3900
3911
|
}
|
|
3912
|
+
async function writeSessionSummary(accountId, summary) {
|
|
3913
|
+
const session = getSession();
|
|
3914
|
+
try {
|
|
3915
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3916
|
+
await session.run(
|
|
3917
|
+
`MERGE (n:CreativeWork {accountId: $accountId, title: 'Session Snapshot'})
|
|
3918
|
+
SET n.abstract = $summary, n.createdAt = $createdAt, n.scope = 'admin'`,
|
|
3919
|
+
{ accountId, summary, createdAt }
|
|
3920
|
+
);
|
|
3921
|
+
console.error(`[session-summary] Written for ${accountId.slice(0, 8)}\u2026`);
|
|
3922
|
+
} catch (err) {
|
|
3923
|
+
console.error(`[session-summary] Failed for ${accountId.slice(0, 8)}\u2026: ${err instanceof Error ? err.message : String(err)}`);
|
|
3924
|
+
} finally {
|
|
3925
|
+
await session.close();
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3901
3928
|
async function loadOnboardingStep(accountId) {
|
|
3902
3929
|
const session = getSession();
|
|
3903
3930
|
try {
|
|
@@ -4777,7 +4804,16 @@ function validateSession(sessionKey, agentType) {
|
|
|
4777
4804
|
if (!session) return false;
|
|
4778
4805
|
if (session.agentType !== agentType) return false;
|
|
4779
4806
|
if (Date.now() - session.createdAt > 24 * 60 * 60 * 1e3) {
|
|
4807
|
+
const history = session.messageHistory;
|
|
4808
|
+
const expiredAccountId = session.accountId;
|
|
4780
4809
|
sessionStore.delete(sessionKey);
|
|
4810
|
+
if (history && history.length >= 2) {
|
|
4811
|
+
writeEndOfTurnSummary(expiredAccountId, history).catch(
|
|
4812
|
+
(err) => console.error(`[session-summary] expiry write failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
4813
|
+
);
|
|
4814
|
+
} else {
|
|
4815
|
+
console.error(`[session-summary] Session ${sessionKey.slice(0, 8)}\u2026 expired without summary \u2014 ${history?.length ?? 0} messages`);
|
|
4816
|
+
}
|
|
4781
4817
|
return false;
|
|
4782
4818
|
}
|
|
4783
4819
|
if (session.grantExpiresAt && Date.now() > session.grantExpiresAt) {
|
|
@@ -5061,6 +5097,80 @@ function getAdminAllowedTools(enabledPlugins) {
|
|
|
5061
5097
|
}
|
|
5062
5098
|
return tools;
|
|
5063
5099
|
}
|
|
5100
|
+
var QUERY_CLASSIFIER_MODEL = "claude-haiku-4-5-20251001";
|
|
5101
|
+
var QUERY_CLASSIFIER_TIMEOUT_MS = 3e3;
|
|
5102
|
+
var QUERY_CLASSIFIER_MSG_CAP = 500;
|
|
5103
|
+
var QUERY_CLASSIFIER_HISTORY_TURNS = 4;
|
|
5104
|
+
var QUERY_CLASSIFIER_FALLBACK = {
|
|
5105
|
+
search: true,
|
|
5106
|
+
query: null,
|
|
5107
|
+
// null means "use raw message"
|
|
5108
|
+
reason: "classifier-fallback"
|
|
5109
|
+
};
|
|
5110
|
+
var QUERY_CLASSIFIER_SYSTEM = `You decide whether a user message to a business assistant requires a knowledge base search.
|
|
5111
|
+
|
|
5112
|
+
The knowledge base contains the business's products, services, pricing, policies, and domain expertise. It does NOT contain conversation history \u2014 the assistant already has that.
|
|
5113
|
+
|
|
5114
|
+
Respond with ONLY a JSON object: {"search": boolean, "query": string | null, "reason": string}
|
|
5115
|
+
|
|
5116
|
+
Set search to false when:
|
|
5117
|
+
- The message is a greeting, farewell, acknowledgement, or filler ("hello", "thanks", "ok", "bye")
|
|
5118
|
+
- The message is about session state ("continue where we left off", "what were we discussing")
|
|
5119
|
+
- The conversation history already contains sufficient information to answer the question
|
|
5120
|
+
- The message is a conversational response that doesn't require new factual knowledge
|
|
5121
|
+
|
|
5122
|
+
Set search to true when:
|
|
5123
|
+
- The message asks about products, services, pricing, capabilities, or business information
|
|
5124
|
+
- The message introduces a new topic not already covered in the conversation
|
|
5125
|
+
- The message requires factual knowledge the assistant wouldn't have from conversation alone
|
|
5126
|
+
|
|
5127
|
+
When search is true, set query to a search-optimised phrase that captures the information need. Resolve references from conversation history \u2014 e.g. if the user says "tell me more about the first one" and the history mentions "property valuations", set query to "property valuations details". If the raw message is already a good search query, use it as-is.
|
|
5128
|
+
|
|
5129
|
+
When search is false, set query to null and reason to a brief category (e.g. "greeting", "acknowledgement", "session-reference", "context-sufficient").`;
|
|
5130
|
+
async function classifyMemoryQuery(message, history) {
|
|
5131
|
+
let apiKey = process.env.ANTHROPIC_API_KEY;
|
|
5132
|
+
if (!apiKey) {
|
|
5133
|
+
try {
|
|
5134
|
+
apiKey = readFileSync5(API_KEY_FILE, "utf-8").trim();
|
|
5135
|
+
} catch {
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
if (!apiKey) return QUERY_CLASSIFIER_FALLBACK;
|
|
5139
|
+
const recentHistory = history.slice(-QUERY_CLASSIFIER_HISTORY_TURNS);
|
|
5140
|
+
const transcript = recentHistory.length > 0 ? recentHistory.map((m) => `[${m.role}] ${m.content.slice(0, QUERY_CLASSIFIER_MSG_CAP)}`).join("\n") : "(no conversation history \u2014 this is the first message)";
|
|
5141
|
+
const userContent = [
|
|
5142
|
+
`Conversation history:
|
|
5143
|
+
${transcript}`,
|
|
5144
|
+
`
|
|
5145
|
+
Current message:
|
|
5146
|
+
${message.slice(0, QUERY_CLASSIFIER_MSG_CAP)}`
|
|
5147
|
+
].join("\n");
|
|
5148
|
+
const controller = new AbortController();
|
|
5149
|
+
const timer = setTimeout(() => controller.abort(), QUERY_CLASSIFIER_TIMEOUT_MS);
|
|
5150
|
+
try {
|
|
5151
|
+
const client = new Anthropic({ apiKey });
|
|
5152
|
+
const response = await client.messages.create({
|
|
5153
|
+
model: QUERY_CLASSIFIER_MODEL,
|
|
5154
|
+
max_tokens: 150,
|
|
5155
|
+
system: QUERY_CLASSIFIER_SYSTEM,
|
|
5156
|
+
messages: [{ role: "user", content: userContent }]
|
|
5157
|
+
}, { signal: controller.signal });
|
|
5158
|
+
const text = response.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("").trim();
|
|
5159
|
+
if (!text) return QUERY_CLASSIFIER_FALLBACK;
|
|
5160
|
+
const jsonStr = text.replace(/^```json\s*/i, "").replace(/```\s*$/, "").trim();
|
|
5161
|
+
const parsed = JSON.parse(jsonStr);
|
|
5162
|
+
if (typeof parsed.search !== "boolean") return QUERY_CLASSIFIER_FALLBACK;
|
|
5163
|
+
return {
|
|
5164
|
+
search: parsed.search,
|
|
5165
|
+
query: parsed.search && typeof parsed.query === "string" ? parsed.query.slice(0, QUERY_CLASSIFIER_MSG_CAP) : null,
|
|
5166
|
+
reason: typeof parsed.reason === "string" ? parsed.reason.replace(/[\n\r]/g, " ").slice(0, 100) : "unknown"
|
|
5167
|
+
};
|
|
5168
|
+
} catch {
|
|
5169
|
+
return QUERY_CLASSIFIER_FALLBACK;
|
|
5170
|
+
} finally {
|
|
5171
|
+
clearTimeout(timer);
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5064
5174
|
async function fetchMemoryContext(accountId, query, sessionKey, options) {
|
|
5065
5175
|
const serverPath = resolve4(PLATFORM_ROOT3, "plugins/memory/mcp/dist/index.js");
|
|
5066
5176
|
if (!existsSync5(serverPath)) {
|
|
@@ -5456,6 +5566,65 @@ Extract preference updates as JSON array.`
|
|
|
5456
5566
|
clearTimeout(timer);
|
|
5457
5567
|
}
|
|
5458
5568
|
}
|
|
5569
|
+
var SESSION_SUMMARY_PROMPT = `You are summarising a conversation between a business assistant and its owner. Write a 3-5 sentence summary covering:
|
|
5570
|
+
- What was discussed and accomplished this session
|
|
5571
|
+
- Any tasks created, decisions made, or state changes
|
|
5572
|
+
- Current state of any ongoing work
|
|
5573
|
+
- What the owner is likely to want to continue next
|
|
5574
|
+
|
|
5575
|
+
Be factual and specific. Use present tense for current state. Do not include greetings or meta-commentary.`;
|
|
5576
|
+
var SESSION_SUMMARY_TIMEOUT_MS = 1e4;
|
|
5577
|
+
var lastSummaryWrite = /* @__PURE__ */ new Map();
|
|
5578
|
+
var SUMMARY_RATE_LIMIT_MS = 5 * 60 * 1e3;
|
|
5579
|
+
async function generateSessionSummary(history) {
|
|
5580
|
+
if (history.length < 2) return null;
|
|
5581
|
+
let apiKey = process.env.ANTHROPIC_API_KEY;
|
|
5582
|
+
if (!apiKey) {
|
|
5583
|
+
try {
|
|
5584
|
+
apiKey = readFileSync5(API_KEY_FILE, "utf-8").trim();
|
|
5585
|
+
} catch {
|
|
5586
|
+
return null;
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
if (!apiKey) {
|
|
5590
|
+
console.error("[session-summary] Skipping \u2014 no API key available");
|
|
5591
|
+
return null;
|
|
5592
|
+
}
|
|
5593
|
+
const transcript = history.slice(-20).map((m) => `[${m.role}] ${m.content}`).join("\n\n");
|
|
5594
|
+
const controller = new AbortController();
|
|
5595
|
+
const timer = setTimeout(() => controller.abort(), SESSION_SUMMARY_TIMEOUT_MS);
|
|
5596
|
+
try {
|
|
5597
|
+
const client = new Anthropic({ apiKey });
|
|
5598
|
+
const response = await client.messages.create({
|
|
5599
|
+
model: REFLECTION_MODEL,
|
|
5600
|
+
max_tokens: 500,
|
|
5601
|
+
system: SESSION_SUMMARY_PROMPT,
|
|
5602
|
+
messages: [{ role: "user", content: `Conversation:
|
|
5603
|
+
${transcript}` }]
|
|
5604
|
+
}, { signal: controller.signal });
|
|
5605
|
+
return response.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
|
|
5606
|
+
} catch (err) {
|
|
5607
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
5608
|
+
console.error(`[session-summary] Haiku call timed out after ${SESSION_SUMMARY_TIMEOUT_MS}ms`);
|
|
5609
|
+
} else {
|
|
5610
|
+
console.error(`[session-summary] Haiku call failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5611
|
+
}
|
|
5612
|
+
return null;
|
|
5613
|
+
} finally {
|
|
5614
|
+
clearTimeout(timer);
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
async function writeEndOfTurnSummary(accountId, history) {
|
|
5618
|
+
const now = Date.now();
|
|
5619
|
+
const lastWrite = lastSummaryWrite.get(accountId) ?? 0;
|
|
5620
|
+
if (now - lastWrite < SUMMARY_RATE_LIMIT_MS) {
|
|
5621
|
+
return;
|
|
5622
|
+
}
|
|
5623
|
+
const summary = await generateSessionSummary(history);
|
|
5624
|
+
if (!summary) return;
|
|
5625
|
+
await writeSessionSummary(accountId, summary);
|
|
5626
|
+
lastSummaryWrite.set(accountId, Date.now());
|
|
5627
|
+
}
|
|
5459
5628
|
var MANAGED_WORK_BUDGET_RATIO = 0.5;
|
|
5460
5629
|
var TOOL_SCHEMA_OVERHEAD_TOKENS = 15e3;
|
|
5461
5630
|
function estimateTokens(text) {
|
|
@@ -6503,6 +6672,9 @@ async function* invokeManagedAdminAgent(message, systemPrompt, accountDir, accou
|
|
|
6503
6672
|
if (responseText) persistMessage(convId, "assistant", responseText, accountId, capturedTokens, assistantTimestamp).catch(() => {
|
|
6504
6673
|
});
|
|
6505
6674
|
}
|
|
6675
|
+
writeEndOfTurnSummary(accountId, afterHistory).catch(
|
|
6676
|
+
(err) => console.error(`[session-summary] end-of-turn write failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
6677
|
+
);
|
|
6506
6678
|
}
|
|
6507
6679
|
yield event;
|
|
6508
6680
|
}
|
|
@@ -6581,18 +6753,32 @@ async function* invokePublicAgent(message, systemPrompt, accountId, accountDir,
|
|
|
6581
6753
|
}
|
|
6582
6754
|
}
|
|
6583
6755
|
}
|
|
6756
|
+
const history = sessionKey ? getMessageHistory(sessionKey) : [];
|
|
6584
6757
|
const fetchOptions = {
|
|
6585
6758
|
...agentSlug ? { agentSlug } : {},
|
|
6586
6759
|
...knowledgeKeywords && knowledgeKeywords.length > 0 ? { knowledgeKeywords } : {}
|
|
6587
6760
|
};
|
|
6761
|
+
const needsClassification = !(knowledgeBaked && !liveMemory);
|
|
6762
|
+
const classifyStartMs = Date.now();
|
|
6763
|
+
const classification = needsClassification ? await classifyMemoryQuery(message, history) : QUERY_CLASSIFIER_FALLBACK;
|
|
6764
|
+
const classifyMs = Date.now() - classifyStartMs;
|
|
6765
|
+
const effectiveQuery = classification.query ?? message;
|
|
6766
|
+
if (needsClassification) {
|
|
6767
|
+
streamLog.write(`[${isoTs()}] [public-query-classifier] search=${classification.search} effectiveQuery=${JSON.stringify(effectiveQuery.slice(0, 200))} reason=${classification.reason} latencyMs=${classifyMs}
|
|
6768
|
+
`);
|
|
6769
|
+
}
|
|
6588
6770
|
let system;
|
|
6589
6771
|
if (knowledgeBaked && !liveMemory) {
|
|
6590
6772
|
streamLog.write(`[${isoTs()}] [public-knowledge-source] baked \u2014 skipping memory-search (liveMemory=false)
|
|
6773
|
+
`);
|
|
6774
|
+
system = systemPrompt;
|
|
6775
|
+
} else if (!classification.search) {
|
|
6776
|
+
streamLog.write(`[${isoTs()}] [public-knowledge-source] skipped \u2014 classifier (reason=${classification.reason})
|
|
6591
6777
|
`);
|
|
6592
6778
|
system = systemPrompt;
|
|
6593
6779
|
} else if (knowledgeBaked && liveMemory) {
|
|
6594
|
-
const memoryContext = await fetchMemoryContext(accountId,
|
|
6595
|
-
streamLog.write(`[${isoTs()}] [public-knowledge-source] baked+live agentSlug=${agentSlug ?? "none"} keywords=${knowledgeKeywords?.length ?? 0} result=${memoryContext ? `ok (${memoryContext.length} chars)` : "null"}
|
|
6780
|
+
const memoryContext = await fetchMemoryContext(accountId, effectiveQuery, sessionKey, fetchOptions);
|
|
6781
|
+
streamLog.write(`[${isoTs()}] [public-knowledge-source] baked+live agentSlug=${agentSlug ?? "none"} keywords=${knowledgeKeywords?.length ?? 0} effectiveQuery=${JSON.stringify(effectiveQuery.slice(0, 200))} result=${memoryContext ? `ok (${memoryContext.length} chars)` : "null"}
|
|
6596
6782
|
`);
|
|
6597
6783
|
system = memoryContext ? `${systemPrompt}
|
|
6598
6784
|
|
|
@@ -6600,8 +6786,8 @@ async function* invokePublicAgent(message, systemPrompt, accountId, accountDir,
|
|
|
6600
6786
|
${memoryContext}
|
|
6601
6787
|
</live-memory>` : systemPrompt;
|
|
6602
6788
|
} else {
|
|
6603
|
-
const memoryContext = await fetchMemoryContext(accountId,
|
|
6604
|
-
streamLog.write(`[${isoTs()}] [public-knowledge-source] memory-search agentSlug=${agentSlug ?? "none"} keywords=${knowledgeKeywords?.length ?? 0} result=${memoryContext ? `ok (${memoryContext.length} chars)` : "null \u2014 agent has no knowledge"}
|
|
6789
|
+
const memoryContext = await fetchMemoryContext(accountId, effectiveQuery, sessionKey, fetchOptions);
|
|
6790
|
+
streamLog.write(`[${isoTs()}] [public-knowledge-source] memory-search agentSlug=${agentSlug ?? "none"} keywords=${knowledgeKeywords?.length ?? 0} effectiveQuery=${JSON.stringify(effectiveQuery.slice(0, 200))} result=${memoryContext ? `ok (${memoryContext.length} chars)` : "null \u2014 agent has no knowledge"}
|
|
6605
6791
|
`);
|
|
6606
6792
|
if (memoryContext) {
|
|
6607
6793
|
streamLog.write(`[${isoTs()}] [public-memory-context] ${JSON.stringify(memoryContext.slice(0, 500))}${memoryContext.length > 500 ? "\u2026" : ""}
|
|
@@ -6613,7 +6799,6 @@ ${memoryContext}
|
|
|
6613
6799
|
${memoryContext}
|
|
6614
6800
|
</memory>` : systemPrompt;
|
|
6615
6801
|
}
|
|
6616
|
-
const history = sessionKey ? getMessageHistory(sessionKey) : [];
|
|
6617
6802
|
const sdkMessages = [
|
|
6618
6803
|
...history.map((m) => ({ role: m.role, content: m.content })),
|
|
6619
6804
|
{ role: "user", content: message }
|
|
@@ -87,9 +87,14 @@ For the most recent session: call `session-list` with `limit: 1`, then call `ses
|
|
|
87
87
|
|
|
88
88
|
## Session Memory
|
|
89
89
|
|
|
90
|
-
The platform automatically injects recalled context into your system prompt as a `<previous-context>` section. This includes the most recent session summary and all open/active tasks.
|
|
90
|
+
The platform automatically injects recalled context into your system prompt as a `<previous-context>` section. This includes the most recent session summary and all open/active tasks. The platform rejects stale summaries (older than 48 hours) — when present, the summary is recent and trustworthy.
|
|
91
91
|
|
|
92
|
-
When `<previous-context>` is present
|
|
92
|
+
When `<previous-context>` is present:
|
|
93
|
+
- **Trust the session summary for state orientation.** It reflects the end of the most recent session. Do not re-verify claims already described — if the summary says onboarding is complete, do not call `onboarding-get`. If it names tasks in progress, acknowledge and resume them.
|
|
94
|
+
- **Do not re-research context captured in the summary.** When the summary describes work in progress, outstanding tasks, or recent decisions, pick up from that state. Redundant memory-search or tool calls for information already in the summary waste the owner's time.
|
|
95
|
+
- Use it to greet the owner with awareness of prior work and outstanding tasks.
|
|
96
|
+
|
|
97
|
+
When `<previous-context>` is absent, Neo4j was unreachable or no prior context exists — proceed normally, using tool calls to establish state.
|
|
93
98
|
|
|
94
99
|
In managed context mode, conversation history is provided within `<conversation-history>` tags. Use `session-compact-status` to retrieve older archived context if needed.
|
|
95
100
|
|