codebot-ai 1.7.0 → 1.8.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 +10 -0
- package/dist/agent.js +89 -3
- package/dist/audit.d.ts +1 -1
- package/dist/capabilities.d.ts +48 -0
- package/dist/capabilities.js +187 -0
- package/dist/cli.js +85 -1
- package/dist/history.d.ts +7 -3
- package/dist/history.js +55 -8
- package/dist/index.d.ts +6 -0
- package/dist/index.js +13 -1
- package/dist/integrity.d.ts +35 -0
- package/dist/integrity.js +135 -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/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 +1 -0
- package/package.json +1 -1
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.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,17 @@ 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; } });
|
|
45
57
|
__exportStar(require("./types"), exports);
|
|
46
58
|
//# 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
|
package/dist/policy.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Policy files: .codebot/policy.json (project) + ~/.codebot/policy.json (global)
|
|
6
6
|
* Project policy overrides global policy where specified.
|
|
7
7
|
*/
|
|
8
|
+
import { ToolCapabilities } from './capabilities';
|
|
8
9
|
export interface PolicyExecution {
|
|
9
10
|
sandbox?: 'docker' | 'host' | 'auto';
|
|
10
11
|
network?: boolean;
|
|
@@ -24,6 +25,7 @@ export interface PolicyTools {
|
|
|
24
25
|
enabled?: string[];
|
|
25
26
|
disabled?: string[];
|
|
26
27
|
permissions?: PolicyToolPermission;
|
|
28
|
+
capabilities?: Record<string, ToolCapabilities>;
|
|
27
29
|
}
|
|
28
30
|
export interface PolicySecrets {
|
|
29
31
|
block_on_detect?: boolean;
|
|
@@ -108,6 +110,13 @@ export declare class PolicyEnforcer {
|
|
|
108
110
|
getMaxFileSizeBytes(): number;
|
|
109
111
|
/** Get cost limit in USD (0 = no limit). */
|
|
110
112
|
getCostLimitUsd(): number;
|
|
113
|
+
/** Get capability restrictions for a tool. undefined = unrestricted. */
|
|
114
|
+
getToolCapabilities(toolName: string): ToolCapabilities | undefined;
|
|
115
|
+
/** Check a specific capability for a tool. Returns { allowed, reason }. */
|
|
116
|
+
checkCapability(toolName: string, capabilityType: keyof ToolCapabilities, value: string | number): {
|
|
117
|
+
allowed: boolean;
|
|
118
|
+
reason?: string;
|
|
119
|
+
};
|
|
111
120
|
/**
|
|
112
121
|
* Simple glob-like pattern matching:
|
|
113
122
|
* - `*` matches any single path component
|
package/dist/policy.js
CHANGED
|
@@ -46,12 +46,13 @@ exports.generateDefaultPolicyFile = generateDefaultPolicyFile;
|
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
48
|
const os = __importStar(require("os"));
|
|
49
|
+
const capabilities_1 = require("./capabilities");
|
|
49
50
|
// ── Default Policy ──
|
|
50
51
|
exports.DEFAULT_POLICY = {
|
|
51
52
|
version: '1.0',
|
|
52
53
|
execution: {
|
|
53
54
|
sandbox: 'auto',
|
|
54
|
-
network:
|
|
55
|
+
network: false, // safe default: no network in sandbox
|
|
55
56
|
timeout_seconds: 120,
|
|
56
57
|
max_memory_mb: 512,
|
|
57
58
|
},
|
|
@@ -67,12 +68,12 @@ exports.DEFAULT_POLICY = {
|
|
|
67
68
|
permissions: {},
|
|
68
69
|
},
|
|
69
70
|
secrets: {
|
|
70
|
-
block_on_detect:
|
|
71
|
+
block_on_detect: true, // safe default: block writes containing secrets
|
|
71
72
|
scan_on_write: true,
|
|
72
73
|
allowed_patterns: [],
|
|
73
74
|
},
|
|
74
75
|
git: {
|
|
75
|
-
always_branch:
|
|
76
|
+
always_branch: true, // safe default: auto-branch on first write
|
|
76
77
|
branch_prefix: 'codebot/',
|
|
77
78
|
require_tests_before_commit: false,
|
|
78
79
|
never_push_main: true,
|
|
@@ -327,6 +328,19 @@ class PolicyEnforcer {
|
|
|
327
328
|
getCostLimitUsd() {
|
|
328
329
|
return this.policy.limits?.cost_limit_usd || 0;
|
|
329
330
|
}
|
|
331
|
+
// ── Capabilities (v1.8.0) ──
|
|
332
|
+
/** Get capability restrictions for a tool. undefined = unrestricted. */
|
|
333
|
+
getToolCapabilities(toolName) {
|
|
334
|
+
return this.policy.tools?.capabilities?.[toolName];
|
|
335
|
+
}
|
|
336
|
+
/** Check a specific capability for a tool. Returns { allowed, reason }. */
|
|
337
|
+
checkCapability(toolName, capabilityType, value) {
|
|
338
|
+
const caps = this.policy.tools?.capabilities;
|
|
339
|
+
if (!caps)
|
|
340
|
+
return { allowed: true };
|
|
341
|
+
const checker = new capabilities_1.CapabilityChecker(caps, this.projectRoot);
|
|
342
|
+
return checker.checkCapability(toolName, capabilityType, value);
|
|
343
|
+
}
|
|
330
344
|
// ── Helpers ──
|
|
331
345
|
/**
|
|
332
346
|
* Simple glob-like pattern matching:
|
|
@@ -374,7 +388,7 @@ function generateDefaultPolicyFile() {
|
|
|
374
388
|
version: '1.0',
|
|
375
389
|
execution: {
|
|
376
390
|
sandbox: 'auto',
|
|
377
|
-
network:
|
|
391
|
+
network: false,
|
|
378
392
|
timeout_seconds: 120,
|
|
379
393
|
max_memory_mb: 512,
|
|
380
394
|
},
|
|
@@ -392,13 +406,25 @@ function generateDefaultPolicyFile() {
|
|
|
392
406
|
write_file: 'prompt',
|
|
393
407
|
edit_file: 'prompt',
|
|
394
408
|
},
|
|
409
|
+
capabilities: {
|
|
410
|
+
execute: {
|
|
411
|
+
shell_commands: [
|
|
412
|
+
'npm', 'npx', 'node', 'git', 'tsc', 'eslint', 'prettier',
|
|
413
|
+
'jest', 'vitest', 'pytest', 'make', 'cargo', 'go', 'python',
|
|
414
|
+
'python3', 'ruby', 'php', 'java', 'javac', 'mvn', 'gradle',
|
|
415
|
+
'docker', 'ls', 'cat', 'head', 'tail', 'wc', 'sort', 'uniq',
|
|
416
|
+
'find', 'which', 'echo', 'pwd', 'env', 'date',
|
|
417
|
+
],
|
|
418
|
+
max_output_kb: 500,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
395
421
|
},
|
|
396
422
|
secrets: {
|
|
397
|
-
block_on_detect:
|
|
423
|
+
block_on_detect: true,
|
|
398
424
|
scan_on_write: true,
|
|
399
425
|
},
|
|
400
426
|
git: {
|
|
401
|
-
always_branch:
|
|
427
|
+
always_branch: true,
|
|
402
428
|
branch_prefix: 'codebot/',
|
|
403
429
|
require_tests_before_commit: false,
|
|
404
430
|
never_push_main: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LLMProvider, Message, ToolSchema, StreamEvent, ProviderConfig } from '../types';
|
|
2
2
|
export declare class AnthropicProvider implements LLMProvider {
|
|
3
3
|
name: string;
|
|
4
|
+
temperature?: number;
|
|
4
5
|
private config;
|
|
5
6
|
constructor(config: ProviderConfig);
|
|
6
7
|
chat(messages: Message[], tools?: ToolSchema[]): AsyncGenerator<StreamEvent>;
|
|
@@ -4,6 +4,7 @@ exports.AnthropicProvider = void 0;
|
|
|
4
4
|
const retry_1 = require("../retry");
|
|
5
5
|
class AnthropicProvider {
|
|
6
6
|
name;
|
|
7
|
+
temperature;
|
|
7
8
|
config;
|
|
8
9
|
constructor(config) {
|
|
9
10
|
this.config = config;
|
|
@@ -22,6 +23,9 @@ class AnthropicProvider {
|
|
|
22
23
|
max_tokens: 8192,
|
|
23
24
|
stream: true,
|
|
24
25
|
};
|
|
26
|
+
if (this.temperature !== undefined) {
|
|
27
|
+
body.temperature = this.temperature;
|
|
28
|
+
}
|
|
25
29
|
if (systemPrompt) {
|
|
26
30
|
body.system = systemPrompt;
|
|
27
31
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LLMProvider, Message, ToolSchema, StreamEvent, ProviderConfig } from '../types';
|
|
2
2
|
export declare class OpenAIProvider implements LLMProvider {
|
|
3
3
|
name: string;
|
|
4
|
+
temperature?: number;
|
|
4
5
|
private config;
|
|
5
6
|
private supportsTools;
|
|
6
7
|
constructor(config: ProviderConfig);
|
package/dist/providers/openai.js
CHANGED
|
@@ -5,6 +5,7 @@ const registry_1 = require("./registry");
|
|
|
5
5
|
const retry_1 = require("../retry");
|
|
6
6
|
class OpenAIProvider {
|
|
7
7
|
name;
|
|
8
|
+
temperature;
|
|
8
9
|
config;
|
|
9
10
|
supportsTools;
|
|
10
11
|
constructor(config) {
|
|
@@ -25,6 +26,9 @@ class OpenAIProvider {
|
|
|
25
26
|
messages: messages.map(m => this.formatMessage(m)),
|
|
26
27
|
stream: true,
|
|
27
28
|
};
|
|
29
|
+
if (this.temperature !== undefined) {
|
|
30
|
+
body.temperature = this.temperature;
|
|
31
|
+
}
|
|
28
32
|
if (tools?.length && this.supportsTools) {
|
|
29
33
|
body.tools = tools;
|
|
30
34
|
}
|
package/dist/replay.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Replay Engine for CodeBot v1.8.0
|
|
3
|
+
*
|
|
4
|
+
* Replays saved sessions by feeding recorded assistant responses
|
|
5
|
+
* instead of calling the LLM. Tool calls are re-executed and outputs
|
|
6
|
+
* compared against recorded results to detect environment divergences.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* codebot --replay <session-id>
|
|
10
|
+
* codebot --replay (replays latest session)
|
|
11
|
+
*/
|
|
12
|
+
import { Message, LLMProvider, ToolSchema, StreamEvent } from './types';
|
|
13
|
+
/**
|
|
14
|
+
* Mock LLM provider that feeds recorded assistant messages.
|
|
15
|
+
* Used during replay to bypass actual LLM calls.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ReplayProvider implements LLMProvider {
|
|
18
|
+
name: string;
|
|
19
|
+
private assistantMessages;
|
|
20
|
+
private callIndex;
|
|
21
|
+
constructor(assistantMessages: Message[]);
|
|
22
|
+
chat(_messages: Message[], _tools?: ToolSchema[]): AsyncGenerator<StreamEvent>;
|
|
23
|
+
}
|
|
24
|
+
export interface SessionReplayData {
|
|
25
|
+
messages: Message[];
|
|
26
|
+
assistantMessages: Message[];
|
|
27
|
+
userMessages: Message[];
|
|
28
|
+
toolResults: Map<string, string>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Load a session from disk and prepare it for replay.
|
|
32
|
+
* Returns null if session doesn't exist or is empty.
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadSessionForReplay(sessionId: string): SessionReplayData | null;
|
|
35
|
+
export interface ReplayDivergence {
|
|
36
|
+
toolCallId: string;
|
|
37
|
+
toolName: string;
|
|
38
|
+
type: 'output_mismatch';
|
|
39
|
+
diff: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Compare recorded vs actual tool output.
|
|
43
|
+
* Returns null if identical, or a diff description.
|
|
44
|
+
*/
|
|
45
|
+
export declare function compareOutputs(recorded: string, actual: string): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* List sessions available for replay.
|
|
48
|
+
*/
|
|
49
|
+
export declare function listReplayableSessions(limit?: number): Array<{
|
|
50
|
+
id: string;
|
|
51
|
+
preview: string;
|
|
52
|
+
messageCount: number;
|
|
53
|
+
date: string;
|
|
54
|
+
}>;
|
|
55
|
+
//# sourceMappingURL=replay.d.ts.map
|
package/dist/replay.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session Replay Engine for CodeBot v1.8.0
|
|
4
|
+
*
|
|
5
|
+
* Replays saved sessions by feeding recorded assistant responses
|
|
6
|
+
* instead of calling the LLM. Tool calls are re-executed and outputs
|
|
7
|
+
* compared against recorded results to detect environment divergences.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* codebot --replay <session-id>
|
|
11
|
+
* codebot --replay (replays latest session)
|
|
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.ReplayProvider = void 0;
|
|
48
|
+
exports.loadSessionForReplay = loadSessionForReplay;
|
|
49
|
+
exports.compareOutputs = compareOutputs;
|
|
50
|
+
exports.listReplayableSessions = listReplayableSessions;
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const os = __importStar(require("os"));
|
|
54
|
+
// ── Replay Provider ──
|
|
55
|
+
/**
|
|
56
|
+
* Mock LLM provider that feeds recorded assistant messages.
|
|
57
|
+
* Used during replay to bypass actual LLM calls.
|
|
58
|
+
*/
|
|
59
|
+
class ReplayProvider {
|
|
60
|
+
name = 'replay';
|
|
61
|
+
assistantMessages;
|
|
62
|
+
callIndex = 0;
|
|
63
|
+
constructor(assistantMessages) {
|
|
64
|
+
this.assistantMessages = assistantMessages;
|
|
65
|
+
}
|
|
66
|
+
async *chat(_messages, _tools) {
|
|
67
|
+
const msg = this.assistantMessages[this.callIndex++];
|
|
68
|
+
if (!msg) {
|
|
69
|
+
yield { type: 'text', text: '[replay: no more recorded responses]' };
|
|
70
|
+
yield { type: 'done' };
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Emit text content
|
|
74
|
+
if (msg.content) {
|
|
75
|
+
yield { type: 'text', text: msg.content };
|
|
76
|
+
}
|
|
77
|
+
// Emit tool calls
|
|
78
|
+
if (msg.tool_calls) {
|
|
79
|
+
for (const tc of msg.tool_calls) {
|
|
80
|
+
yield {
|
|
81
|
+
type: 'tool_call_end',
|
|
82
|
+
toolCall: tc,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
yield { type: 'done' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.ReplayProvider = ReplayProvider;
|
|
90
|
+
/**
|
|
91
|
+
* Load a session from disk and prepare it for replay.
|
|
92
|
+
* Returns null if session doesn't exist or is empty.
|
|
93
|
+
*/
|
|
94
|
+
function loadSessionForReplay(sessionId) {
|
|
95
|
+
const sessionsDir = path.join(os.homedir(), '.codebot', 'sessions');
|
|
96
|
+
const filePath = path.join(sessionsDir, `${sessionId}.jsonl`);
|
|
97
|
+
if (!fs.existsSync(filePath))
|
|
98
|
+
return null;
|
|
99
|
+
let content;
|
|
100
|
+
try {
|
|
101
|
+
content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (!content)
|
|
107
|
+
return null;
|
|
108
|
+
const messages = [];
|
|
109
|
+
for (const line of content.split('\n')) {
|
|
110
|
+
try {
|
|
111
|
+
const obj = JSON.parse(line);
|
|
112
|
+
delete obj._ts;
|
|
113
|
+
delete obj._model;
|
|
114
|
+
delete obj._sig;
|
|
115
|
+
messages.push(obj);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (messages.length === 0)
|
|
122
|
+
return null;
|
|
123
|
+
const assistantMessages = messages.filter(m => m.role === 'assistant');
|
|
124
|
+
const userMessages = messages.filter(m => m.role === 'user');
|
|
125
|
+
const toolResults = new Map();
|
|
126
|
+
for (const msg of messages) {
|
|
127
|
+
if (msg.role === 'tool' && msg.tool_call_id) {
|
|
128
|
+
toolResults.set(msg.tool_call_id, msg.content);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { messages, assistantMessages, userMessages, toolResults };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Compare recorded vs actual tool output.
|
|
135
|
+
* Returns null if identical, or a diff description.
|
|
136
|
+
*/
|
|
137
|
+
function compareOutputs(recorded, actual) {
|
|
138
|
+
if (recorded === actual)
|
|
139
|
+
return null;
|
|
140
|
+
// Normalize whitespace for soft comparison
|
|
141
|
+
const normRecorded = recorded.trim().replace(/\s+/g, ' ');
|
|
142
|
+
const normActual = actual.trim().replace(/\s+/g, ' ');
|
|
143
|
+
if (normRecorded === normActual)
|
|
144
|
+
return null;
|
|
145
|
+
const maxShow = 200;
|
|
146
|
+
const expectedSnippet = normRecorded.length > maxShow
|
|
147
|
+
? normRecorded.substring(0, maxShow) + '...'
|
|
148
|
+
: normRecorded;
|
|
149
|
+
const actualSnippet = normActual.length > maxShow
|
|
150
|
+
? normActual.substring(0, maxShow) + '...'
|
|
151
|
+
: normActual;
|
|
152
|
+
return `Expected: ${expectedSnippet}\nActual: ${actualSnippet}`;
|
|
153
|
+
}
|
|
154
|
+
// ── Session Listing ──
|
|
155
|
+
/**
|
|
156
|
+
* List sessions available for replay.
|
|
157
|
+
*/
|
|
158
|
+
function listReplayableSessions(limit = 10) {
|
|
159
|
+
const sessionsDir = path.join(os.homedir(), '.codebot', 'sessions');
|
|
160
|
+
if (!fs.existsSync(sessionsDir))
|
|
161
|
+
return [];
|
|
162
|
+
const files = fs.readdirSync(sessionsDir)
|
|
163
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
164
|
+
.map(f => {
|
|
165
|
+
const stat = fs.statSync(path.join(sessionsDir, f));
|
|
166
|
+
return { name: f, mtime: stat.mtime };
|
|
167
|
+
})
|
|
168
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
|
|
169
|
+
.slice(0, limit);
|
|
170
|
+
return files.map(f => {
|
|
171
|
+
const id = f.name.replace('.jsonl', '');
|
|
172
|
+
const fullPath = path.join(sessionsDir, f.name);
|
|
173
|
+
try {
|
|
174
|
+
const content = fs.readFileSync(fullPath, 'utf-8').trim();
|
|
175
|
+
const lines = content ? content.split('\n') : [];
|
|
176
|
+
let preview = '';
|
|
177
|
+
for (const line of lines) {
|
|
178
|
+
try {
|
|
179
|
+
const msg = JSON.parse(line);
|
|
180
|
+
if (msg.role === 'user') {
|
|
181
|
+
preview = msg.content.substring(0, 80);
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { id, preview, messageCount: lines.length, date: f.mtime.toISOString() };
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return { id, preview: '', messageCount: 0, date: f.mtime.toISOString() };
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=replay.js.map
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Tool } from '../types';
|
|
2
|
+
import { PolicyEnforcer } from '../policy';
|
|
2
3
|
export declare class BatchEditTool implements Tool {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
permission: Tool['permission'];
|
|
7
|
+
private policyEnforcer?;
|
|
8
|
+
constructor(policyEnforcer?: PolicyEnforcer);
|
|
6
9
|
parameters: {
|
|
7
10
|
type: string;
|
|
8
11
|
properties: {
|
package/dist/tools/batch-edit.js
CHANGED
|
@@ -42,6 +42,10 @@ class BatchEditTool {
|
|
|
42
42
|
name = 'batch_edit';
|
|
43
43
|
description = 'Apply multiple find-and-replace edits across one or more files atomically. All edits are validated before any are applied. Useful for renaming, refactoring, and coordinated multi-file changes.';
|
|
44
44
|
permission = 'prompt';
|
|
45
|
+
policyEnforcer;
|
|
46
|
+
constructor(policyEnforcer) {
|
|
47
|
+
this.policyEnforcer = policyEnforcer;
|
|
48
|
+
}
|
|
45
49
|
parameters = {
|
|
46
50
|
type: 'object',
|
|
47
51
|
properties: {
|
|
@@ -85,6 +89,14 @@ class BatchEditTool {
|
|
|
85
89
|
errors.push(`${safety.reason}`);
|
|
86
90
|
continue;
|
|
87
91
|
}
|
|
92
|
+
// Policy: filesystem restrictions
|
|
93
|
+
if (this.policyEnforcer) {
|
|
94
|
+
const policyCheck = this.policyEnforcer.isPathWritable(filePath);
|
|
95
|
+
if (!policyCheck.allowed) {
|
|
96
|
+
errors.push(`Blocked by policy — ${policyCheck.reason}`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
88
100
|
if (!byFile.has(filePath))
|
|
89
101
|
byFile.set(filePath, []);
|
|
90
102
|
byFile.get(filePath).push(edit);
|
package/dist/tools/edit.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Tool } from '../types';
|
|
2
|
+
import { PolicyEnforcer } from '../policy';
|
|
2
3
|
export declare class EditFileTool implements Tool {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
permission: Tool['permission'];
|
|
7
|
+
private policyEnforcer?;
|
|
8
|
+
constructor(policyEnforcer?: PolicyEnforcer);
|
|
6
9
|
parameters: {
|
|
7
10
|
type: string;
|
|
8
11
|
properties: {
|