codebot-ai 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +18 -0
- package/dist/agent.js +135 -6
- package/dist/audit.d.ts +1 -1
- package/dist/capabilities.d.ts +48 -0
- package/dist/capabilities.js +187 -0
- package/dist/cli.js +167 -4
- package/dist/history.d.ts +7 -3
- package/dist/history.js +55 -8
- package/dist/index.d.ts +12 -0
- package/dist/index.js +20 -1
- package/dist/integrity.d.ts +35 -0
- package/dist/integrity.js +135 -0
- package/dist/metrics.d.ts +60 -0
- package/dist/metrics.js +296 -0
- package/dist/policy.d.ts +9 -0
- package/dist/policy.js +32 -6
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +4 -0
- package/dist/providers/openai.d.ts +1 -0
- package/dist/providers/openai.js +4 -0
- package/dist/replay.d.ts +55 -0
- package/dist/replay.js +196 -0
- package/dist/risk.d.ts +52 -0
- package/dist/risk.js +367 -0
- package/dist/sarif.d.ts +82 -0
- package/dist/sarif.js +176 -0
- package/dist/tools/batch-edit.d.ts +3 -0
- package/dist/tools/batch-edit.js +12 -0
- package/dist/tools/edit.d.ts +3 -0
- package/dist/tools/edit.js +11 -0
- package/dist/tools/git.d.ts +5 -0
- package/dist/tools/git.js +31 -0
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +6 -6
- package/dist/tools/write.d.ts +3 -0
- package/dist/tools/write.js +11 -0
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -49,7 +49,10 @@ const scheduler_1 = require("./scheduler");
|
|
|
49
49
|
const audit_1 = require("./audit");
|
|
50
50
|
const policy_1 = require("./policy");
|
|
51
51
|
const sandbox_1 = require("./sandbox");
|
|
52
|
-
const
|
|
52
|
+
const replay_1 = require("./replay");
|
|
53
|
+
const risk_1 = require("./risk");
|
|
54
|
+
const sarif_1 = require("./sarif");
|
|
55
|
+
const VERSION = '1.9.0';
|
|
53
56
|
const C = {
|
|
54
57
|
reset: '\x1b[0m',
|
|
55
58
|
bold: '\x1b[1m',
|
|
@@ -168,6 +171,78 @@ async function main() {
|
|
|
168
171
|
console.log(` Network: ${info.defaults.network ? 'enabled' : 'disabled'} by default`);
|
|
169
172
|
return;
|
|
170
173
|
}
|
|
174
|
+
// --replay: Replay a saved session
|
|
175
|
+
if (args.replay) {
|
|
176
|
+
const replayId = typeof args.replay === 'string'
|
|
177
|
+
? args.replay
|
|
178
|
+
: history_1.SessionManager.latest();
|
|
179
|
+
if (!replayId) {
|
|
180
|
+
console.log(c('No session to replay. Specify an ID or ensure a previous session exists.', 'yellow'));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const data = (0, replay_1.loadSessionForReplay)(replayId);
|
|
184
|
+
if (!data) {
|
|
185
|
+
console.log(c(`Session ${replayId} not found or empty.`, 'red'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
console.log(c(`\nReplaying session ${replayId.substring(0, 12)}...`, 'cyan'));
|
|
189
|
+
console.log(c(` ${data.messages.length} messages (${data.userMessages.length} user, ${data.assistantMessages.length} assistant)`, 'dim'));
|
|
190
|
+
const replayProvider = new replay_1.ReplayProvider(data.assistantMessages);
|
|
191
|
+
const config = await resolveConfig(args);
|
|
192
|
+
const agent = new agent_1.Agent({
|
|
193
|
+
provider: replayProvider,
|
|
194
|
+
model: config.model,
|
|
195
|
+
providerName: 'replay',
|
|
196
|
+
autoApprove: true,
|
|
197
|
+
});
|
|
198
|
+
// Collect recorded tool results in order for sequential comparison
|
|
199
|
+
const recordedResults = Array.from(data.toolResults.values());
|
|
200
|
+
let resultIndex = 0;
|
|
201
|
+
let divergences = 0;
|
|
202
|
+
for (const userMsg of data.userMessages) {
|
|
203
|
+
console.log(c(`\n> ${truncate(userMsg.content, 100)}`, 'cyan'));
|
|
204
|
+
for await (const event of agent.run(userMsg.content)) {
|
|
205
|
+
if (event.type === 'tool_result' && event.toolResult && !event.toolResult.is_error) {
|
|
206
|
+
const recorded = recordedResults[resultIndex++];
|
|
207
|
+
if (recorded !== undefined) {
|
|
208
|
+
const diff = (0, replay_1.compareOutputs)(recorded, event.toolResult.result);
|
|
209
|
+
if (diff) {
|
|
210
|
+
divergences++;
|
|
211
|
+
console.log(c(` ⚠ Divergence in ${event.toolResult.name || 'tool'}:`, 'yellow'));
|
|
212
|
+
console.log(c(` ${diff.split('\n').join('\n ')}`, 'dim'));
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
console.log(c(` ✓ ${event.toolResult.name || 'tool'} — output matches`, 'green'));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (event.type === 'text') {
|
|
220
|
+
process.stdout.write(c('.', 'dim'));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
console.log(c(`\n\nReplay complete.`, 'bold'));
|
|
225
|
+
if (divergences === 0) {
|
|
226
|
+
console.log(c(' All tool outputs match — session is reproducible.', 'green'));
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
console.log(c(` ${divergences} divergence(s) detected — environment may have changed.`, 'yellow'));
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// --export-audit sarif: Export audit log as SARIF 2.1.0
|
|
234
|
+
if (args['export-audit'] === 'sarif' || args['export-audit'] === true) {
|
|
235
|
+
const logger = new audit_1.AuditLogger();
|
|
236
|
+
const sessionId = typeof args['session'] === 'string' ? args['session'] : undefined;
|
|
237
|
+
const entries = sessionId ? logger.query({ sessionId }) : logger.query();
|
|
238
|
+
if (entries.length === 0) {
|
|
239
|
+
console.error(c('No audit entries found.', 'yellow'));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
const sarif = (0, sarif_1.exportSarif)(entries, { version: VERSION, sessionId });
|
|
243
|
+
process.stdout.write((0, sarif_1.sarifToString)(sarif) + '\n');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
171
246
|
// First run: auto-launch setup if nothing is configured
|
|
172
247
|
if ((0, setup_1.isFirstRun)() && process.stdin.isTTY && !args.message) {
|
|
173
248
|
console.log(c('Welcome! No configuration found — launching setup...', 'cyan'));
|
|
@@ -177,6 +252,11 @@ async function main() {
|
|
|
177
252
|
}
|
|
178
253
|
const config = await resolveConfig(args);
|
|
179
254
|
const provider = createProvider(config);
|
|
255
|
+
// Deterministic mode: set temperature=0
|
|
256
|
+
if (args.deterministic) {
|
|
257
|
+
provider.temperature = 0;
|
|
258
|
+
console.log(c(' Deterministic mode: temperature=0', 'dim'));
|
|
259
|
+
}
|
|
180
260
|
// Session management
|
|
181
261
|
let resumeId;
|
|
182
262
|
if (args.continue) {
|
|
@@ -234,7 +314,7 @@ async function main() {
|
|
|
234
314
|
// Cleanup scheduler on exit
|
|
235
315
|
scheduler.stop();
|
|
236
316
|
}
|
|
237
|
-
/** Print session summary with tokens, cost, tool calls, files modified */
|
|
317
|
+
/** Print session summary with tokens, cost, tool calls, files modified, metrics */
|
|
238
318
|
function printSessionSummary(agent) {
|
|
239
319
|
const tracker = agent.getTokenTracker();
|
|
240
320
|
tracker.saveUsage();
|
|
@@ -249,6 +329,27 @@ function printSessionSummary(agent) {
|
|
|
249
329
|
console.log(` Requests: ${summary.requestCount}`);
|
|
250
330
|
console.log(` Tools: ${summary.toolCalls} calls`);
|
|
251
331
|
console.log(` Files: ${summary.filesModified} modified`);
|
|
332
|
+
// v1.9.0: Per-tool breakdown from MetricsCollector
|
|
333
|
+
const metrics = agent.getMetrics();
|
|
334
|
+
const snap = metrics.snapshot();
|
|
335
|
+
const toolCounters = snap.counters.filter(c => c.name === 'tool_calls_total');
|
|
336
|
+
if (toolCounters.length > 0) {
|
|
337
|
+
console.log(c(' Per-tool:', 'dim'));
|
|
338
|
+
for (const tc of toolCounters.sort((a, b) => b.value - a.value)) {
|
|
339
|
+
const hist = snap.histograms.find(h => h.name === 'tool_latency_seconds' && h.labels.tool === tc.labels.tool);
|
|
340
|
+
const avg = hist && hist.count > 0 ? (hist.sum / hist.count * 1000).toFixed(0) : '?';
|
|
341
|
+
console.log(c(` ${tc.labels.tool}: ${tc.value} calls (avg ${avg}ms)`, 'dim'));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Risk summary
|
|
345
|
+
const riskScorer = agent.getRiskScorer();
|
|
346
|
+
const riskAvg = riskScorer.getSessionAverage();
|
|
347
|
+
if (riskScorer.getHistory().length > 0) {
|
|
348
|
+
console.log(` Risk: avg ${riskAvg}/100`);
|
|
349
|
+
}
|
|
350
|
+
// Save metrics
|
|
351
|
+
metrics.save();
|
|
352
|
+
metrics.exportOtel();
|
|
252
353
|
}
|
|
253
354
|
function createProvider(config) {
|
|
254
355
|
if (config.provider === 'anthropic') {
|
|
@@ -328,8 +429,14 @@ function renderEvent(event, agent) {
|
|
|
328
429
|
process.stdout.write('\n');
|
|
329
430
|
isThinking = false;
|
|
330
431
|
}
|
|
331
|
-
|
|
332
|
-
|
|
432
|
+
{
|
|
433
|
+
const riskStr = event.risk
|
|
434
|
+
? ' ' + risk_1.RiskScorer.formatIndicator({ score: event.risk.score, level: event.risk.level, factors: [] })
|
|
435
|
+
: '';
|
|
436
|
+
console.log(c(`\n⚡ ${event.toolCall?.name}`, 'yellow') +
|
|
437
|
+
c(`(${formatArgs(event.toolCall?.args)})`, 'dim') +
|
|
438
|
+
riskStr);
|
|
439
|
+
}
|
|
333
440
|
break;
|
|
334
441
|
case 'tool_result':
|
|
335
442
|
if (event.toolResult?.is_error) {
|
|
@@ -402,6 +509,8 @@ function handleSlashCommand(input, agent, config) {
|
|
|
402
509
|
/undo Undo last file edit (/undo [path])
|
|
403
510
|
/usage Show token usage & cost for this session
|
|
404
511
|
/cost Show running cost
|
|
512
|
+
/metrics Show session metrics (counters + histograms)
|
|
513
|
+
/risk Show risk assessment summary
|
|
405
514
|
/policy Show current security policy
|
|
406
515
|
/audit Verify audit chain for this session
|
|
407
516
|
/config Show current config
|
|
@@ -478,6 +587,26 @@ function handleSlashCommand(input, agent, config) {
|
|
|
478
587
|
console.log(c(` ${tracker.formatStatusLine()}`, 'dim'));
|
|
479
588
|
break;
|
|
480
589
|
}
|
|
590
|
+
case '/metrics': {
|
|
591
|
+
const metricsOutput = agent.getMetrics().formatSummary();
|
|
592
|
+
console.log('\n' + metricsOutput);
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
case '/risk': {
|
|
596
|
+
const riskHistory = agent.getRiskScorer().getHistory();
|
|
597
|
+
if (riskHistory.length === 0) {
|
|
598
|
+
console.log(c('No risk assessments yet.', 'dim'));
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
const avg = agent.getRiskScorer().getSessionAverage();
|
|
602
|
+
console.log(c(`\nRisk Summary: ${riskHistory.length} assessments, avg ${avg}/100`, 'bold'));
|
|
603
|
+
const last5 = riskHistory.slice(-5);
|
|
604
|
+
for (const a of last5) {
|
|
605
|
+
console.log(` ${risk_1.RiskScorer.formatIndicator(a)}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
481
610
|
case '/policy': {
|
|
482
611
|
const policy = agent.getPolicyEnforcer().getPolicy();
|
|
483
612
|
console.log(c('\nCurrent Policy:', 'bold'));
|
|
@@ -637,6 +766,32 @@ function parseArgs(argv) {
|
|
|
637
766
|
}
|
|
638
767
|
continue;
|
|
639
768
|
}
|
|
769
|
+
if (arg === '--export-audit') {
|
|
770
|
+
const next = argv[i + 1];
|
|
771
|
+
if (next && !next.startsWith('--')) {
|
|
772
|
+
result['export-audit'] = next;
|
|
773
|
+
i++;
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
result['export-audit'] = true;
|
|
777
|
+
}
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (arg === '--replay') {
|
|
781
|
+
const next = argv[i + 1];
|
|
782
|
+
if (next && !next.startsWith('--')) {
|
|
783
|
+
result['replay'] = next;
|
|
784
|
+
i++;
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
result['replay'] = true; // replay latest
|
|
788
|
+
}
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (arg === '--deterministic') {
|
|
792
|
+
result['deterministic'] = true;
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
640
795
|
if (arg.startsWith('--')) {
|
|
641
796
|
const key = arg.slice(2);
|
|
642
797
|
const next = argv[i + 1];
|
|
@@ -690,8 +845,13 @@ ${c('Options:', 'bold')}
|
|
|
690
845
|
${c('Security & Policy:', 'bold')}
|
|
691
846
|
--init-policy Generate default .codebot/policy.json
|
|
692
847
|
--verify-audit [id] Verify audit log hash chain integrity
|
|
848
|
+
--export-audit sarif Export audit log as SARIF 2.1.0 JSON
|
|
693
849
|
--sandbox-info Show Docker sandbox status
|
|
694
850
|
|
|
851
|
+
${c('Debugging & Replay:', 'bold')}
|
|
852
|
+
--replay [id] Replay a session, re-execute tools, compare outputs
|
|
853
|
+
--deterministic Set temperature=0 for reproducible outputs
|
|
854
|
+
|
|
695
855
|
${c('Supported Providers:', 'bold')}
|
|
696
856
|
Local: Ollama, LM Studio, vLLM (auto-detected)
|
|
697
857
|
Anthropic: Claude Opus/Sonnet/Haiku (ANTHROPIC_API_KEY)
|
|
@@ -711,6 +871,7 @@ ${c('Examples:', 'bold')}
|
|
|
711
871
|
codebot --autonomous "refactor src/" Full auto, no prompts
|
|
712
872
|
codebot --init-policy Create security policy
|
|
713
873
|
codebot --verify-audit Check audit integrity
|
|
874
|
+
codebot --export-audit sarif > r.sarif Export SARIF report
|
|
714
875
|
|
|
715
876
|
${c('Interactive Commands:', 'bold')}
|
|
716
877
|
/help Show commands
|
|
@@ -722,6 +883,8 @@ ${c('Interactive Commands:', 'bold')}
|
|
|
722
883
|
/compact Force context compaction
|
|
723
884
|
/usage Show token usage & cost
|
|
724
885
|
/cost Show running cost
|
|
886
|
+
/metrics Show session metrics
|
|
887
|
+
/risk Show risk assessment summary
|
|
725
888
|
/policy Show security policy
|
|
726
889
|
/audit Verify session audit chain
|
|
727
890
|
/config Show configuration
|
package/dist/history.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Message } from './types';
|
|
2
|
+
import { IntegrityResult } from './integrity';
|
|
2
3
|
export interface SessionMeta {
|
|
3
4
|
id: string;
|
|
4
5
|
model: string;
|
|
@@ -11,14 +12,17 @@ export declare class SessionManager {
|
|
|
11
12
|
private sessionId;
|
|
12
13
|
private filePath;
|
|
13
14
|
private model;
|
|
15
|
+
private integrityKey;
|
|
14
16
|
constructor(model: string, sessionId?: string);
|
|
15
17
|
getId(): string;
|
|
16
|
-
/** Append a message to the session file */
|
|
18
|
+
/** Append a message to the session file (HMAC signed) */
|
|
17
19
|
save(message: Message): void;
|
|
18
|
-
/** Save all messages (atomic overwrite
|
|
20
|
+
/** Save all messages (atomic overwrite, HMAC signed) */
|
|
19
21
|
saveAll(messages: Message[]): void;
|
|
20
|
-
/** Load messages from a session file (
|
|
22
|
+
/** Load messages from a session file (verifies HMAC, drops tampered) */
|
|
21
23
|
load(): Message[];
|
|
24
|
+
/** Verify integrity of all messages in this session. */
|
|
25
|
+
verifyIntegrity(): IntegrityResult;
|
|
22
26
|
/** List recent sessions */
|
|
23
27
|
static list(limit?: number): SessionMeta[];
|
|
24
28
|
/** Get the most recent session ID */
|
package/dist/history.js
CHANGED
|
@@ -38,38 +38,50 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
40
|
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const integrity_1 = require("./integrity");
|
|
41
42
|
const SESSIONS_DIR = path.join(os.homedir(), '.codebot', 'sessions');
|
|
42
43
|
class SessionManager {
|
|
43
44
|
sessionId;
|
|
44
45
|
filePath;
|
|
45
46
|
model;
|
|
47
|
+
integrityKey;
|
|
46
48
|
constructor(model, sessionId) {
|
|
47
49
|
this.model = model;
|
|
48
50
|
this.sessionId = sessionId || crypto.randomUUID();
|
|
51
|
+
this.integrityKey = (0, integrity_1.deriveSessionKey)(this.sessionId);
|
|
49
52
|
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
50
53
|
this.filePath = path.join(SESSIONS_DIR, `${this.sessionId}.jsonl`);
|
|
51
54
|
}
|
|
52
55
|
getId() {
|
|
53
56
|
return this.sessionId;
|
|
54
57
|
}
|
|
55
|
-
/** Append a message to the session file */
|
|
58
|
+
/** Append a message to the session file (HMAC signed) */
|
|
56
59
|
save(message) {
|
|
57
60
|
try {
|
|
58
|
-
const
|
|
61
|
+
const record = {
|
|
59
62
|
...message,
|
|
60
63
|
_ts: new Date().toISOString(),
|
|
61
64
|
_model: this.model,
|
|
62
|
-
}
|
|
63
|
-
|
|
65
|
+
};
|
|
66
|
+
record._sig = (0, integrity_1.signMessage)(record, this.integrityKey);
|
|
67
|
+
fs.appendFileSync(this.filePath, JSON.stringify(record) + '\n');
|
|
64
68
|
}
|
|
65
69
|
catch {
|
|
66
70
|
// Don't crash on write failure — session persistence is best-effort
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
|
-
/** Save all messages (atomic overwrite
|
|
73
|
+
/** Save all messages (atomic overwrite, HMAC signed) */
|
|
70
74
|
saveAll(messages) {
|
|
71
75
|
try {
|
|
72
|
-
const lines = messages.map(m =>
|
|
76
|
+
const lines = messages.map(m => {
|
|
77
|
+
const record = {
|
|
78
|
+
...m,
|
|
79
|
+
_ts: new Date().toISOString(),
|
|
80
|
+
_model: this.model,
|
|
81
|
+
};
|
|
82
|
+
record._sig = (0, integrity_1.signMessage)(record, this.integrityKey);
|
|
83
|
+
return JSON.stringify(record);
|
|
84
|
+
});
|
|
73
85
|
const tmpPath = this.filePath + '.tmp';
|
|
74
86
|
fs.writeFileSync(tmpPath, lines.join('\n') + '\n');
|
|
75
87
|
fs.renameSync(tmpPath, this.filePath);
|
|
@@ -78,7 +90,7 @@ class SessionManager {
|
|
|
78
90
|
// Don't crash — session persistence is best-effort
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
|
-
/** Load messages from a session file (
|
|
93
|
+
/** Load messages from a session file (verifies HMAC, drops tampered) */
|
|
82
94
|
load() {
|
|
83
95
|
if (!fs.existsSync(this.filePath))
|
|
84
96
|
return [];
|
|
@@ -92,20 +104,55 @@ class SessionManager {
|
|
|
92
104
|
if (!content)
|
|
93
105
|
return [];
|
|
94
106
|
const messages = [];
|
|
107
|
+
let dropped = 0;
|
|
95
108
|
for (const line of content.split('\n')) {
|
|
96
109
|
try {
|
|
97
110
|
const obj = JSON.parse(line);
|
|
111
|
+
// Verify integrity if signature present (backward compat: unsigned messages pass)
|
|
112
|
+
if (obj._sig) {
|
|
113
|
+
if (!(0, integrity_1.verifyMessage)(obj, this.integrityKey)) {
|
|
114
|
+
dropped++;
|
|
115
|
+
continue; // Drop tampered message
|
|
116
|
+
}
|
|
117
|
+
}
|
|
98
118
|
delete obj._ts;
|
|
99
119
|
delete obj._model;
|
|
120
|
+
delete obj._sig;
|
|
100
121
|
messages.push(obj);
|
|
101
122
|
}
|
|
102
123
|
catch {
|
|
103
|
-
// Skip malformed line — don't crash the whole load
|
|
104
124
|
continue;
|
|
105
125
|
}
|
|
106
126
|
}
|
|
127
|
+
if (dropped > 0) {
|
|
128
|
+
console.warn(`Warning: Dropped ${dropped} tampered message(s) from session ${this.sessionId.substring(0, 8)}.`);
|
|
129
|
+
}
|
|
107
130
|
return messages;
|
|
108
131
|
}
|
|
132
|
+
/** Verify integrity of all messages in this session. */
|
|
133
|
+
verifyIntegrity() {
|
|
134
|
+
if (!fs.existsSync(this.filePath)) {
|
|
135
|
+
return { valid: 0, tampered: 0, unsigned: 0, tamperedIndices: [] };
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const content = fs.readFileSync(this.filePath, 'utf-8').trim();
|
|
139
|
+
if (!content)
|
|
140
|
+
return { valid: 0, tampered: 0, unsigned: 0, tamperedIndices: [] };
|
|
141
|
+
const records = [];
|
|
142
|
+
for (const line of content.split('\n')) {
|
|
143
|
+
try {
|
|
144
|
+
records.push(JSON.parse(line));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return (0, integrity_1.verifyMessages)(records, this.integrityKey);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return { valid: 0, tampered: 0, unsigned: 0, tamperedIndices: [] };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
109
156
|
/** List recent sessions */
|
|
110
157
|
static list(limit = 10) {
|
|
111
158
|
if (!fs.existsSync(SESSIONS_DIR))
|
package/dist/index.d.ts
CHANGED
|
@@ -11,5 +11,17 @@ export { loadPlugins } from './plugins';
|
|
|
11
11
|
export { loadMCPTools } from './mcp';
|
|
12
12
|
export { MODEL_REGISTRY, PROVIDER_DEFAULTS, getModelInfo, detectProvider } from './providers/registry';
|
|
13
13
|
export type { ModelInfo } from './providers/registry';
|
|
14
|
+
export { CapabilityChecker } from './capabilities';
|
|
15
|
+
export type { ToolCapabilities, CapabilityConfig } from './capabilities';
|
|
16
|
+
export { deriveSessionKey, signMessage, verifyMessage, verifyMessages } from './integrity';
|
|
17
|
+
export type { IntegrityResult } from './integrity';
|
|
18
|
+
export { ReplayProvider, loadSessionForReplay, compareOutputs, listReplayableSessions } from './replay';
|
|
19
|
+
export type { SessionReplayData, ReplayDivergence } from './replay';
|
|
20
|
+
export { MetricsCollector } from './metrics';
|
|
21
|
+
export type { MetricsSnapshot, CounterValue, HistogramValue } from './metrics';
|
|
22
|
+
export { RiskScorer } from './risk';
|
|
23
|
+
export type { RiskAssessment, RiskFactor } from './risk';
|
|
24
|
+
export { exportSarif, sarifToString } from './sarif';
|
|
25
|
+
export type { SarifLog, SarifResult, SarifRule } from './sarif';
|
|
14
26
|
export * from './types';
|
|
15
27
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.detectProvider = exports.getModelInfo = exports.PROVIDER_DEFAULTS = exports.MODEL_REGISTRY = exports.loadMCPTools = exports.loadPlugins = exports.parseToolCalls = exports.MemoryManager = exports.SessionManager = exports.buildRepoMap = exports.ContextManager = exports.ToolRegistry = exports.AnthropicProvider = exports.OpenAIProvider = exports.Agent = void 0;
|
|
17
|
+
exports.sarifToString = exports.exportSarif = exports.RiskScorer = exports.MetricsCollector = exports.listReplayableSessions = exports.compareOutputs = exports.loadSessionForReplay = exports.ReplayProvider = exports.verifyMessages = exports.verifyMessage = exports.signMessage = exports.deriveSessionKey = exports.CapabilityChecker = exports.detectProvider = exports.getModelInfo = exports.PROVIDER_DEFAULTS = exports.MODEL_REGISTRY = exports.loadMCPTools = exports.loadPlugins = exports.parseToolCalls = exports.MemoryManager = exports.SessionManager = exports.buildRepoMap = exports.ContextManager = exports.ToolRegistry = exports.AnthropicProvider = exports.OpenAIProvider = exports.Agent = void 0;
|
|
18
18
|
var agent_1 = require("./agent");
|
|
19
19
|
Object.defineProperty(exports, "Agent", { enumerable: true, get: function () { return agent_1.Agent; } });
|
|
20
20
|
var openai_1 = require("./providers/openai");
|
|
@@ -42,5 +42,24 @@ Object.defineProperty(exports, "MODEL_REGISTRY", { enumerable: true, get: functi
|
|
|
42
42
|
Object.defineProperty(exports, "PROVIDER_DEFAULTS", { enumerable: true, get: function () { return registry_1.PROVIDER_DEFAULTS; } });
|
|
43
43
|
Object.defineProperty(exports, "getModelInfo", { enumerable: true, get: function () { return registry_1.getModelInfo; } });
|
|
44
44
|
Object.defineProperty(exports, "detectProvider", { enumerable: true, get: function () { return registry_1.detectProvider; } });
|
|
45
|
+
var capabilities_1 = require("./capabilities");
|
|
46
|
+
Object.defineProperty(exports, "CapabilityChecker", { enumerable: true, get: function () { return capabilities_1.CapabilityChecker; } });
|
|
47
|
+
var integrity_1 = require("./integrity");
|
|
48
|
+
Object.defineProperty(exports, "deriveSessionKey", { enumerable: true, get: function () { return integrity_1.deriveSessionKey; } });
|
|
49
|
+
Object.defineProperty(exports, "signMessage", { enumerable: true, get: function () { return integrity_1.signMessage; } });
|
|
50
|
+
Object.defineProperty(exports, "verifyMessage", { enumerable: true, get: function () { return integrity_1.verifyMessage; } });
|
|
51
|
+
Object.defineProperty(exports, "verifyMessages", { enumerable: true, get: function () { return integrity_1.verifyMessages; } });
|
|
52
|
+
var replay_1 = require("./replay");
|
|
53
|
+
Object.defineProperty(exports, "ReplayProvider", { enumerable: true, get: function () { return replay_1.ReplayProvider; } });
|
|
54
|
+
Object.defineProperty(exports, "loadSessionForReplay", { enumerable: true, get: function () { return replay_1.loadSessionForReplay; } });
|
|
55
|
+
Object.defineProperty(exports, "compareOutputs", { enumerable: true, get: function () { return replay_1.compareOutputs; } });
|
|
56
|
+
Object.defineProperty(exports, "listReplayableSessions", { enumerable: true, get: function () { return replay_1.listReplayableSessions; } });
|
|
57
|
+
var metrics_1 = require("./metrics");
|
|
58
|
+
Object.defineProperty(exports, "MetricsCollector", { enumerable: true, get: function () { return metrics_1.MetricsCollector; } });
|
|
59
|
+
var risk_1 = require("./risk");
|
|
60
|
+
Object.defineProperty(exports, "RiskScorer", { enumerable: true, get: function () { return risk_1.RiskScorer; } });
|
|
61
|
+
var sarif_1 = require("./sarif");
|
|
62
|
+
Object.defineProperty(exports, "exportSarif", { enumerable: true, get: function () { return sarif_1.exportSarif; } });
|
|
63
|
+
Object.defineProperty(exports, "sarifToString", { enumerable: true, get: function () { return sarif_1.sarifToString; } });
|
|
45
64
|
__exportStar(require("./types"), exports);
|
|
46
65
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session History Integrity for CodeBot v1.8.0
|
|
3
|
+
*
|
|
4
|
+
* HMAC-SHA256 signing/verification for session messages.
|
|
5
|
+
* Key: SHA-256(sessionId + machineId) where machineId = hostname:username.
|
|
6
|
+
*
|
|
7
|
+
* This is tamper-DETECTION, not tamper-prevention. The key is deterministic
|
|
8
|
+
* per session+machine so verification works without storing keys separately.
|
|
9
|
+
*
|
|
10
|
+
* Uses Node's built-in crypto module — zero runtime dependencies.
|
|
11
|
+
*/
|
|
12
|
+
/** Derive a per-session HMAC key. Deterministic for same session+machine. */
|
|
13
|
+
export declare function deriveSessionKey(sessionId: string): Buffer;
|
|
14
|
+
/**
|
|
15
|
+
* Compute HMAC-SHA256 signature for a message object.
|
|
16
|
+
* Excludes the _sig field from the signing input.
|
|
17
|
+
*/
|
|
18
|
+
export declare function signMessage(message: Record<string, unknown>, key: Buffer): string;
|
|
19
|
+
/**
|
|
20
|
+
* Verify a message's HMAC signature.
|
|
21
|
+
* Returns false if no signature present or if it doesn't match.
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyMessage(message: Record<string, unknown>, key: Buffer): boolean;
|
|
24
|
+
export interface IntegrityResult {
|
|
25
|
+
valid: number;
|
|
26
|
+
tampered: number;
|
|
27
|
+
unsigned: number;
|
|
28
|
+
tamperedIndices: number[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Verify an array of signed messages.
|
|
32
|
+
* Unsigned messages (pre-v1.8.0) are counted but not flagged as tampered.
|
|
33
|
+
*/
|
|
34
|
+
export declare function verifyMessages(messages: Array<Record<string, unknown>>, key: Buffer): IntegrityResult;
|
|
35
|
+
//# sourceMappingURL=integrity.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session History Integrity for CodeBot v1.8.0
|
|
4
|
+
*
|
|
5
|
+
* HMAC-SHA256 signing/verification for session messages.
|
|
6
|
+
* Key: SHA-256(sessionId + machineId) where machineId = hostname:username.
|
|
7
|
+
*
|
|
8
|
+
* This is tamper-DETECTION, not tamper-prevention. The key is deterministic
|
|
9
|
+
* per session+machine so verification works without storing keys separately.
|
|
10
|
+
*
|
|
11
|
+
* Uses Node's built-in crypto module — zero runtime dependencies.
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.deriveSessionKey = deriveSessionKey;
|
|
48
|
+
exports.signMessage = signMessage;
|
|
49
|
+
exports.verifyMessage = verifyMessage;
|
|
50
|
+
exports.verifyMessages = verifyMessages;
|
|
51
|
+
const crypto = __importStar(require("crypto"));
|
|
52
|
+
const os = __importStar(require("os"));
|
|
53
|
+
// ── Key Derivation ──
|
|
54
|
+
/** Derive a per-session HMAC key. Deterministic for same session+machine. */
|
|
55
|
+
function deriveSessionKey(sessionId) {
|
|
56
|
+
const machineId = os.hostname() + ':' + getUserName();
|
|
57
|
+
return crypto.createHash('sha256')
|
|
58
|
+
.update(sessionId + machineId)
|
|
59
|
+
.digest();
|
|
60
|
+
}
|
|
61
|
+
/** Get username safely. */
|
|
62
|
+
function getUserName() {
|
|
63
|
+
try {
|
|
64
|
+
return os.userInfo().username;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return 'unknown';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// ── Signing & Verification ──
|
|
71
|
+
/**
|
|
72
|
+
* Compute HMAC-SHA256 signature for a message object.
|
|
73
|
+
* Excludes the _sig field from the signing input.
|
|
74
|
+
*/
|
|
75
|
+
function signMessage(message, key) {
|
|
76
|
+
const payload = canonicalize(message);
|
|
77
|
+
return crypto.createHmac('sha256', key)
|
|
78
|
+
.update(payload)
|
|
79
|
+
.digest('hex');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Verify a message's HMAC signature.
|
|
83
|
+
* Returns false if no signature present or if it doesn't match.
|
|
84
|
+
*/
|
|
85
|
+
function verifyMessage(message, key) {
|
|
86
|
+
const sig = message._sig;
|
|
87
|
+
if (!sig)
|
|
88
|
+
return false;
|
|
89
|
+
const expected = signMessage(message, key);
|
|
90
|
+
try {
|
|
91
|
+
return crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false; // Different lengths or invalid hex
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Canonical JSON representation for signing.
|
|
99
|
+
* Excludes _sig field. Keys are sorted for determinism.
|
|
100
|
+
*/
|
|
101
|
+
function canonicalize(obj) {
|
|
102
|
+
const sorted = {};
|
|
103
|
+
for (const key of Object.keys(obj).sort()) {
|
|
104
|
+
if (key === '_sig')
|
|
105
|
+
continue;
|
|
106
|
+
sorted[key] = obj[key];
|
|
107
|
+
}
|
|
108
|
+
return JSON.stringify(sorted);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Verify an array of signed messages.
|
|
112
|
+
* Unsigned messages (pre-v1.8.0) are counted but not flagged as tampered.
|
|
113
|
+
*/
|
|
114
|
+
function verifyMessages(messages, key) {
|
|
115
|
+
let valid = 0;
|
|
116
|
+
let tampered = 0;
|
|
117
|
+
let unsigned = 0;
|
|
118
|
+
const tamperedIndices = [];
|
|
119
|
+
for (let i = 0; i < messages.length; i++) {
|
|
120
|
+
const msg = messages[i];
|
|
121
|
+
if (!msg._sig) {
|
|
122
|
+
unsigned++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (verifyMessage(msg, key)) {
|
|
126
|
+
valid++;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
tampered++;
|
|
130
|
+
tamperedIndices.push(i);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { valid, tampered, unsigned, tamperedIndices };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=integrity.js.map
|