llm-cli-gateway 1.0.0 → 1.1.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/CHANGELOG.md +21 -0
- package/README.md +31 -1
- package/dist/approval-manager.js +7 -4
- package/dist/async-job-manager.js +18 -11
- package/dist/claude-mcp-config.js +7 -4
- package/dist/config.js +15 -9
- package/dist/db.js +4 -4
- package/dist/executor.js +20 -13
- package/dist/flight-recorder.d.ts +48 -0
- package/dist/flight-recorder.js +220 -0
- package/dist/health.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +812 -259
- package/dist/logger.js +1 -1
- package/dist/metrics.js +9 -12
- package/dist/migrate-sessions.js +2 -2
- package/dist/model-registry.js +12 -14
- package/dist/optimizer.js +9 -9
- package/dist/process-monitor.js +24 -8
- package/dist/request-helpers.d.ts +7 -0
- package/dist/request-helpers.js +24 -2
- package/dist/resources.js +32 -32
- package/dist/retry.js +6 -4
- package/dist/review-integrity.d.ts +6 -38
- package/dist/review-integrity.js +41 -275
- package/dist/session-manager-pg.js +6 -4
- package/dist/session-manager.js +7 -4
- package/dist/stream-json-parser.js +8 -6
- package/package.json +7 -3
package/dist/logger.js
CHANGED
package/dist/metrics.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { CLI_TYPES } from "./session-manager.js";
|
|
2
|
-
const createEmptyMetrics = () => Object.fromEntries(CLI_TYPES.map(cli => [
|
|
2
|
+
const createEmptyMetrics = () => Object.fromEntries(CLI_TYPES.map(cli => [
|
|
3
|
+
cli,
|
|
4
|
+
{ requestCount: 0, successCount: 0, failureCount: 0, totalResponseTimeMs: 0 },
|
|
5
|
+
]));
|
|
3
6
|
export class PerformanceMetrics {
|
|
4
7
|
metrics = createEmptyMetrics();
|
|
5
8
|
recordRequest(cli, durationMs, success) {
|
|
@@ -21,22 +24,16 @@ export class PerformanceMetrics {
|
|
|
21
24
|
let totalFailures = 0;
|
|
22
25
|
for (const cli of CLI_TYPES) {
|
|
23
26
|
const metrics = this.metrics[cli];
|
|
24
|
-
const averageResponseTimeMs = metrics.requestCount > 0
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const successRate = metrics.requestCount > 0
|
|
28
|
-
? metrics.successCount / metrics.requestCount
|
|
29
|
-
: 0;
|
|
30
|
-
const failureRate = metrics.requestCount > 0
|
|
31
|
-
? metrics.failureCount / metrics.requestCount
|
|
32
|
-
: 0;
|
|
27
|
+
const averageResponseTimeMs = metrics.requestCount > 0 ? metrics.totalResponseTimeMs / metrics.requestCount : 0;
|
|
28
|
+
const successRate = metrics.requestCount > 0 ? metrics.successCount / metrics.requestCount : 0;
|
|
29
|
+
const failureRate = metrics.requestCount > 0 ? metrics.failureCount / metrics.requestCount : 0;
|
|
33
30
|
byTool[cli] = {
|
|
34
31
|
requestCount: metrics.requestCount,
|
|
35
32
|
successCount: metrics.successCount,
|
|
36
33
|
failureCount: metrics.failureCount,
|
|
37
34
|
averageResponseTimeMs,
|
|
38
35
|
successRate,
|
|
39
|
-
failureRate
|
|
36
|
+
failureRate,
|
|
40
37
|
};
|
|
41
38
|
totalRequests += metrics.requestCount;
|
|
42
39
|
totalSuccesses += metrics.successCount;
|
|
@@ -51,7 +48,7 @@ export class PerformanceMetrics {
|
|
|
51
48
|
overallSuccessRate,
|
|
52
49
|
overallFailureRate,
|
|
53
50
|
byTool,
|
|
54
|
-
generatedAt: new Date().toISOString()
|
|
51
|
+
generatedAt: new Date().toISOString(),
|
|
55
52
|
};
|
|
56
53
|
}
|
|
57
54
|
}
|
package/dist/migrate-sessions.js
CHANGED
|
@@ -9,7 +9,7 @@ import { createDatabaseConnection } from "./db.js";
|
|
|
9
9
|
const logger = {
|
|
10
10
|
info: (message, meta) => console.error(`[INFO] ${message}`, meta || ""),
|
|
11
11
|
error: (message, meta) => console.error(`[ERROR] ${message}`, meta || ""),
|
|
12
|
-
debug: (message, meta) => console.error(`[DEBUG] ${message}`, meta || "")
|
|
12
|
+
debug: (message, meta) => console.error(`[DEBUG] ${message}`, meta || ""),
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
15
|
* Migrate sessions from file-based storage to PostgreSQL
|
|
@@ -18,7 +18,7 @@ export async function migrateFromFile(filePath, pgManager) {
|
|
|
18
18
|
const result = {
|
|
19
19
|
migrated: 0,
|
|
20
20
|
failed: 0,
|
|
21
|
-
errors: []
|
|
21
|
+
errors: [],
|
|
22
22
|
};
|
|
23
23
|
// Read file-based sessions
|
|
24
24
|
let fileData;
|
package/dist/model-registry.js
CHANGED
|
@@ -7,29 +7,26 @@ const FALLBACK_INFO = {
|
|
|
7
7
|
models: {
|
|
8
8
|
opus: "Most capable model. Best for: complex reasoning, nuanced analysis, difficult problems, research",
|
|
9
9
|
sonnet: "Balanced performance. Best for: everyday coding, code review, general tasks (default)",
|
|
10
|
-
haiku: "Fastest model. Best for: simple queries, quick answers, high-volume tasks, cost-sensitive use"
|
|
10
|
+
haiku: "Fastest model. Best for: simple queries, quick answers, high-volume tasks, cost-sensitive use",
|
|
11
11
|
},
|
|
12
12
|
defaultModel: "sonnet",
|
|
13
|
-
modelOrder: ["opus", "sonnet", "haiku"]
|
|
13
|
+
modelOrder: ["opus", "sonnet", "haiku"],
|
|
14
14
|
},
|
|
15
15
|
codex: {
|
|
16
16
|
description: "OpenAI's Codex CLI - best for code execution in sandboxed environments",
|
|
17
17
|
models: {
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"gpt-
|
|
21
|
-
"gpt-5-pro": "Highest-capability GPT-5 model. Best for: deep reasoning and difficult professional workflows"
|
|
18
|
+
o3: "Most capable reasoning model. Best for: complex multi-step problems, math, science",
|
|
19
|
+
"o4-mini": "Fast reasoning model. Best for: coding tasks, quick iterations",
|
|
20
|
+
"gpt-4.1": "Latest GPT-4 variant. Best for: general coding, instruction following",
|
|
22
21
|
},
|
|
23
|
-
defaultModel: "gpt-5.4",
|
|
24
|
-
modelOrder: ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2", "gpt-5-pro"]
|
|
25
22
|
},
|
|
26
23
|
gemini: {
|
|
27
24
|
description: "Google's Gemini CLI - best for multimodal tasks and Google ecosystem integration",
|
|
28
25
|
models: {
|
|
29
26
|
"gemini-2.5-pro": "Most capable model. Best for: complex reasoning, long context, multimodal",
|
|
30
|
-
"gemini-2.5-flash": "Fast model. Best for: quick responses, high throughput, cost-sensitive use"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
27
|
+
"gemini-2.5-flash": "Fast model. Best for: quick responses, high throughput, cost-sensitive use",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
33
30
|
};
|
|
34
31
|
const MODEL_CACHE_TTL_MS = 2 * 60 * 1000;
|
|
35
32
|
let cachedInfo = null;
|
|
@@ -66,7 +63,7 @@ function buildCliInfo() {
|
|
|
66
63
|
const info = {
|
|
67
64
|
claude: cloneInfo(FALLBACK_INFO.claude),
|
|
68
65
|
codex: cloneInfo(FALLBACK_INFO.codex),
|
|
69
|
-
gemini: cloneInfo(FALLBACK_INFO.gemini)
|
|
66
|
+
gemini: cloneInfo(FALLBACK_INFO.gemini),
|
|
70
67
|
};
|
|
71
68
|
applyClaudeOverrides(info.claude);
|
|
72
69
|
applyCodexOverrides(info.codex);
|
|
@@ -78,7 +75,7 @@ function cloneInfo(source) {
|
|
|
78
75
|
description: source.description,
|
|
79
76
|
models: { ...source.models },
|
|
80
77
|
defaultModel: source.defaultModel,
|
|
81
|
-
modelOrder: source.modelOrder ? [...source.modelOrder] : undefined
|
|
78
|
+
modelOrder: source.modelOrder ? [...source.modelOrder] : undefined,
|
|
82
79
|
};
|
|
83
80
|
}
|
|
84
81
|
function applyClaudeOverrides(info) {
|
|
@@ -273,7 +270,8 @@ function collectGeminiModels() {
|
|
|
273
270
|
.map(([name]) => name);
|
|
274
271
|
const describedModels = {};
|
|
275
272
|
order.forEach(model => {
|
|
276
|
-
describedModels[model] =
|
|
273
|
+
describedModels[model] =
|
|
274
|
+
`Observed in local Gemini sessions (last seen ${formatDate(models[model].lastSeen)})`;
|
|
277
275
|
});
|
|
278
276
|
return { models: describedModels, order };
|
|
279
277
|
}
|
package/dist/optimizer.js
CHANGED
|
@@ -5,18 +5,18 @@ const COURTESY_PATTERNS = [
|
|
|
5
5
|
/\bI would like\b\s*/gi,
|
|
6
6
|
/\bI need you to\b\s*/gi,
|
|
7
7
|
/\bI just implemented\b\s*/gi,
|
|
8
|
-
/\bPlease do the following:?\s*/gi
|
|
8
|
+
/\bPlease do the following:?\s*/gi,
|
|
9
9
|
];
|
|
10
10
|
const ADJECTIVE_PATTERNS = [
|
|
11
11
|
/\bcomprehensive\b/gi,
|
|
12
12
|
/\bdetailed\b/gi,
|
|
13
13
|
/\bthorough\b/gi,
|
|
14
14
|
/\boverall\b/gi,
|
|
15
|
-
/\bcritical\b/gi
|
|
15
|
+
/\bcritical\b/gi,
|
|
16
16
|
];
|
|
17
17
|
const TASK_PREFIXES = [
|
|
18
18
|
/^(First|Then|After that|Finally),?\s*/i,
|
|
19
|
-
/^(First|Then|After that|Finally),?\s*you should\s*/i
|
|
19
|
+
/^(First|Then|After that|Finally),?\s*you should\s*/i,
|
|
20
20
|
];
|
|
21
21
|
export function estimateTokens(text) {
|
|
22
22
|
const trimmed = text.trim();
|
|
@@ -49,15 +49,15 @@ function optimizeText(text, mode) {
|
|
|
49
49
|
function optimizeSegment(segment, mode) {
|
|
50
50
|
const inlineParts = segment.split(/(`[^`]*`)/g);
|
|
51
51
|
return inlineParts
|
|
52
|
-
.map(
|
|
52
|
+
.map(part => (part.startsWith("`") ? part : optimizePlain(part, mode)))
|
|
53
53
|
.join("");
|
|
54
54
|
}
|
|
55
55
|
function optimizePlain(text, mode) {
|
|
56
56
|
let output = text;
|
|
57
|
-
COURTESY_PATTERNS.forEach(
|
|
57
|
+
COURTESY_PATTERNS.forEach(pattern => {
|
|
58
58
|
output = output.replace(pattern, "");
|
|
59
59
|
});
|
|
60
|
-
ADJECTIVE_PATTERNS.forEach(
|
|
60
|
+
ADJECTIVE_PATTERNS.forEach(pattern => {
|
|
61
61
|
output = output.replace(pattern, "");
|
|
62
62
|
});
|
|
63
63
|
output = output.replace(/\bfound in the [^:.\n]+/gi, "");
|
|
@@ -147,7 +147,7 @@ function compressTaskLists(text) {
|
|
|
147
147
|
}
|
|
148
148
|
function cleanTaskItem(item) {
|
|
149
149
|
let cleaned = item.trim();
|
|
150
|
-
TASK_PREFIXES.forEach(
|
|
150
|
+
TASK_PREFIXES.forEach(pattern => {
|
|
151
151
|
cleaned = cleaned.replace(pattern, "");
|
|
152
152
|
});
|
|
153
153
|
cleaned = cleaned.replace(/^you should\s*/i, "");
|
|
@@ -157,7 +157,7 @@ function cleanTaskItem(item) {
|
|
|
157
157
|
}
|
|
158
158
|
function applyArrowNotation(text) {
|
|
159
159
|
const lines = text.split("\n");
|
|
160
|
-
const output = lines.map(
|
|
160
|
+
const output = lines.map(line => {
|
|
161
161
|
let updated = line;
|
|
162
162
|
updated = updated.replace(/\bChange\s+(?:the\s+)?([A-Za-z][\w-]*)\s+to\s+(?:a\s+|an\s+)?([A-Za-z][\w-]*)([.!?]|$)/gi, (_m, from, to, end) => {
|
|
163
163
|
return `${from.trim()} → ${to.trim()}${end || ""}`;
|
|
@@ -172,7 +172,7 @@ function applyArrowNotation(text) {
|
|
|
172
172
|
}
|
|
173
173
|
function applySlashNotation(text) {
|
|
174
174
|
const lines = text.split("\n");
|
|
175
|
-
const output = lines.map(
|
|
175
|
+
const output = lines.map(line => {
|
|
176
176
|
const trimmed = line.trim();
|
|
177
177
|
if (trimmed.length < 50 && /^[A-Za-z0-9][A-Za-z0-9\s/&-]+$/.test(trimmed)) {
|
|
178
178
|
return line.replace(/\s+and\s+/, "/");
|
package/dist/process-monitor.js
CHANGED
|
@@ -68,7 +68,14 @@ export class ProcessMonitor {
|
|
|
68
68
|
}
|
|
69
69
|
catch (err) {
|
|
70
70
|
if (err.code === "ESRCH") {
|
|
71
|
-
return {
|
|
71
|
+
return {
|
|
72
|
+
pid,
|
|
73
|
+
alive: false,
|
|
74
|
+
state: null,
|
|
75
|
+
cpuPercent: null,
|
|
76
|
+
memoryRssKb: null,
|
|
77
|
+
sampledAt: now,
|
|
78
|
+
};
|
|
72
79
|
}
|
|
73
80
|
// EPERM = process exists but we can't signal it
|
|
74
81
|
if (err.code === "EPERM") {
|
|
@@ -87,7 +94,7 @@ export class ProcessMonitor {
|
|
|
87
94
|
const totalJiffies = getTotalCpuJiffies();
|
|
88
95
|
const prev = this.prevSamples.get(pid);
|
|
89
96
|
if (prev && totalJiffies !== null) {
|
|
90
|
-
const processJiffiesDelta =
|
|
97
|
+
const processJiffiesDelta = parsed.utime + parsed.stime - (prev.utime + prev.stime);
|
|
91
98
|
const totalJiffiesDelta = totalJiffies - prev.totalJiffies;
|
|
92
99
|
if (totalJiffiesDelta > 0) {
|
|
93
100
|
cpuPercent = (processJiffiesDelta / totalJiffiesDelta) * 100;
|
|
@@ -96,8 +103,10 @@ export class ProcessMonitor {
|
|
|
96
103
|
// Store for next delta
|
|
97
104
|
if (totalJiffies !== null) {
|
|
98
105
|
this.prevSamples.set(pid, {
|
|
99
|
-
utime: parsed.utime,
|
|
100
|
-
|
|
106
|
+
utime: parsed.utime,
|
|
107
|
+
stime: parsed.stime,
|
|
108
|
+
totalJiffies,
|
|
109
|
+
timestamp: Date.now(),
|
|
101
110
|
});
|
|
102
111
|
}
|
|
103
112
|
}
|
|
@@ -121,17 +130,24 @@ export class ProcessMonitor {
|
|
|
121
130
|
const runningForMs = Date.now() - new Date(job.startedAt).getTime();
|
|
122
131
|
if (!job.pid) {
|
|
123
132
|
return {
|
|
124
|
-
jobId: job.jobId,
|
|
125
|
-
|
|
133
|
+
jobId: job.jobId,
|
|
134
|
+
cli: job.cli,
|
|
135
|
+
status: job.status,
|
|
136
|
+
processHealth: null,
|
|
137
|
+
isDead: false,
|
|
138
|
+
isZombie: false,
|
|
139
|
+
runningForMs,
|
|
126
140
|
};
|
|
127
141
|
}
|
|
128
142
|
const health = this.sampleProcess(job.pid);
|
|
129
143
|
return {
|
|
130
|
-
jobId: job.jobId,
|
|
144
|
+
jobId: job.jobId,
|
|
145
|
+
cli: job.cli,
|
|
146
|
+
status: job.status,
|
|
131
147
|
processHealth: health,
|
|
132
148
|
isDead: job.status === "running" && !health.alive,
|
|
133
149
|
isZombie: job.status === "running" && health.state === "Z",
|
|
134
|
-
runningForMs
|
|
150
|
+
runningForMs,
|
|
135
151
|
};
|
|
136
152
|
});
|
|
137
153
|
}
|
|
@@ -18,6 +18,13 @@ export declare function validateSessionId(sessionId: string): void;
|
|
|
18
18
|
* Pure function: determine --resume args and session provenance from request flags.
|
|
19
19
|
* Does NOT perform any session I/O — callers handle create/update separately.
|
|
20
20
|
*/
|
|
21
|
+
/**
|
|
22
|
+
* Reject CLI arg values that start with "-" to prevent argument injection.
|
|
23
|
+
* spawn() doesn't invoke a shell so there's no shell injection, but a value
|
|
24
|
+
* like "--dangerously-skip-permissions" passed as a tool name would be
|
|
25
|
+
* interpreted as a flag by the child CLI.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sanitizeCliArgValues(values: string[], fieldName: string): string[];
|
|
21
28
|
export declare function resolveSessionResumeArgs(opts: {
|
|
22
29
|
sessionId?: string;
|
|
23
30
|
resumeLatest?: boolean;
|
package/dist/request-helpers.js
CHANGED
|
@@ -17,16 +17,38 @@ export function validateSessionId(sessionId) {
|
|
|
17
17
|
* Pure function: determine --resume args and session provenance from request flags.
|
|
18
18
|
* Does NOT perform any session I/O — callers handle create/update separately.
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Reject CLI arg values that start with "-" to prevent argument injection.
|
|
22
|
+
* spawn() doesn't invoke a shell so there's no shell injection, but a value
|
|
23
|
+
* like "--dangerously-skip-permissions" passed as a tool name would be
|
|
24
|
+
* interpreted as a flag by the child CLI.
|
|
25
|
+
*/
|
|
26
|
+
export function sanitizeCliArgValues(values, fieldName) {
|
|
27
|
+
for (const v of values) {
|
|
28
|
+
if (v.startsWith("-")) {
|
|
29
|
+
throw new Error(`Invalid ${fieldName} value "${v}": values must not start with "-" (argument injection prevention)`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return values;
|
|
33
|
+
}
|
|
20
34
|
export function resolveSessionResumeArgs(opts) {
|
|
21
35
|
if (opts.createNewSession) {
|
|
22
36
|
return { resumeArgs: [], effectiveSessionId: undefined, userProvidedSession: false };
|
|
23
37
|
}
|
|
24
38
|
if (opts.resumeLatest && !opts.sessionId) {
|
|
25
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
resumeArgs: ["--resume", "latest"],
|
|
41
|
+
effectiveSessionId: undefined,
|
|
42
|
+
userProvidedSession: false,
|
|
43
|
+
};
|
|
26
44
|
}
|
|
27
45
|
if (opts.sessionId) {
|
|
28
46
|
validateSessionId(opts.sessionId);
|
|
29
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
resumeArgs: ["--resume", opts.sessionId],
|
|
49
|
+
effectiveSessionId: opts.sessionId,
|
|
50
|
+
userProvidedSession: true,
|
|
51
|
+
};
|
|
30
52
|
}
|
|
31
53
|
return { resumeArgs: [], effectiveSessionId: undefined, userProvidedSession: false };
|
|
32
54
|
}
|
package/dist/resources.js
CHANGED
|
@@ -18,8 +18,8 @@ export class ResourceProvider {
|
|
|
18
18
|
annotations: {
|
|
19
19
|
audience: ["user", "assistant"],
|
|
20
20
|
priority: 0.7,
|
|
21
|
-
lastModified: new Date().toISOString()
|
|
22
|
-
}
|
|
21
|
+
lastModified: new Date().toISOString(),
|
|
22
|
+
},
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
uri: "sessions://claude",
|
|
@@ -29,8 +29,8 @@ export class ResourceProvider {
|
|
|
29
29
|
mimeType: "application/json",
|
|
30
30
|
annotations: {
|
|
31
31
|
audience: ["user", "assistant"],
|
|
32
|
-
priority: 0.6
|
|
33
|
-
}
|
|
32
|
+
priority: 0.6,
|
|
33
|
+
},
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
uri: "sessions://codex",
|
|
@@ -40,8 +40,8 @@ export class ResourceProvider {
|
|
|
40
40
|
mimeType: "application/json",
|
|
41
41
|
annotations: {
|
|
42
42
|
audience: ["user", "assistant"],
|
|
43
|
-
priority: 0.6
|
|
44
|
-
}
|
|
43
|
+
priority: 0.6,
|
|
44
|
+
},
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
uri: "sessions://gemini",
|
|
@@ -51,8 +51,8 @@ export class ResourceProvider {
|
|
|
51
51
|
mimeType: "application/json",
|
|
52
52
|
annotations: {
|
|
53
53
|
audience: ["user", "assistant"],
|
|
54
|
-
priority: 0.6
|
|
55
|
-
}
|
|
54
|
+
priority: 0.6,
|
|
55
|
+
},
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
uri: "models://claude",
|
|
@@ -62,8 +62,8 @@ export class ResourceProvider {
|
|
|
62
62
|
mimeType: "application/json",
|
|
63
63
|
annotations: {
|
|
64
64
|
audience: ["user", "assistant"],
|
|
65
|
-
priority: 0.8
|
|
66
|
-
}
|
|
65
|
+
priority: 0.8,
|
|
66
|
+
},
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
uri: "models://codex",
|
|
@@ -73,8 +73,8 @@ export class ResourceProvider {
|
|
|
73
73
|
mimeType: "application/json",
|
|
74
74
|
annotations: {
|
|
75
75
|
audience: ["user", "assistant"],
|
|
76
|
-
priority: 0.8
|
|
77
|
-
}
|
|
76
|
+
priority: 0.8,
|
|
77
|
+
},
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
uri: "models://gemini",
|
|
@@ -84,8 +84,8 @@ export class ResourceProvider {
|
|
|
84
84
|
mimeType: "application/json",
|
|
85
85
|
annotations: {
|
|
86
86
|
audience: ["user", "assistant"],
|
|
87
|
-
priority: 0.8
|
|
88
|
-
}
|
|
87
|
+
priority: 0.8,
|
|
88
|
+
},
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
uri: "metrics://performance",
|
|
@@ -95,9 +95,9 @@ export class ResourceProvider {
|
|
|
95
95
|
mimeType: "application/json",
|
|
96
96
|
annotations: {
|
|
97
97
|
audience: ["user", "assistant"],
|
|
98
|
-
priority: 0.9
|
|
99
|
-
}
|
|
100
|
-
}
|
|
98
|
+
priority: 0.9,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
101
|
];
|
|
102
102
|
}
|
|
103
103
|
// Read a specific resource by URI
|
|
@@ -110,19 +110,19 @@ export class ResourceProvider {
|
|
|
110
110
|
mimeType: "application/json",
|
|
111
111
|
text: JSON.stringify({
|
|
112
112
|
total: sessions.length,
|
|
113
|
-
sessions: sessions.map(
|
|
113
|
+
sessions: sessions.map(s => ({
|
|
114
114
|
id: s.id,
|
|
115
115
|
cli: s.cli,
|
|
116
116
|
description: s.description,
|
|
117
117
|
createdAt: s.createdAt,
|
|
118
|
-
lastUsedAt: s.lastUsedAt
|
|
118
|
+
lastUsedAt: s.lastUsedAt,
|
|
119
119
|
})),
|
|
120
120
|
activeSessions: {
|
|
121
121
|
claude: (await this.sessionManager.getActiveSession("claude"))?.id || null,
|
|
122
122
|
codex: (await this.sessionManager.getActiveSession("codex"))?.id || null,
|
|
123
|
-
gemini: (await this.sessionManager.getActiveSession("gemini"))?.id || null
|
|
124
|
-
}
|
|
125
|
-
}, null, 2)
|
|
123
|
+
gemini: (await this.sessionManager.getActiveSession("gemini"))?.id || null,
|
|
124
|
+
},
|
|
125
|
+
}, null, 2),
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
if (uri === "sessions://claude") {
|
|
@@ -134,8 +134,8 @@ export class ResourceProvider {
|
|
|
134
134
|
cli: "claude",
|
|
135
135
|
total: sessions.length,
|
|
136
136
|
sessions,
|
|
137
|
-
activeSession: (await this.sessionManager.getActiveSession("claude"))?.id || null
|
|
138
|
-
}, null, 2)
|
|
137
|
+
activeSession: (await this.sessionManager.getActiveSession("claude"))?.id || null,
|
|
138
|
+
}, null, 2),
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
if (uri === "sessions://codex") {
|
|
@@ -147,8 +147,8 @@ export class ResourceProvider {
|
|
|
147
147
|
cli: "codex",
|
|
148
148
|
total: sessions.length,
|
|
149
149
|
sessions,
|
|
150
|
-
activeSession: (await this.sessionManager.getActiveSession("codex"))?.id || null
|
|
151
|
-
}, null, 2)
|
|
150
|
+
activeSession: (await this.sessionManager.getActiveSession("codex"))?.id || null,
|
|
151
|
+
}, null, 2),
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
if (uri === "sessions://gemini") {
|
|
@@ -160,8 +160,8 @@ export class ResourceProvider {
|
|
|
160
160
|
cli: "gemini",
|
|
161
161
|
total: sessions.length,
|
|
162
162
|
sessions,
|
|
163
|
-
activeSession: (await this.sessionManager.getActiveSession("gemini"))?.id || null
|
|
164
|
-
}, null, 2)
|
|
163
|
+
activeSession: (await this.sessionManager.getActiveSession("gemini"))?.id || null,
|
|
164
|
+
}, null, 2),
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
// Model capability resources
|
|
@@ -170,7 +170,7 @@ export class ResourceProvider {
|
|
|
170
170
|
return {
|
|
171
171
|
uri,
|
|
172
172
|
mimeType: "application/json",
|
|
173
|
-
text: JSON.stringify(cliInfo.claude, null, 2)
|
|
173
|
+
text: JSON.stringify(cliInfo.claude, null, 2),
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
176
|
if (uri === "models://codex") {
|
|
@@ -178,7 +178,7 @@ export class ResourceProvider {
|
|
|
178
178
|
return {
|
|
179
179
|
uri,
|
|
180
180
|
mimeType: "application/json",
|
|
181
|
-
text: JSON.stringify(cliInfo.codex, null, 2)
|
|
181
|
+
text: JSON.stringify(cliInfo.codex, null, 2),
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
184
|
if (uri === "models://gemini") {
|
|
@@ -186,14 +186,14 @@ export class ResourceProvider {
|
|
|
186
186
|
return {
|
|
187
187
|
uri,
|
|
188
188
|
mimeType: "application/json",
|
|
189
|
-
text: JSON.stringify(cliInfo.gemini, null, 2)
|
|
189
|
+
text: JSON.stringify(cliInfo.gemini, null, 2),
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
if (uri === "metrics://performance") {
|
|
193
193
|
return {
|
|
194
194
|
uri,
|
|
195
195
|
mimeType: "application/json",
|
|
196
|
-
text: JSON.stringify(this.performanceMetrics.snapshot(), null, 2)
|
|
196
|
+
text: JSON.stringify(this.performanceMetrics.snapshot(), null, 2),
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
return null;
|
package/dist/retry.js
CHANGED
|
@@ -27,15 +27,17 @@ const isDefaultTransient = (error) => {
|
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
29
|
// Shell command-related errors
|
|
30
|
-
if (error.code === 124) {
|
|
30
|
+
if (error.code === 124) {
|
|
31
|
+
// wall-clock timeout (explicit, caller-set) — transient
|
|
31
32
|
return true;
|
|
32
33
|
}
|
|
33
34
|
// Note: exit code 125 = idle timeout (stuck process) — intentionally non-transient
|
|
34
|
-
if (error.code ===
|
|
35
|
+
if (error.code === "ENOENT") {
|
|
36
|
+
// command not found
|
|
35
37
|
return false;
|
|
36
38
|
}
|
|
37
39
|
// Node.js network errors
|
|
38
|
-
const transientErrorCodes = [
|
|
40
|
+
const transientErrorCodes = ["ECONNRESET", "ETIMEDOUT", "ECONNREFUSED", "EPIPE"];
|
|
39
41
|
if (transientErrorCodes.includes(error.code)) {
|
|
40
42
|
return true;
|
|
41
43
|
}
|
|
@@ -142,5 +144,5 @@ export async function withRetry(operation, circuitBreaker, retryOptions, logger)
|
|
|
142
144
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
|
-
throw new Error(
|
|
147
|
+
throw new Error("[Retry] Operation failed after all retry attempts.");
|
|
146
148
|
}
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* Review Integrity Bypass Detection
|
|
3
|
-
*
|
|
4
|
-
* Detects when orchestrating agents neuter the multi-LLM review process by:
|
|
5
|
-
* - Embedding tool-suppression language in review prompts
|
|
6
|
-
* - Inlining full code instead of letting reviewers read files directly
|
|
7
|
-
* - Setting allowedTools:[] to strip tool access from reviewers
|
|
8
|
-
*
|
|
9
|
-
* Two-gate design: violations only emitted when BOTH review context AND
|
|
10
|
-
* a restriction are detected. This avoids false positives on non-review
|
|
11
|
-
* prompts that happen to contain similar language.
|
|
12
|
-
*/
|
|
1
|
+
export type ReviewIntegrityViolationType = "empty_allowed_tools" | "critical_tools_disallowed" | "tool_suppression";
|
|
13
2
|
export interface ReviewIntegrityViolation {
|
|
14
|
-
type:
|
|
3
|
+
type: ReviewIntegrityViolationType;
|
|
15
4
|
score: number;
|
|
16
5
|
detail: string;
|
|
17
6
|
}
|
|
@@ -20,31 +9,10 @@ export interface ReviewIntegrityResult {
|
|
|
20
9
|
violations: ReviewIntegrityViolation[];
|
|
21
10
|
totalScore: number;
|
|
22
11
|
}
|
|
23
|
-
|
|
24
|
-
* Detect whether the prompt is a review/audit context.
|
|
25
|
-
* Uses two-part detection: unambiguous phrases match alone,
|
|
26
|
-
* ambiguous verbs (review, analyze, etc.) require a code anchor.
|
|
27
|
-
* Normalizes Unicode before matching to prevent confusable bypasses.
|
|
28
|
-
*/
|
|
29
|
-
export declare function isReviewContext(prompt: string): boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Detect tool-suppression language in a prompt.
|
|
32
|
-
* Returns the matched patterns for diagnostics.
|
|
33
|
-
*/
|
|
34
|
-
export declare function detectToolSuppression(prompt: string): string[];
|
|
35
|
-
/**
|
|
36
|
-
* Detect inlined code blocks that look like full file dumps.
|
|
37
|
-
* Two detection strategies:
|
|
38
|
-
* 1. Any single code block with 200+ chars is flagged.
|
|
39
|
-
* 2. Fallback: if total chars across ALL code blocks (even small ones)
|
|
40
|
-
* exceeds 1000, flag to catch split-block bypass attempts.
|
|
41
|
-
*/
|
|
42
|
-
export declare function detectInlinedCode(prompt: string): {
|
|
43
|
-
count: number;
|
|
44
|
-
totalChars: number;
|
|
45
|
-
};
|
|
46
|
-
export declare function checkReviewIntegrity(params: {
|
|
12
|
+
export interface ReviewIntegrityInput {
|
|
47
13
|
prompt: string;
|
|
48
14
|
allowedTools?: string[];
|
|
49
15
|
disallowedTools?: string[];
|
|
50
|
-
}
|
|
16
|
+
}
|
|
17
|
+
export declare function isReviewContext(prompt: string): boolean;
|
|
18
|
+
export declare function checkReviewIntegrity(input: ReviewIntegrityInput): ReviewIntegrityResult;
|