llm-cli-gateway 1.0.1 → 1.4.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 +42 -0
- package/README.md +153 -9
- package/dist/approval-manager.d.ts +1 -1
- package/dist/approval-manager.js +7 -4
- package/dist/async-job-manager.d.ts +53 -4
- package/dist/async-job-manager.js +254 -27
- package/dist/claude-mcp-config.js +7 -4
- package/dist/cli-updater.d.ts +38 -0
- package/dist/cli-updater.js +145 -0
- 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 +28 -0
- package/dist/index.js +1456 -278
- package/dist/job-store.d.ts +84 -0
- package/dist/job-store.js +251 -0
- package/dist/logger.js +1 -1
- package/dist/metrics.js +9 -12
- package/dist/migrate-sessions.js +2 -2
- package/dist/model-registry.d.ts +14 -0
- package/dist/model-registry.js +448 -140
- package/dist/optimizer.js +9 -9
- package/dist/process-monitor.js +24 -8
- package/dist/request-helpers.d.ts +48 -0
- package/dist/request-helpers.js +64 -2
- package/dist/resources.js +76 -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 +7 -4
- package/dist/session-manager.d.ts +1 -1
- package/dist/session-manager.js +9 -5
- package/dist/stream-json-parser.js +8 -6
- package/package.json +7 -4
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,8 +18,56 @@ 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;
|
|
24
31
|
createNewSession?: boolean;
|
|
25
32
|
}): SessionResumeResult;
|
|
33
|
+
/**
|
|
34
|
+
* Codex-specific resume planning.
|
|
35
|
+
*
|
|
36
|
+
* Codex CLI ≥ 0.30 exposes session resume as a subcommand (`codex exec resume`),
|
|
37
|
+
* not a flag pair like Claude/Gemini/Grok. So we can't return a simple list of
|
|
38
|
+
* args — we describe the *mode* and let the caller branch when building argv:
|
|
39
|
+
*
|
|
40
|
+
* - "new" → `codex exec [...flags] PROMPT`
|
|
41
|
+
* - "resume-by-id" → `codex exec resume [...resume-safe flags] <SESSION_ID> PROMPT`
|
|
42
|
+
* - "resume-latest" → `codex exec resume --last [...resume-safe flags] PROMPT`
|
|
43
|
+
*
|
|
44
|
+
* `codex exec resume` rejects `--full-auto`; the original session's approval
|
|
45
|
+
* policy is inherited. Callers MUST filter `--full-auto` out of the flag set
|
|
46
|
+
* when mode is one of the resume forms (see `prepareCodexRequest`).
|
|
47
|
+
*
|
|
48
|
+
* `sessionId` MUST be a real Codex session UUID (as recorded under
|
|
49
|
+
* `~/.codex/sessions/`). Gateway-generated `gw-*` IDs are rejected, since
|
|
50
|
+
* they are bookkeeping handles and would 404 against `codex resume`.
|
|
51
|
+
*/
|
|
52
|
+
export type CodexSessionMode = "new" | "resume-by-id" | "resume-latest";
|
|
53
|
+
export interface CodexSessionPlan {
|
|
54
|
+
mode: CodexSessionMode;
|
|
55
|
+
/** Real Codex session UUID. Present only when mode === "resume-by-id". */
|
|
56
|
+
sessionId?: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function resolveCodexSessionArgs(opts: {
|
|
59
|
+
sessionId?: string;
|
|
60
|
+
resumeLatest?: boolean;
|
|
61
|
+
createNewSession?: boolean;
|
|
62
|
+
}): CodexSessionPlan;
|
|
63
|
+
/**
|
|
64
|
+
* Grok-specific resume args. Grok accepts `--resume <id>` to resume a named session,
|
|
65
|
+
* and `--continue` to resume the most recent session for the current working directory.
|
|
66
|
+
* Unlike `resolveSessionResumeArgs`, "resume latest" maps to `--continue` (not `--resume latest`)
|
|
67
|
+
* because Grok would interpret a literal "latest" as a session ID.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveGrokSessionArgs(opts: {
|
|
70
|
+
sessionId?: string;
|
|
71
|
+
resumeLatest?: boolean;
|
|
72
|
+
createNewSession?: boolean;
|
|
73
|
+
}): SessionResumeResult;
|
package/dist/request-helpers.js
CHANGED
|
@@ -17,16 +17,78 @@ 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
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (opts.sessionId) {
|
|
46
|
+
validateSessionId(opts.sessionId);
|
|
47
|
+
return {
|
|
48
|
+
resumeArgs: ["--resume", opts.sessionId],
|
|
49
|
+
effectiveSessionId: opts.sessionId,
|
|
50
|
+
userProvidedSession: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { resumeArgs: [], effectiveSessionId: undefined, userProvidedSession: false };
|
|
54
|
+
}
|
|
55
|
+
export function resolveCodexSessionArgs(opts) {
|
|
56
|
+
if (opts.createNewSession) {
|
|
57
|
+
return { mode: "new" };
|
|
58
|
+
}
|
|
59
|
+
if (opts.sessionId) {
|
|
60
|
+
validateSessionId(opts.sessionId);
|
|
61
|
+
return { mode: "resume-by-id", sessionId: opts.sessionId };
|
|
62
|
+
}
|
|
63
|
+
if (opts.resumeLatest) {
|
|
64
|
+
return { mode: "resume-latest" };
|
|
65
|
+
}
|
|
66
|
+
return { mode: "new" };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Grok-specific resume args. Grok accepts `--resume <id>` to resume a named session,
|
|
70
|
+
* and `--continue` to resume the most recent session for the current working directory.
|
|
71
|
+
* Unlike `resolveSessionResumeArgs`, "resume latest" maps to `--continue` (not `--resume latest`)
|
|
72
|
+
* because Grok would interpret a literal "latest" as a session ID.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveGrokSessionArgs(opts) {
|
|
75
|
+
if (opts.createNewSession) {
|
|
76
|
+
return { resumeArgs: [], effectiveSessionId: undefined, userProvidedSession: false };
|
|
77
|
+
}
|
|
78
|
+
if (opts.resumeLatest && !opts.sessionId) {
|
|
79
|
+
return {
|
|
80
|
+
resumeArgs: ["--continue"],
|
|
81
|
+
effectiveSessionId: undefined,
|
|
82
|
+
userProvidedSession: false,
|
|
83
|
+
};
|
|
26
84
|
}
|
|
27
85
|
if (opts.sessionId) {
|
|
28
86
|
validateSessionId(opts.sessionId);
|
|
29
|
-
return {
|
|
87
|
+
return {
|
|
88
|
+
resumeArgs: ["--resume", opts.sessionId],
|
|
89
|
+
effectiveSessionId: opts.sessionId,
|
|
90
|
+
userProvidedSession: true,
|
|
91
|
+
};
|
|
30
92
|
}
|
|
31
93
|
return { resumeArgs: [], effectiveSessionId: undefined, userProvidedSession: false };
|
|
32
94
|
}
|
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,19 @@ 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
|
+
},
|
|
57
|
+
{
|
|
58
|
+
uri: "sessions://grok",
|
|
59
|
+
name: "Grok Sessions",
|
|
60
|
+
title: "⚡ Grok Sessions",
|
|
61
|
+
description: "List of Grok conversation sessions",
|
|
62
|
+
mimeType: "application/json",
|
|
63
|
+
annotations: {
|
|
64
|
+
audience: ["user", "assistant"],
|
|
65
|
+
priority: 0.6,
|
|
66
|
+
},
|
|
56
67
|
},
|
|
57
68
|
{
|
|
58
69
|
uri: "models://claude",
|
|
@@ -62,8 +73,8 @@ export class ResourceProvider {
|
|
|
62
73
|
mimeType: "application/json",
|
|
63
74
|
annotations: {
|
|
64
75
|
audience: ["user", "assistant"],
|
|
65
|
-
priority: 0.8
|
|
66
|
-
}
|
|
76
|
+
priority: 0.8,
|
|
77
|
+
},
|
|
67
78
|
},
|
|
68
79
|
{
|
|
69
80
|
uri: "models://codex",
|
|
@@ -73,8 +84,8 @@ export class ResourceProvider {
|
|
|
73
84
|
mimeType: "application/json",
|
|
74
85
|
annotations: {
|
|
75
86
|
audience: ["user", "assistant"],
|
|
76
|
-
priority: 0.8
|
|
77
|
-
}
|
|
87
|
+
priority: 0.8,
|
|
88
|
+
},
|
|
78
89
|
},
|
|
79
90
|
{
|
|
80
91
|
uri: "models://gemini",
|
|
@@ -84,8 +95,19 @@ export class ResourceProvider {
|
|
|
84
95
|
mimeType: "application/json",
|
|
85
96
|
annotations: {
|
|
86
97
|
audience: ["user", "assistant"],
|
|
87
|
-
priority: 0.8
|
|
88
|
-
}
|
|
98
|
+
priority: 0.8,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
uri: "models://grok",
|
|
103
|
+
name: "Grok Models",
|
|
104
|
+
title: "⚡ Grok Models & Capabilities",
|
|
105
|
+
description: "Available Grok models and their capabilities",
|
|
106
|
+
mimeType: "application/json",
|
|
107
|
+
annotations: {
|
|
108
|
+
audience: ["user", "assistant"],
|
|
109
|
+
priority: 0.8,
|
|
110
|
+
},
|
|
89
111
|
},
|
|
90
112
|
{
|
|
91
113
|
uri: "metrics://performance",
|
|
@@ -95,9 +117,9 @@ export class ResourceProvider {
|
|
|
95
117
|
mimeType: "application/json",
|
|
96
118
|
annotations: {
|
|
97
119
|
audience: ["user", "assistant"],
|
|
98
|
-
priority: 0.9
|
|
99
|
-
}
|
|
100
|
-
}
|
|
120
|
+
priority: 0.9,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
101
123
|
];
|
|
102
124
|
}
|
|
103
125
|
// Read a specific resource by URI
|
|
@@ -110,19 +132,20 @@ export class ResourceProvider {
|
|
|
110
132
|
mimeType: "application/json",
|
|
111
133
|
text: JSON.stringify({
|
|
112
134
|
total: sessions.length,
|
|
113
|
-
sessions: sessions.map(
|
|
135
|
+
sessions: sessions.map(s => ({
|
|
114
136
|
id: s.id,
|
|
115
137
|
cli: s.cli,
|
|
116
138
|
description: s.description,
|
|
117
139
|
createdAt: s.createdAt,
|
|
118
|
-
lastUsedAt: s.lastUsedAt
|
|
140
|
+
lastUsedAt: s.lastUsedAt,
|
|
119
141
|
})),
|
|
120
142
|
activeSessions: {
|
|
121
143
|
claude: (await this.sessionManager.getActiveSession("claude"))?.id || null,
|
|
122
144
|
codex: (await this.sessionManager.getActiveSession("codex"))?.id || null,
|
|
123
|
-
gemini: (await this.sessionManager.getActiveSession("gemini"))?.id || null
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
gemini: (await this.sessionManager.getActiveSession("gemini"))?.id || null,
|
|
146
|
+
grok: (await this.sessionManager.getActiveSession("grok"))?.id || null,
|
|
147
|
+
},
|
|
148
|
+
}, null, 2),
|
|
126
149
|
};
|
|
127
150
|
}
|
|
128
151
|
if (uri === "sessions://claude") {
|
|
@@ -134,8 +157,8 @@ export class ResourceProvider {
|
|
|
134
157
|
cli: "claude",
|
|
135
158
|
total: sessions.length,
|
|
136
159
|
sessions,
|
|
137
|
-
activeSession: (await this.sessionManager.getActiveSession("claude"))?.id || null
|
|
138
|
-
}, null, 2)
|
|
160
|
+
activeSession: (await this.sessionManager.getActiveSession("claude"))?.id || null,
|
|
161
|
+
}, null, 2),
|
|
139
162
|
};
|
|
140
163
|
}
|
|
141
164
|
if (uri === "sessions://codex") {
|
|
@@ -147,8 +170,8 @@ export class ResourceProvider {
|
|
|
147
170
|
cli: "codex",
|
|
148
171
|
total: sessions.length,
|
|
149
172
|
sessions,
|
|
150
|
-
activeSession: (await this.sessionManager.getActiveSession("codex"))?.id || null
|
|
151
|
-
}, null, 2)
|
|
173
|
+
activeSession: (await this.sessionManager.getActiveSession("codex"))?.id || null,
|
|
174
|
+
}, null, 2),
|
|
152
175
|
};
|
|
153
176
|
}
|
|
154
177
|
if (uri === "sessions://gemini") {
|
|
@@ -160,8 +183,21 @@ export class ResourceProvider {
|
|
|
160
183
|
cli: "gemini",
|
|
161
184
|
total: sessions.length,
|
|
162
185
|
sessions,
|
|
163
|
-
activeSession: (await this.sessionManager.getActiveSession("gemini"))?.id || null
|
|
164
|
-
}, null, 2)
|
|
186
|
+
activeSession: (await this.sessionManager.getActiveSession("gemini"))?.id || null,
|
|
187
|
+
}, null, 2),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (uri === "sessions://grok") {
|
|
191
|
+
const sessions = await this.sessionManager.listSessions("grok");
|
|
192
|
+
return {
|
|
193
|
+
uri,
|
|
194
|
+
mimeType: "application/json",
|
|
195
|
+
text: JSON.stringify({
|
|
196
|
+
cli: "grok",
|
|
197
|
+
total: sessions.length,
|
|
198
|
+
sessions,
|
|
199
|
+
activeSession: (await this.sessionManager.getActiveSession("grok"))?.id || null,
|
|
200
|
+
}, null, 2),
|
|
165
201
|
};
|
|
166
202
|
}
|
|
167
203
|
// Model capability resources
|
|
@@ -170,7 +206,7 @@ export class ResourceProvider {
|
|
|
170
206
|
return {
|
|
171
207
|
uri,
|
|
172
208
|
mimeType: "application/json",
|
|
173
|
-
text: JSON.stringify(cliInfo.claude, null, 2)
|
|
209
|
+
text: JSON.stringify(cliInfo.claude, null, 2),
|
|
174
210
|
};
|
|
175
211
|
}
|
|
176
212
|
if (uri === "models://codex") {
|
|
@@ -178,7 +214,7 @@ export class ResourceProvider {
|
|
|
178
214
|
return {
|
|
179
215
|
uri,
|
|
180
216
|
mimeType: "application/json",
|
|
181
|
-
text: JSON.stringify(cliInfo.codex, null, 2)
|
|
217
|
+
text: JSON.stringify(cliInfo.codex, null, 2),
|
|
182
218
|
};
|
|
183
219
|
}
|
|
184
220
|
if (uri === "models://gemini") {
|
|
@@ -186,14 +222,22 @@ export class ResourceProvider {
|
|
|
186
222
|
return {
|
|
187
223
|
uri,
|
|
188
224
|
mimeType: "application/json",
|
|
189
|
-
text: JSON.stringify(cliInfo.gemini, null, 2)
|
|
225
|
+
text: JSON.stringify(cliInfo.gemini, null, 2),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (uri === "models://grok") {
|
|
229
|
+
const cliInfo = getCliInfo();
|
|
230
|
+
return {
|
|
231
|
+
uri,
|
|
232
|
+
mimeType: "application/json",
|
|
233
|
+
text: JSON.stringify(cliInfo.grok, null, 2),
|
|
190
234
|
};
|
|
191
235
|
}
|
|
192
236
|
if (uri === "metrics://performance") {
|
|
193
237
|
return {
|
|
194
238
|
uri,
|
|
195
239
|
mimeType: "application/json",
|
|
196
|
-
text: JSON.stringify(this.performanceMetrics.snapshot(), null, 2)
|
|
240
|
+
text: JSON.stringify(this.performanceMetrics.snapshot(), null, 2),
|
|
197
241
|
};
|
|
198
242
|
}
|
|
199
243
|
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;
|