clementine-agent 1.0.55 → 1.0.56
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.
|
@@ -64,6 +64,8 @@ export declare class PersonalAssistant {
|
|
|
64
64
|
private _lastTerminalReason?;
|
|
65
65
|
/** Per-session stall nudge — set after a query shows stall signals, consumed on the next query. */
|
|
66
66
|
private stallNudges;
|
|
67
|
+
/** Last contradiction finding per session, consumed by the session transcript writer to splice a correction note. */
|
|
68
|
+
private _lastContradictionFinding;
|
|
67
69
|
private _compactedSessions;
|
|
68
70
|
/** Last auto-matched project per session — exposed for CLI display. */
|
|
69
71
|
private _lastMatchedProject;
|
package/dist/agent/assistant.js
CHANGED
|
@@ -21,6 +21,7 @@ import { agentWorkingMemoryFile, listAllGoals } from '../tools/shared.js';
|
|
|
21
21
|
import { AgentManager } from './agent-manager.js';
|
|
22
22
|
import { extractLinks } from './link-extractor.js';
|
|
23
23
|
import { StallGuard } from './stall-guard.js';
|
|
24
|
+
import { collectToolCalls, detectContradiction, buildCorrectionPrompt } from './contradiction-validator.js';
|
|
24
25
|
import { assembleContext } from '../memory/context-assembler.js';
|
|
25
26
|
import { PromptCache } from './prompt-cache.js';
|
|
26
27
|
import { searchSkills as searchSkillsSync } from './skill-extractor.js';
|
|
@@ -611,6 +612,8 @@ export class PersonalAssistant {
|
|
|
611
612
|
_lastTerminalReason;
|
|
612
613
|
/** Per-session stall nudge — set after a query shows stall signals, consumed on the next query. */
|
|
613
614
|
stallNudges = new Map();
|
|
615
|
+
/** Last contradiction finding per session, consumed by the session transcript writer to splice a correction note. */
|
|
616
|
+
_lastContradictionFinding = new Map();
|
|
614
617
|
_compactedSessions = new Set();
|
|
615
618
|
/** Last auto-matched project per session — exposed for CLI display. */
|
|
616
619
|
_lastMatchedProject = new Map();
|
|
@@ -1083,21 +1086,9 @@ If you're unsure what's happening first, run \`where_is_source\` — it reports
|
|
|
1083
1086
|
|
|
1084
1087
|
### Calling Claude Desktop connector tools (Drive, Gmail, etc.)
|
|
1085
1088
|
|
|
1086
|
-
|
|
1089
|
+
Just call the tool — e.g. \`mcp__claude_ai_Google_Drive__search_files\`, \`mcp__claude_ai_Gmail__authenticate\`. Report the literal result: real data, auth error, whatever. Your replies are validated against actual tool results; claims that contradict a tool's return value are rejected and you're asked to retry. Don't pre-check with \`integration_status\` — that's for env-var integrations, not schema-driven connectors.
|
|
1087
1090
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
1. **Just call the tool.** \`mcp__claude_ai_Google_Drive__search_files\`, \`mcp__claude_ai_Gmail__authenticate\`, etc. Attempt it. Report the literal result — real data, auth error, or whatever.
|
|
1091
|
-
2. **If refused** with "not in my function schema" / "tool not allowed," call \`allow_tool(exact_name)\` and retry. \`allow_tool\` auto-refreshes the inventory if the name is new — handles the case where the owner just added a connector at claude.ai.
|
|
1092
|
-
3. **If the owner says "I just added X at claude.ai"** or anything similar, call \`refresh_tool_inventory\` first to pick up the new connector. Report what came online.
|
|
1093
|
-
|
|
1094
|
-
**Never** say the tool "isn't loaded in this session," "doesn't carry over from Claude Desktop," "the tools array is empty," "MCP server still connecting," "the connector is a dead end," "the tool doesn't exist in the current session's schema," or "schema-level rejection." If any of those phrasings come to mind, call the tool directly and report what actually happens instead.
|
|
1095
|
-
|
|
1096
|
-
**Per-call errors are not connector failures.** If a \`mcp__claude_ai_*\` tool returns an error (invalid arguments, auth failure, rate limit, etc.), that error is about **this one call** — it does NOT mean the tool doesn't exist or the connector is broken. Report the literal error message and, if it's an argument error, re-read the tool's schema from your SDK inventory and retry with correct args. Example: if \`search_files\` complains about unknown field \`orderBy\`, that field belongs to \`list_recent_files\`, not \`search_files\` — fix the args, don't declare the connector dead.
|
|
1097
|
-
|
|
1098
|
-
\`list_allowed_tools\` / \`disallow_tool\` manage the whitelist. \`integration_status\` is for env-var (API key) integrations — **not** for claude_ai_* connectors, which are schema-driven. Don't use \`integration_status\` as a proxy for "can I call Drive / Gmail / etc." — those are always tried by direct tool call, not status lookup.
|
|
1099
|
-
|
|
1100
|
-
**Critical rule: if the user asks you to use a claude_ai_* connector, you call the connector tool. Full stop.** Do not report "I tried and it failed" unless there was an actual tool call that returned an actual error — your audit log records every tool call, so narrating a failed attempt when the audit shows no call will be spotted.
|
|
1091
|
+
If a tool returns an argument error, fix the args and retry — it's a per-call error, not a connector failure. \`allow_tool(name)\` + \`refresh_tool_inventory\` exist for the case where the owner just added a connector at claude.ai.
|
|
1101
1092
|
|
|
1102
1093
|
## Context Window Management
|
|
1103
1094
|
|
|
@@ -2052,6 +2043,18 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2052
2043
|
if (key && this.memoryStore) {
|
|
2053
2044
|
try {
|
|
2054
2045
|
this.memoryStore.saveTurn(key, 'user', text);
|
|
2046
|
+
// Fix B: if a contradiction fired this turn, splice a system-role note
|
|
2047
|
+
// into the transcript BEFORE the assistant reply. Future turns that
|
|
2048
|
+
// read transcript history will see the correction and won't anchor on
|
|
2049
|
+
// the bad phrasing. Generic across all connectors; triggered only when
|
|
2050
|
+
// Fix A fired, so benign otherwise.
|
|
2051
|
+
const finding = this._lastContradictionFinding.get(key);
|
|
2052
|
+
if (finding) {
|
|
2053
|
+
this._lastContradictionFinding.delete(key);
|
|
2054
|
+
const note = `[system: Previous draft reply contained "${finding.matchedPhrase}" but ${finding.tool.name} ${finding.tool.resultClass === 'success' ? 'succeeded' : 'returned a per-call error (not a connector failure)'}. ` +
|
|
2055
|
+
`Corrected reply above is based on the actual tool result.]`;
|
|
2056
|
+
this.memoryStore.saveTurn(key, 'system', note);
|
|
2057
|
+
}
|
|
2055
2058
|
this.memoryStore.saveTurn(key, 'assistant', responseText, model ?? MODEL);
|
|
2056
2059
|
}
|
|
2057
2060
|
catch (err) {
|
|
@@ -2137,6 +2140,10 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2137
2140
|
logger.warn({ sessionKey, timeoutMs }, 'Chat query timed out');
|
|
2138
2141
|
}, timeoutMs);
|
|
2139
2142
|
}
|
|
2143
|
+
// One-shot flag so a contradiction retry can't chain into infinite loops.
|
|
2144
|
+
// Flipped true on the first intervention; subsequent replies go through
|
|
2145
|
+
// un-validated (but still logged).
|
|
2146
|
+
let contradictionRetried = false;
|
|
2140
2147
|
try {
|
|
2141
2148
|
for (let attempt = 0; attempt <= PersonalAssistant.RATE_LIMIT_MAX_RETRIES; attempt++) {
|
|
2142
2149
|
const sdkOptions = this.buildOptions({ model, maxTurns: maxTurnsOverride ?? null, retrievalContext, profile, sessionKey, streaming: !!onText, verboseLevel, abortController, stallGuard, intentClassification, effort: intentClassification?.suggestedEffort });
|
|
@@ -2196,6 +2203,10 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2196
2203
|
let staleSession = false;
|
|
2197
2204
|
let contextRecovery = false;
|
|
2198
2205
|
let lastAssistantBlocks = [];
|
|
2206
|
+
// Raw SDK messages for post-turn contradiction validation. We capture
|
|
2207
|
+
// assistant messages (tool_use blocks) and user messages (tool_result
|
|
2208
|
+
// blocks) so we can pair them and compare against the outgoing reply.
|
|
2209
|
+
const collectedSdkMessages = [];
|
|
2199
2210
|
const queryStartMs = Date.now();
|
|
2200
2211
|
// Event log: track query lifecycle
|
|
2201
2212
|
const eventLog = getEventLog();
|
|
@@ -2206,6 +2217,12 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2206
2217
|
const stream = query({ prompt, options: sdkOptions });
|
|
2207
2218
|
let gotStreamEvents = false;
|
|
2208
2219
|
for await (const message of stream) {
|
|
2220
|
+
// Capture assistant + user messages for post-turn contradiction
|
|
2221
|
+
// validation. Must happen before the switch below so we catch
|
|
2222
|
+
// every message type, including ones we don't otherwise handle.
|
|
2223
|
+
if (message.type === 'assistant' || message.type === 'user') {
|
|
2224
|
+
collectedSdkMessages.push({ type: message.type, message: message.message });
|
|
2225
|
+
}
|
|
2209
2226
|
if (message.type === 'assistant') {
|
|
2210
2227
|
const blocks = getContentBlocks(message);
|
|
2211
2228
|
lastAssistantBlocks = blocks; // Track for fallback text extraction
|
|
@@ -2522,6 +2539,46 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2522
2539
|
}
|
|
2523
2540
|
}
|
|
2524
2541
|
}
|
|
2542
|
+
// ── Contradiction validator ─────────────────────────────────────
|
|
2543
|
+
// If the model's reply claims a claude_ai_* connector is broken but
|
|
2544
|
+
// the audit log (this turn's tool_use/tool_result pairs) shows the
|
|
2545
|
+
// tool actually succeeded or returned a fixable arg error, reject the
|
|
2546
|
+
// reply and force one more turn with the literal tool result in hand.
|
|
2547
|
+
// Deterministic — does not rely on prompt obedience.
|
|
2548
|
+
if (!contradictionRetried && attempt < PersonalAssistant.RATE_LIMIT_MAX_RETRIES && responseText.trim()) {
|
|
2549
|
+
try {
|
|
2550
|
+
const toolCallRecords = collectToolCalls(collectedSdkMessages);
|
|
2551
|
+
const finding = detectContradiction(responseText, toolCallRecords);
|
|
2552
|
+
if (finding) {
|
|
2553
|
+
contradictionRetried = true;
|
|
2554
|
+
logger.warn({
|
|
2555
|
+
sessionKey,
|
|
2556
|
+
tool: finding.tool.name,
|
|
2557
|
+
resultClass: finding.tool.resultClass,
|
|
2558
|
+
matchedPhrase: finding.matchedPhrase,
|
|
2559
|
+
}, 'Contradiction detected — rewriting reply');
|
|
2560
|
+
logAuditJsonl({
|
|
2561
|
+
event_type: 'confabulation_corrected',
|
|
2562
|
+
tool_name: finding.tool.name,
|
|
2563
|
+
result_class: finding.tool.resultClass,
|
|
2564
|
+
matched_phrase: finding.matchedPhrase,
|
|
2565
|
+
rejected_reply_preview: responseText.slice(0, 300),
|
|
2566
|
+
tool_result_preview: finding.tool.resultPreview,
|
|
2567
|
+
});
|
|
2568
|
+
// Hand the correction prompt to the SDK as the next user turn.
|
|
2569
|
+
// Resume the same session so the model keeps its context.
|
|
2570
|
+
prompt = buildCorrectionPrompt(finding);
|
|
2571
|
+
responseText = '';
|
|
2572
|
+
// Also record the contradiction for Fix B (session splice) later.
|
|
2573
|
+
this._lastContradictionFinding.set(sessionKey ?? '__no_session__', finding);
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
catch (err) {
|
|
2578
|
+
// Validator errors must never break the main reply path.
|
|
2579
|
+
logger.debug({ err }, 'Contradiction validator errored — passing reply through');
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2525
2582
|
// Event log: query completed successfully
|
|
2526
2583
|
if (sessionKey) {
|
|
2527
2584
|
eventLog.emitQueryEnd(sessionKey, {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-turn contradiction validator.
|
|
3
|
+
*
|
|
4
|
+
* After a chat turn's SDK stream completes, compares the assistant's outgoing
|
|
5
|
+
* reply against the actual tool_use/tool_result pairs from that turn. If a
|
|
6
|
+
* claude_ai_* connector succeeded (or returned an argument error — a fixable
|
|
7
|
+
* per-call failure) but the reply claims the connector is broken, missing from
|
|
8
|
+
* the schema, or otherwise generalizes a single failure into connector-level
|
|
9
|
+
* "deadness," we flag it.
|
|
10
|
+
*
|
|
11
|
+
* This is deterministic: it does NOT rely on the model obeying prompt rules.
|
|
12
|
+
* It's the load-bearing guardrail that replaces the forbidden-phrase list we
|
|
13
|
+
* used to patch into the system prompt.
|
|
14
|
+
*/
|
|
15
|
+
export type ToolResultClass = 'success' | 'arg_error' | 'auth_error' | 'other_error';
|
|
16
|
+
export interface ToolCallRecord {
|
|
17
|
+
/** Tool name, e.g. mcp__claude_ai_Google_Drive__search_files */
|
|
18
|
+
name: string;
|
|
19
|
+
/** tool_use_id from the assistant's request */
|
|
20
|
+
id: string;
|
|
21
|
+
/** Classification of the paired tool_result */
|
|
22
|
+
resultClass: ToolResultClass;
|
|
23
|
+
/** First ~200 chars of the literal result content (or error text) */
|
|
24
|
+
resultPreview: string;
|
|
25
|
+
}
|
|
26
|
+
/** Regex matching reply phrasings that claim a connector-wide failure. */
|
|
27
|
+
export declare const CONTRADICTION_RE: RegExp;
|
|
28
|
+
export declare function classifyResult(content: string, isError: boolean): ToolResultClass;
|
|
29
|
+
/**
|
|
30
|
+
* Walk collected SDK messages (assistant + user) and pair every tool_use with
|
|
31
|
+
* its tool_result. Returns one record per tool_use; unpaired ones (still
|
|
32
|
+
* running at end of stream) are skipped.
|
|
33
|
+
*/
|
|
34
|
+
export declare function collectToolCalls(messages: Array<{
|
|
35
|
+
type: string;
|
|
36
|
+
message?: any;
|
|
37
|
+
}>): ToolCallRecord[];
|
|
38
|
+
export interface ContradictionFinding {
|
|
39
|
+
/** The tool call whose result contradicts the reply */
|
|
40
|
+
tool: ToolCallRecord;
|
|
41
|
+
/** The exact phrase from the reply that triggered detection */
|
|
42
|
+
matchedPhrase: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check a reply against a set of tool-call records. Returns the first
|
|
46
|
+
* contradiction found, or null if the reply is consistent with tool results.
|
|
47
|
+
*
|
|
48
|
+
* Contradiction = reply contains a CONTRADICTION_RE phrase AND at least one
|
|
49
|
+
* mcp__claude_ai_* tool in this turn classified `success` or `arg_error`.
|
|
50
|
+
* `auth_error` and `other_error` are legitimate failures that can support
|
|
51
|
+
* those reply phrasings.
|
|
52
|
+
*/
|
|
53
|
+
export declare function detectContradiction(reply: string, calls: ToolCallRecord[]): ContradictionFinding | null;
|
|
54
|
+
/**
|
|
55
|
+
* Build the system-follow-up message we inject when a contradiction fires.
|
|
56
|
+
* The SDK will run one more turn with this as a user-role message (using
|
|
57
|
+
* `canUseTool` or similar hook), and the model's next reply replaces the
|
|
58
|
+
* bad one.
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildCorrectionPrompt(finding: ContradictionFinding): string;
|
|
61
|
+
//# sourceMappingURL=contradiction-validator.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-turn contradiction validator.
|
|
3
|
+
*
|
|
4
|
+
* After a chat turn's SDK stream completes, compares the assistant's outgoing
|
|
5
|
+
* reply against the actual tool_use/tool_result pairs from that turn. If a
|
|
6
|
+
* claude_ai_* connector succeeded (or returned an argument error — a fixable
|
|
7
|
+
* per-call failure) but the reply claims the connector is broken, missing from
|
|
8
|
+
* the schema, or otherwise generalizes a single failure into connector-level
|
|
9
|
+
* "deadness," we flag it.
|
|
10
|
+
*
|
|
11
|
+
* This is deterministic: it does NOT rely on the model obeying prompt rules.
|
|
12
|
+
* It's the load-bearing guardrail that replaces the forbidden-phrase list we
|
|
13
|
+
* used to patch into the system prompt.
|
|
14
|
+
*/
|
|
15
|
+
const ARG_ERROR_RE = /\b(invalid|unknown field|required|missing parameter|schema|unrecognized|unexpected property)\b/i;
|
|
16
|
+
const AUTH_ERROR_RE = /\b(unauthori[sz]ed|401|not authenticated|token expired|token has expired|invalid[_ ]?token|access denied)\b/i;
|
|
17
|
+
/** Regex matching reply phrasings that claim a connector-wide failure. */
|
|
18
|
+
export const CONTRADICTION_RE = /(dead\s*end|doesn'?t exist|not in (the |my )?schema|schema[- ]level|not available|isn'?t loaded|tools array is empty|MCP server still connecting|connector is (a )?dead|no such tool available|tool doesn't exist)/i;
|
|
19
|
+
export function classifyResult(content, isError) {
|
|
20
|
+
if (!isError)
|
|
21
|
+
return 'success';
|
|
22
|
+
if (ARG_ERROR_RE.test(content))
|
|
23
|
+
return 'arg_error';
|
|
24
|
+
if (AUTH_ERROR_RE.test(content))
|
|
25
|
+
return 'auth_error';
|
|
26
|
+
return 'other_error';
|
|
27
|
+
}
|
|
28
|
+
/** Extract string content from a tool_result block (which can be string or array of content blocks). */
|
|
29
|
+
function stringifyResultContent(content) {
|
|
30
|
+
if (typeof content === 'string')
|
|
31
|
+
return content;
|
|
32
|
+
if (Array.isArray(content)) {
|
|
33
|
+
return content
|
|
34
|
+
.map((b) => (typeof b === 'string' ? b : (b?.text ?? b?.content ?? JSON.stringify(b))))
|
|
35
|
+
.join('\n');
|
|
36
|
+
}
|
|
37
|
+
if (content == null)
|
|
38
|
+
return '';
|
|
39
|
+
try {
|
|
40
|
+
return JSON.stringify(content);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return String(content);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Walk collected SDK messages (assistant + user) and pair every tool_use with
|
|
48
|
+
* its tool_result. Returns one record per tool_use; unpaired ones (still
|
|
49
|
+
* running at end of stream) are skipped.
|
|
50
|
+
*/
|
|
51
|
+
export function collectToolCalls(messages) {
|
|
52
|
+
const toolUses = new Map();
|
|
53
|
+
const results = new Map();
|
|
54
|
+
for (const msg of messages) {
|
|
55
|
+
if (msg.type === 'assistant' && msg.message?.content) {
|
|
56
|
+
const blocks = Array.isArray(msg.message.content) ? msg.message.content : [];
|
|
57
|
+
for (const b of blocks) {
|
|
58
|
+
if (b?.type === 'tool_use' && b.id && b.name) {
|
|
59
|
+
toolUses.set(b.id, { name: b.name, id: b.id });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (msg.type === 'user' && msg.message?.content) {
|
|
64
|
+
const blocks = Array.isArray(msg.message.content) ? msg.message.content : [];
|
|
65
|
+
for (const b of blocks) {
|
|
66
|
+
if (b?.type === 'tool_result' && b.tool_use_id) {
|
|
67
|
+
results.set(b.tool_use_id, {
|
|
68
|
+
content: stringifyResultContent(b.content),
|
|
69
|
+
isError: !!b.is_error,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const records = [];
|
|
76
|
+
for (const [id, tu] of toolUses) {
|
|
77
|
+
const r = results.get(id);
|
|
78
|
+
if (!r)
|
|
79
|
+
continue;
|
|
80
|
+
records.push({
|
|
81
|
+
name: tu.name,
|
|
82
|
+
id,
|
|
83
|
+
resultClass: classifyResult(r.content, r.isError),
|
|
84
|
+
resultPreview: r.content.slice(0, 200),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return records;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check a reply against a set of tool-call records. Returns the first
|
|
91
|
+
* contradiction found, or null if the reply is consistent with tool results.
|
|
92
|
+
*
|
|
93
|
+
* Contradiction = reply contains a CONTRADICTION_RE phrase AND at least one
|
|
94
|
+
* mcp__claude_ai_* tool in this turn classified `success` or `arg_error`.
|
|
95
|
+
* `auth_error` and `other_error` are legitimate failures that can support
|
|
96
|
+
* those reply phrasings.
|
|
97
|
+
*/
|
|
98
|
+
export function detectContradiction(reply, calls) {
|
|
99
|
+
if (!reply)
|
|
100
|
+
return null;
|
|
101
|
+
const match = reply.match(CONTRADICTION_RE);
|
|
102
|
+
if (!match)
|
|
103
|
+
return null;
|
|
104
|
+
const connectorCalls = calls.filter(c => c.name.startsWith('mcp__claude_ai_'));
|
|
105
|
+
const recoverable = connectorCalls.find(c => c.resultClass === 'success' || c.resultClass === 'arg_error');
|
|
106
|
+
if (!recoverable)
|
|
107
|
+
return null;
|
|
108
|
+
return { tool: recoverable, matchedPhrase: match[0] };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Build the system-follow-up message we inject when a contradiction fires.
|
|
112
|
+
* The SDK will run one more turn with this as a user-role message (using
|
|
113
|
+
* `canUseTool` or similar hook), and the model's next reply replaces the
|
|
114
|
+
* bad one.
|
|
115
|
+
*/
|
|
116
|
+
export function buildCorrectionPrompt(finding) {
|
|
117
|
+
const { tool, matchedPhrase } = finding;
|
|
118
|
+
const classLabel = tool.resultClass === 'success' ? 'returned successful content' :
|
|
119
|
+
tool.resultClass === 'arg_error' ? 'returned an argument error (fixable by correcting the args — the connector itself works)' :
|
|
120
|
+
tool.resultClass;
|
|
121
|
+
return (`Your previous reply contained "${matchedPhrase}" but ${tool.name} ${classLabel}.\n\n` +
|
|
122
|
+
`Literal tool result (first 200 chars):\n${tool.resultPreview}\n\n` +
|
|
123
|
+
`Rewrite your reply using the actual tool result. ` +
|
|
124
|
+
(tool.resultClass === 'arg_error'
|
|
125
|
+
? `This was an argument error for one call — the connector is NOT broken. Re-read the tool's schema (the rejected argument names are in the error above), retry the call with correct args, and report what comes back.`
|
|
126
|
+
: `Do not generalize this to "the connector is broken" or "the tool doesn't exist" — those claims contradict the tool's actual return value.`));
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=contradiction-validator.js.map
|