@recapt/mcp 0.0.26 → 0.0.28
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/index.js +846 -184
- package/dist/tools/catalog/anthropicToolCatalog.json +242 -1
- package/dist/tools/catalog/toolCatalog.json +4074 -382
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1181,7 +1181,7 @@ var updateImprovementRunTool = {
|
|
|
1181
1181
|
inputSchema: z27.object({
|
|
1182
1182
|
run_id: z27.string().describe("The ID of the improvement run to update"),
|
|
1183
1183
|
status: z27.enum(["running", "completed", "failed", "cancelled"]).optional().describe("New status for the run"),
|
|
1184
|
-
title: z27.string().optional().describe("A
|
|
1184
|
+
title: z27.string().optional().describe("A user-friendly title summarizing what was improved (e.g., 'Fixed checkout responsiveness, improved mobile navigation'). Avoid technical jargon - write for non-developers."),
|
|
1185
1185
|
phases: z27.array(z27.object({
|
|
1186
1186
|
name: z27.string(),
|
|
1187
1187
|
status: z27.enum([
|
|
@@ -1233,9 +1233,9 @@ var updateImprovementRunTool = {
|
|
|
1233
1233
|
};
|
|
1234
1234
|
var recordImprovementActionTool = {
|
|
1235
1235
|
name: "record_improvement_action",
|
|
1236
|
-
description: "Record an action taken during an improvement run.
|
|
1236
|
+
description: "Record an action taken during an improvement run. REQUIRED: run_id, action_type, hypothesis, expected_improvement. For code_fix: include code_changes array with actual unified diff format (not descriptions). For needs_more_data: include deferral_reason. For dismissed: include dismissal_reason. Use expected_improvement='N/A' for non-fix actions.",
|
|
1237
1237
|
inputSchema: z27.object({
|
|
1238
|
-
run_id: z27.string().describe("The ID of the improvement run"),
|
|
1238
|
+
run_id: z27.string().describe("REQUIRED. The ID of the improvement run"),
|
|
1239
1239
|
issue_id: z27.string().optional().describe("The ID of the issue this action relates to (if any)"),
|
|
1240
1240
|
action_type: z27.enum([
|
|
1241
1241
|
"code_fix",
|
|
@@ -1243,9 +1243,9 @@ var recordImprovementActionTool = {
|
|
|
1243
1243
|
"dismissed",
|
|
1244
1244
|
"marked_intended",
|
|
1245
1245
|
"knowledge_added"
|
|
1246
|
-
]).describe("Type of action taken"),
|
|
1247
|
-
hypothesis: z27.string().describe("AI's reasoning for this action"),
|
|
1248
|
-
expected_improvement: z27.string().describe("What improvement is expected
|
|
1246
|
+
]).describe("REQUIRED. Type of action taken"),
|
|
1247
|
+
hypothesis: z27.string().describe("REQUIRED. AI's reasoning for this action"),
|
|
1248
|
+
expected_improvement: z27.string().describe("REQUIRED. What improvement is expected (use 'N/A' for non-fix actions)"),
|
|
1249
1249
|
code_changes: z27.array(z27.object({
|
|
1250
1250
|
file: z27.string().describe("Path to the changed file"),
|
|
1251
1251
|
diff: z27.string().describe("Unified diff format code changes"),
|
|
@@ -1269,6 +1269,12 @@ var recordImprovementActionTool = {
|
|
|
1269
1269
|
if (!isApiConfigured()) {
|
|
1270
1270
|
return apiNotConfiguredResult();
|
|
1271
1271
|
}
|
|
1272
|
+
if (remediation_id) {
|
|
1273
|
+
const { data: remediation, error: remediationError } = await apiGet(`/remediations/${remediation_id}`);
|
|
1274
|
+
if (remediationError || !remediation) {
|
|
1275
|
+
return errorResult(`Remediation ${remediation_id} not found`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1272
1278
|
const { data, error } = await apiPost(`/improvement-runs/${run_id}/actions`, {
|
|
1273
1279
|
issue_id,
|
|
1274
1280
|
action_type,
|
|
@@ -1569,17 +1575,28 @@ var predictOutcomesTool = {
|
|
|
1569
1575
|
import { z as z32 } from "zod";
|
|
1570
1576
|
var proposeFixTool = {
|
|
1571
1577
|
name: "propose_fix",
|
|
1572
|
-
description: "Propose a fix for an issue. Creates a remediation record with baseline metrics for later evaluation.
|
|
1578
|
+
description: "Propose a fix for an issue. REQUIRED: issue_id, diagnosis, proposed_fix, confidence. Creates a remediation record with baseline metrics for later evaluation. Returns remediation_id for tracking.",
|
|
1573
1579
|
inputSchema: z32.object({
|
|
1574
|
-
issue_id: z32.string().describe("The ID of the issue to fix"),
|
|
1575
|
-
diagnosis: z32.string().describe("Detailed analysis of the root cause of the issue"),
|
|
1576
|
-
proposed_fix: z32.string().describe("Description of the proposed fix and how it addresses the root cause"),
|
|
1580
|
+
issue_id: z32.string().describe("REQUIRED. The ID of the issue to fix"),
|
|
1581
|
+
diagnosis: z32.string().describe("REQUIRED. Detailed analysis of the root cause of the issue"),
|
|
1582
|
+
proposed_fix: z32.string().describe("REQUIRED. Description of the proposed fix and how it addresses the root cause"),
|
|
1577
1583
|
code_snippet: z32.string().optional().describe("Suggested code changes (if applicable)"),
|
|
1578
1584
|
affected_files: z32.array(z32.string()).optional().describe("List of files that need to be modified"),
|
|
1579
|
-
confidence: z32.number().min(0).max(1).describe("Confidence level in the fix (0-1). Use <0.7 when uncertain.")
|
|
1585
|
+
confidence: z32.number().min(0).max(1).describe("REQUIRED. Confidence level in the fix (0-1). Use <0.7 when uncertain."),
|
|
1586
|
+
title: z32.string().optional().describe("User-friendly title describing the issue from the user's perspective"),
|
|
1587
|
+
execution_metadata: z32.object({
|
|
1588
|
+
difficulty_class: z32.enum(["trivial", "light", "heavy"]),
|
|
1589
|
+
difficulty_reasoning: z32.string(),
|
|
1590
|
+
model_used: z32.string(),
|
|
1591
|
+
was_escalated: z32.boolean().optional(),
|
|
1592
|
+
escalation_reason: z32.string().optional(),
|
|
1593
|
+
iterations: z32.number().optional(),
|
|
1594
|
+
credits_used: z32.number().optional(),
|
|
1595
|
+
duration_ms: z32.number().optional()
|
|
1596
|
+
}).optional().describe("AI execution metadata for tracking model selection and performance")
|
|
1580
1597
|
}),
|
|
1581
1598
|
handler: async (args) => {
|
|
1582
|
-
const { issue_id, diagnosis, proposed_fix, code_snippet, affected_files, confidence } = args;
|
|
1599
|
+
const { issue_id, diagnosis, proposed_fix, code_snippet, affected_files, confidence, title, execution_metadata } = args;
|
|
1583
1600
|
if (!isApiConfigured()) {
|
|
1584
1601
|
return apiNotConfiguredResult();
|
|
1585
1602
|
}
|
|
@@ -1589,7 +1606,9 @@ var proposeFixTool = {
|
|
|
1589
1606
|
proposed_fix,
|
|
1590
1607
|
code_snippet,
|
|
1591
1608
|
affected_files,
|
|
1592
|
-
confidence
|
|
1609
|
+
confidence,
|
|
1610
|
+
title,
|
|
1611
|
+
execution_metadata
|
|
1593
1612
|
});
|
|
1594
1613
|
if (error) {
|
|
1595
1614
|
return errorResult(error);
|
|
@@ -1624,7 +1643,8 @@ var listRemediationsTool = {
|
|
|
1624
1643
|
"evaluating",
|
|
1625
1644
|
"succeeded",
|
|
1626
1645
|
"failed",
|
|
1627
|
-
"reverted"
|
|
1646
|
+
"reverted",
|
|
1647
|
+
"deferred"
|
|
1628
1648
|
]).optional().describe("Filter by remediation status"),
|
|
1629
1649
|
issue_id: z32.string().optional().describe("Filter by issue ID"),
|
|
1630
1650
|
page_path: z32.string().optional().describe("Filter by page path"),
|
|
@@ -1704,10 +1724,10 @@ var getFixHistoryTool = {
|
|
|
1704
1724
|
};
|
|
1705
1725
|
var updateRemediationStatusTool = {
|
|
1706
1726
|
name: "update_remediation_status",
|
|
1707
|
-
description: "Update
|
|
1727
|
+
description: "Update remediation status based on PR events. REQUIRED: remediation_id, status. Status values: 'waiting' (PR created), 'deployed' (PR merged), 'dismissed' (PR closed without merge).",
|
|
1708
1728
|
inputSchema: z32.object({
|
|
1709
|
-
remediation_id: z32.string().describe("The ID of the remediation to update"),
|
|
1710
|
-
status: z32.enum(["waiting", "deployed", "dismissed"]).describe("
|
|
1729
|
+
remediation_id: z32.string().describe("REQUIRED. The ID of the remediation to update"),
|
|
1730
|
+
status: z32.enum(["waiting", "deployed", "dismissed"]).describe("REQUIRED. One of: waiting, deployed, dismissed"),
|
|
1711
1731
|
pr_url: z32.string().optional().describe("URL of the pull request"),
|
|
1712
1732
|
pr_number: z32.number().optional().describe("PR number"),
|
|
1713
1733
|
pr_merged_at: z32.string().optional().describe("ISO timestamp when PR was merged"),
|
|
@@ -1733,7 +1753,7 @@ var updateRemediationStatusTool = {
|
|
|
1733
1753
|
};
|
|
1734
1754
|
var listRemediationsByStatusTool = {
|
|
1735
1755
|
name: "list_remediations_by_status",
|
|
1736
|
-
description: "List remediations filtered by one or more statuses. Use to find all 'waiting' PRs or 'deployed' fixes ready for evaluation.",
|
|
1756
|
+
description: "List remediations filtered by one or more statuses. Use to find all 'waiting' PRs or 'deployed' fixes ready for evaluation. Set include_execution to get difficulty classification for model selection.",
|
|
1737
1757
|
inputSchema: z32.object({
|
|
1738
1758
|
statuses: z32.array(z32.enum([
|
|
1739
1759
|
"proposed",
|
|
@@ -1743,16 +1763,19 @@ var listRemediationsByStatusTool = {
|
|
|
1743
1763
|
"evaluating",
|
|
1744
1764
|
"succeeded",
|
|
1745
1765
|
"failed",
|
|
1746
|
-
"reverted"
|
|
1747
|
-
|
|
1766
|
+
"reverted",
|
|
1767
|
+
"deferred"
|
|
1768
|
+
])).describe("List of statuses to filter by"),
|
|
1769
|
+
include_execution: z32.boolean().optional().describe("Include execution metadata (difficulty class, model used) for each remediation")
|
|
1748
1770
|
}),
|
|
1749
1771
|
handler: async (args) => {
|
|
1750
|
-
const { statuses } = args;
|
|
1772
|
+
const { statuses, include_execution } = args;
|
|
1751
1773
|
if (!isApiConfigured()) {
|
|
1752
1774
|
return apiNotConfiguredResult();
|
|
1753
1775
|
}
|
|
1754
1776
|
const { data, error } = await apiGet(`/remediations/by-status`, {
|
|
1755
|
-
statuses: statuses.join(",")
|
|
1777
|
+
statuses: statuses.join(","),
|
|
1778
|
+
include_execution: include_execution ? "true" : void 0
|
|
1756
1779
|
});
|
|
1757
1780
|
if (error) {
|
|
1758
1781
|
return errorResult(error);
|
|
@@ -1778,6 +1801,34 @@ var getRemediationByPrTool = {
|
|
|
1778
1801
|
return successResult(data);
|
|
1779
1802
|
}
|
|
1780
1803
|
};
|
|
1804
|
+
var deferIssueTool = {
|
|
1805
|
+
name: "defer_issue",
|
|
1806
|
+
description: "Defer an issue that needs more data. REQUIRED: issue_id, diagnosis, deferral_reason, confidence. Creates a remediation record with 'deferred' status. Returns remediation_id for tracking.",
|
|
1807
|
+
inputSchema: z32.object({
|
|
1808
|
+
issue_id: z32.string().describe("REQUIRED. The ID of the issue to defer"),
|
|
1809
|
+
diagnosis: z32.string().describe("REQUIRED. Analysis of what you think the problem might be"),
|
|
1810
|
+
deferral_reason: z32.string().describe("REQUIRED. Explanation of why the issue cannot be fixed yet"),
|
|
1811
|
+
confidence: z32.number().min(0).max(1).describe("REQUIRED. Confidence level in the diagnosis (0-1). Typically low for deferred issues."),
|
|
1812
|
+
affected_files: z32.array(z32.string()).optional().describe("List of files that might need to be modified")
|
|
1813
|
+
}),
|
|
1814
|
+
handler: async (args) => {
|
|
1815
|
+
const { issue_id, diagnosis, deferral_reason, confidence, affected_files } = args;
|
|
1816
|
+
if (!isApiConfigured()) {
|
|
1817
|
+
return apiNotConfiguredResult();
|
|
1818
|
+
}
|
|
1819
|
+
const { data, error } = await apiPost("/remediations/defer", {
|
|
1820
|
+
issue_id,
|
|
1821
|
+
diagnosis,
|
|
1822
|
+
deferral_reason,
|
|
1823
|
+
confidence,
|
|
1824
|
+
affected_files
|
|
1825
|
+
});
|
|
1826
|
+
if (error) {
|
|
1827
|
+
return errorResult(error);
|
|
1828
|
+
}
|
|
1829
|
+
return successResult(data);
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1781
1832
|
|
|
1782
1833
|
// ../../libraries/mcp-tools/dist/tools/scanSite.js
|
|
1783
1834
|
import { z as z33 } from "zod";
|
|
@@ -1841,11 +1892,11 @@ var DISMISS_REASONS = [
|
|
|
1841
1892
|
];
|
|
1842
1893
|
var dismissIssueTool = {
|
|
1843
1894
|
name: "dismiss_issue",
|
|
1844
|
-
description: "Dismiss an issue as a false positive. Creates site knowledge to prevent re-flagging.
|
|
1895
|
+
description: "Dismiss an issue as a false positive. REQUIRED: issue_id, reason (enum), explanation. Creates site knowledge to prevent re-flagging. Confirm with user before dismissing in interactive mode.",
|
|
1845
1896
|
inputSchema: z35.object({
|
|
1846
|
-
issue_id: z35.string().describe("The ID of the issue to dismiss"),
|
|
1847
|
-
reason: z35.enum(DISMISS_REASONS).describe("
|
|
1848
|
-
explanation: z35.string().describe("Detailed explanation of why this issue is being dismissed"),
|
|
1897
|
+
issue_id: z35.string().describe("REQUIRED. The ID of the issue to dismiss"),
|
|
1898
|
+
reason: z35.enum(DISMISS_REASONS).describe("REQUIRED. One of: stale, intended, duplicate, false_positive"),
|
|
1899
|
+
explanation: z35.string().describe("REQUIRED. Detailed explanation of why this issue is being dismissed"),
|
|
1849
1900
|
create_knowledge: z35.boolean().optional().default(true).describe("Whether to create site knowledge entry (default: true)")
|
|
1850
1901
|
}),
|
|
1851
1902
|
handler: async (args) => {
|
|
@@ -2078,22 +2129,32 @@ var PROPOSAL_STATUSES = [
|
|
|
2078
2129
|
];
|
|
2079
2130
|
var createProposalTool = {
|
|
2080
2131
|
name: "create_proposal",
|
|
2081
|
-
description: "Create a fix proposal
|
|
2132
|
+
description: "AUDIT MODE ONLY. Create a fix proposal. REQUIRED: title, diagnosis, proposed_fix, confidence, page_path, category, severity. Proposals are suggestions that developers review and implement manually. Do NOT use in fix mode - use propose_fix instead.",
|
|
2082
2133
|
inputSchema: z37.object({
|
|
2083
2134
|
issue_id: z37.string().optional().describe("The ID of the related issue (if any)"),
|
|
2084
2135
|
improvement_run_id: z37.string().optional().describe("The ID of the improvement run creating this proposal"),
|
|
2085
|
-
title: z37.string().describe("Short title describing the
|
|
2086
|
-
diagnosis: z37.string().describe("Detailed analysis of the root cause of the issue"),
|
|
2087
|
-
proposed_fix: z37.string().describe("Description of the proposed fix and how it addresses the root cause"),
|
|
2136
|
+
title: z37.string().describe("REQUIRED. Short user-friendly title describing the issue from the user's perspective"),
|
|
2137
|
+
diagnosis: z37.string().describe("REQUIRED. Detailed analysis of the root cause of the issue"),
|
|
2138
|
+
proposed_fix: z37.string().describe("REQUIRED. Description of the proposed fix and how it addresses the root cause"),
|
|
2088
2139
|
affected_files: z37.array(z37.string()).optional().describe("List of files that likely need to be modified"),
|
|
2089
|
-
confidence: z37.number().min(0).max(1).describe("Confidence level in the fix (0-1). Use <0.7 when uncertain."),
|
|
2090
|
-
page_path: z37.string().describe("The page path where the issue occurs"),
|
|
2140
|
+
confidence: z37.number().min(0).max(1).describe("REQUIRED. Confidence level in the fix (0-1). Use <0.7 when uncertain."),
|
|
2141
|
+
page_path: z37.string().describe("REQUIRED. The page path where the issue occurs"),
|
|
2091
2142
|
element_selector: z37.string().optional().describe("CSS selector of the affected element (if applicable)"),
|
|
2092
|
-
category: z37.enum(ISSUE_CATEGORIES2).describe("Category
|
|
2093
|
-
severity: z37.enum(ISSUE_SEVERITIES).describe("Severity
|
|
2143
|
+
category: z37.enum(ISSUE_CATEGORIES2).describe("REQUIRED. Category: code_error, dead_click, rage_click, ux_friction, performance, form_issue, behavioral_anomaly, structural_issue"),
|
|
2144
|
+
severity: z37.enum(ISSUE_SEVERITIES).describe("REQUIRED. Severity: critical, high, medium, low, info"),
|
|
2145
|
+
execution_metadata: z37.object({
|
|
2146
|
+
difficulty_class: z37.enum(["trivial", "light", "heavy"]),
|
|
2147
|
+
difficulty_reasoning: z37.string(),
|
|
2148
|
+
model_used: z37.string(),
|
|
2149
|
+
was_escalated: z37.boolean().optional(),
|
|
2150
|
+
escalation_reason: z37.string().optional(),
|
|
2151
|
+
iterations: z37.number().optional(),
|
|
2152
|
+
credits_used: z37.number().optional(),
|
|
2153
|
+
duration_ms: z37.number().optional()
|
|
2154
|
+
}).optional().describe("AI execution metadata for tracking model selection and performance")
|
|
2094
2155
|
}),
|
|
2095
2156
|
handler: async (args) => {
|
|
2096
|
-
const { issue_id, improvement_run_id, title, diagnosis, proposed_fix, affected_files, confidence, page_path, element_selector, category, severity } = args;
|
|
2157
|
+
const { issue_id, improvement_run_id, title, diagnosis, proposed_fix, affected_files, confidence, page_path, element_selector, category, severity, execution_metadata } = args;
|
|
2097
2158
|
if (!isApiConfigured()) {
|
|
2098
2159
|
return apiNotConfiguredResult();
|
|
2099
2160
|
}
|
|
@@ -2108,7 +2169,8 @@ var createProposalTool = {
|
|
|
2108
2169
|
page_path,
|
|
2109
2170
|
element_selector,
|
|
2110
2171
|
category,
|
|
2111
|
-
severity
|
|
2172
|
+
severity,
|
|
2173
|
+
execution_metadata
|
|
2112
2174
|
});
|
|
2113
2175
|
if (error) {
|
|
2114
2176
|
return errorResult(error);
|
|
@@ -2194,11 +2256,11 @@ var resolveProposalTool = {
|
|
|
2194
2256
|
};
|
|
2195
2257
|
var dismissProposalTool = {
|
|
2196
2258
|
name: "dismiss_proposal",
|
|
2197
|
-
description: "Dismiss a proposal that won't be implemented. Use when the proposal is not applicable, the issue is intended behavior, or a different approach is preferred.",
|
|
2259
|
+
description: "Dismiss a proposal that won't be implemented. REQUIRED: proposal_id, reason. Use when the proposal is not applicable, the issue is intended behavior, or a different approach is preferred.",
|
|
2198
2260
|
inputSchema: z37.object({
|
|
2199
|
-
proposal_id: z37.string().describe("The ID of the proposal to dismiss"),
|
|
2200
|
-
reason: z37.string().describe("Explanation of why the proposal is being dismissed"),
|
|
2201
|
-
member_id: z37.string().optional().describe("The ID of the member who dismissed it
|
|
2261
|
+
proposal_id: z37.string().describe("REQUIRED. The ID of the proposal to dismiss"),
|
|
2262
|
+
reason: z37.string().describe("REQUIRED. Explanation of why the proposal is being dismissed"),
|
|
2263
|
+
member_id: z37.string().optional().describe("The ID of the member who dismissed it")
|
|
2202
2264
|
}),
|
|
2203
2265
|
handler: async (args) => {
|
|
2204
2266
|
const { proposal_id, reason, member_id } = args;
|
|
@@ -2293,10 +2355,11 @@ async function gitlabRequest(endpoint, options = {}) {
|
|
|
2293
2355
|
const ctx = getGitContext();
|
|
2294
2356
|
const baseUrl = ctx.baseUrl || "https://gitlab.com";
|
|
2295
2357
|
const url = `${baseUrl}/api/v4${endpoint}`;
|
|
2358
|
+
const authHeader = ctx.token.startsWith("glpat-") ? { "PRIVATE-TOKEN": ctx.token } : { Authorization: `Bearer ${ctx.token}` };
|
|
2296
2359
|
return fetch(url, {
|
|
2297
2360
|
...options,
|
|
2298
2361
|
headers: {
|
|
2299
|
-
|
|
2362
|
+
...authHeader,
|
|
2300
2363
|
"Content-Type": "application/json",
|
|
2301
2364
|
...options.headers
|
|
2302
2365
|
}
|
|
@@ -2417,13 +2480,13 @@ var getFileContentTool = {
|
|
|
2417
2480
|
};
|
|
2418
2481
|
var updateFileTool = {
|
|
2419
2482
|
name: "update_file",
|
|
2420
|
-
description: "Update or create a file in the repository. Works with both GitHub and GitLab.",
|
|
2483
|
+
description: "Update or create a file in the repository. Works with both GitHub and GitLab. SHA is auto-fetched for existing files.",
|
|
2421
2484
|
inputSchema: z38.object({
|
|
2422
2485
|
path: z38.string().describe("Path to the file in the repository"),
|
|
2423
2486
|
content: z38.string().describe("New content for the file"),
|
|
2424
2487
|
message: z38.string().describe("Commit message"),
|
|
2425
2488
|
branch: z38.string().describe("Branch to commit to"),
|
|
2426
|
-
sha: z38.string().optional().describe("SHA of the file being replaced (
|
|
2489
|
+
sha: z38.string().optional().describe("SHA of the file being replaced (auto-fetched if not provided)")
|
|
2427
2490
|
}),
|
|
2428
2491
|
handler: async (args) => {
|
|
2429
2492
|
try {
|
|
@@ -2432,8 +2495,15 @@ var updateFileTool = {
|
|
|
2432
2495
|
const content = args.content;
|
|
2433
2496
|
const message = args.message;
|
|
2434
2497
|
const branch = args.branch;
|
|
2435
|
-
|
|
2498
|
+
let sha = args.sha;
|
|
2436
2499
|
if (ctx.provider === "github") {
|
|
2500
|
+
if (!sha) {
|
|
2501
|
+
const checkRes = await githubRequest(`/repos/${ctx.repoFullName}/contents/${filePath}?ref=${branch}`);
|
|
2502
|
+
if (checkRes.ok) {
|
|
2503
|
+
const existingFile = await checkRes.json();
|
|
2504
|
+
sha = existingFile.sha;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2437
2507
|
const body = {
|
|
2438
2508
|
message,
|
|
2439
2509
|
content: Buffer.from(content).toString("base64"),
|
|
@@ -2461,7 +2531,15 @@ var updateFileTool = {
|
|
|
2461
2531
|
} else {
|
|
2462
2532
|
const encodedPath = encodeURIComponent(ctx.repoFullName);
|
|
2463
2533
|
const encodedFilePath = encodeURIComponent(filePath);
|
|
2464
|
-
|
|
2534
|
+
let method = "POST";
|
|
2535
|
+
if (!sha) {
|
|
2536
|
+
const checkRes = await gitlabRequest(`/projects/${encodedPath}/repository/files/${encodedFilePath}?ref=${branch}`);
|
|
2537
|
+
if (checkRes.ok) {
|
|
2538
|
+
method = "PUT";
|
|
2539
|
+
}
|
|
2540
|
+
} else {
|
|
2541
|
+
method = "PUT";
|
|
2542
|
+
}
|
|
2465
2543
|
const res = await gitlabRequest(`/projects/${encodedPath}/repository/files/${encodedFilePath}`, {
|
|
2466
2544
|
method,
|
|
2467
2545
|
body: JSON.stringify({
|
|
@@ -2859,6 +2937,7 @@ var allTools = [
|
|
|
2859
2937
|
updateRemediationStatusTool,
|
|
2860
2938
|
listRemediationsByStatusTool,
|
|
2861
2939
|
getRemediationByPrTool,
|
|
2940
|
+
deferIssueTool,
|
|
2862
2941
|
// Scan
|
|
2863
2942
|
scanSiteTool,
|
|
2864
2943
|
// Search
|
|
@@ -2902,6 +2981,604 @@ var EXPOSED_TOOL_NAMES = [
|
|
|
2902
2981
|
var exposedTools = allTools.filter((t) => EXPOSED_TOOL_NAMES.includes(t.name));
|
|
2903
2982
|
var hiddenTools = allTools.filter((t) => !EXPOSED_TOOL_NAMES.includes(t.name));
|
|
2904
2983
|
|
|
2984
|
+
// ../../libraries/mcp-tools/dist/memory.js
|
|
2985
|
+
var MAX_ENTRIES = 100;
|
|
2986
|
+
var MAX_SIZE_BYTES = 2 * 1024 * 1024;
|
|
2987
|
+
var PRUNE_TARGET_RATIO = 0.8;
|
|
2988
|
+
var MemoryStore = class _MemoryStore {
|
|
2989
|
+
store = /* @__PURE__ */ new Map();
|
|
2990
|
+
/**
|
|
2991
|
+
* Save a value to memory. Updates access tracking and triggers pruning if needed.
|
|
2992
|
+
*/
|
|
2993
|
+
save(key, value, annotation) {
|
|
2994
|
+
const now = Date.now();
|
|
2995
|
+
const existing = this.store.get(key);
|
|
2996
|
+
this.store.set(key, {
|
|
2997
|
+
key,
|
|
2998
|
+
value,
|
|
2999
|
+
timestamp: existing?.timestamp ?? now,
|
|
3000
|
+
annotation,
|
|
3001
|
+
lastAccessedAt: now,
|
|
3002
|
+
accessCount: existing?.accessCount ?? 0
|
|
3003
|
+
});
|
|
3004
|
+
this.pruneIfNeeded();
|
|
3005
|
+
return {
|
|
3006
|
+
content: [
|
|
3007
|
+
{
|
|
3008
|
+
type: "text",
|
|
3009
|
+
text: JSON.stringify({ success: true, key, size: value.length })
|
|
3010
|
+
}
|
|
3011
|
+
]
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Save a value directly without returning ToolResult (for internal use).
|
|
3016
|
+
*/
|
|
3017
|
+
saveRaw(key, value) {
|
|
3018
|
+
const now = Date.now();
|
|
3019
|
+
const existing = this.store.get(key);
|
|
3020
|
+
this.store.set(key, {
|
|
3021
|
+
key,
|
|
3022
|
+
value,
|
|
3023
|
+
timestamp: existing?.timestamp ?? now,
|
|
3024
|
+
annotation: void 0,
|
|
3025
|
+
lastAccessedAt: now,
|
|
3026
|
+
accessCount: existing?.accessCount ?? 0
|
|
3027
|
+
});
|
|
3028
|
+
this.pruneIfNeeded();
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Recall a value from memory. Updates access tracking.
|
|
3032
|
+
* Supports optional query parameters for filtering and selecting data.
|
|
3033
|
+
*/
|
|
3034
|
+
recall(key, options) {
|
|
3035
|
+
const entry = this.store.get(key);
|
|
3036
|
+
if (!entry) {
|
|
3037
|
+
return {
|
|
3038
|
+
content: [
|
|
3039
|
+
{
|
|
3040
|
+
type: "text",
|
|
3041
|
+
text: JSON.stringify({
|
|
3042
|
+
error: `Key "${key}" not found in memory`,
|
|
3043
|
+
available_keys: this.keys().slice(0, 10)
|
|
3044
|
+
})
|
|
3045
|
+
}
|
|
3046
|
+
],
|
|
3047
|
+
isError: true
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
entry.lastAccessedAt = Date.now();
|
|
3051
|
+
entry.accessCount++;
|
|
3052
|
+
if (!options || !options.where && !options.select && !options.limit) {
|
|
3053
|
+
return {
|
|
3054
|
+
content: [
|
|
3055
|
+
{
|
|
3056
|
+
type: "text",
|
|
3057
|
+
text: entry.value
|
|
3058
|
+
}
|
|
3059
|
+
]
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
try {
|
|
3063
|
+
const data = JSON.parse(entry.value);
|
|
3064
|
+
const result = this.queryData(data, options);
|
|
3065
|
+
return {
|
|
3066
|
+
content: [
|
|
3067
|
+
{
|
|
3068
|
+
type: "text",
|
|
3069
|
+
text: JSON.stringify(result)
|
|
3070
|
+
}
|
|
3071
|
+
]
|
|
3072
|
+
};
|
|
3073
|
+
} catch {
|
|
3074
|
+
return {
|
|
3075
|
+
content: [
|
|
3076
|
+
{
|
|
3077
|
+
type: "text",
|
|
3078
|
+
text: entry.value
|
|
3079
|
+
}
|
|
3080
|
+
]
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Query data with filtering, selection, and pagination.
|
|
3086
|
+
*/
|
|
3087
|
+
queryData(data, options) {
|
|
3088
|
+
let items;
|
|
3089
|
+
if (Array.isArray(data)) {
|
|
3090
|
+
items = data;
|
|
3091
|
+
} else if (this.isRecord(data)) {
|
|
3092
|
+
const arrayField = Object.entries(data).find(([, v]) => Array.isArray(v));
|
|
3093
|
+
if (arrayField) {
|
|
3094
|
+
items = arrayField[1];
|
|
3095
|
+
} else {
|
|
3096
|
+
return data;
|
|
3097
|
+
}
|
|
3098
|
+
} else {
|
|
3099
|
+
return data;
|
|
3100
|
+
}
|
|
3101
|
+
if (options.where && options.where.length > 0) {
|
|
3102
|
+
items = items.filter((item) => this.matchesWhere(item, options.where));
|
|
3103
|
+
}
|
|
3104
|
+
if (options.select && options.select.length > 0) {
|
|
3105
|
+
items = items.map((item) => this.selectFields(item, options.select));
|
|
3106
|
+
}
|
|
3107
|
+
const offset = options.offset ?? 0;
|
|
3108
|
+
const limit = options.limit ?? items.length;
|
|
3109
|
+
items = items.slice(offset, offset + limit);
|
|
3110
|
+
return {
|
|
3111
|
+
items,
|
|
3112
|
+
total: items.length,
|
|
3113
|
+
offset,
|
|
3114
|
+
limit
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
isRecord(value) {
|
|
3118
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3119
|
+
}
|
|
3120
|
+
matchesWhere(item, conditions) {
|
|
3121
|
+
if (!this.isRecord(item))
|
|
3122
|
+
return true;
|
|
3123
|
+
for (const condition of conditions) {
|
|
3124
|
+
const match = condition.match(/^(\w+)\s*(>|<|>=|<=|==|!=|=)\s*(.+)$/);
|
|
3125
|
+
if (!match)
|
|
3126
|
+
continue;
|
|
3127
|
+
const [, field, op, rawValue] = match;
|
|
3128
|
+
const itemValue = item[field];
|
|
3129
|
+
let compareValue = rawValue.trim();
|
|
3130
|
+
if (compareValue === "true")
|
|
3131
|
+
compareValue = true;
|
|
3132
|
+
else if (compareValue === "false")
|
|
3133
|
+
compareValue = false;
|
|
3134
|
+
else if (!isNaN(Number(compareValue)))
|
|
3135
|
+
compareValue = Number(compareValue);
|
|
3136
|
+
else if (compareValue.startsWith('"') && compareValue.endsWith('"')) {
|
|
3137
|
+
compareValue = compareValue.slice(1, -1);
|
|
3138
|
+
}
|
|
3139
|
+
let passes = false;
|
|
3140
|
+
switch (op) {
|
|
3141
|
+
case ">":
|
|
3142
|
+
passes = Number(itemValue) > Number(compareValue);
|
|
3143
|
+
break;
|
|
3144
|
+
case "<":
|
|
3145
|
+
passes = Number(itemValue) < Number(compareValue);
|
|
3146
|
+
break;
|
|
3147
|
+
case ">=":
|
|
3148
|
+
passes = Number(itemValue) >= Number(compareValue);
|
|
3149
|
+
break;
|
|
3150
|
+
case "<=":
|
|
3151
|
+
passes = Number(itemValue) <= Number(compareValue);
|
|
3152
|
+
break;
|
|
3153
|
+
case "==":
|
|
3154
|
+
case "=":
|
|
3155
|
+
passes = itemValue === compareValue;
|
|
3156
|
+
break;
|
|
3157
|
+
case "!=":
|
|
3158
|
+
passes = itemValue !== compareValue;
|
|
3159
|
+
break;
|
|
3160
|
+
}
|
|
3161
|
+
if (!passes)
|
|
3162
|
+
return false;
|
|
3163
|
+
}
|
|
3164
|
+
return true;
|
|
3165
|
+
}
|
|
3166
|
+
selectFields(item, fields) {
|
|
3167
|
+
if (!this.isRecord(item))
|
|
3168
|
+
return {};
|
|
3169
|
+
const result = {};
|
|
3170
|
+
for (const field of fields) {
|
|
3171
|
+
if (field in item) {
|
|
3172
|
+
result[field] = item[field];
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
return result;
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Recall a value directly without ToolResult wrapper (for internal use).
|
|
3179
|
+
*/
|
|
3180
|
+
recallRaw(key) {
|
|
3181
|
+
const entry = this.store.get(key);
|
|
3182
|
+
if (!entry)
|
|
3183
|
+
return void 0;
|
|
3184
|
+
entry.lastAccessedAt = Date.now();
|
|
3185
|
+
entry.accessCount++;
|
|
3186
|
+
return entry.value;
|
|
3187
|
+
}
|
|
3188
|
+
/**
|
|
3189
|
+
* Check if a key exists in memory.
|
|
3190
|
+
*/
|
|
3191
|
+
has(key) {
|
|
3192
|
+
return this.store.has(key);
|
|
3193
|
+
}
|
|
3194
|
+
/**
|
|
3195
|
+
* Get all keys in memory.
|
|
3196
|
+
*/
|
|
3197
|
+
keys() {
|
|
3198
|
+
return [...this.store.keys()];
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Get the number of entries in memory.
|
|
3202
|
+
*/
|
|
3203
|
+
get size() {
|
|
3204
|
+
return this.store.size;
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* List all entries with metadata.
|
|
3208
|
+
*/
|
|
3209
|
+
list() {
|
|
3210
|
+
const entries = [...this.store.values()].map((entry) => ({
|
|
3211
|
+
key: entry.key,
|
|
3212
|
+
size: entry.value.length,
|
|
3213
|
+
timestamp: new Date(entry.timestamp).toISOString(),
|
|
3214
|
+
annotation: entry.annotation,
|
|
3215
|
+
accessCount: entry.accessCount
|
|
3216
|
+
}));
|
|
3217
|
+
return {
|
|
3218
|
+
content: [
|
|
3219
|
+
{
|
|
3220
|
+
type: "text",
|
|
3221
|
+
text: JSON.stringify({ count: entries.length, entries })
|
|
3222
|
+
}
|
|
3223
|
+
]
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Delete a key from memory.
|
|
3228
|
+
*/
|
|
3229
|
+
delete(key) {
|
|
3230
|
+
const existed = this.store.delete(key);
|
|
3231
|
+
return {
|
|
3232
|
+
content: [
|
|
3233
|
+
{
|
|
3234
|
+
type: "text",
|
|
3235
|
+
text: JSON.stringify({ success: true, deleted: existed })
|
|
3236
|
+
}
|
|
3237
|
+
]
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
/**
|
|
3241
|
+
* Clear all entries from memory.
|
|
3242
|
+
*/
|
|
3243
|
+
clear() {
|
|
3244
|
+
const count = this.store.size;
|
|
3245
|
+
this.store.clear();
|
|
3246
|
+
return {
|
|
3247
|
+
content: [
|
|
3248
|
+
{
|
|
3249
|
+
type: "text",
|
|
3250
|
+
text: JSON.stringify({ success: true, cleared: count })
|
|
3251
|
+
}
|
|
3252
|
+
]
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
/**
|
|
3256
|
+
* Build a human-readable memory key from tool name + input parameters.
|
|
3257
|
+
*/
|
|
3258
|
+
static buildKey(toolName, input) {
|
|
3259
|
+
const priorityFields = [
|
|
3260
|
+
"page_path",
|
|
3261
|
+
"session_id",
|
|
3262
|
+
"session_ids",
|
|
3263
|
+
"page_path_a",
|
|
3264
|
+
"page_path_b",
|
|
3265
|
+
"expression",
|
|
3266
|
+
"query",
|
|
3267
|
+
"moment_type",
|
|
3268
|
+
"action",
|
|
3269
|
+
"issue_id",
|
|
3270
|
+
"remediation_id",
|
|
3271
|
+
"proposal_id",
|
|
3272
|
+
"run_id"
|
|
3273
|
+
];
|
|
3274
|
+
for (const field of priorityFields) {
|
|
3275
|
+
const value = input[field];
|
|
3276
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
3277
|
+
const strValue = Array.isArray(value) ? value.slice(0, 3).join(",") + (value.length > 3 ? "..." : "") : String(value);
|
|
3278
|
+
return `${toolName}:${strValue}`;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
const params = Object.entries(input).filter(([, v]) => v !== void 0 && v !== null).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`).join("&");
|
|
3282
|
+
if (params) {
|
|
3283
|
+
const hash = params.split("").reduce((acc, char) => {
|
|
3284
|
+
return (acc << 5) - acc + char.charCodeAt(0) | 0;
|
|
3285
|
+
}, 0);
|
|
3286
|
+
return `${toolName}:${Math.abs(hash).toString(36)}`;
|
|
3287
|
+
}
|
|
3288
|
+
return toolName;
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Get total size of all stored values in bytes.
|
|
3292
|
+
*/
|
|
3293
|
+
getTotalSize() {
|
|
3294
|
+
let total = 0;
|
|
3295
|
+
for (const entry of this.store.values()) {
|
|
3296
|
+
total += entry.value.length;
|
|
3297
|
+
}
|
|
3298
|
+
return total;
|
|
3299
|
+
}
|
|
3300
|
+
/**
|
|
3301
|
+
* Prune entries if limits are exceeded.
|
|
3302
|
+
* Uses a scoring system based on recency, access frequency, and size.
|
|
3303
|
+
*/
|
|
3304
|
+
pruneIfNeeded() {
|
|
3305
|
+
const overEntryLimit = this.store.size > MAX_ENTRIES;
|
|
3306
|
+
const overSizeLimit = this.getTotalSize() > MAX_SIZE_BYTES;
|
|
3307
|
+
if (!overEntryLimit && !overSizeLimit)
|
|
3308
|
+
return;
|
|
3309
|
+
const scored = [...this.store.entries()].map(([key, entry]) => ({
|
|
3310
|
+
key,
|
|
3311
|
+
entry,
|
|
3312
|
+
score: this.computePruneScore(key, entry)
|
|
3313
|
+
}));
|
|
3314
|
+
scored.sort((a, b) => a.score - b.score);
|
|
3315
|
+
const targetEntries = Math.floor(MAX_ENTRIES * PRUNE_TARGET_RATIO);
|
|
3316
|
+
const targetSize = Math.floor(MAX_SIZE_BYTES * PRUNE_TARGET_RATIO);
|
|
3317
|
+
while ((this.store.size > targetEntries || this.getTotalSize() > targetSize) && scored.length > 0) {
|
|
3318
|
+
const toPrune = scored.shift();
|
|
3319
|
+
if (!toPrune || toPrune.score >= 1e3)
|
|
3320
|
+
break;
|
|
3321
|
+
this.store.delete(toPrune.key);
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Compute a score for pruning. Higher scores = more valuable = keep longer.
|
|
3326
|
+
* System keys ($meta, $findings, etc.) are protected.
|
|
3327
|
+
*/
|
|
3328
|
+
computePruneScore(key, entry) {
|
|
3329
|
+
if (key.startsWith("$"))
|
|
3330
|
+
return 1e3;
|
|
3331
|
+
const now = Date.now();
|
|
3332
|
+
const msSinceAccess = now - entry.lastAccessedAt;
|
|
3333
|
+
const recencyScore = Math.max(0, 100 - msSinceAccess / 600);
|
|
3334
|
+
const accessScore = Math.min(entry.accessCount * 10, 50);
|
|
3335
|
+
const sizeScore = Math.max(0, 50 - entry.value.length / 1e3);
|
|
3336
|
+
return recencyScore + accessScore + sizeScore;
|
|
3337
|
+
}
|
|
3338
|
+
/**
|
|
3339
|
+
* Serialize all entries to a JSON string for external storage.
|
|
3340
|
+
*/
|
|
3341
|
+
serialize() {
|
|
3342
|
+
const plain = {};
|
|
3343
|
+
for (const [k, entry] of this.store) {
|
|
3344
|
+
plain[k] = entry.value;
|
|
3345
|
+
}
|
|
3346
|
+
return JSON.stringify(plain);
|
|
3347
|
+
}
|
|
3348
|
+
/**
|
|
3349
|
+
* Hydrate a MemoryStore from a serialized JSON string.
|
|
3350
|
+
*/
|
|
3351
|
+
static deserialize(json) {
|
|
3352
|
+
const memory = new _MemoryStore();
|
|
3353
|
+
const entries = JSON.parse(json);
|
|
3354
|
+
const now = Date.now();
|
|
3355
|
+
for (const [k, v] of Object.entries(entries)) {
|
|
3356
|
+
memory.store.set(k, {
|
|
3357
|
+
key: k,
|
|
3358
|
+
value: v,
|
|
3359
|
+
timestamp: now,
|
|
3360
|
+
annotation: void 0,
|
|
3361
|
+
lastAccessedAt: now,
|
|
3362
|
+
accessCount: 0
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
return memory;
|
|
3366
|
+
}
|
|
3367
|
+
};
|
|
3368
|
+
function createMemoryStore() {
|
|
3369
|
+
return new MemoryStore();
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
// ../../libraries/mcp-tools/dist/wrapToolsWithMemory.js
|
|
3373
|
+
var SUMMARIZE_THRESHOLD = 2e3;
|
|
3374
|
+
var MAX_ARRAY_ITEMS = 2;
|
|
3375
|
+
var MAX_STRING_LENGTH = 120;
|
|
3376
|
+
var MAX_DEPTH = 3;
|
|
3377
|
+
var PASSTHROUGH_TOOLS = /* @__PURE__ */ new Set([
|
|
3378
|
+
"memory_save",
|
|
3379
|
+
"memory_recall",
|
|
3380
|
+
"memory_list",
|
|
3381
|
+
"memory_delete",
|
|
3382
|
+
"memory_clear",
|
|
3383
|
+
"search_tools",
|
|
3384
|
+
"get_domains"
|
|
3385
|
+
]);
|
|
3386
|
+
function isRecord(value) {
|
|
3387
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3388
|
+
}
|
|
3389
|
+
function wrapToolResult(toolName, args, result, memory, options) {
|
|
3390
|
+
if (PASSTHROUGH_TOOLS.has(toolName)) {
|
|
3391
|
+
return result;
|
|
3392
|
+
}
|
|
3393
|
+
const text = result.content[0]?.text;
|
|
3394
|
+
if (!text || result.isError) {
|
|
3395
|
+
return result;
|
|
3396
|
+
}
|
|
3397
|
+
if (text.length <= SUMMARIZE_THRESHOLD) {
|
|
3398
|
+
return result;
|
|
3399
|
+
}
|
|
3400
|
+
let parsed;
|
|
3401
|
+
try {
|
|
3402
|
+
parsed = JSON.parse(text);
|
|
3403
|
+
} catch {
|
|
3404
|
+
return result;
|
|
3405
|
+
}
|
|
3406
|
+
if (isRecord(parsed) && parsed.error) {
|
|
3407
|
+
return result;
|
|
3408
|
+
}
|
|
3409
|
+
const key = MemoryStore.buildKey(toolName, args);
|
|
3410
|
+
memory.saveRaw(key, text);
|
|
3411
|
+
const strategy = options?.strategy ?? "truncate";
|
|
3412
|
+
const summary = strategy === "schema" ? summarizeSchema(parsed, key) : summarizeTruncate(parsed, key);
|
|
3413
|
+
return {
|
|
3414
|
+
content: [
|
|
3415
|
+
{
|
|
3416
|
+
type: "text",
|
|
3417
|
+
text: JSON.stringify(summary)
|
|
3418
|
+
}
|
|
3419
|
+
]
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
function summarizeTruncate(data, memoryKey) {
|
|
3423
|
+
const summary = truncateDeep(data, 0);
|
|
3424
|
+
addMemoryMetadata(summary, memoryKey, "truncate");
|
|
3425
|
+
return isRecord(summary) ? summary : { data: summary, _memory_key: memoryKey };
|
|
3426
|
+
}
|
|
3427
|
+
function truncateDeep(value, depth) {
|
|
3428
|
+
if (value == null || typeof value === "number" || typeof value === "boolean")
|
|
3429
|
+
return value;
|
|
3430
|
+
if (typeof value === "string") {
|
|
3431
|
+
return value.length > MAX_STRING_LENGTH ? value.slice(0, 80) + "..." : value;
|
|
3432
|
+
}
|
|
3433
|
+
if (Array.isArray(value)) {
|
|
3434
|
+
const items = value.slice(0, MAX_ARRAY_ITEMS).map((v) => truncateDeep(v, depth + 1));
|
|
3435
|
+
if (value.length > MAX_ARRAY_ITEMS) {
|
|
3436
|
+
items.push(`+${value.length - MAX_ARRAY_ITEMS} more in memory`);
|
|
3437
|
+
}
|
|
3438
|
+
return items;
|
|
3439
|
+
}
|
|
3440
|
+
if (isRecord(value)) {
|
|
3441
|
+
if (depth >= MAX_DEPTH) {
|
|
3442
|
+
const keys = Object.keys(value);
|
|
3443
|
+
return `{${keys.slice(0, 5).join(", ")}${keys.length > 5 ? ", ..." : ""}}`;
|
|
3444
|
+
}
|
|
3445
|
+
const result = {};
|
|
3446
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3447
|
+
result[k] = truncateDeep(v, depth + 1);
|
|
3448
|
+
}
|
|
3449
|
+
return result;
|
|
3450
|
+
}
|
|
3451
|
+
return value;
|
|
3452
|
+
}
|
|
3453
|
+
function summarizeSchema(data, memoryKey) {
|
|
3454
|
+
const summary = describeSchema(data, 0);
|
|
3455
|
+
addMemoryMetadata(summary, memoryKey, "schema");
|
|
3456
|
+
return isRecord(summary) ? summary : { data: summary, _memory_key: memoryKey };
|
|
3457
|
+
}
|
|
3458
|
+
var SCHEMA_MAX_DISTINCT = 6;
|
|
3459
|
+
var SCHEMA_PRIMITIVE_CAP = 5;
|
|
3460
|
+
var SCHEMA_DEPTH_LIMIT = 2;
|
|
3461
|
+
function round4(n) {
|
|
3462
|
+
return Math.round(n * 1e4) / 1e4;
|
|
3463
|
+
}
|
|
3464
|
+
function describeSchema(value, depth) {
|
|
3465
|
+
if (value == null)
|
|
3466
|
+
return null;
|
|
3467
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
3468
|
+
return value;
|
|
3469
|
+
if (typeof value === "string") {
|
|
3470
|
+
return value.length > 200 ? value.slice(0, 150) + "..." : value;
|
|
3471
|
+
}
|
|
3472
|
+
if (Array.isArray(value)) {
|
|
3473
|
+
if (value.length === 0)
|
|
3474
|
+
return [];
|
|
3475
|
+
if (value[0] !== void 0 && isRecord(value[0])) {
|
|
3476
|
+
return describeObjectArray(value);
|
|
3477
|
+
}
|
|
3478
|
+
if (value.length <= SCHEMA_PRIMITIVE_CAP)
|
|
3479
|
+
return value;
|
|
3480
|
+
return [
|
|
3481
|
+
...value.slice(0, SCHEMA_PRIMITIVE_CAP),
|
|
3482
|
+
`+${value.length - SCHEMA_PRIMITIVE_CAP} more`
|
|
3483
|
+
];
|
|
3484
|
+
}
|
|
3485
|
+
if (isRecord(value)) {
|
|
3486
|
+
if (depth >= SCHEMA_DEPTH_LIMIT) {
|
|
3487
|
+
return { _keys: Object.keys(value) };
|
|
3488
|
+
}
|
|
3489
|
+
const result = {};
|
|
3490
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3491
|
+
result[k] = describeSchema(v, depth + 1);
|
|
3492
|
+
}
|
|
3493
|
+
return result;
|
|
3494
|
+
}
|
|
3495
|
+
return value;
|
|
3496
|
+
}
|
|
3497
|
+
function computeFieldStat(values) {
|
|
3498
|
+
if (values.length === 0)
|
|
3499
|
+
return { type: "null" };
|
|
3500
|
+
if (values.every((v) => typeof v === "number")) {
|
|
3501
|
+
const nums = values;
|
|
3502
|
+
return {
|
|
3503
|
+
type: "number",
|
|
3504
|
+
min: round4(Math.min(...nums)),
|
|
3505
|
+
max: round4(Math.max(...nums)),
|
|
3506
|
+
avg: round4(nums.reduce((a, b) => a + b, 0) / nums.length)
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
if (values.every((v) => typeof v === "string")) {
|
|
3510
|
+
const unique = [...new Set(values)];
|
|
3511
|
+
return unique.length <= SCHEMA_MAX_DISTINCT ? { type: "string", values: unique } : { type: "string", distinct: unique.length, sample: unique.slice(0, 3) };
|
|
3512
|
+
}
|
|
3513
|
+
if (values.every((v) => typeof v === "boolean")) {
|
|
3514
|
+
const trueCount = values.filter(Boolean).length;
|
|
3515
|
+
return {
|
|
3516
|
+
type: "boolean",
|
|
3517
|
+
true_count: trueCount,
|
|
3518
|
+
false_count: values.length - trueCount
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
if (values.every((v) => Array.isArray(v))) {
|
|
3522
|
+
const arrays = values;
|
|
3523
|
+
const avgLen = arrays.reduce((sum, a) => sum + a.length, 0) / arrays.length;
|
|
3524
|
+
return { type: "array", avg_length: round4(avgLen) };
|
|
3525
|
+
}
|
|
3526
|
+
if (values.every((v) => isRecord(v))) {
|
|
3527
|
+
const nestedKeys = /* @__PURE__ */ new Set();
|
|
3528
|
+
for (const v of values) {
|
|
3529
|
+
for (const k of Object.keys(v)) {
|
|
3530
|
+
nestedKeys.add(k);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
return { type: "object", keys: [...nestedKeys] };
|
|
3534
|
+
}
|
|
3535
|
+
return { type: "mixed" };
|
|
3536
|
+
}
|
|
3537
|
+
function describeObjectArray(items) {
|
|
3538
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
3539
|
+
for (const item of items) {
|
|
3540
|
+
for (const key of Object.keys(item))
|
|
3541
|
+
allKeys.add(key);
|
|
3542
|
+
}
|
|
3543
|
+
const fields = [...allKeys];
|
|
3544
|
+
const stats = {};
|
|
3545
|
+
for (const field of fields) {
|
|
3546
|
+
const values = items.map((item) => item[field]).filter((v) => v !== void 0 && v !== null);
|
|
3547
|
+
stats[field] = computeFieldStat(values);
|
|
3548
|
+
}
|
|
3549
|
+
return {
|
|
3550
|
+
_type: "array",
|
|
3551
|
+
_count: items.length,
|
|
3552
|
+
_fields: fields,
|
|
3553
|
+
_stats: stats
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
function addMemoryMetadata(summary, memoryKey, strategy) {
|
|
3557
|
+
if (!isRecord(summary))
|
|
3558
|
+
return;
|
|
3559
|
+
summary._memory_key = memoryKey;
|
|
3560
|
+
if (strategy === "truncate") {
|
|
3561
|
+
summary._recall = `Full data available via memory_recall with key "${memoryKey}"`;
|
|
3562
|
+
} else {
|
|
3563
|
+
const arrayPaths = detectArrayPaths(summary);
|
|
3564
|
+
const pathHint = arrayPaths.length > 0 ? ` Array fields: ${arrayPaths.join(", ")}.` : "";
|
|
3565
|
+
summary._hint = `Use memory_recall with key "${memoryKey}" to get full data.${pathHint}`;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
function detectArrayPaths(data) {
|
|
3569
|
+
if (!isRecord(data))
|
|
3570
|
+
return [];
|
|
3571
|
+
const paths = [];
|
|
3572
|
+
for (const [key, value] of Object.entries(data)) {
|
|
3573
|
+
if (key.startsWith("_"))
|
|
3574
|
+
continue;
|
|
3575
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
3576
|
+
paths.push(key);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
return paths;
|
|
3580
|
+
}
|
|
3581
|
+
|
|
2905
3582
|
// src/tools/catalog/searchTools.ts
|
|
2906
3583
|
import { z as z39 } from "zod";
|
|
2907
3584
|
import { readFileSync } from "fs";
|
|
@@ -3053,16 +3730,108 @@ function registerSearchTools(server) {
|
|
|
3053
3730
|
}
|
|
3054
3731
|
|
|
3055
3732
|
// src/tools/catalog/callTool.ts
|
|
3733
|
+
import { z as z41 } from "zod";
|
|
3734
|
+
|
|
3735
|
+
// src/tools/memory.ts
|
|
3056
3736
|
import { z as z40 } from "zod";
|
|
3737
|
+
var memoryStore = null;
|
|
3738
|
+
function getMemoryStore() {
|
|
3739
|
+
if (!memoryStore) {
|
|
3740
|
+
memoryStore = createMemoryStore();
|
|
3741
|
+
}
|
|
3742
|
+
return memoryStore;
|
|
3743
|
+
}
|
|
3744
|
+
function getSharedMemoryStore() {
|
|
3745
|
+
return getMemoryStore();
|
|
3746
|
+
}
|
|
3747
|
+
function registerMemoryTools(server) {
|
|
3748
|
+
const memory = getMemoryStore();
|
|
3749
|
+
server.registerTool(
|
|
3750
|
+
"memory_save",
|
|
3751
|
+
{
|
|
3752
|
+
description: "Save a value to working memory for later retrieval. Use this to store intermediate results from tool calls that you want to reference later.",
|
|
3753
|
+
inputSchema: z40.object({
|
|
3754
|
+
key: z40.string().describe("Unique key to store the value under"),
|
|
3755
|
+
value: z40.string().describe("Value to store (will be stored as string)"),
|
|
3756
|
+
annotation: z40.string().optional().describe("Optional note about what this data represents")
|
|
3757
|
+
})
|
|
3758
|
+
},
|
|
3759
|
+
async ({
|
|
3760
|
+
key,
|
|
3761
|
+
value,
|
|
3762
|
+
annotation
|
|
3763
|
+
}) => {
|
|
3764
|
+
return memory.save(key, value, annotation);
|
|
3765
|
+
}
|
|
3766
|
+
);
|
|
3767
|
+
server.registerTool(
|
|
3768
|
+
"memory_recall",
|
|
3769
|
+
{
|
|
3770
|
+
description: "Retrieve a value from working memory by key. Supports optional filtering and selection for querying stored data. Use this to access data you previously stored or that was auto-saved from tool responses.",
|
|
3771
|
+
inputSchema: z40.object({
|
|
3772
|
+
key: z40.string().describe("Key to retrieve"),
|
|
3773
|
+
where: z40.array(z40.string()).optional().describe(
|
|
3774
|
+
'Filter conditions like ["frustration > 0.3", "status = active"]'
|
|
3775
|
+
),
|
|
3776
|
+
select: z40.array(z40.string()).optional().describe("Fields to include in results"),
|
|
3777
|
+
limit: z40.number().optional().describe("Maximum number of items to return"),
|
|
3778
|
+
offset: z40.number().optional().describe("Number of items to skip")
|
|
3779
|
+
})
|
|
3780
|
+
},
|
|
3781
|
+
async ({
|
|
3782
|
+
key,
|
|
3783
|
+
where,
|
|
3784
|
+
select,
|
|
3785
|
+
limit,
|
|
3786
|
+
offset
|
|
3787
|
+
}) => {
|
|
3788
|
+
return memory.recall(key, { where, select, limit, offset });
|
|
3789
|
+
}
|
|
3790
|
+
);
|
|
3791
|
+
server.registerTool(
|
|
3792
|
+
"memory_list",
|
|
3793
|
+
{
|
|
3794
|
+
description: "List all keys currently stored in working memory. Returns key names, sizes, timestamps, annotations, and access counts.",
|
|
3795
|
+
inputSchema: z40.object({})
|
|
3796
|
+
},
|
|
3797
|
+
async () => {
|
|
3798
|
+
return memory.list();
|
|
3799
|
+
}
|
|
3800
|
+
);
|
|
3801
|
+
server.registerTool(
|
|
3802
|
+
"memory_delete",
|
|
3803
|
+
{
|
|
3804
|
+
description: "Delete a value from working memory by key.",
|
|
3805
|
+
inputSchema: z40.object({
|
|
3806
|
+
key: z40.string().describe("Key to delete")
|
|
3807
|
+
})
|
|
3808
|
+
},
|
|
3809
|
+
async ({ key }) => {
|
|
3810
|
+
return memory.delete(key);
|
|
3811
|
+
}
|
|
3812
|
+
);
|
|
3813
|
+
server.registerTool(
|
|
3814
|
+
"memory_clear",
|
|
3815
|
+
{
|
|
3816
|
+
description: "Clear all values from working memory. Use with caution.",
|
|
3817
|
+
inputSchema: z40.object({})
|
|
3818
|
+
},
|
|
3819
|
+
async () => {
|
|
3820
|
+
return memory.clear();
|
|
3821
|
+
}
|
|
3822
|
+
);
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
// src/tools/catalog/callTool.ts
|
|
3057
3826
|
var toolRegistry = /* @__PURE__ */ new Map();
|
|
3058
3827
|
function registerToolHandler(name, handler) {
|
|
3059
3828
|
toolRegistry.set(name, handler);
|
|
3060
3829
|
}
|
|
3061
|
-
var callToolSchema =
|
|
3062
|
-
tool_name:
|
|
3830
|
+
var callToolSchema = z41.object({
|
|
3831
|
+
tool_name: z41.string().describe(
|
|
3063
3832
|
"The exact name of the tool to call (e.g., 'get_page_metrics', 'analyze_flow', 'compare_cohorts')"
|
|
3064
3833
|
),
|
|
3065
|
-
arguments:
|
|
3834
|
+
arguments: z41.record(z41.string(), z41.unknown()).describe(
|
|
3066
3835
|
"The arguments to pass to the tool as a JSON object. Check the tool description from search_tools for required parameters."
|
|
3067
3836
|
)
|
|
3068
3837
|
});
|
|
@@ -3070,7 +3839,7 @@ function registerCallTool(server) {
|
|
|
3070
3839
|
server.registerTool(
|
|
3071
3840
|
"call_tool",
|
|
3072
3841
|
{
|
|
3073
|
-
description: "Execute any analysis tool by name. Use search_tools first to discover available tools, then call them through this proxy. Pass the exact tool name and arguments as a JSON object. Example: call_tool({ tool_name: 'get_page_metrics', arguments: { page_path: '/checkout' } })",
|
|
3842
|
+
description: "Execute any analysis tool by name. Use search_tools first to discover available tools, then call them through this proxy. Pass the exact tool name and arguments as a JSON object. Large responses are automatically saved to memory - use memory_recall to retrieve full data. Example: call_tool({ tool_name: 'get_page_metrics', arguments: { page_path: '/checkout' } })",
|
|
3074
3843
|
inputSchema: callToolSchema
|
|
3075
3844
|
},
|
|
3076
3845
|
async ({ tool_name, arguments: args }) => {
|
|
@@ -3109,7 +3878,9 @@ function registerCallTool(server) {
|
|
|
3109
3878
|
};
|
|
3110
3879
|
}
|
|
3111
3880
|
try {
|
|
3112
|
-
|
|
3881
|
+
const rawResult = await handler(args);
|
|
3882
|
+
const memory = getSharedMemoryStore();
|
|
3883
|
+
return wrapToolResult(tool_name, args, rawResult, memory);
|
|
3113
3884
|
} catch (err) {
|
|
3114
3885
|
const message = err instanceof Error ? err.message : String(err);
|
|
3115
3886
|
return {
|
|
@@ -3130,138 +3901,6 @@ function registerCallTool(server) {
|
|
|
3130
3901
|
);
|
|
3131
3902
|
}
|
|
3132
3903
|
|
|
3133
|
-
// src/tools/memory.ts
|
|
3134
|
-
import { z as z41 } from "zod";
|
|
3135
|
-
var memoryStore = /* @__PURE__ */ new Map();
|
|
3136
|
-
function registerMemoryTools(server) {
|
|
3137
|
-
server.registerTool(
|
|
3138
|
-
"memory_save",
|
|
3139
|
-
{
|
|
3140
|
-
description: "Save a value to working memory for later retrieval. Use this to store intermediate results from tool calls that you want to reference later.",
|
|
3141
|
-
inputSchema: z41.object({
|
|
3142
|
-
key: z41.string().describe("Unique key to store the value under"),
|
|
3143
|
-
value: z41.string().describe("Value to store (will be stored as string)"),
|
|
3144
|
-
annotation: z41.string().optional().describe("Optional note about what this data represents")
|
|
3145
|
-
})
|
|
3146
|
-
},
|
|
3147
|
-
async ({
|
|
3148
|
-
key,
|
|
3149
|
-
value,
|
|
3150
|
-
annotation
|
|
3151
|
-
}) => {
|
|
3152
|
-
memoryStore.set(key, {
|
|
3153
|
-
key,
|
|
3154
|
-
value,
|
|
3155
|
-
timestamp: Date.now(),
|
|
3156
|
-
annotation
|
|
3157
|
-
});
|
|
3158
|
-
return {
|
|
3159
|
-
content: [
|
|
3160
|
-
{
|
|
3161
|
-
type: "text",
|
|
3162
|
-
text: JSON.stringify({ success: true, key, size: value.length })
|
|
3163
|
-
}
|
|
3164
|
-
]
|
|
3165
|
-
};
|
|
3166
|
-
}
|
|
3167
|
-
);
|
|
3168
|
-
server.registerTool(
|
|
3169
|
-
"memory_recall",
|
|
3170
|
-
{
|
|
3171
|
-
description: "Retrieve a value from working memory by key. Use this to access data you previously stored with memory_save.",
|
|
3172
|
-
inputSchema: z41.object({
|
|
3173
|
-
key: z41.string().describe("Key to retrieve")
|
|
3174
|
-
})
|
|
3175
|
-
},
|
|
3176
|
-
async ({ key }) => {
|
|
3177
|
-
const entry = memoryStore.get(key);
|
|
3178
|
-
if (!entry) {
|
|
3179
|
-
return {
|
|
3180
|
-
content: [
|
|
3181
|
-
{
|
|
3182
|
-
type: "text",
|
|
3183
|
-
text: JSON.stringify({
|
|
3184
|
-
error: `Key "${key}" not found in memory`
|
|
3185
|
-
})
|
|
3186
|
-
}
|
|
3187
|
-
],
|
|
3188
|
-
isError: true
|
|
3189
|
-
};
|
|
3190
|
-
}
|
|
3191
|
-
return {
|
|
3192
|
-
content: [
|
|
3193
|
-
{
|
|
3194
|
-
type: "text",
|
|
3195
|
-
text: entry.value
|
|
3196
|
-
}
|
|
3197
|
-
]
|
|
3198
|
-
};
|
|
3199
|
-
}
|
|
3200
|
-
);
|
|
3201
|
-
server.registerTool(
|
|
3202
|
-
"memory_list",
|
|
3203
|
-
{
|
|
3204
|
-
description: "List all keys currently stored in working memory. Returns key names, sizes, timestamps, and annotations.",
|
|
3205
|
-
inputSchema: z41.object({})
|
|
3206
|
-
},
|
|
3207
|
-
async () => {
|
|
3208
|
-
const entries = [...memoryStore.values()].map((entry) => ({
|
|
3209
|
-
key: entry.key,
|
|
3210
|
-
size: entry.value.length,
|
|
3211
|
-
timestamp: new Date(entry.timestamp).toISOString(),
|
|
3212
|
-
annotation: entry.annotation
|
|
3213
|
-
}));
|
|
3214
|
-
return {
|
|
3215
|
-
content: [
|
|
3216
|
-
{
|
|
3217
|
-
type: "text",
|
|
3218
|
-
text: JSON.stringify({ count: entries.length, entries })
|
|
3219
|
-
}
|
|
3220
|
-
]
|
|
3221
|
-
};
|
|
3222
|
-
}
|
|
3223
|
-
);
|
|
3224
|
-
server.registerTool(
|
|
3225
|
-
"memory_delete",
|
|
3226
|
-
{
|
|
3227
|
-
description: "Delete a value from working memory by key.",
|
|
3228
|
-
inputSchema: z41.object({
|
|
3229
|
-
key: z41.string().describe("Key to delete")
|
|
3230
|
-
})
|
|
3231
|
-
},
|
|
3232
|
-
async ({ key }) => {
|
|
3233
|
-
const existed = memoryStore.delete(key);
|
|
3234
|
-
return {
|
|
3235
|
-
content: [
|
|
3236
|
-
{
|
|
3237
|
-
type: "text",
|
|
3238
|
-
text: JSON.stringify({ success: true, deleted: existed })
|
|
3239
|
-
}
|
|
3240
|
-
]
|
|
3241
|
-
};
|
|
3242
|
-
}
|
|
3243
|
-
);
|
|
3244
|
-
server.registerTool(
|
|
3245
|
-
"memory_clear",
|
|
3246
|
-
{
|
|
3247
|
-
description: "Clear all values from working memory. Use with caution.",
|
|
3248
|
-
inputSchema: z41.object({})
|
|
3249
|
-
},
|
|
3250
|
-
async () => {
|
|
3251
|
-
const count = memoryStore.size;
|
|
3252
|
-
memoryStore.clear();
|
|
3253
|
-
return {
|
|
3254
|
-
content: [
|
|
3255
|
-
{
|
|
3256
|
-
type: "text",
|
|
3257
|
-
text: JSON.stringify({ success: true, cleared: count })
|
|
3258
|
-
}
|
|
3259
|
-
]
|
|
3260
|
-
};
|
|
3261
|
-
}
|
|
3262
|
-
);
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
3904
|
// src/index.ts
|
|
3266
3905
|
function registerToolWithServer(server, tool) {
|
|
3267
3906
|
server.tool(
|
|
@@ -3296,6 +3935,29 @@ call_tool({ tool_name: "analyze_flow", arguments: { start_page: "/pricing", end_
|
|
|
3296
3935
|
\u2192 Returns: flow analysis with paths, bottlenecks, friction scores
|
|
3297
3936
|
\`\`\`
|
|
3298
3937
|
|
|
3938
|
+
## Automatic Memory Storage
|
|
3939
|
+
|
|
3940
|
+
Large tool responses (>500 chars) are automatically saved to memory and you receive a summary instead.
|
|
3941
|
+
This keeps your context lean while preserving all data for later retrieval.
|
|
3942
|
+
|
|
3943
|
+
When you see a response with \`_memory_key\`, the full data is stored in memory:
|
|
3944
|
+
\`\`\`json
|
|
3945
|
+
{
|
|
3946
|
+
"sessions": [{...}, {...}, "+18 more in memory"],
|
|
3947
|
+
"_memory_key": "list_sessions:/checkout",
|
|
3948
|
+
"_recall": "Full data available via memory_recall..."
|
|
3949
|
+
}
|
|
3950
|
+
\`\`\`
|
|
3951
|
+
|
|
3952
|
+
To retrieve full data or query it:
|
|
3953
|
+
\`\`\`
|
|
3954
|
+
memory_recall({ key: "list_sessions:/checkout" })
|
|
3955
|
+
\u2192 Returns: Full JSON data
|
|
3956
|
+
|
|
3957
|
+
memory_recall({ key: "...", where: ["frustration > 0.5"], select: ["session_id", "frustration"] })
|
|
3958
|
+
\u2192 Returns: Filtered and selected fields only
|
|
3959
|
+
\`\`\`
|
|
3960
|
+
|
|
3299
3961
|
## Always-Available Tools
|
|
3300
3962
|
|
|
3301
3963
|
These tools are always visible (no search needed):
|
|
@@ -3303,7 +3965,7 @@ These tools are always visible (no search needed):
|
|
|
3303
3965
|
- **run_full_diagnostic** \u2014 Quick site health overview (lightweight, fast)
|
|
3304
3966
|
- **triage_sessions** \u2014 Find sessions needing attention
|
|
3305
3967
|
- **get_upgrade_options** \u2014 Check available plan upgrades and features
|
|
3306
|
-
- **memory_*** \u2014 Store/retrieve intermediate results
|
|
3968
|
+
- **memory_*** \u2014 Store/retrieve intermediate results (memory_recall supports where, select, limit, offset)
|
|
3307
3969
|
|
|
3308
3970
|
## Tier-Based Access
|
|
3309
3971
|
|
|
@@ -3348,7 +4010,7 @@ All behavioral scores are 0-1 where higher = more of that signal:
|
|
|
3348
4010
|
- Backtrack hotspots suggest confusion on subsequent pages
|
|
3349
4011
|
- Low confidence + high confusion = users don't understand what to do
|
|
3350
4012
|
- High frustration + low confusion = users know what to do but can't (broken UI)
|
|
3351
|
-
-
|
|
4013
|
+
- Large responses are auto-saved to memory - use \`memory_recall\` to query stored data
|
|
3352
4014
|
|
|
3353
4015
|
## Tool Limits
|
|
3354
4016
|
|