@causyne/agent 1.10.21 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/native-skills/media/professional-document-publishing/SKILL.md +44 -0
- package/native-skills/media/screenplay-and-shotlist-forge/SKILL.md +42 -0
- package/package.json +1 -1
- package/src/acp/client.js +235 -0
- package/src/acp/client.ts +282 -0
- package/src/acp/peerProfiles.js +122 -0
- package/src/acp/peerProfiles.ts +128 -0
- package/src/acp/protocol.js +54 -0
- package/src/acp/protocol.ts +434 -0
- package/src/acp/server.js +609 -0
- package/src/acp/server.ts +676 -0
- package/src/agentLoop.d.ts +43 -0
- package/src/agentLoop.js +142 -8
- package/src/commands.js +203 -0
- package/src/compiledSkills.js +10 -0
- package/src/documentEngine/antiSlopAuditor.js +156 -0
- package/src/documentEngine/antiSlopAuditor.ts +221 -0
- package/src/documentEngine/documentForge.js +443 -0
- package/src/documentEngine/documentForge.ts +519 -0
- package/src/documentEngine/screenplayFormatter.js +336 -0
- package/src/documentEngine/screenplayFormatter.ts +407 -0
- package/src/entrypoint.js +13 -0
- package/src/execSafe.js +10 -13
- package/src/execSafe.ts +25 -0
- package/src/modelRouter.js +56 -123
- package/src/modelRouter.ts +134 -0
- package/src/observer/advisory.js +164 -0
- package/src/observer/advisory.ts +171 -0
- package/src/observer/coldObserver.js +462 -0
- package/src/observer/coldObserver.ts +540 -0
- package/src/observer/evidence.js +435 -0
- package/src/observer/evidence.ts +499 -0
- package/src/perception.js +303 -118
- package/src/perception.ts +387 -0
- package/src/procSignal.js +15 -16
- package/src/procSignal.ts +31 -0
- package/src/promptMentions.js +170 -0
- package/src/promptMentions.ts +212 -0
- package/src/sandbox.js +1 -1
- package/src/textDiff.js +117 -109
- package/src/textDiff.ts +169 -0
- package/src/toolPacks.js +5 -2
- package/src/tools/documentForge.js +215 -0
- package/src/tools/documentForge.ts +265 -0
- package/src/tools/peerAgent.js +102 -0
- package/src/tools/peerAgent.ts +116 -0
- package/src/tools/registry.js +3 -1
- package/src/tui/completion.js +97 -41
- package/src/tui/prompt.js +17 -17
- package/src/uncertainty.js +93 -205
- package/src/uncertainty.ts +255 -0
- package/src/utils/paths.js +15 -18
- package/src/utils/paths.ts +36 -0
- package/src/vfeTracker.js +122 -181
- package/src/vfeTracker.ts +242 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { auditLog } from '../auditLog.js';
|
|
4
|
+
import { causyneHome } from '../utils/paths.js';
|
|
5
|
+
import { boundedArgs, outcomeFromResult, scanMutation } from './evidence.js';
|
|
6
|
+
import { isLoopbackEndpoint, probeAdvisory, queryAdvisory } from './advisory.js';
|
|
7
|
+
const CONFIG_FILE = path.join(causyneHome(), 'observer.json');
|
|
8
|
+
const MAX_AUDIT_RECORDS = 200;
|
|
9
|
+
const MAX_JOURNAL_TASKS = 24;
|
|
10
|
+
const MAX_JOURNAL_OUTCOMES = 160;
|
|
11
|
+
const ADVISORY_FAILURE_LIMIT = 3;
|
|
12
|
+
const ADVISORY_COOLDOWN_MS = 5 * 60 * 1000;
|
|
13
|
+
const DEFAULT_CONFIG = {
|
|
14
|
+
enabled: true,
|
|
15
|
+
mode: 'deterministic',
|
|
16
|
+
endpoint: '',
|
|
17
|
+
model: '',
|
|
18
|
+
timeoutMs: 800,
|
|
19
|
+
strictness: 'standard'
|
|
20
|
+
};
|
|
21
|
+
const COMPLETION_CLAIMS = [
|
|
22
|
+
/\b(?:done|finished|complete(?:d)?|implemented|resolved|fixed|ready)\b/i,
|
|
23
|
+
/\b(?:all|everything)\b[^.\n]{0,60}\b(?:works?|pass(?:es|ed|ing)?|fixed|done|clean)\b/i,
|
|
24
|
+
/\b(?:no|zero)\s+(?:errors?|issues?|failures?|bugs?)\b/i
|
|
25
|
+
];
|
|
26
|
+
const HONEST_CAVEATS = [
|
|
27
|
+
/\b(?:not|never)\s+(?:run|ran|tested|verified|checked|completed?)\b/i,
|
|
28
|
+
/\b(?:have not|haven't|did not|didn't|could not|couldn't|unable to)\b[^.\n]{0,60}\b(?:run|test|verify|finish|complete|fix|resolve)\b/i,
|
|
29
|
+
/\b(?:still|remains?)\s+(?:failing|broken|unresolved|incomplete|open)\b/i,
|
|
30
|
+
/\b(?:partially|incompletely)\s+(?:done|fixed|implemented|verified)\b/i
|
|
31
|
+
];
|
|
32
|
+
const TEST_COMMAND = /\b(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?test|node\s+--test|pytest|cargo\s+test|go\s+test|mvn\s+test|gradle\s+test|dotnet\s+test|swift\s+test)\b/i;
|
|
33
|
+
const BUILD_OR_CHECK_COMMAND = /\b(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:build|check|lint|typecheck|types)|tsc(?:\s|$)|eslint|biome\s+(?:check|ci)|cargo\s+check|go\s+vet|ruby\s+-c|php\s+-l)\b/i;
|
|
34
|
+
const TESTS_CLAIM = /\b(?:all\s+)?tests?\b[^.\n]{0,40}\b(?:pass|passed|passing|green|succeed|succeeded)\b/i;
|
|
35
|
+
const BUILD_CLAIM = /\b(?:build|typecheck|types|lint|compilation)\b[^.\n]{0,40}\b(?:pass|passed|passing|clean|succeed|succeeded)\b/i;
|
|
36
|
+
const MUTATING_TOOLS = new Set([
|
|
37
|
+
'fs_write', 'fs_edit', 'MultiEdit', 'fs_delete', 'fs_move',
|
|
38
|
+
'sysRun', 'sysRunBg', 'terminal_persistent_shell', 'gitOp'
|
|
39
|
+
]);
|
|
40
|
+
function normalizeMode(value) {
|
|
41
|
+
if (value === 'deterministic')
|
|
42
|
+
return 'deterministic';
|
|
43
|
+
if (value === 'advisory' || value === 'local' || value === 'hybrid')
|
|
44
|
+
return 'advisory';
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function normalizeStrictness(value) {
|
|
48
|
+
if (value === 'relaxed' || value === 'moderate')
|
|
49
|
+
return 'relaxed';
|
|
50
|
+
if (value === 'standard')
|
|
51
|
+
return 'standard';
|
|
52
|
+
if (value === 'strict' || value === 'paranoid')
|
|
53
|
+
return 'strict';
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function boundedTimeout(value) {
|
|
57
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
58
|
+
return null;
|
|
59
|
+
return Math.max(200, Math.min(5000, Math.round(value)));
|
|
60
|
+
}
|
|
61
|
+
function safeConfigPath() {
|
|
62
|
+
return CONFIG_FILE;
|
|
63
|
+
}
|
|
64
|
+
function readStoredConfig() {
|
|
65
|
+
try {
|
|
66
|
+
const raw = JSON.parse(fs.readFileSync(safeConfigPath(), 'utf8'));
|
|
67
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
68
|
+
return {};
|
|
69
|
+
const record = raw;
|
|
70
|
+
const result = {};
|
|
71
|
+
if (typeof record.enabled === 'boolean')
|
|
72
|
+
result.enabled = record.enabled;
|
|
73
|
+
const mode = normalizeMode(record.mode);
|
|
74
|
+
if (mode)
|
|
75
|
+
result.mode = mode;
|
|
76
|
+
if (typeof record.endpoint === 'string' && (record.endpoint === '' || isLoopbackEndpoint(record.endpoint))) {
|
|
77
|
+
result.endpoint = record.endpoint.trim().replace(/\/+$/, '');
|
|
78
|
+
}
|
|
79
|
+
if (typeof record.model === 'string')
|
|
80
|
+
result.model = record.model.trim().slice(0, 200);
|
|
81
|
+
const timeoutMs = boundedTimeout(record.timeoutMs);
|
|
82
|
+
if (timeoutMs !== null)
|
|
83
|
+
result.timeoutMs = timeoutMs;
|
|
84
|
+
const strictness = normalizeStrictness(record.strictness);
|
|
85
|
+
if (strictness)
|
|
86
|
+
result.strictness = strictness;
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function writeStoredConfig(config) {
|
|
94
|
+
try {
|
|
95
|
+
const directory = path.dirname(safeConfigPath());
|
|
96
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
97
|
+
fs.writeFileSync(safeConfigPath(), JSON.stringify({ schemaVersion: 1, ...config }, null, 2), {
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
mode: 0o600
|
|
100
|
+
});
|
|
101
|
+
fs.chmodSync(safeConfigPath(), 0o600);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
auditLog.log({
|
|
105
|
+
event: 'observer_config_write_failed',
|
|
106
|
+
error: error instanceof Error ? error.message : String(error)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function confirmThreshold(strictness) {
|
|
111
|
+
if (strictness === 'relaxed')
|
|
112
|
+
return 0.8;
|
|
113
|
+
if (strictness === 'strict')
|
|
114
|
+
return 0.45;
|
|
115
|
+
return 0.62;
|
|
116
|
+
}
|
|
117
|
+
function nestedError(result) {
|
|
118
|
+
if (result === null || typeof result !== 'object' || Array.isArray(result))
|
|
119
|
+
return '';
|
|
120
|
+
const record = result;
|
|
121
|
+
return typeof record.error === 'string' ? record.error : '';
|
|
122
|
+
}
|
|
123
|
+
function normalizeOutcome(entry) {
|
|
124
|
+
const args = entry.args && typeof entry.args === 'object' && !Array.isArray(entry.args)
|
|
125
|
+
? entry.args
|
|
126
|
+
: {};
|
|
127
|
+
const result = entry.result;
|
|
128
|
+
const error = nestedError(result);
|
|
129
|
+
const command = entry.command
|
|
130
|
+
|| (typeof args.command === 'string' ? args.command : '')
|
|
131
|
+
|| (typeof args.cmd === 'string' ? args.cmd : '');
|
|
132
|
+
return {
|
|
133
|
+
tool: entry.tool,
|
|
134
|
+
args: boundedArgs(args),
|
|
135
|
+
success: entry.success === true && !error,
|
|
136
|
+
error: error.slice(0, 600),
|
|
137
|
+
exitCode: result !== null && typeof result === 'object' && !Array.isArray(result)
|
|
138
|
+
&& typeof result.exitCode === 'number'
|
|
139
|
+
? result.exitCode
|
|
140
|
+
: null,
|
|
141
|
+
command: command.slice(0, 600),
|
|
142
|
+
path: entry.path
|
|
143
|
+
|| (typeof args.path === 'string' ? args.path : '')
|
|
144
|
+
|| (typeof args.file_path === 'string' ? args.file_path : ''),
|
|
145
|
+
at: 0
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function claimText(agentClaim) {
|
|
149
|
+
return agentClaim.replace(/\s+/g, ' ').trim().slice(0, 2000);
|
|
150
|
+
}
|
|
151
|
+
function hasCompletionClaim(claim) {
|
|
152
|
+
return COMPLETION_CLAIMS.some((pattern) => pattern.test(claim));
|
|
153
|
+
}
|
|
154
|
+
function hasHonestCaveat(claim) {
|
|
155
|
+
return HONEST_CAVEATS.some((pattern) => pattern.test(claim));
|
|
156
|
+
}
|
|
157
|
+
function failureSummary(outcomes) {
|
|
158
|
+
const last = [...outcomes].reverse().find((outcome) => !outcome.success);
|
|
159
|
+
if (!last)
|
|
160
|
+
return 'a recent tool call failed';
|
|
161
|
+
const detail = last.error || last.command || last.path || 'no error text';
|
|
162
|
+
return `${last.tool} failed: ${detail.slice(0, 180)}`;
|
|
163
|
+
}
|
|
164
|
+
export class ColdObserver {
|
|
165
|
+
config;
|
|
166
|
+
auditHistory = [];
|
|
167
|
+
journals = new Map();
|
|
168
|
+
advisoryFailures = 0;
|
|
169
|
+
advisoryCooldownUntil = 0;
|
|
170
|
+
static instance = null;
|
|
171
|
+
constructor(configPartial) {
|
|
172
|
+
this.config = { ...DEFAULT_CONFIG, ...readStoredConfig(), ...(configPartial || {}) };
|
|
173
|
+
}
|
|
174
|
+
static getInstance() {
|
|
175
|
+
if (!ColdObserver.instance)
|
|
176
|
+
ColdObserver.instance = new ColdObserver();
|
|
177
|
+
return ColdObserver.instance;
|
|
178
|
+
}
|
|
179
|
+
getConfig() {
|
|
180
|
+
return { ...this.config };
|
|
181
|
+
}
|
|
182
|
+
updateConfig(patch) {
|
|
183
|
+
const next = { ...this.config };
|
|
184
|
+
if (typeof patch.enabled === 'boolean')
|
|
185
|
+
next.enabled = patch.enabled;
|
|
186
|
+
if (patch.mode !== undefined) {
|
|
187
|
+
const mode = normalizeMode(patch.mode);
|
|
188
|
+
if (!mode)
|
|
189
|
+
throw new Error('mode must be deterministic or advisory');
|
|
190
|
+
next.mode = mode;
|
|
191
|
+
}
|
|
192
|
+
if (patch.endpoint !== undefined) {
|
|
193
|
+
const endpoint = String(patch.endpoint).trim().replace(/\/+$/, '');
|
|
194
|
+
if (endpoint && !isLoopbackEndpoint(endpoint)) {
|
|
195
|
+
throw new Error('endpoint must be a loopback HTTP URL');
|
|
196
|
+
}
|
|
197
|
+
next.endpoint = endpoint;
|
|
198
|
+
}
|
|
199
|
+
if (patch.model !== undefined)
|
|
200
|
+
next.model = String(patch.model).trim().slice(0, 200);
|
|
201
|
+
if (patch.timeoutMs !== undefined) {
|
|
202
|
+
const timeoutMs = boundedTimeout(patch.timeoutMs);
|
|
203
|
+
if (timeoutMs === null)
|
|
204
|
+
throw new Error('timeout must be a number');
|
|
205
|
+
next.timeoutMs = timeoutMs;
|
|
206
|
+
}
|
|
207
|
+
if (patch.strictness !== undefined) {
|
|
208
|
+
const strictness = normalizeStrictness(patch.strictness);
|
|
209
|
+
if (!strictness)
|
|
210
|
+
throw new Error('strictness must be relaxed, standard, or strict');
|
|
211
|
+
next.strictness = strictness;
|
|
212
|
+
}
|
|
213
|
+
this.config = next;
|
|
214
|
+
writeStoredConfig(next);
|
|
215
|
+
}
|
|
216
|
+
getAuditHistory() {
|
|
217
|
+
return [...this.auditHistory];
|
|
218
|
+
}
|
|
219
|
+
beginTask(taskId) {
|
|
220
|
+
if (!taskId)
|
|
221
|
+
return;
|
|
222
|
+
this.journals.set(taskId, []);
|
|
223
|
+
while (this.journals.size > MAX_JOURNAL_TASKS) {
|
|
224
|
+
const oldest = this.journals.keys().next().value;
|
|
225
|
+
if (typeof oldest !== 'string')
|
|
226
|
+
break;
|
|
227
|
+
this.journals.delete(oldest);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
recordToolOutcome(taskId, tool, args, result) {
|
|
231
|
+
if (!taskId)
|
|
232
|
+
return;
|
|
233
|
+
const journal = this.journals.get(taskId) ?? [];
|
|
234
|
+
journal.push(outcomeFromResult(tool, args, result, Date.now()));
|
|
235
|
+
if (journal.length > MAX_JOURNAL_OUTCOMES)
|
|
236
|
+
journal.splice(0, journal.length - MAX_JOURNAL_OUTCOMES);
|
|
237
|
+
this.journals.set(taskId, journal);
|
|
238
|
+
}
|
|
239
|
+
getToolOutcomes(taskId) {
|
|
240
|
+
return [...(this.journals.get(taskId) ?? [])];
|
|
241
|
+
}
|
|
242
|
+
recordAudit(entry) {
|
|
243
|
+
this.auditHistory.push(entry);
|
|
244
|
+
if (this.auditHistory.length > MAX_AUDIT_RECORDS) {
|
|
245
|
+
this.auditHistory.splice(0, this.auditHistory.length - MAX_AUDIT_RECORDS);
|
|
246
|
+
}
|
|
247
|
+
auditLog.log({
|
|
248
|
+
event: 'observer_audit',
|
|
249
|
+
type: entry.type,
|
|
250
|
+
tool: entry.tool ?? null,
|
|
251
|
+
riskScore: entry.riskScore ?? 0,
|
|
252
|
+
veto: entry.veto ?? false,
|
|
253
|
+
details: entry.details
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async advisoryReview(toolName, args, context, riskScore, threshold, evidence) {
|
|
257
|
+
if (this.config.mode !== 'advisory')
|
|
258
|
+
return { riskScore, confidence: 0, findings: [] };
|
|
259
|
+
if (!this.config.endpoint || !this.config.model)
|
|
260
|
+
return { riskScore, confidence: 0, findings: [] };
|
|
261
|
+
if (riskScore < 0.2 || riskScore >= threshold)
|
|
262
|
+
return { riskScore, confidence: 0, findings: [] };
|
|
263
|
+
if (Date.now() < this.advisoryCooldownUntil)
|
|
264
|
+
return { riskScore, confidence: 0, findings: [] };
|
|
265
|
+
if (!isLoopbackEndpoint(this.config.endpoint))
|
|
266
|
+
return { riskScore, confidence: 0, findings: [] };
|
|
267
|
+
const verdict = await queryAdvisory({
|
|
268
|
+
tool: toolName,
|
|
269
|
+
args,
|
|
270
|
+
findings: evidence.map((item) => item.detail),
|
|
271
|
+
workspaceRoot: context.workspaceRoot
|
|
272
|
+
}, {
|
|
273
|
+
endpoint: this.config.endpoint,
|
|
274
|
+
model: this.config.model,
|
|
275
|
+
timeoutMs: this.config.timeoutMs
|
|
276
|
+
});
|
|
277
|
+
if (!verdict) {
|
|
278
|
+
this.advisoryFailures += 1;
|
|
279
|
+
if (this.advisoryFailures >= ADVISORY_FAILURE_LIMIT) {
|
|
280
|
+
this.advisoryCooldownUntil = Date.now() + ADVISORY_COOLDOWN_MS;
|
|
281
|
+
this.advisoryFailures = 0;
|
|
282
|
+
}
|
|
283
|
+
return { riskScore, confidence: 0, findings: [] };
|
|
284
|
+
}
|
|
285
|
+
this.advisoryFailures = 0;
|
|
286
|
+
if (verdict.risk <= riskScore || verdict.confidence < 0.5) {
|
|
287
|
+
return { riskScore, confidence: 0, findings: [] };
|
|
288
|
+
}
|
|
289
|
+
const delta = Math.min(0.15, (verdict.risk - riskScore) * verdict.confidence);
|
|
290
|
+
const raised = Math.min(0.97, riskScore + delta);
|
|
291
|
+
evidence.push({
|
|
292
|
+
code: 'advisory-review',
|
|
293
|
+
severity: raised >= 0.78 ? 'high' : 'medium',
|
|
294
|
+
weight: raised,
|
|
295
|
+
confidence: verdict.confidence,
|
|
296
|
+
detail: verdict.observation || `local advisory model raised risk from ${riskScore.toFixed(2)} to ${raised.toFixed(2)}`,
|
|
297
|
+
source: 'advisory'
|
|
298
|
+
});
|
|
299
|
+
return {
|
|
300
|
+
riskScore: raised,
|
|
301
|
+
confidence: verdict.confidence,
|
|
302
|
+
findings: [verdict.observation || 'local advisory model raised the risk estimate']
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
async evaluateMutation(toolName, args = {}, context) {
|
|
306
|
+
if (!this.config.enabled) {
|
|
307
|
+
return {
|
|
308
|
+
allowed: true,
|
|
309
|
+
veto: false,
|
|
310
|
+
requiresConfirmation: false,
|
|
311
|
+
riskScore: 0,
|
|
312
|
+
confidence: 0,
|
|
313
|
+
reason: 'observer disabled',
|
|
314
|
+
findings: [],
|
|
315
|
+
evidence: []
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (!MUTATING_TOOLS.has(toolName)) {
|
|
319
|
+
return {
|
|
320
|
+
allowed: true,
|
|
321
|
+
veto: false,
|
|
322
|
+
requiresConfirmation: false,
|
|
323
|
+
riskScore: 0,
|
|
324
|
+
confidence: 0,
|
|
325
|
+
reason: 'no mutation evidence',
|
|
326
|
+
findings: [],
|
|
327
|
+
evidence: []
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const resolvedContext = {
|
|
331
|
+
workspaceRoot: context?.workspaceRoot || process.cwd()
|
|
332
|
+
};
|
|
333
|
+
const scanned = scanMutation(toolName, args, resolvedContext);
|
|
334
|
+
const evidence = [...scanned.evidence];
|
|
335
|
+
const threshold = confirmThreshold(this.config.strictness);
|
|
336
|
+
const advisory = await this.advisoryReview(toolName, args, resolvedContext, scanned.riskScore, threshold, evidence);
|
|
337
|
+
const riskScore = advisory.riskScore;
|
|
338
|
+
const confidence = Math.max(scanned.confidence, advisory.confidence * 0.8);
|
|
339
|
+
const veto = scanned.veto;
|
|
340
|
+
const requiresConfirmation = !veto && riskScore >= threshold;
|
|
341
|
+
const findings = evidence.map((item) => item.detail);
|
|
342
|
+
for (const finding of advisory.findings) {
|
|
343
|
+
if (!findings.includes(finding))
|
|
344
|
+
findings.push(finding);
|
|
345
|
+
}
|
|
346
|
+
const reason = findings.length > 0
|
|
347
|
+
? findings.slice(0, 3).join(' | ')
|
|
348
|
+
: 'no destructive evidence found';
|
|
349
|
+
if (veto || requiresConfirmation) {
|
|
350
|
+
this.recordAudit({
|
|
351
|
+
timestamp: Date.now(),
|
|
352
|
+
type: 'mutation',
|
|
353
|
+
tool: toolName,
|
|
354
|
+
riskScore,
|
|
355
|
+
veto,
|
|
356
|
+
details: reason
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
allowed: !veto,
|
|
361
|
+
veto,
|
|
362
|
+
requiresConfirmation,
|
|
363
|
+
riskScore,
|
|
364
|
+
confidence,
|
|
365
|
+
reason,
|
|
366
|
+
findings,
|
|
367
|
+
evidence
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async auditTurnVerification(turnState) {
|
|
371
|
+
if (!this.config.enabled) {
|
|
372
|
+
return { verified: true, overconfident: false, confidence: 0, discrepancy: '', coldDirective: '' };
|
|
373
|
+
}
|
|
374
|
+
const claim = claimText(turnState.agentClaim);
|
|
375
|
+
if (!claim || !hasCompletionClaim(claim)) {
|
|
376
|
+
return { verified: true, overconfident: false, confidence: 0, discrepancy: '', coldDirective: '' };
|
|
377
|
+
}
|
|
378
|
+
const journal = turnState.taskId ? this.journals.get(turnState.taskId) : undefined;
|
|
379
|
+
const outcomes = journal && journal.length > 0
|
|
380
|
+
? journal
|
|
381
|
+
: turnState.toolHistory.map(normalizeOutcome);
|
|
382
|
+
const caveat = hasHonestCaveat(claim);
|
|
383
|
+
const lastFailure = outcomes.findLastIndex((outcome) => !outcome.success);
|
|
384
|
+
const successAfterFailure = lastFailure >= 0
|
|
385
|
+
&& outcomes.slice(lastFailure + 1).some((outcome) => outcome.success);
|
|
386
|
+
const hasTestRun = outcomes.some((outcome) => outcome.success
|
|
387
|
+
&& (TEST_COMMAND.test(outcome.command) || TEST_COMMAND.test(outcome.tool)));
|
|
388
|
+
const hasBuildRun = outcomes.some((outcome) => outcome.success
|
|
389
|
+
&& (BUILD_OR_CHECK_COMMAND.test(outcome.command) || outcome.tool === 'code_verify_syntax'));
|
|
390
|
+
const modifiedCode = outcomes.some((outcome) => outcome.success
|
|
391
|
+
&& ['fs_write', 'fs_edit', 'MultiEdit'].includes(outcome.tool)
|
|
392
|
+
&& /\.(?:[cm]?[jt]sx?|py|rb|php|go|rs|java|kt|kts|swift|c|cc|cpp|h|hpp|cs|fs|sh|sql|html|css|scss|sass|vue|svelte)$/i.test(outcome.path));
|
|
393
|
+
let overconfident = false;
|
|
394
|
+
let confidence = 0;
|
|
395
|
+
let discrepancy = '';
|
|
396
|
+
let coldDirective = '';
|
|
397
|
+
if (!caveat && lastFailure >= 0 && !successAfterFailure) {
|
|
398
|
+
overconfident = true;
|
|
399
|
+
confidence = 0.92;
|
|
400
|
+
discrepancy = `completion claim conflicts with tool evidence: ${failureSummary(outcomes)}`;
|
|
401
|
+
coldDirective = `[OBSERVER] Your completion claim conflicts with the latest tool evidence: ${failureSummary(outcomes)}. Resolve the failure and rerun the failing check before claiming success.`;
|
|
402
|
+
}
|
|
403
|
+
else if (!caveat && TESTS_CLAIM.test(claim) && !hasTestRun) {
|
|
404
|
+
overconfident = true;
|
|
405
|
+
confidence = 0.86;
|
|
406
|
+
discrepancy = 'claimed passing tests without a successful test command in this task';
|
|
407
|
+
coldDirective = '[OBSERVER] You claimed that tests passed, but no successful test command appears in this task. Run the test command and report its actual result, or state plainly that you did not run it.';
|
|
408
|
+
}
|
|
409
|
+
else if (!caveat && BUILD_CLAIM.test(claim) && !hasBuildRun) {
|
|
410
|
+
overconfident = true;
|
|
411
|
+
confidence = 0.82;
|
|
412
|
+
discrepancy = 'claimed a passing build or check without a successful verification command';
|
|
413
|
+
coldDirective = '[OBSERVER] You claimed that a build or check passed, but no successful verification command appears in this task. Run the relevant command and report its actual result, or state that it is unverified.';
|
|
414
|
+
}
|
|
415
|
+
else if (!caveat && modifiedCode && !hasBuildRun && !hasTestRun) {
|
|
416
|
+
overconfident = true;
|
|
417
|
+
confidence = 0.72;
|
|
418
|
+
discrepancy = 'code was modified without a successful test or syntax check';
|
|
419
|
+
coldDirective = '[OBSERVER] You changed code but there is no successful test or syntax check in this task. Verify the change before finishing, or state plainly that it is unverified.';
|
|
420
|
+
}
|
|
421
|
+
if (overconfident) {
|
|
422
|
+
this.recordAudit({
|
|
423
|
+
timestamp: Date.now(),
|
|
424
|
+
type: 'verification',
|
|
425
|
+
riskScore: confidence,
|
|
426
|
+
veto: false,
|
|
427
|
+
details: discrepancy
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
verified: !overconfident,
|
|
432
|
+
overconfident,
|
|
433
|
+
confidence,
|
|
434
|
+
discrepancy,
|
|
435
|
+
coldDirective
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
async healthCheck() {
|
|
439
|
+
if (!this.config.endpoint) {
|
|
440
|
+
return {
|
|
441
|
+
healthy: false,
|
|
442
|
+
endpoint: '',
|
|
443
|
+
model: this.config.model,
|
|
444
|
+
latencyMs: 0,
|
|
445
|
+
detail: 'no endpoint configured'
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const probe = await probeAdvisory({
|
|
449
|
+
endpoint: this.config.endpoint,
|
|
450
|
+
model: this.config.model,
|
|
451
|
+
timeoutMs: this.config.timeoutMs
|
|
452
|
+
});
|
|
453
|
+
return {
|
|
454
|
+
healthy: probe.healthy,
|
|
455
|
+
endpoint: this.config.endpoint,
|
|
456
|
+
model: this.config.model,
|
|
457
|
+
latencyMs: probe.latencyMs,
|
|
458
|
+
detail: probe.detail
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
export const coldObserver = ColdObserver.getInstance();
|